mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-10-01 15:41:36 +00:00
Compare commits
156
Commits
widget
..
af27fc4933
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af27fc4933 | ||
|
|
1fffab25bd | ||
|
|
706f30ea49 | ||
|
|
3f29d725ea | ||
|
|
977896d296 | ||
|
|
654529c851 | ||
|
|
366f425539 | ||
|
|
37076a9bf0 | ||
|
|
8c99046edb | ||
|
|
c990a495b7 | ||
|
|
0e61723db1 | ||
|
|
8815907563 | ||
|
|
566e2ec977 | ||
|
|
65a7ccb221 | ||
|
|
2201b7d0fe | ||
|
|
a9c354ad8a | ||
|
|
9365bc0215 | ||
|
|
efa4bab144 | ||
|
|
6cce33c5f6 | ||
|
|
16fbfae0c2 | ||
|
|
0d174f1550 | ||
|
|
eb82c2afc1 | ||
|
|
c774fd1536 | ||
|
|
54b2905196 | ||
|
|
c4450296db | ||
|
|
d1908d629a | ||
|
|
67b56b3990 | ||
|
|
b0c5d6375d | ||
|
|
29ff2805c3 | ||
|
|
b4d509aad9 | ||
|
|
44c6382356 | ||
|
|
489e27b038 | ||
|
|
6be167bbed | ||
|
|
61dfc2ce0d | ||
|
|
dce186737d | ||
|
|
cf272e8030 | ||
|
|
ffa6958a89 | ||
|
|
1d9879fb50 | ||
|
|
3fa072ce1e | ||
|
|
69245be8dd | ||
|
|
0f1feb88b8 | ||
|
|
5ddb8f5da9 | ||
|
|
0875b114d5 | ||
|
|
61daad459b | ||
|
|
14e941031c | ||
|
|
952fd5a871 | ||
|
|
ac02205f76 | ||
|
|
68ad57b78f | ||
|
|
73c393df64 | ||
|
|
0b3ce486cb | ||
|
|
7160eb792a | ||
|
|
07983d9403 | ||
|
|
cb9fe8e716 | ||
|
|
540766eefb | ||
|
|
af2c852bac | ||
|
|
8dc5bbbdde | ||
|
|
aac05a3b1c | ||
|
|
b827fdccbe | ||
|
|
2586190e5c | ||
|
|
1a6753d2aa | ||
|
|
9c581a5ebd | ||
|
|
0add667ef8 | ||
|
|
c122b71b46 | ||
|
|
f8ec899335 | ||
|
|
b95edfa2a1 | ||
|
|
5ed7d117f5 | ||
|
|
16cd7abce8 | ||
|
|
6a6369ae9e | ||
|
|
1bbaf66c24 | ||
|
|
ba03be808e | ||
|
|
bb1c29bbb4 | ||
|
|
f0760f9ed0 | ||
|
|
61a2439f84 | ||
|
|
5bb9fe5209 | ||
|
|
26fc0ced56 | ||
|
|
371a850d45 | ||
|
|
d833a89c17 | ||
|
|
1a570170ca | ||
|
|
1566876106 | ||
|
|
019f07d827 | ||
|
|
4474e6689d | ||
|
|
28cb824bf3 | ||
|
|
0a643053af | ||
|
|
5989f48355 | ||
|
|
de3d312a32 | ||
|
|
cd33d06ecf | ||
|
|
6ea9946412 | ||
|
|
ec3058b511 | ||
|
|
26842d1b1b | ||
|
|
b74b04cda9 | ||
|
|
481c0b6d9a | ||
|
|
a3bebcf290 | ||
|
|
fb09004d13 | ||
|
|
dd1a62d184 | ||
|
|
f96f00e05a | ||
|
|
3c17e93d0f | ||
|
|
f8ce81e932 | ||
|
|
b3cf823c3f | ||
|
|
7843be7004 | ||
|
|
f0f1d9830e | ||
|
|
c404f4f436 | ||
|
|
71ba5e0207 | ||
|
|
fb09350825 | ||
|
|
615fb85204 | ||
|
|
7bb7693876 | ||
|
|
2b33042fe0 | ||
|
|
3687ff2c21 | ||
|
|
c973030d9d | ||
|
|
9f7e452850 | ||
|
|
37e5efa874 | ||
|
|
c5cebd8213 | ||
|
|
cfa52bce9b | ||
|
|
acd990d32b | ||
|
|
c8690152bc | ||
|
|
eff2fadb55 | ||
|
|
8f790eea41 | ||
|
|
9d0e68fb8a | ||
|
|
31fab9dba4 | ||
|
|
7af8e00e6f | ||
|
|
4e12962c05 | ||
|
|
b3a4ced28d | ||
|
|
d7b10b1ae8 | ||
|
|
942d843601 | ||
|
|
4d8dcde26e | ||
|
|
3b762d6195 | ||
|
|
023af17b23 | ||
|
|
2901bcf0da | ||
|
|
266658342a | ||
|
|
a4b159da45 | ||
|
|
fa8546f283 | ||
|
|
d86ee00109 | ||
|
|
6c9d057917 | ||
|
|
a044d24f5f | ||
|
|
1d21155198 | ||
|
|
09ee139add | ||
|
|
8d9c507645 | ||
|
|
c947e2519c | ||
|
|
9bcbe85b7f | ||
|
|
a72718273c | ||
|
|
bd0cc46398 | ||
|
|
a011ae39a4 | ||
|
|
0b1eed7da9 | ||
|
|
36c3e25fd3 | ||
|
|
ac05dfc541 | ||
|
|
220a61a553 | ||
|
|
d847f600f1 | ||
|
|
f7a4039d66 | ||
|
|
1eed6a4c17 | ||
|
|
1a09e40d1d | ||
|
|
13051145b7 | ||
|
|
1a150ce7ce | ||
|
|
39ba48d867 | ||
|
|
3f324ca141 | ||
|
|
1e28a8c249 | ||
|
|
9f5f806786 | ||
|
|
f7b3b7256a |
@@ -10,3 +10,10 @@ app/src/main/res/values/keys.xml
|
||||
app/src/test/resources/com/github/nacabaro/vbhelper/source/com.bandai.vitalbraceletarena.apk
|
||||
|
||||
app/src/test/resources/com/github/nacabaro/vbhelper/source/classes.dex
|
||||
|
||||
app/src/main/java/com/github/nacabaro/vbhelper/battle/Battle_Sprites_Reference/
|
||||
|
||||
app/src/main/assets/battle_sprites
|
||||
app/src/main/assets/extracted_audio
|
||||
|
||||
API-ACR122USAM-2.01.pdf
|
||||
@@ -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 Helper. 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.
|
||||
|
||||
+37
-13
@@ -1,21 +1,23 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
id("com.google.devtools.ksp") version "2.0.21-1.0.27"
|
||||
id("com.google.devtools.ksp") version "2.3.0"
|
||||
id("com.google.protobuf")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.github.nacabaro.vbhelper"
|
||||
compileSdk = 35
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.github.nacabaro.vbhelper"
|
||||
minSdk = 28
|
||||
targetSdk = 35
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "Alpha 0.1"
|
||||
versionName = "Alpha 0.6.3"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -33,9 +35,7 @@ android {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
@@ -45,6 +45,12 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_11
|
||||
}
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:4.27.0"
|
||||
@@ -68,10 +74,13 @@ dependencies {
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.vb.nfc.reader)
|
||||
implementation(libs.dim.reader)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
annotationProcessor(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation("androidx.room:room-ktx:2.6.1")
|
||||
implementation(libs.androidx.room.ktx)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.material)
|
||||
implementation(libs.protobuf.javalite)
|
||||
implementation(libs.androidx.compose.material)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
@@ -80,18 +89,33 @@ dependencies {
|
||||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.compose.material.icons.extended)
|
||||
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
|
||||
ksp(libs.androidx.room.compiler)
|
||||
annotationProcessor(libs.androidx.room.compiler)
|
||||
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
implementation("androidx.navigation:navigation-compose:2.7.0")
|
||||
implementation("com.google.android.material:material:1.2.0")
|
||||
implementation(libs.protobuf.javalite)
|
||||
implementation("androidx.compose.material:material")
|
||||
implementation("androidx.glance:glance:1.1.1")
|
||||
implementation("androidx.glance:glance-appwidget:1.1.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
|
||||
// HTTP request logging
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
|
||||
}
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
|
||||
<application
|
||||
android:name=".di.VBHelper"
|
||||
@@ -15,6 +20,7 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.VBHelper"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
@@ -26,16 +32,20 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<receiver android:name=".widget.CharacterDisplayWidgetReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="vbhelper" android:host="auth" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info" />
|
||||
</receiver>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http" android:host="localhost" android:port="8080" android:pathPrefix="/authenticate" />
|
||||
<data android:scheme="http" android:host="127.0.0.1" android:port="8080" android:pathPrefix="/authenticate" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -14,6 +14,8 @@ import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImp
|
||||
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
|
||||
|
||||
@@ -45,6 +47,8 @@ class MainActivity : ComponentActivity() {
|
||||
val adventureScreenController = AdventureScreenControllerImpl(this)
|
||||
val storageScreenController = StorageScreenControllerImpl(this)
|
||||
val homeScreenController = HomeScreenControllerImpl(this)
|
||||
val spriteViewerController = SpriteViewerControllerImpl(this)
|
||||
val cardScreenController = CardScreenControllerImpl(this)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -58,7 +62,9 @@ class MainActivity : ComponentActivity() {
|
||||
itemsScreenController = itemsScreenController,
|
||||
adventureScreenController = adventureScreenController,
|
||||
homeScreenController = homeScreenController,
|
||||
storageScreenController = storageScreenController
|
||||
storageScreenController = storageScreenController,
|
||||
spriteViewerController = spriteViewerController,
|
||||
cardScreenController = cardScreenController
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -90,8 +96,9 @@ class MainActivity : ComponentActivity() {
|
||||
adventureScreenController: AdventureScreenControllerImpl,
|
||||
storageScreenController: StorageScreenControllerImpl,
|
||||
homeScreenController: HomeScreenControllerImpl,
|
||||
|
||||
) {
|
||||
spriteViewerController: SpriteViewerControllerImpl,
|
||||
cardScreenController: CardScreenControllerImpl
|
||||
) {
|
||||
AppNavigation(
|
||||
applicationNavigationHandlers = AppNavigationHandlers(
|
||||
settingsScreenController,
|
||||
@@ -99,7 +106,9 @@ class MainActivity : ComponentActivity() {
|
||||
itemsScreenController,
|
||||
adventureScreenController,
|
||||
storageScreenController,
|
||||
homeScreenController
|
||||
homeScreenController,
|
||||
spriteViewerController,
|
||||
cardScreenController
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
data class APIBattleCharacter(
|
||||
val name: String,
|
||||
val namekey: String,
|
||||
val charaId: String,
|
||||
val stage: Int,
|
||||
val attribute: Int,
|
||||
val baseHp: Int,
|
||||
val currentHp: Int,
|
||||
val baseBp: Float,
|
||||
val baseAp: Float,
|
||||
val displayName: String? = null
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
fun AnimatedSpriteImage(
|
||||
characterId: String,
|
||||
animationType: DigimonAnimationType = DigimonAnimationType.IDLE,
|
||||
modifier: Modifier = Modifier,
|
||||
contentScale: ContentScale = ContentScale.Fit,
|
||||
reloadMappings: Boolean = false,
|
||||
animationOffset: Long = 0L // New parameter for offsetting animation timing
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val spriteManager = remember { IndividualSpriteManager(context) }
|
||||
|
||||
// Calculate frame offset based on animation offset
|
||||
// 750ms is the idle animation duration, so we calculate how many frames to offset
|
||||
val frameOffset = if (animationOffset > 0L) {
|
||||
// Convert time offset to frame offset (2 frames per cycle, 750ms per frame)
|
||||
((animationOffset / 750L) * 2).toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
val animationStateMachine = remember { DigimonAnimationStateMachine(characterId, context, frameOffset, animationOffset) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var bitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
|
||||
|
||||
// Reload mappings when reloadMappings parameter changes
|
||||
LaunchedEffect(reloadMappings) {
|
||||
if (reloadMappings) {
|
||||
animationStateMachine.reloadMappings()
|
||||
}
|
||||
}
|
||||
|
||||
// Start the animation when the component is first created
|
||||
LaunchedEffect(characterId) {
|
||||
coroutineScope.launch {
|
||||
animationStateMachine.playIdleAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
// Change animation when animationType changes
|
||||
LaunchedEffect(animationType) {
|
||||
coroutineScope.launch {
|
||||
if (animationType == DigimonAnimationType.IDLE) {
|
||||
animationStateMachine.playIdleAnimation()
|
||||
} else {
|
||||
animationStateMachine.playAnimation(animationType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update sprite when animation state changes
|
||||
LaunchedEffect(animationStateMachine.currentFrameNumber) {
|
||||
val frameNumber = animationStateMachine.getCurrentFrame()
|
||||
|
||||
bitmap = spriteManager.loadSpriteFrame(characterId, frameNumber)
|
||||
|
||||
if (bitmap == null) {
|
||||
println("Failed to load animated sprite frame: $frameNumber for character: $characterId")
|
||||
}
|
||||
}
|
||||
|
||||
bitmap?.let { bmp ->
|
||||
Image(
|
||||
bitmap = bmp.asImageBitmap(),
|
||||
contentDescription = "Animated Sprite: $characterId - ${animationStateMachine.currentAnimation}",
|
||||
modifier = modifier,
|
||||
contentScale = contentScale
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.State
|
||||
|
||||
class ArenaBattleSystem {
|
||||
companion object {
|
||||
private const val TAG = "ArenaBattleSystem"
|
||||
}
|
||||
|
||||
// Attack phases: 0=Idle, 1=Player attack on player screen, 2=Player attack on opponent screen,
|
||||
// 3=Opponent attack on opponent screen, 4=Opponent attack on player screen
|
||||
private var _attackPhase by mutableStateOf(0)
|
||||
val attackPhase: Int get() = _attackPhase
|
||||
|
||||
private var _attackProgress by mutableStateOf(0f)
|
||||
val attackProgress: Float get() = _attackProgress
|
||||
|
||||
private var _isPlayerAttacking by mutableStateOf(false)
|
||||
val isPlayerAttacking: Boolean get() = _isPlayerAttacking
|
||||
|
||||
private var _attackIsHit by mutableStateOf(false)
|
||||
val attackIsHit: Boolean get() = _attackIsHit
|
||||
|
||||
private var _isAttackButtonEnabled by mutableStateOf(true)
|
||||
val isAttackButtonEnabled: Boolean get() = _isAttackButtonEnabled
|
||||
|
||||
private var _currentView by mutableStateOf(0)
|
||||
val currentView: Int get() = _currentView
|
||||
|
||||
private var _playerHP by mutableStateOf(100f)
|
||||
val playerHP: Float get() = _playerHP
|
||||
|
||||
private var _opponentHP by mutableStateOf(100f)
|
||||
val opponentHP: Float get() = _opponentHP
|
||||
|
||||
private var _isBattleOver by mutableStateOf(false)
|
||||
val isBattleOver: Boolean get() = _isBattleOver
|
||||
|
||||
private var _critBarProgress by mutableStateOf(0)
|
||||
val critBarProgress: Int get() = _critBarProgress
|
||||
|
||||
// Dodge animation states
|
||||
private var _isDodging by mutableStateOf(false)
|
||||
val isDodging: Boolean get() = _isDodging
|
||||
|
||||
private var _dodgeProgress by mutableStateOf(0f)
|
||||
val dodgeProgress: Float get() = _dodgeProgress
|
||||
|
||||
private var _dodgeDirection by mutableStateOf(1f) // 1f = up, -1f = down
|
||||
val dodgeDirection: Float get() = _dodgeDirection
|
||||
|
||||
private var _isHit by mutableStateOf(false)
|
||||
val isHit: Boolean get() = _isHit
|
||||
|
||||
private var _hitProgress by mutableStateOf(0f)
|
||||
val hitProgress: Float get() = _hitProgress
|
||||
|
||||
// Separate states for player and opponent
|
||||
private var _isPlayerDodging by mutableStateOf(false)
|
||||
val isPlayerDodging: Boolean get() = _isPlayerDodging
|
||||
|
||||
private var _isOpponentDodging by mutableStateOf(false)
|
||||
val isOpponentDodging: Boolean get() = _isOpponentDodging
|
||||
|
||||
// Separate dodge progress and direction for player and opponent
|
||||
private var _playerDodgeProgress by mutableStateOf(0f)
|
||||
val playerDodgeProgress: Float get() = _playerDodgeProgress
|
||||
|
||||
private var _playerDodgeDirection by mutableStateOf(1f)
|
||||
val playerDodgeDirection: Float get() = _playerDodgeDirection
|
||||
|
||||
private var _opponentDodgeProgress by mutableStateOf(0f)
|
||||
val opponentDodgeProgress: Float get() = _opponentDodgeProgress
|
||||
|
||||
private var _opponentDodgeDirection by mutableStateOf(1f)
|
||||
val opponentDodgeDirection: Float get() = _opponentDodgeDirection
|
||||
|
||||
private var _isPlayerHit by mutableStateOf(false)
|
||||
val isPlayerHit: Boolean get() = _isPlayerHit
|
||||
|
||||
private var _isOpponentHit by mutableStateOf(false)
|
||||
val isOpponentHit: Boolean get() = _isOpponentHit
|
||||
|
||||
// Delayed hit states for SLEEP animation timing
|
||||
private var _isPlayerHitDelayed by mutableStateOf(false)
|
||||
val isPlayerHitDelayed: Boolean get() = _isPlayerHitDelayed
|
||||
|
||||
private var _isOpponentHitDelayed by mutableStateOf(false)
|
||||
val isOpponentHitDelayed: Boolean get() = _isOpponentHitDelayed
|
||||
|
||||
// Delayed shake states for shake animation timing
|
||||
private var _isPlayerShakeDelayed by mutableStateOf(false)
|
||||
val isPlayerShakeDelayed: Boolean get() = _isPlayerShakeDelayed
|
||||
|
||||
private var _isOpponentShakeDelayed by mutableStateOf(false)
|
||||
val isOpponentShakeDelayed: Boolean get() = _isOpponentShakeDelayed
|
||||
|
||||
// Counter-attack tracking
|
||||
private var _shouldCounterAttack by mutableStateOf(false)
|
||||
val shouldCounterAttack: Boolean get() = _shouldCounterAttack
|
||||
|
||||
private var _counterAttackIsHit by mutableStateOf(false)
|
||||
val counterAttackIsHit: Boolean get() = _counterAttackIsHit
|
||||
|
||||
// Separate tracking for opponent attack result
|
||||
private var _opponentAttackIsHit by mutableStateOf(false)
|
||||
val opponentAttackIsHit: Boolean get() = _opponentAttackIsHit
|
||||
|
||||
fun startPlayerAttack() {
|
||||
_attackPhase = 1
|
||||
_attackProgress = 0f
|
||||
_isPlayerAttacking = true
|
||||
_isAttackButtonEnabled = false
|
||||
_currentView = 0
|
||||
}
|
||||
|
||||
fun startOpponentAttack() {
|
||||
_attackPhase = 3
|
||||
_attackProgress = 0f
|
||||
_isPlayerAttacking = false
|
||||
_currentView = 1
|
||||
}
|
||||
|
||||
fun advanceAttackPhase() {
|
||||
_attackPhase++
|
||||
_attackProgress = 0f
|
||||
}
|
||||
|
||||
fun setAttackProgress(progress: Float) {
|
||||
_attackProgress = progress
|
||||
}
|
||||
|
||||
fun setAttackHitState(isHit: Boolean) {
|
||||
_attackIsHit = isHit
|
||||
}
|
||||
|
||||
fun switchToView(view: Int) {
|
||||
_currentView = view
|
||||
}
|
||||
|
||||
fun enableAttackButton() {
|
||||
_isAttackButtonEnabled = true
|
||||
}
|
||||
|
||||
fun applyDamage(isPlayer: Boolean, damage: Float) {
|
||||
if (isPlayer) {
|
||||
_playerHP = (_playerHP - damage).coerceAtLeast(0f)
|
||||
} else {
|
||||
_opponentHP = (_opponentHP - damage).coerceAtLeast(0f)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateHPFromAPI(playerHP: Float, opponentHP: Float) {
|
||||
_playerHP = playerHP
|
||||
_opponentHP = opponentHP
|
||||
}
|
||||
|
||||
fun initializeHP(playerHP: Float, opponentHP: Float) {
|
||||
_playerHP = playerHP
|
||||
_opponentHP = opponentHP
|
||||
}
|
||||
|
||||
fun completeAttackAnimation(playerDamage: Float = 0f, opponentDamage: Float = 0f) {
|
||||
if (playerDamage > 0f) {
|
||||
applyDamage(true, playerDamage)
|
||||
}
|
||||
if (opponentDamage > 0f) {
|
||||
applyDamage(false, opponentDamage)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetAttackState() {
|
||||
_attackPhase = 0
|
||||
_attackProgress = 0f
|
||||
_isPlayerAttacking = false
|
||||
_attackIsHit = false
|
||||
_currentView = 0
|
||||
_isDodging = false
|
||||
_dodgeProgress = 0f
|
||||
_dodgeDirection = 1f
|
||||
_isHit = false
|
||||
_hitProgress = 0f
|
||||
_isPlayerDodging = false
|
||||
_isOpponentDodging = false
|
||||
_playerDodgeProgress = 0f
|
||||
_playerDodgeDirection = 1f
|
||||
_opponentDodgeProgress = 0f
|
||||
_opponentDodgeDirection = 1f
|
||||
_isPlayerHit = false
|
||||
_isOpponentHit = false
|
||||
_isPlayerHitDelayed = false
|
||||
_isOpponentHitDelayed = false
|
||||
_isPlayerShakeDelayed = false
|
||||
_isOpponentShakeDelayed = false
|
||||
_shouldCounterAttack = false
|
||||
_counterAttackIsHit = false
|
||||
_opponentAttackIsHit = false
|
||||
}
|
||||
|
||||
fun checkBattleOver(): Boolean {
|
||||
return _playerHP <= 0f || _opponentHP <= 0f
|
||||
}
|
||||
|
||||
fun endBattle() {
|
||||
_isBattleOver = true
|
||||
}
|
||||
|
||||
fun updateCritBarProgress(progress: Int) {
|
||||
_critBarProgress = progress
|
||||
//Log.d(TAG, "Updated crit bar progress: $progress")
|
||||
}
|
||||
|
||||
// Dodge animation methods
|
||||
fun startDodge() {
|
||||
_isDodging = true
|
||||
_dodgeProgress = 0f
|
||||
_dodgeDirection = 1f // Start moving up
|
||||
}
|
||||
|
||||
fun setDodgeProgress(progress: Float) {
|
||||
_dodgeProgress = progress
|
||||
}
|
||||
|
||||
fun setDodgeDirection(direction: Float) {
|
||||
_dodgeDirection = direction
|
||||
}
|
||||
|
||||
fun endDodge() {
|
||||
_isDodging = false
|
||||
_dodgeProgress = 0f
|
||||
}
|
||||
|
||||
// Hit animation methods
|
||||
fun startHit() {
|
||||
_isHit = true
|
||||
_hitProgress = 0f
|
||||
}
|
||||
|
||||
fun setHitProgress(progress: Float) {
|
||||
_hitProgress = progress
|
||||
}
|
||||
|
||||
fun endHit() {
|
||||
_isHit = false
|
||||
_hitProgress = 0f
|
||||
}
|
||||
|
||||
// Player-specific dodge methods
|
||||
fun startPlayerDodge() {
|
||||
_isPlayerDodging = true
|
||||
_playerDodgeProgress = 0f
|
||||
_playerDodgeDirection = 1f
|
||||
}
|
||||
|
||||
fun endPlayerDodge() {
|
||||
_isPlayerDodging = false
|
||||
_playerDodgeProgress = 0f
|
||||
}
|
||||
|
||||
fun setPlayerDodgeProgress(progress: Float) {
|
||||
_playerDodgeProgress = progress
|
||||
}
|
||||
|
||||
fun setPlayerDodgeDirection(direction: Float) {
|
||||
_playerDodgeDirection = direction
|
||||
}
|
||||
|
||||
// Opponent-specific dodge methods
|
||||
fun startOpponentDodge() {
|
||||
_isOpponentDodging = true
|
||||
_opponentDodgeProgress = 0f
|
||||
_opponentDodgeDirection = 1f
|
||||
}
|
||||
|
||||
fun endOpponentDodge() {
|
||||
_isOpponentDodging = false
|
||||
_opponentDodgeProgress = 0f
|
||||
}
|
||||
|
||||
fun setOpponentDodgeProgress(progress: Float) {
|
||||
_opponentDodgeProgress = progress
|
||||
}
|
||||
|
||||
fun setOpponentDodgeDirection(direction: Float) {
|
||||
_opponentDodgeDirection = direction
|
||||
}
|
||||
|
||||
// Player-specific hit methods
|
||||
fun startPlayerHit() {
|
||||
_isPlayerHit = true
|
||||
_hitProgress = 0f
|
||||
}
|
||||
|
||||
fun startPlayerHitDelayed() {
|
||||
_isPlayerHitDelayed = true
|
||||
}
|
||||
|
||||
fun endPlayerHit() {
|
||||
_isPlayerHit = false
|
||||
_hitProgress = 0f
|
||||
}
|
||||
|
||||
fun endPlayerHitDelayed() {
|
||||
_isPlayerHitDelayed = false
|
||||
}
|
||||
|
||||
// Opponent-specific hit methods
|
||||
fun startOpponentHit() {
|
||||
_isOpponentHit = true
|
||||
_hitProgress = 0f
|
||||
}
|
||||
|
||||
fun startOpponentHitDelayed() {
|
||||
_isOpponentHitDelayed = true
|
||||
}
|
||||
|
||||
fun endOpponentHit() {
|
||||
_isOpponentHit = false
|
||||
_hitProgress = 0f
|
||||
}
|
||||
|
||||
fun endOpponentHitDelayed() {
|
||||
_isOpponentHitDelayed = false
|
||||
}
|
||||
|
||||
// Delayed shake methods
|
||||
fun startPlayerShakeDelayed() {
|
||||
_isPlayerShakeDelayed = true
|
||||
}
|
||||
|
||||
fun endPlayerShakeDelayed() {
|
||||
_isPlayerShakeDelayed = false
|
||||
}
|
||||
|
||||
fun startOpponentShakeDelayed() {
|
||||
_isOpponentShakeDelayed = true
|
||||
}
|
||||
|
||||
fun endOpponentShakeDelayed() {
|
||||
_isOpponentShakeDelayed = false
|
||||
}
|
||||
|
||||
// Combined method to handle attack result
|
||||
fun handleAttackResult(isHit: Boolean) {
|
||||
_attackIsHit = isHit
|
||||
if (isHit) {
|
||||
// Player attack hit - opponent gets hit
|
||||
startOpponentHit()
|
||||
} else {
|
||||
// Player attack missed - opponent dodges
|
||||
startOpponentDodge()
|
||||
}
|
||||
}
|
||||
|
||||
// Method to handle opponent attack result
|
||||
fun handleOpponentAttackResult(isHit: Boolean) {
|
||||
_opponentAttackIsHit = isHit
|
||||
if (isHit) {
|
||||
// Opponent attack hit - player gets hit
|
||||
startPlayerHit()
|
||||
} else {
|
||||
// Opponent attack missed - player dodges
|
||||
startPlayerDodge()
|
||||
}
|
||||
}
|
||||
|
||||
// Counter-attack methods
|
||||
fun setupCounterAttack(isHit: Boolean) {
|
||||
_shouldCounterAttack = true
|
||||
_counterAttackIsHit = isHit
|
||||
}
|
||||
|
||||
fun startCounterAttack() {
|
||||
_attackPhase = 3
|
||||
_attackProgress = 0f
|
||||
_isPlayerAttacking = false
|
||||
_currentView = 1
|
||||
_opponentAttackIsHit = _counterAttackIsHit
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Environment
|
||||
import com.google.gson.Gson
|
||||
import java.io.File
|
||||
|
||||
data class CharacterData(
|
||||
val name: String,
|
||||
val charaId: String,
|
||||
val smalefilename: String,
|
||||
val laugeFileName: String
|
||||
)
|
||||
|
||||
data class CharacterDataResponse(
|
||||
val name: String,
|
||||
val type: String,
|
||||
val source_file: String,
|
||||
val collection: String,
|
||||
val unity_collection_id: String,
|
||||
val relative_path: String,
|
||||
val all_attributes: CharacterDataAttributes
|
||||
)
|
||||
|
||||
data class CharacterDataAttributes(
|
||||
val DataList: List<String>
|
||||
)
|
||||
|
||||
class AttackSpriteManager(private val context: Context) {
|
||||
private val gson = Gson()
|
||||
private val characterDataCache = mutableMapOf<String, CharacterData>()
|
||||
|
||||
// Get the external storage directory for attack sprites
|
||||
private fun getAttackTexturesBaseDir(): File {
|
||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
||||
return File(externalDir, "VBHelper/battle_sprites/extracted_atksprites")
|
||||
}
|
||||
|
||||
fun getAttackSprite(characterId: String, isLarge: Boolean = false): Bitmap? {
|
||||
println("AttackSpriteManager: Getting attack sprite for characterId=$characterId, isLarge=$isLarge")
|
||||
try {
|
||||
// Get character data
|
||||
val characterData = getCharacterData(characterId) ?: return null
|
||||
// Determine which attack file to use
|
||||
val attackFileName = if (isLarge) {
|
||||
characterData.laugeFileName
|
||||
} else {
|
||||
characterData.smalefilename
|
||||
}
|
||||
|
||||
// Skip if no attack file
|
||||
if (attackFileName == "0") {
|
||||
println("AttackSpriteManager: Skipping attack file (filename is '0')")
|
||||
return null
|
||||
}
|
||||
|
||||
// Load the attack sprite from external storage
|
||||
val attackFile = File(getAttackTexturesBaseDir(), "$attackFileName.png")
|
||||
|
||||
return if (attackFile.exists()) {
|
||||
val bitmap = BitmapFactory.decodeFile(attackFile.absolutePath)
|
||||
bitmap
|
||||
} else {
|
||||
println("AttackSpriteManager: Attack file does not exist")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("AttackSpriteManager: Exception occurred: ${e.message}")
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCharacterData(characterId: String): CharacterData? {
|
||||
// Check cache first
|
||||
if (characterDataCache.containsKey(characterId)) {
|
||||
return characterDataCache[characterId]
|
||||
}
|
||||
|
||||
try {
|
||||
// Load character data from JSON file in external storage
|
||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
||||
val characterDataFile = File(externalDir, "VBHelper/battle_sprites/extracted_digimon_stats/character_data/CharacterData.json")
|
||||
|
||||
if (!characterDataFile.exists()) {
|
||||
println("AttackSpriteManager: Character data file does not exist, using default data")
|
||||
// For now, return a default character data
|
||||
val characterData = CharacterData(
|
||||
name = characterId,
|
||||
charaId = characterId,
|
||||
smalefilename = "atk_s_02", // Default small attack
|
||||
laugeFileName = "atk_l_04" // Default large attack
|
||||
)
|
||||
|
||||
characterDataCache[characterId] = characterData
|
||||
return characterData
|
||||
}
|
||||
|
||||
val jsonContent = characterDataFile.readText()
|
||||
|
||||
// Parse the JSON response
|
||||
val response = gson.fromJson(jsonContent, CharacterDataResponse::class.java)
|
||||
|
||||
// Search through the DataList for the matching characterId
|
||||
for (characterString in response.all_attributes.DataList) {
|
||||
// Extract charaId from the string format: "<UnknownObject<Character> id=0, charaId='dim000_mon03', ...>"
|
||||
val charaIdMatch = Regex("charaId='([^']+)'").find(characterString)
|
||||
if (charaIdMatch != null) {
|
||||
val foundCharaId = charaIdMatch.groupValues[1]
|
||||
if (foundCharaId == characterId) {
|
||||
// Extract smalefilename and laugeFileName
|
||||
val smallFileMatch = Regex("smalefilename='([^']+)'").find(characterString)
|
||||
val largeFileMatch = Regex("laugeFileName='([^']+)'").find(characterString)
|
||||
|
||||
val smallFileName = smallFileMatch?.groupValues?.get(1) ?: "0"
|
||||
val largeFileName = largeFileMatch?.groupValues?.get(1) ?: "0"
|
||||
|
||||
val characterData = CharacterData(
|
||||
name = characterId,
|
||||
charaId = characterId,
|
||||
smalefilename = smallFileName,
|
||||
laugeFileName = largeFileName
|
||||
)
|
||||
|
||||
characterDataCache[characterId] = characterData
|
||||
return characterData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If character not found, return default data
|
||||
println("AttackSpriteManager: Character not found in JSON, using default data")
|
||||
val characterData = CharacterData(
|
||||
name = characterId,
|
||||
charaId = characterId,
|
||||
smalefilename = "atk_s_02", // Default small attack
|
||||
laugeFileName = "atk_l_04" // Default large attack
|
||||
)
|
||||
|
||||
characterDataCache[characterId] = characterData
|
||||
return characterData
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("AttackSpriteManager: Exception in getCharacterData: ${e.message}")
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* OkHttp interceptor that adds Authorization header to API requests.
|
||||
* Skips adding header for auth endpoints.
|
||||
*/
|
||||
class AuthInterceptor(private val token: String) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
// Skip adding auth header for auth endpoints
|
||||
if (originalRequest.url.encodedPath.startsWith("/api/auth")) {
|
||||
return chain.proceed(originalRequest)
|
||||
}
|
||||
|
||||
// Add authentication header for game endpoints
|
||||
// Use X-Session-Token header (preferred) or Authorization: Bearer
|
||||
val authenticatedRequest = originalRequest.newBuilder()
|
||||
.header("X-Session-Token", token)
|
||||
.build()
|
||||
|
||||
// Debug: Log which header is being used (first few chars of token for security)
|
||||
val tokenPreview = if (token.length > 8) "${token.take(4)}...${token.takeLast(4)}" else "***"
|
||||
println("AuthInterceptor: Adding X-Session-Token header (token: $tokenPreview)")
|
||||
|
||||
return chain.proceed(authenticatedRequest)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface AuthService {
|
||||
@POST("api/auth/validate")
|
||||
fun validate(@Body request: AuthenticateRequest): Call<AuthenticateResponse>
|
||||
|
||||
@POST("api/auth/login")
|
||||
fun login(@Body request: AuthenticateRequest): Call<AuthenticateResponse>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
data class AuthenticateRequest(
|
||||
val userToken: String
|
||||
)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
data class AdditionalInfo(
|
||||
val avatar: String? = null,
|
||||
val id: Long? = null,
|
||||
val name: String? = null,
|
||||
val status: String? = null
|
||||
)
|
||||
|
||||
data class UserInfo(
|
||||
val userId: String? = null,
|
||||
val additionalInfo: AdditionalInfo? = null
|
||||
)
|
||||
|
||||
data class AuthenticateResponse(
|
||||
val success: Boolean,
|
||||
val message: String? = null,
|
||||
val userInfo: UserInfo? = null,
|
||||
val sessionToken: String? = null
|
||||
)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.github.nacabaro.vbhelper.source.AuthRepository
|
||||
|
||||
private const val BATTLE_AUTH_PREFERENCES_NAME = "battle_auth_preferences"
|
||||
val Context.battleAuthStore: androidx.datastore.core.DataStore<Preferences> by preferencesDataStore(
|
||||
name = BATTLE_AUTH_PREFERENCES_NAME
|
||||
)
|
||||
|
||||
class BattleAuthContainer(private val context: Context) {
|
||||
val authRepository: AuthRepository = AuthRepository(context.battleAuthStore)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Environment
|
||||
import com.google.gson.Gson
|
||||
import java.io.File
|
||||
|
||||
data class SpriteMapping(
|
||||
val atlas_name: String,
|
||||
val atlas_file: String,
|
||||
val texture: TextureInfo,
|
||||
val sprites: List<String>
|
||||
)
|
||||
|
||||
data class TextureInfo(
|
||||
val name: String,
|
||||
val file: String,
|
||||
val path_id: Long
|
||||
)
|
||||
|
||||
data class SpriteData(
|
||||
val name: String,
|
||||
val atlas_name: String,
|
||||
val m_Name: String,
|
||||
val texture_rect: TextureRect
|
||||
)
|
||||
|
||||
data class TextureRect(
|
||||
val height: Float,
|
||||
val width: Float,
|
||||
val x: Float,
|
||||
val y: Float
|
||||
)
|
||||
|
||||
class BattleSpriteManager(private val context: Context) {
|
||||
private val gson = Gson()
|
||||
private val spriteCache = mutableMapOf<String, Bitmap>()
|
||||
|
||||
// Get the external storage directory for sprite files
|
||||
private fun getSpriteBaseDir(): File {
|
||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
||||
return File(externalDir, "VBHelper/battle_sprites/extracted_assets")
|
||||
}
|
||||
|
||||
fun loadSprite(spriteName: String, atlasName: String): Bitmap? {
|
||||
val cacheKey = "${spriteName}_${atlasName}"
|
||||
|
||||
// Check cache first
|
||||
if (spriteCache.containsKey(cacheKey)) {
|
||||
return spriteCache[cacheKey]
|
||||
}
|
||||
|
||||
// Debug: Check if base directory exists
|
||||
val spriteBaseDir = getSpriteBaseDir()
|
||||
if (!spriteBaseDir.exists()) {
|
||||
println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
println("Sprite base directory exists: ${spriteBaseDir.absolutePath}")
|
||||
println("Available directories: ${spriteBaseDir.listFiles()?.map { it.name }}")
|
||||
|
||||
try {
|
||||
// Load the PNG texture file directly using the atlas name
|
||||
val textureFile = File(spriteBaseDir, "extracted_textures/${atlasName}.png")
|
||||
|
||||
if (!textureFile.exists()) {
|
||||
println("Texture file not found: ${textureFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
val fullBitmap = BitmapFactory.decodeFile(textureFile.absolutePath)
|
||||
if (fullBitmap == null) {
|
||||
println("Failed to decode texture file: ${textureFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
// Load the specific sprite data file
|
||||
val spriteDataFile = File(spriteBaseDir, "sprites/${spriteName}.json")
|
||||
if (!spriteDataFile.exists()) {
|
||||
println("Sprite data file not found: ${spriteDataFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
val spriteDataJson = spriteDataFile.readText()
|
||||
val spriteData = gson.fromJson(spriteDataJson, SpriteData::class.java)
|
||||
|
||||
// Debug: Print sprite coordinates
|
||||
println("Sprite coordinates: x=${spriteData.texture_rect.x}, y=${spriteData.texture_rect.y}, width=${spriteData.texture_rect.width}, height=${spriteData.texture_rect.height}")
|
||||
println("Texture dimensions: width=${fullBitmap.width}, height=${fullBitmap.height}")
|
||||
|
||||
// Calculate the correct Y coordinate (inverted coordinate system)
|
||||
val correctedY = fullBitmap.height - spriteData.texture_rect.y.toInt() - spriteData.texture_rect.height.toInt()
|
||||
|
||||
// Extract the sprite from the atlas using texture_rect coordinates
|
||||
val spriteBitmap = Bitmap.createBitmap(
|
||||
fullBitmap,
|
||||
spriteData.texture_rect.x.toInt(),
|
||||
correctedY,
|
||||
spriteData.texture_rect.width.toInt(),
|
||||
spriteData.texture_rect.height.toInt()
|
||||
)
|
||||
|
||||
// Ensure the bitmap is not scaled and has proper quality
|
||||
val finalBitmap = if (spriteBitmap.width != spriteData.texture_rect.width.toInt() ||
|
||||
spriteBitmap.height != spriteData.texture_rect.height.toInt()) {
|
||||
// If the bitmap was scaled during creation, create a new one with exact dimensions
|
||||
Bitmap.createScaledBitmap(spriteBitmap,
|
||||
spriteData.texture_rect.width.toInt(),
|
||||
spriteData.texture_rect.height.toInt(),
|
||||
false) // false = no filtering/interpolation
|
||||
} else {
|
||||
spriteBitmap
|
||||
}
|
||||
|
||||
println("Extracted sprite dimensions: ${finalBitmap.width}x${finalBitmap.height}")
|
||||
|
||||
// Cache the result
|
||||
spriteCache[cacheKey] = finalBitmap
|
||||
|
||||
return finalBitmap
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCache() {
|
||||
spriteCache.clear()
|
||||
}
|
||||
|
||||
// Helper method to get available sprites for an atlas
|
||||
fun getAvailableSprites(atlasName: String): List<String> {
|
||||
try {
|
||||
val spritesDir = File(getSpriteBaseDir(), "sprites")
|
||||
if (!spritesDir.exists()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val spriteFiles = spritesDir.listFiles { file ->
|
||||
file.name.startsWith("${atlasName}_sprite_") && file.name.endsWith(".json")
|
||||
} ?: emptyArray()
|
||||
|
||||
return spriteFiles.map { file ->
|
||||
// Extract sprite number from filename (e.g., "dim000_mon01_sprite_00.json" -> "00")
|
||||
val spriteNumber = file.name.substringAfter("_sprite_").substringBefore(".json")
|
||||
spriteNumber
|
||||
}.sorted()
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to get available atlases
|
||||
fun getAvailableAtlases(): List<String> {
|
||||
try {
|
||||
val texturesDir = File(getSpriteBaseDir(), "extracted_textures")
|
||||
if (!texturesDir.exists()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val textureFiles = texturesDir.listFiles { file ->
|
||||
file.name.endsWith(".png")
|
||||
} ?: emptyArray()
|
||||
|
||||
return textureFiles.map { file ->
|
||||
// Extract atlas name from filename (e.g., "dim000_mon01.png" -> "dim000_mon01")
|
||||
file.name.substringBefore(".png")
|
||||
}.sorted()
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import kotlinx.coroutines.delay
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
|
||||
enum class DigimonAnimationType {
|
||||
IDLE,
|
||||
IDLE2,
|
||||
WALK,
|
||||
WALK2,
|
||||
RUN,
|
||||
RUN2,
|
||||
WORKOUT,
|
||||
WORKOUT2,
|
||||
HAPPY,
|
||||
SLEEP,
|
||||
ATTACK,
|
||||
FLEE
|
||||
}
|
||||
|
||||
data class AnimationState(
|
||||
val type: DigimonAnimationType,
|
||||
val frameNumber: Int, // 1-12 for individual PNG files
|
||||
val duration: Long = 100L, // Duration in milliseconds
|
||||
val loop: Boolean = true
|
||||
)
|
||||
|
||||
class DigimonAnimationStateMachine(
|
||||
private val characterId: String,
|
||||
private val context: Context,
|
||||
private val initialFrameOffset: Int = 0, // New parameter for offsetting the starting frame
|
||||
private val timingOffset: Long = 0L // New parameter for offsetting the timing
|
||||
) {
|
||||
var currentAnimation by mutableStateOf<DigimonAnimationType>(DigimonAnimationType.IDLE)
|
||||
private set
|
||||
|
||||
var currentFrameNumber by mutableStateOf(1)
|
||||
private set
|
||||
|
||||
var isPlaying by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
// Direct mapping of frame numbers (1-12) to animation types
|
||||
// This is based on the standard Digimon sprite frame order
|
||||
private val frameToAnimationType = mapOf(
|
||||
1 to DigimonAnimationType.IDLE,
|
||||
2 to DigimonAnimationType.IDLE2,
|
||||
3 to DigimonAnimationType.WALK,
|
||||
4 to DigimonAnimationType.WALK2,
|
||||
5 to DigimonAnimationType.RUN,
|
||||
6 to DigimonAnimationType.RUN2,
|
||||
7 to DigimonAnimationType.WORKOUT,
|
||||
8 to DigimonAnimationType.WORKOUT2,
|
||||
9 to DigimonAnimationType.HAPPY,
|
||||
10 to DigimonAnimationType.SLEEP,
|
||||
11 to DigimonAnimationType.ATTACK,
|
||||
12 to DigimonAnimationType.FLEE
|
||||
)
|
||||
|
||||
// Reverse mapping for getting frame numbers for each animation type
|
||||
private val animationTypeToFrames = frameToAnimationType.entries.groupBy({ it.value }, { it.key })
|
||||
|
||||
// Animation durations for each type
|
||||
private val animationDurations = mapOf(
|
||||
DigimonAnimationType.IDLE to 750L,
|
||||
DigimonAnimationType.IDLE2 to 750L,
|
||||
DigimonAnimationType.WALK to 200L,
|
||||
DigimonAnimationType.WALK2 to 200L,
|
||||
DigimonAnimationType.RUN to 150L,
|
||||
DigimonAnimationType.RUN2 to 150L,
|
||||
DigimonAnimationType.WORKOUT to 300L,
|
||||
DigimonAnimationType.WORKOUT2 to 300L,
|
||||
DigimonAnimationType.HAPPY to 400L,
|
||||
DigimonAnimationType.SLEEP to 1500L,
|
||||
DigimonAnimationType.ATTACK to 650L,
|
||||
DigimonAnimationType.FLEE to 150L
|
||||
)
|
||||
|
||||
/*
|
||||
init {
|
||||
println("Initialized DigimonAnimationStateMachine for character: $characterId with frame offset: $initialFrameOffset, timing offset: $timingOffset")
|
||||
println("Available animation types: ${animationTypeToFrames.keys}")
|
||||
}
|
||||
*/
|
||||
|
||||
suspend fun playAnimation(animationType: DigimonAnimationType) {
|
||||
if (currentAnimation == animationType && isPlaying) {
|
||||
return // Already playing this animation
|
||||
}
|
||||
|
||||
currentAnimation = animationType
|
||||
isPlaying = true
|
||||
|
||||
val frameNumbers = animationTypeToFrames[animationType] ?: listOf(1)
|
||||
val duration = animationDurations[animationType] ?: 100L
|
||||
|
||||
// For non-looping animations like ATTACK, play once and return to IDLE
|
||||
if (animationType == DigimonAnimationType.ATTACK) {
|
||||
currentFrameNumber = frameNumbers.firstOrNull() ?: 1
|
||||
delay(duration)
|
||||
playAnimation(DigimonAnimationType.IDLE)
|
||||
} else {
|
||||
// For looping animations, cycle through frames
|
||||
var frameIndex = 0
|
||||
while (isPlaying && currentAnimation == animationType) {
|
||||
val frameNumber = frameNumbers[frameIndex % frameNumbers.size]
|
||||
currentFrameNumber = frameNumber
|
||||
delay(duration)
|
||||
frameIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special method for idle animation that cycles between IDLE and IDLE2
|
||||
suspend fun playIdleAnimation() {
|
||||
if (currentAnimation == DigimonAnimationType.IDLE && isPlaying) {
|
||||
return // Already playing idle animation
|
||||
}
|
||||
|
||||
currentAnimation = DigimonAnimationType.IDLE
|
||||
isPlaying = true
|
||||
|
||||
val idleFrames = animationTypeToFrames[DigimonAnimationType.IDLE] ?: listOf(1)
|
||||
val idle2Frames = animationTypeToFrames[DigimonAnimationType.IDLE2] ?: listOf(2)
|
||||
|
||||
// Combine frames for cycling idle animation
|
||||
val combinedFrames = (idleFrames + idle2Frames).distinct()
|
||||
|
||||
val duration = animationDurations[DigimonAnimationType.IDLE] ?: 500L
|
||||
|
||||
// Apply initial timing offset
|
||||
if (timingOffset > 0L) {
|
||||
delay(timingOffset)
|
||||
}
|
||||
|
||||
// Cycle through idle frames, starting from the offset
|
||||
var frameIndex = initialFrameOffset
|
||||
while (isPlaying && currentAnimation == DigimonAnimationType.IDLE) {
|
||||
val frameNumber = combinedFrames[frameIndex % combinedFrames.size]
|
||||
currentFrameNumber = frameNumber
|
||||
delay(duration)
|
||||
frameIndex++
|
||||
}
|
||||
}
|
||||
|
||||
fun stopAnimation() {
|
||||
isPlaying = false
|
||||
}
|
||||
|
||||
fun getCurrentFrame(): Int {
|
||||
return currentFrameNumber
|
||||
}
|
||||
|
||||
fun getCurrentCharacterId(): String {
|
||||
return characterId
|
||||
}
|
||||
|
||||
// Method to reload mappings (useful for testing)
|
||||
fun reloadMappings() {
|
||||
println("Reloading mappings for character: $characterId")
|
||||
// No need to reload since we use direct frame mapping
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun HitEffectOverlay(
|
||||
isVisible: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
isPlayerScreen: Boolean = false,
|
||||
onAnimationComplete: () -> Unit = {}
|
||||
) {
|
||||
if (!isVisible) return
|
||||
|
||||
val context = LocalContext.current
|
||||
val configuration = LocalConfiguration.current
|
||||
val isLandscapeMode = configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE
|
||||
val hitEffectManager = remember { HitEffectSpriteManager(context) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var currentFrame by remember { mutableStateOf(0) }
|
||||
var currentSprite by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
|
||||
var animationProgress by remember { mutableStateOf(0f) }
|
||||
var scale by remember { mutableStateOf(0.5f) }
|
||||
var alpha by remember { mutableStateOf(1f) }
|
||||
|
||||
LaunchedEffect(isVisible) {
|
||||
if (isVisible) {
|
||||
|
||||
// Add delay before starting hit effect animation
|
||||
delay(400) // Increased from 200ms to 400ms delay before hit effect appears
|
||||
|
||||
// Randomly choose between hit_01, hit_02, and hit_02_white
|
||||
val hitSpriteName = when (kotlin.random.Random.nextInt(3)) {
|
||||
0 -> "hit_01"
|
||||
1 -> "hit_02"
|
||||
else -> "hit_02_white"
|
||||
}
|
||||
currentSprite = hitEffectManager.loadHitSprite(hitSpriteName)
|
||||
|
||||
if (currentSprite != null) {
|
||||
// Animate the hit effect
|
||||
animationProgress = 0f
|
||||
scale = 0.5f
|
||||
alpha = 1f
|
||||
|
||||
// Scale up animation - slowed down
|
||||
while (scale < 1.2f) {
|
||||
scale += 0.05f // Reduced from 0.1f to 0.05f
|
||||
delay(32) // Increased from 16ms to 32ms
|
||||
}
|
||||
|
||||
// Hold for a moment - increased duration
|
||||
delay(300) // Increased from 100ms to 300ms
|
||||
|
||||
// Fade out - slowed down
|
||||
while (alpha > 0f) {
|
||||
alpha -= 0.03f // Reduced from 0.05f to 0.03f
|
||||
delay(32) // Increased from 16ms to 32ms
|
||||
}
|
||||
|
||||
println("DEBUG: Hit effect animation completed")
|
||||
onAnimationComplete()
|
||||
} else {
|
||||
println("DEBUG: Failed to load hit sprite")
|
||||
onAnimationComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentSprite?.let { sprite ->
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
bitmap = sprite.asImageBitmap(),
|
||||
contentDescription = "Hit Effect",
|
||||
modifier = Modifier
|
||||
.size((sprite.width * scale).dp, (sprite.height * scale).dp)
|
||||
.offset(
|
||||
x = if (isPlayerScreen) {
|
||||
// On player screen, position further to the left
|
||||
if (isLandscapeMode) {
|
||||
// In landscape mode, move even further left for player screen
|
||||
(-sprite.width * scale / 2 - 300).dp
|
||||
} else {
|
||||
// In portrait mode, use original positioning
|
||||
(-sprite.width * scale / 2 - 100).dp
|
||||
}
|
||||
} else {
|
||||
// On enemy screen, position further to the right
|
||||
if (isLandscapeMode) {
|
||||
// In landscape mode, move even further right for enemy screen
|
||||
(-sprite.width * scale / 2 + 350).dp
|
||||
} else {
|
||||
// In portrait mode, use original positioning
|
||||
(-sprite.width * scale / 2 + 150).dp
|
||||
}
|
||||
},
|
||||
y = (-sprite.height * scale / 2 + 40).dp // Position lower on screen (was -60, now +40)
|
||||
),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Rect
|
||||
import android.os.Environment
|
||||
import java.io.File
|
||||
|
||||
class HitEffectSpriteManager(private val context: Context) {
|
||||
private val spriteCache = mutableMapOf<String, Bitmap>()
|
||||
|
||||
// Get the external storage directory for hit effect sprites
|
||||
private fun getHitSpritesDir(): File {
|
||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
||||
return File(externalDir, "VBHelper/battle_sprites/extracted_hit_sprites")
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a hit sprite (hit_01.png, hit_02.png, hit_02_white.png)
|
||||
* @param spriteName The sprite name (e.g., "hit_01", "hit_02", "hit_02_white")
|
||||
* @return Bitmap of the hit sprite, or null if not found
|
||||
*/
|
||||
fun loadHitSprite(spriteName: String): Bitmap? {
|
||||
val cacheKey = "hit_$spriteName"
|
||||
|
||||
// Check cache first
|
||||
if (spriteCache.containsKey(cacheKey)) {
|
||||
return spriteCache[cacheKey]
|
||||
}
|
||||
|
||||
try {
|
||||
val hitSpritesDir = getHitSpritesDir()
|
||||
val spriteFile = File(hitSpritesDir, "$spriteName.png")
|
||||
|
||||
if (!spriteFile.exists()) {
|
||||
println("Hit sprite file not found: ${spriteFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
val bitmap = BitmapFactory.decodeFile(spriteFile.absolutePath)
|
||||
if (bitmap == null) {
|
||||
println("Failed to decode hit sprite file: ${spriteFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
spriteCache[cacheKey] = bitmap
|
||||
|
||||
return bitmap
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("Error loading hit sprite: ${e.message}")
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a damage effect sprite from spritesheet
|
||||
* @param spritesheetName The spritesheet name (e.g., "dmg_ef1", "dmg_ef2")
|
||||
* @param frameIndex The frame index (0-3 for dmg_ef1 and dmg_ef2, 0 for dmg_ef3)
|
||||
* @return Bitmap of the damage effect frame, or null if not found
|
||||
*/
|
||||
fun loadDamageEffectSprite(spritesheetName: String, frameIndex: Int = 0): Bitmap? {
|
||||
val cacheKey = "dmg_${spritesheetName}_frame_${frameIndex}"
|
||||
|
||||
// Check cache first
|
||||
if (spriteCache.containsKey(cacheKey)) {
|
||||
return spriteCache[cacheKey]
|
||||
}
|
||||
|
||||
try {
|
||||
val spritesheetFile = File(getHitSpritesDir(), "$spritesheetName.png")
|
||||
|
||||
if (!spritesheetFile.exists()) {
|
||||
println("Damage effect spritesheet not found: ${spritesheetFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
val spritesheet = BitmapFactory.decodeFile(spritesheetFile.absolutePath)
|
||||
if (spritesheet == null) {
|
||||
println("Failed to decode damage effect spritesheet: ${spritesheetFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
// Extract frame from spritesheet
|
||||
val frameBitmap = when (spritesheetName) {
|
||||
"dmg_ef1", "dmg_ef2" -> {
|
||||
// These are 2x2 spritesheets (4 frames)
|
||||
val frameWidth = spritesheet.width / 2
|
||||
val frameHeight = spritesheet.height / 2
|
||||
val row = frameIndex / 2
|
||||
val col = frameIndex % 2
|
||||
val x = col * frameWidth
|
||||
val y = row * frameHeight
|
||||
Bitmap.createBitmap(spritesheet, x, y, frameWidth, frameHeight)
|
||||
}
|
||||
"dmg_ef3" -> {
|
||||
// This is a single sprite
|
||||
spritesheet
|
||||
}
|
||||
else -> {
|
||||
println("Unknown spritesheet name: $spritesheetName")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
println("Successfully loaded damage effect frame: $spritesheetName frame $frameIndex (${frameBitmap.width}x${frameBitmap.height})")
|
||||
|
||||
// Cache the result
|
||||
spriteCache[cacheKey] = frameBitmap
|
||||
|
||||
return frameBitmap
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("Error loading damage effect sprite: ${e.message}")
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available hit sprites
|
||||
* @return List of hit sprite names (without .png extension)
|
||||
*/
|
||||
fun getAvailableHitSprites(): List<String> {
|
||||
val hitSpritesDir = getHitSpritesDir()
|
||||
|
||||
if (!hitSpritesDir.exists()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return hitSpritesDir.listFiles { file ->
|
||||
file.name.startsWith("hit_") && file.name.endsWith(".png")
|
||||
}?.map { file ->
|
||||
file.name.substringBefore(".png")
|
||||
}?.sorted() ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available damage effect spritesheet names
|
||||
* @return List of available damage effect spritesheet names
|
||||
*/
|
||||
fun getAvailableDamageEffectSpritesheets(): List<String> {
|
||||
try {
|
||||
if (!getHitSpritesDir().exists()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val dmgFiles = getHitSpritesDir().listFiles { file ->
|
||||
file.name.startsWith("dmg_ef") && file.name.endsWith(".png")
|
||||
} ?: emptyArray()
|
||||
|
||||
return dmgFiles.map { file ->
|
||||
file.name.substringBefore(".png")
|
||||
}.sorted()
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("Error getting available damage effect spritesheets: ${e.message}")
|
||||
e.printStackTrace()
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the sprite cache
|
||||
*/
|
||||
fun clearCache() {
|
||||
spriteCache.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Environment
|
||||
import java.io.File
|
||||
|
||||
class IndividualSpriteManager(private val context: Context) {
|
||||
private val spriteCache = mutableMapOf<String, Bitmap>()
|
||||
|
||||
// Get the external storage directory for sprite files
|
||||
private fun getSpriteBaseDir(): File {
|
||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
||||
return File(externalDir, "VBHelper/battle_sprites/extracted_assets/sprites")
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a specific sprite frame for a character
|
||||
* @param characterId The character ID (e.g., "dim012_mon03")
|
||||
* @param frameNumber The frame number (1-12)
|
||||
* @return Bitmap of the sprite frame, or null if not found
|
||||
*/
|
||||
fun loadSpriteFrame(characterId: String, frameNumber: Int): Bitmap? {
|
||||
val cacheKey = "${characterId}_frame_${frameNumber}"
|
||||
|
||||
// Check cache first
|
||||
if (spriteCache.containsKey(cacheKey)) {
|
||||
return spriteCache[cacheKey]
|
||||
}
|
||||
|
||||
// Debug: Check if base directory exists
|
||||
val spriteBaseDir = getSpriteBaseDir()
|
||||
if (!spriteBaseDir.exists()) {
|
||||
println("Sprite base directory does not exist: ${spriteBaseDir.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
// Construct the sprite file path
|
||||
val spriteFileName = "${characterId}_${String.format("%02d", frameNumber)}.png"
|
||||
val spriteFile = File(spriteBaseDir, "$characterId/$spriteFileName")
|
||||
|
||||
if (!spriteFile.exists()) {
|
||||
println("Sprite file not found: ${spriteFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
// Load the PNG file directly
|
||||
val bitmap = BitmapFactory.decodeFile(spriteFile.absolutePath)
|
||||
if (bitmap == null) {
|
||||
println("Failed to decode sprite file: ${spriteFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
spriteCache[cacheKey] = bitmap
|
||||
|
||||
return bitmap
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("Error loading sprite frame: ${e.message}")
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available sprite frames for a character
|
||||
* @param characterId The character ID
|
||||
* @return List of frame numbers (1-12) that exist for this character
|
||||
*/
|
||||
fun getAvailableFrames(characterId: String): List<Int> {
|
||||
val spriteBaseDir = getSpriteBaseDir()
|
||||
val characterDir = File(spriteBaseDir, characterId)
|
||||
|
||||
if (!characterDir.exists()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val spriteFiles = characterDir.listFiles { file ->
|
||||
file.name.startsWith("${characterId}_") && file.name.endsWith(".png")
|
||||
} ?: emptyArray()
|
||||
|
||||
return spriteFiles.mapNotNull { file ->
|
||||
val fileName = file.name
|
||||
val frameMatch = Regex("${characterId}_(\\d{2})\\.png").find(fileName)
|
||||
frameMatch?.groupValues?.get(1)?.toIntOrNull()
|
||||
}.sorted()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available character IDs
|
||||
* @return List of character IDs that have sprite directories
|
||||
*/
|
||||
fun getAvailableCharacters(): List<String> {
|
||||
val spriteBaseDir = getSpriteBaseDir()
|
||||
|
||||
if (!spriteBaseDir.exists()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return spriteBaseDir.listFiles { file ->
|
||||
file.isDirectory && file.listFiles()?.any { it.name.endsWith(".png") } == true
|
||||
}?.map { it.name }?.sorted() ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the sprite cache
|
||||
*/
|
||||
fun clearCache() {
|
||||
spriteCache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character has sprite files
|
||||
* @param characterId The character ID to check
|
||||
* @return true if the character has sprite files, false otherwise
|
||||
*/
|
||||
fun hasCharacterSprites(characterId: String): Boolean {
|
||||
val characterDir = File(getSpriteBaseDir(), characterId)
|
||||
if (!characterDir.exists()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val spriteFiles = characterDir.listFiles { file ->
|
||||
file.name.startsWith("${characterId}_") && file.name.endsWith(".png")
|
||||
} ?: emptyArray()
|
||||
|
||||
return spriteFiles.isNotEmpty()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface OpponentService {
|
||||
@GET("api/opponents")
|
||||
// This method returns a Call object with a generic
|
||||
// type of DataModel, which represents
|
||||
// the data model for the response.
|
||||
fun getopponents(@Query("stage") stage: String): Call<OpponentsDataModel>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
data class OpponentsDataModel (
|
||||
val opponentsList: ArrayList<APIBattleCharacter>
|
||||
):java.io.Serializable
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
data class PVPDataModel (
|
||||
val status: String,
|
||||
val state: Int,
|
||||
val currentRound: Int,
|
||||
val playerHP: Int,
|
||||
val opponentHP: Int,
|
||||
val playerAttackHit: Boolean,
|
||||
val playerAttackDamage: Int,
|
||||
val opponentAttackDamage: Int,
|
||||
val winner: String,
|
||||
val opponentCharaId: String? = null, // Server provides opponent's charaId from the match
|
||||
val playerMaxHP: Int? = null, // Server should provide max HP for resumed matches
|
||||
val opponentMaxHP: Int? = null // Server should provide max HP for resumed matches
|
||||
):java.io.Serializable
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface PVPService {
|
||||
@GET("api/pvp")
|
||||
// This method returns a Call object with a generic
|
||||
// type of DataModel, which represents
|
||||
// the data model for the response.
|
||||
fun getwinner(
|
||||
@Query("apiStage") apiStage: Int,
|
||||
@Query("playerID") playerID: Long,
|
||||
@Query("playerDigi") playerDigi: String,
|
||||
@Query("playerStage") playerStage: Int,
|
||||
@Query("critBar") critBar: Int,
|
||||
@Query("opponentDigi") opponentDigi: String,
|
||||
@Query("opponentStage") opponentStage: Int,
|
||||
@Query("action") action: String? = null // Optional: "quit" or "rejoin"
|
||||
): Call<PVPDataModel>
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.content.Context
|
||||
import retrofit2.Retrofit
|
||||
import android.widget.Toast
|
||||
import retrofit2.*
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import com.github.nacabaro.vbhelper.battle.BattleAuthContainer
|
||||
|
||||
class RetrofitHelper {
|
||||
|
||||
/**
|
||||
* Creates an OkHttpClient with authentication interceptor for game endpoints.
|
||||
* Requires a non-null, non-empty token.
|
||||
*/
|
||||
private fun createAuthenticatedClient(token: String): OkHttpClient {
|
||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(AuthInterceptor(token))
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session token from AuthRepository for API calls.
|
||||
* Falls back to nacatech token if session token is not available (backward compatibility).
|
||||
*/
|
||||
private fun getAuthToken(context: Context): String? {
|
||||
return try {
|
||||
val authContainer = BattleAuthContainer(context)
|
||||
runBlocking {
|
||||
// Prefer session token, fall back to nacatech token for backward compatibility
|
||||
val sessionToken = authContainer.authRepository.sessionToken.first()
|
||||
if (!sessionToken.isNullOrEmpty()) {
|
||||
println("RetrofitHelper: Using sessionToken for API call")
|
||||
sessionToken
|
||||
} else {
|
||||
// Fallback to nacatech token (slower, but works)
|
||||
val nacatechToken = authContainer.authRepository.authToken.first()
|
||||
if (!nacatechToken.isNullOrEmpty()) {
|
||||
println("RetrofitHelper: No sessionToken found, falling back to nacatechToken")
|
||||
}
|
||||
nacatechToken
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("RetrofitHelper: Error getting auth token: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Retrofit instance with authentication for game endpoints.
|
||||
*/
|
||||
private fun createAuthenticatedRetrofit(context: Context): Retrofit? {
|
||||
val token = getAuthToken(context)
|
||||
if (token.isNullOrEmpty()) {
|
||||
println("RetrofitHelper: No auth token available")
|
||||
return null
|
||||
}
|
||||
|
||||
val client = createAuthenticatedClient(token)
|
||||
return Retrofit.Builder()
|
||||
.baseUrl("http://battle.io-void.com:8080/")
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles HTTP error responses (401, 403, 429).
|
||||
* For 401/403, clears authentication state to trigger re-authentication.
|
||||
*/
|
||||
private fun handleErrorResponse(context: Context, response: Response<*>, errorMessage: String) {
|
||||
when (response.code()) {
|
||||
401 -> {
|
||||
println("RetrofitHelper: Authentication failed (401) - token may be expired")
|
||||
clearAuthAndNotify(context, "Authentication failed. Please log in again.")
|
||||
}
|
||||
403 -> {
|
||||
println("RetrofitHelper: Access forbidden (403) - token may be expired or invalid")
|
||||
// 403 could mean expired token, so clear auth state to trigger re-authentication
|
||||
clearAuthAndNotify(context, "Session expired. Please log in again.")
|
||||
}
|
||||
429 -> {
|
||||
println("RetrofitHelper: Rate limit exceeded (429)")
|
||||
Toast.makeText(context, "Too many requests. Please wait a moment.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
else -> {
|
||||
println("RetrofitHelper: API error (${response.code()}): $errorMessage")
|
||||
Toast.makeText(context, "Request failed: ${response.code()}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears authentication state and shows a message.
|
||||
* This will trigger BattlesScreen to detect the auth state change and open the login page.
|
||||
*/
|
||||
private fun clearAuthAndNotify(context: Context, message: String) {
|
||||
try {
|
||||
val authContainer = BattleAuthContainer(context)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
authContainer.authRepository.logout()
|
||||
println("RetrofitHelper: Cleared authentication state due to expired/invalid token")
|
||||
}
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
println("RetrofitHelper: Error clearing auth state: ${e.message}")
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun getOpponents(context: Context, stage: String, callback: (OpponentsDataModel) -> Unit) {
|
||||
//println("RetrofitHelper: Starting API call for stage: $stage")
|
||||
|
||||
try {
|
||||
// Create an authenticated Retrofit instance
|
||||
val retrofit = createAuthenticatedRetrofit(context)
|
||||
if (retrofit == null) {
|
||||
println("RetrofitHelper: Cannot create authenticated Retrofit - no token available")
|
||||
Toast.makeText(context, "Authentication required. Please log in.", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
// Create an ApiService instance from the Retrofit instance.
|
||||
val service: OpponentService = retrofit.create<OpponentService>(OpponentService::class.java)
|
||||
//println("RetrofitHelper: Service created")
|
||||
|
||||
// Call the getopponents() method of the ApiService
|
||||
// to make an API request.
|
||||
val call: Call<OpponentsDataModel> = service.getopponents(stage)
|
||||
//println("RetrofitHelper: API call created, enqueueing...")
|
||||
|
||||
// Use the enqueue() method of the Call object to
|
||||
// make an asynchronous API request.
|
||||
call.enqueue(object : Callback<OpponentsDataModel> {
|
||||
override fun onFailure(call: Call<OpponentsDataModel>, t: Throwable) {
|
||||
println("RetrofitHelper: API call failed: ${t.message}")
|
||||
t.printStackTrace()
|
||||
Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<OpponentsDataModel>, response: Response<OpponentsDataModel>) {
|
||||
println("RetrofitHelper: API response received - Code: ${response.code()}")
|
||||
println("RetrofitHelper: Response body: ${response.body()}")
|
||||
|
||||
if(response.isSuccessful){
|
||||
//println("RetrofitHelper: Response successful, calling callback")
|
||||
val opponentsList: OpponentsDataModel = response.body() as OpponentsDataModel
|
||||
callback(opponentsList)
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string()
|
||||
println("RetrofitHelper: Response not successful - Error: $errorBody")
|
||||
handleErrorResponse(context, response, errorBody ?: "Unknown error")
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
println("RetrofitHelper: Exception in getOpponents: ${e.message}")
|
||||
e.printStackTrace()
|
||||
Toast.makeText(context, "Request failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fun getCombatWinner(context: Context, stage: String, callback: (CombatDataModel) -> Unit) {
|
||||
|
||||
// Create a Retrofit instance with the base URL and
|
||||
// a GsonConverterFactory for parsing the response.
|
||||
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://battle.io-void.com:8080/").addConverterFactory(
|
||||
GsonConverterFactory.create()).build()
|
||||
|
||||
// Create an ApiService instance from the Retrofit instance.
|
||||
val service: CombatService = retrofit.create<CombatService>(CombatService::class.java)
|
||||
|
||||
// Call the getwinner() method of the ApiService
|
||||
// to make an API request.
|
||||
val call: Call<CombatDataModel> = service.getwinner(stage)
|
||||
|
||||
// Use the enqueue() method of the Call object to
|
||||
// make an asynchronous API request.
|
||||
call.enqueue(object : Callback<CombatDataModel> {
|
||||
// This is an anonymous inner class that implements the Callback interface.
|
||||
|
||||
override fun onFailure(call: Call<CombatDataModel>, t: Throwable) {
|
||||
// This method is called when the API request fails.
|
||||
Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<CombatDataModel>, response: Response<CombatDataModel>) {
|
||||
// This method is called when the API response is received successfully.
|
||||
|
||||
if(response.isSuccessful){
|
||||
// If the response is successful, parse the
|
||||
// response body to a DataModel object.
|
||||
val winner: CombatDataModel = response.body() as CombatDataModel
|
||||
|
||||
// Call the callback function with the DataModel
|
||||
// object as a parameter.
|
||||
callback(winner)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun getBattleWinner(context: Context, playerDigi: String, playerStage: Int, opponentDigi: String, opponentStage: Int, callback: (BattleDataModel) -> Unit) {
|
||||
|
||||
// Create a Retrofit instance with the base URL and
|
||||
// a GsonConverterFactory for parsing the response.
|
||||
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://battle.io-void.com:8080/").addConverterFactory(
|
||||
GsonConverterFactory.create()).build()
|
||||
|
||||
// Create an ApiService instance from the Retrofit instance.
|
||||
val service: BattleService = retrofit.create<BattleService>(BattleService::class.java)
|
||||
|
||||
// Call the getwinner() method of the ApiService
|
||||
// to make an API request.
|
||||
val call: Call<BattleDataModel> = service.getwinner(playerDigi, playerStage, opponentDigi, opponentStage)
|
||||
|
||||
// Use the enqueue() method of the Call object to
|
||||
// make an asynchronous API request.
|
||||
call.enqueue(object : Callback<BattleDataModel> {
|
||||
// This is an anonymous inner class that implements the Callback interface.
|
||||
|
||||
override fun onFailure(call: Call<BattleDataModel>, t: Throwable) {
|
||||
// This method is called when the API request fails.
|
||||
Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<BattleDataModel>, response: Response<BattleDataModel>) {
|
||||
// This method is called when the API response is received successfully.
|
||||
|
||||
if(response.isSuccessful){
|
||||
// If the response is successful, parse the
|
||||
// response body to a DataModel object.
|
||||
val winner: BattleDataModel = response.body() as BattleDataModel
|
||||
|
||||
// Call the callback function with the DataModel
|
||||
// object as a parameter.
|
||||
callback(winner)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
fun getPVPWinner(context: Context, apiStage: Int, playerID: Long, playerDigi: String, playerStage: Int, critBar: Int, opponentDigi: String, opponentStage: Int, callback: (PVPDataModel) -> Unit) {
|
||||
getPVPWinner(context, apiStage, playerID, playerDigi, playerStage, critBar, opponentDigi, opponentStage, null, callback)
|
||||
}
|
||||
|
||||
fun getPVPWinner(context: Context, apiStage: Int, playerID: Long, playerDigi: String, playerStage: Int, critBar: Int, opponentDigi: String, opponentStage: Int, action: String?, callback: (PVPDataModel) -> Unit) {
|
||||
|
||||
try {
|
||||
// Create an authenticated Retrofit instance
|
||||
val retrofit = createAuthenticatedRetrofit(context)
|
||||
if (retrofit == null) {
|
||||
println("RetrofitHelper: Cannot create authenticated Retrofit - no token available")
|
||||
Toast.makeText(context, "Authentication required. Please log in.", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
// Create an ApiService instance from the Retrofit instance.
|
||||
val service: PVPService = retrofit.create<PVPService>(PVPService::class.java)
|
||||
|
||||
// Call the getwinner() method of the ApiService
|
||||
// to make an API request.
|
||||
val call: Call<PVPDataModel> = service.getwinner(apiStage, playerID, playerDigi, playerStage, critBar, opponentDigi, opponentStage, action)
|
||||
|
||||
// Use the enqueue() method of the Call object to
|
||||
// make an asynchronous API request.
|
||||
call.enqueue(object : Callback<PVPDataModel> {
|
||||
// This is an anonymous inner class that implements the Callback interface.
|
||||
|
||||
override fun onFailure(call: Call<PVPDataModel>, t: Throwable) {
|
||||
// This method is called when the API request fails.
|
||||
println("RetrofitHelper: PVP API call failed: ${t.message}")
|
||||
t.printStackTrace()
|
||||
Toast.makeText(context, "Request Fail", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<PVPDataModel>, response: Response<PVPDataModel>) {
|
||||
// This method is called when the API response is received successfully.
|
||||
println("RetrofitHelper: PVP API response received - Code: ${response.code()}")
|
||||
|
||||
if(response.isSuccessful){
|
||||
// If the response is successful, parse the
|
||||
// response body to a DataModel object.
|
||||
val apiResults: PVPDataModel = response.body() as PVPDataModel
|
||||
|
||||
// Call the callback function with the DataModel
|
||||
// object as a parameter.
|
||||
callback(apiResults)
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string()
|
||||
println("RetrofitHelper: PVP API response not successful - Code: ${response.code()}, Error: $errorBody")
|
||||
handleErrorResponse(context, response, errorBody ?: "Unknown error")
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
println("RetrofitHelper: Exception in getPVPWinner: ${e.message}")
|
||||
e.printStackTrace()
|
||||
Toast.makeText(context, "Request failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun authenticate(context: Context, token: String, callback: (AuthenticateResponse) -> Unit) {
|
||||
//println("RetrofitHelper: Starting validate API call with token: $token")
|
||||
|
||||
if (token.isEmpty()) {
|
||||
println("RetrofitHelper: ERROR - Token is empty!")
|
||||
Toast.makeText(context, "Authentication failed: Token is empty", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Add logging interceptor to see the actual HTTP request
|
||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
val okHttpClient = OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.build()
|
||||
|
||||
val retrofit: Retrofit = Retrofit.Builder()
|
||||
.baseUrl("http://battle.io-void.com:8080/")
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
|
||||
val service: AuthService = retrofit.create<AuthService>(AuthService::class.java)
|
||||
val request = AuthenticateRequest(userToken = token)
|
||||
// Use login endpoint instead of validate to get sessionToken
|
||||
val call: Call<AuthenticateResponse> = service.login(request)
|
||||
|
||||
call.enqueue(object : Callback<AuthenticateResponse> {
|
||||
override fun onFailure(call: Call<AuthenticateResponse>, t: Throwable) {
|
||||
println("RetrofitHelper: Validate API call failed: ${t.message}")
|
||||
t.printStackTrace()
|
||||
Toast.makeText(context, "Authentication failed: ${t.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<AuthenticateResponse>, response: Response<AuthenticateResponse>) {
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val authResponse: AuthenticateResponse? = response.body()
|
||||
if (authResponse != null) {
|
||||
callback(authResponse)
|
||||
} else {
|
||||
println("RetrofitHelper: Validation failed: Invalid response body")
|
||||
Toast.makeText(context, "Authentication failed: Invalid response", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string()
|
||||
println("RetrofitHelper: Validate response not successful - Code: ${response.code()}, Error: $errorBody")
|
||||
Toast.makeText(context, "Authentication failed: ${response.code()}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
println("RetrofitHelper: Exception in validate: ${e.message}")
|
||||
e.printStackTrace()
|
||||
Toast.makeText(context, "Authentication failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.FileInputStream
|
||||
|
||||
class SpriteFileManager(private val context: Context) {
|
||||
|
||||
// Get the external storage directory where files are already located
|
||||
fun getExternalSpriteBaseDir(): File {
|
||||
val externalDir = android.os.Environment.getExternalStorageDirectory()
|
||||
return File(externalDir, "VBHelper/battle_sprites")
|
||||
}
|
||||
|
||||
// Get the internal storage directory for sprite files
|
||||
private fun getInternalSpriteBaseDir(): File {
|
||||
return File(context.filesDir, "battle_sprites")
|
||||
}
|
||||
|
||||
fun copySpriteFilesToInternalStorage() {
|
||||
try {
|
||||
println("Starting sprite file copy process from external storage to internal storage...")
|
||||
|
||||
val externalDir = getExternalSpriteBaseDir()
|
||||
val internalDir = getInternalSpriteBaseDir()
|
||||
|
||||
// Check if external directory exists
|
||||
if (!externalDir.exists()) {
|
||||
println("External sprite directory does not exist: ${externalDir.absolutePath}")
|
||||
return
|
||||
}
|
||||
|
||||
println("External sprite directory exists: ${externalDir.absolutePath}")
|
||||
println("Copying to internal storage: ${internalDir.absolutePath}")
|
||||
|
||||
// Create internal directory if it doesn't exist
|
||||
if (!internalDir.exists()) {
|
||||
val created = internalDir.mkdirs()
|
||||
println("Created internal sprite directory: $created")
|
||||
}
|
||||
|
||||
// Copy all subdirectories from external to internal storage
|
||||
val externalFiles = externalDir.listFiles()
|
||||
if (externalFiles != null) {
|
||||
println("Found ${externalFiles.size} items in external directory")
|
||||
externalFiles.forEach { item ->
|
||||
val targetItem = File(internalDir, item.name)
|
||||
if (item.isDirectory) {
|
||||
println("Copying directory: ${item.name}")
|
||||
copyDirectory(item, targetItem)
|
||||
} else {
|
||||
println("Copying file: ${item.name}")
|
||||
copyFile(item, targetItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("Sprite files copied successfully to internal storage: ${internalDir.absolutePath}")
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("Error copying sprite files to internal storage: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun copySpriteFilesToExternalStorage() {
|
||||
try {
|
||||
println("Starting sprite file copy process to external storage...")
|
||||
|
||||
// Debug: List what's in the assets directory
|
||||
val assetManager = context.assets
|
||||
val battleSpritesFiles = assetManager.list("battle_sprites")
|
||||
println("battle_sprites directory in assets contains: ${battleSpritesFiles?.joinToString(", ")}")
|
||||
|
||||
val extractedAssetsFiles = assetManager.list("battle_sprites/extracted_assets")
|
||||
println("battle_sprites/extracted_assets directory in assets contains: ${extractedAssetsFiles?.joinToString(", ")}")
|
||||
|
||||
// Check specifically for extracted_atksprites in assets (now directly under battle_sprites)
|
||||
val atkspritesInAssets = assetManager.list("battle_sprites/extracted_atksprites")
|
||||
println("extracted_atksprites in assets contains: ${atkspritesInAssets?.size ?: 0} files")
|
||||
if (atkspritesInAssets != null && atkspritesInAssets.isNotEmpty()) {
|
||||
println("First few attack files in assets: ${atkspritesInAssets.take(5).joinToString(", ")}")
|
||||
}
|
||||
|
||||
// Check for extracted_battlebgs in assets (now directly under battle_sprites)
|
||||
val battlebgsInAssets = assetManager.list("battle_sprites/extracted_battlebgs")
|
||||
println("extracted_battlebgs in assets contains: ${battlebgsInAssets?.size ?: 0} files")
|
||||
if (battlebgsInAssets != null && battlebgsInAssets.isNotEmpty()) {
|
||||
println("First few battle background files in assets: ${battlebgsInAssets.take(5).joinToString(", ")}")
|
||||
}
|
||||
|
||||
// Try to list all possible subdirectories in battle_sprites
|
||||
println("Checking all possible subdirectories in battle_sprites...")
|
||||
battleSpritesFiles?.forEach { subdir ->
|
||||
try {
|
||||
val subdirFiles = assetManager.list("battle_sprites/$subdir")
|
||||
println(" $subdir contains: ${subdirFiles?.size ?: 0} files")
|
||||
if (subdirFiles != null && subdirFiles.isNotEmpty()) {
|
||||
println(" First few files: ${subdirFiles.take(3).joinToString(", ")}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println(" Error listing $subdir: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// Create the base directory for battle_sprites in external storage
|
||||
val battleSpritesDir = getExternalSpriteBaseDir()
|
||||
if (!battleSpritesDir.exists()) {
|
||||
battleSpritesDir.mkdirs()
|
||||
println("Created battle_sprites directory in external storage: ${battleSpritesDir.absolutePath}")
|
||||
} else {
|
||||
println("battle_sprites directory already exists in external storage: ${battleSpritesDir.absolutePath}")
|
||||
}
|
||||
|
||||
// Copy all subdirectories from battle_sprites assets to external storage
|
||||
println("Copying all battle_sprites subdirectories to external storage...")
|
||||
battleSpritesFiles?.forEach { subdir ->
|
||||
val sourcePath = "battle_sprites/$subdir"
|
||||
val targetDir = File(battleSpritesDir, subdir)
|
||||
println("Copying $sourcePath to ${targetDir.absolutePath}")
|
||||
copyAssetDirectory(sourcePath, targetDir)
|
||||
}
|
||||
|
||||
println("Sprite files copied successfully to external storage: ${battleSpritesDir.absolutePath}")
|
||||
|
||||
// Verify that attack sprites were copied
|
||||
val atkspritesDir = File(battleSpritesDir, "extracted_atksprites")
|
||||
if (atkspritesDir.exists()) {
|
||||
val attackFiles = atkspritesDir.listFiles()
|
||||
println("Attack sprites directory exists with ${attackFiles?.size ?: 0} files")
|
||||
if (attackFiles != null && attackFiles.isNotEmpty()) {
|
||||
println("First few attack files: ${attackFiles.take(5).map { it.name }}")
|
||||
}
|
||||
} else {
|
||||
println("WARNING: extracted_atksprites directory does not exist!")
|
||||
// List what's actually in the battle_sprites directory
|
||||
val battleSpritesContents = battleSpritesDir.listFiles()
|
||||
println("battle_sprites directory contains: ${battleSpritesContents?.map { it.name }?.joinToString(", ")}")
|
||||
}
|
||||
|
||||
// Verify that battle backgrounds were copied
|
||||
val battlebgsDir = File(battleSpritesDir, "extracted_battlebgs")
|
||||
if (battlebgsDir.exists()) {
|
||||
val bgFiles = battlebgsDir.listFiles()
|
||||
println("Battle backgrounds directory exists with ${bgFiles?.size ?: 0} files")
|
||||
if (bgFiles != null && bgFiles.isNotEmpty()) {
|
||||
println("First few battle background files: ${bgFiles.take(5).map { it.name }}")
|
||||
}
|
||||
} else {
|
||||
println("WARNING: extracted_battlebgs directory does not exist!")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("Error copying sprite files to external storage: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyAssetDirectory(assetPath: String, targetDir: File) {
|
||||
try {
|
||||
val assetManager = context.assets
|
||||
val files = assetManager.list(assetPath) ?: return
|
||||
|
||||
println("Copying asset directory: $assetPath (${files.size} items)")
|
||||
println("Files found: ${files.joinToString(", ")}")
|
||||
|
||||
for (file in files) {
|
||||
val assetFilePath = if (assetPath.isEmpty()) file else "$assetPath/$file"
|
||||
val targetFile = File(targetDir, file)
|
||||
|
||||
// Create subdirectories if needed
|
||||
if (targetFile.parentFile != null && !targetFile.parentFile!!.exists()) {
|
||||
targetFile.parentFile!!.mkdirs()
|
||||
}
|
||||
|
||||
// Check if it's a directory by trying to list its contents
|
||||
try {
|
||||
val subFiles = assetManager.list(assetFilePath)
|
||||
if (subFiles != null && subFiles.isNotEmpty()) {
|
||||
// It's a directory, create it and copy contents
|
||||
println("Copying subdirectory: $assetFilePath (${subFiles.size} files)")
|
||||
if (!targetFile.exists()) {
|
||||
targetFile.mkdirs()
|
||||
}
|
||||
copyAssetDirectory(assetFilePath, targetFile)
|
||||
} else {
|
||||
// It's a file, copy it
|
||||
copyAssetFile(assetFilePath, targetFile)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// If we can't list contents, it's probably a file
|
||||
println("Treating $assetFilePath as file (could not list contents)")
|
||||
copyAssetFile(assetFilePath, targetFile)
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for extracted_atksprites - try to copy it directly if it wasn't found
|
||||
if (assetPath == "battle_sprites/extracted_assets") {
|
||||
println("Special handling: Checking for extracted_atksprites directory...")
|
||||
try {
|
||||
val atkspritesFiles = assetManager.list("battle_sprites/extracted_assets/extracted_atksprites")
|
||||
if (atkspritesFiles != null && atkspritesFiles.isNotEmpty()) {
|
||||
println("Found extracted_atksprites with ${atkspritesFiles.size} files")
|
||||
val atkspritesDir = File(targetDir, "extracted_atksprites")
|
||||
if (!atkspritesDir.exists()) {
|
||||
atkspritesDir.mkdirs()
|
||||
}
|
||||
copyAssetDirectory("battle_sprites/extracted_assets/extracted_atksprites", atkspritesDir)
|
||||
} else {
|
||||
println("extracted_atksprites directory not found in assets")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Error checking extracted_atksprites: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("Error copying asset directory $assetPath: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyDirectory(sourceDir: File, targetDir: File) {
|
||||
if (!targetDir.exists()) {
|
||||
targetDir.mkdirs()
|
||||
}
|
||||
|
||||
val files = sourceDir.listFiles()
|
||||
if (files != null) {
|
||||
files.forEach { file ->
|
||||
val targetFile = File(targetDir, file.name)
|
||||
if (file.isDirectory) {
|
||||
copyDirectory(file, targetFile)
|
||||
} else {
|
||||
copyFile(file, targetFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyFile(sourceFile: File, targetFile: File) {
|
||||
try {
|
||||
val inputStream = FileInputStream(sourceFile)
|
||||
val outputStream = FileOutputStream(targetFile)
|
||||
|
||||
inputStream.copyTo(outputStream)
|
||||
inputStream.close()
|
||||
outputStream.close()
|
||||
|
||||
println("Copied: ${sourceFile.name} -> ${targetFile.absolutePath}")
|
||||
} catch (e: IOException) {
|
||||
println("Error copying file ${sourceFile.name}: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyAssetFile(assetPath: String, targetFile: File) {
|
||||
try {
|
||||
val inputStream = context.assets.open(assetPath)
|
||||
val outputStream = FileOutputStream(targetFile)
|
||||
|
||||
inputStream.copyTo(outputStream)
|
||||
inputStream.close()
|
||||
outputStream.close()
|
||||
|
||||
println("Copied: $assetPath -> ${targetFile.absolutePath}")
|
||||
} catch (e: IOException) {
|
||||
println("Error copying asset file $assetPath: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun checkSpriteFilesExist(): Boolean {
|
||||
val battleSpritesDir = getExternalSpriteBaseDir()
|
||||
val extractedAssetsDir = File(battleSpritesDir, "extracted_assets")
|
||||
val extractedStatsDir = File(battleSpritesDir, "extracted_digimon_stats")
|
||||
val atkspritesDir = File(battleSpritesDir, "extracted_atksprites")
|
||||
val battlebgsDir = File(battleSpritesDir, "extracted_battlebgs")
|
||||
|
||||
val battleSpritesExist = battleSpritesDir.exists() && battleSpritesDir.listFiles()?.isNotEmpty() == true
|
||||
val assetsExist = extractedAssetsDir.exists() && extractedAssetsDir.listFiles()?.isNotEmpty() == true
|
||||
val statsExist = extractedStatsDir.exists() && extractedStatsDir.listFiles()?.isNotEmpty() == true
|
||||
val atkspritesExist = atkspritesDir.exists() && atkspritesDir.listFiles()?.isNotEmpty() == true
|
||||
val battlebgsExist = battlebgsDir.exists() && battlebgsDir.listFiles()?.isNotEmpty() == true
|
||||
|
||||
/*
|
||||
println("Checking sprite files exist in external storage:")
|
||||
println(" battle_sprites exists: $battleSpritesExist")
|
||||
println(" extracted_assets exists: $assetsExist")
|
||||
println(" extracted_digimon_stats exists: $statsExist")
|
||||
println(" extracted_atksprites exists: $atkspritesExist")
|
||||
println(" extracted_battlebgs exists: $battlebgsExist")
|
||||
*/
|
||||
|
||||
return battleSpritesExist && assetsExist && statsExist && atkspritesExist && battlebgsExist
|
||||
}
|
||||
|
||||
fun clearSpriteFiles() {
|
||||
try {
|
||||
val battleSpritesDir = getInternalSpriteBaseDir()
|
||||
|
||||
if (battleSpritesDir.exists()) {
|
||||
deleteDirectory(battleSpritesDir)
|
||||
println("Cleared battle_sprites directory from internal storage")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
println("Error clearing sprite files: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDirectory(directory: File) {
|
||||
if (directory.exists()) {
|
||||
val files = directory.listFiles()
|
||||
if (files != null) {
|
||||
for (file in files) {
|
||||
if (file.isDirectory) {
|
||||
deleteDirectory(file)
|
||||
} else {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
directory.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
@Composable
|
||||
fun SpriteImage(
|
||||
characterId: String,
|
||||
frameNumber: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
contentScale: ContentScale = ContentScale.Fit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val spriteManager = remember { IndividualSpriteManager(context) }
|
||||
|
||||
var bitmap by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
|
||||
|
||||
LaunchedEffect(characterId, frameNumber) {
|
||||
println("Loading sprite frame: $frameNumber for character: $characterId")
|
||||
bitmap = spriteManager.loadSpriteFrame(characterId, frameNumber)
|
||||
if (bitmap == null) {
|
||||
println("Failed to load sprite frame: $frameNumber for character: $characterId")
|
||||
}
|
||||
}
|
||||
|
||||
bitmap?.let { bmp ->
|
||||
Image(
|
||||
bitmap = bmp.asImageBitmap(),
|
||||
contentDescription = "Sprite: $characterId frame $frameNumber",
|
||||
modifier = modifier,
|
||||
contentScale = contentScale
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.github.nacabaro.vbhelper.battle
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.github.nacabaro.vbhelper.battle.AttackSpriteManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun AttackSpriteImage(
|
||||
characterId: String,
|
||||
isLarge: Boolean = false,
|
||||
modifier: Modifier = Modifier,
|
||||
contentScale: ContentScale = ContentScale.Fit
|
||||
) {
|
||||
var bitmap by remember { mutableStateOf<Bitmap?>(null) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(characterId, isLarge) {
|
||||
//println("AttackSpriteImage: Loading attack sprite for characterId=$characterId, isLarge=$isLarge")
|
||||
coroutineScope.launch {
|
||||
val attackSpriteManager = AttackSpriteManager(context)
|
||||
val loadedBitmap = withContext(Dispatchers.IO) {
|
||||
attackSpriteManager.getAttackSprite(characterId, isLarge)
|
||||
}
|
||||
bitmap = loadedBitmap
|
||||
}
|
||||
}
|
||||
|
||||
bitmap?.let { bmp ->
|
||||
Image(
|
||||
bitmap = bmp.asImageBitmap(),
|
||||
contentDescription = "Attack Sprite",
|
||||
modifier = modifier,
|
||||
contentScale = contentScale
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -28,21 +32,31 @@ 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 = 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
|
||||
@@ -55,7 +69,8 @@ fun CharacterEntry(
|
||||
},
|
||||
modifier = modifier
|
||||
.aspectRatio(1f)
|
||||
.padding(8.dp)
|
||||
.padding(8.dp),
|
||||
colors = cardColors
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.BottomCenter,
|
||||
@@ -73,7 +88,24 @@ fun CharacterEntry(
|
||||
},
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,4 +146,117 @@ fun ItemDisplay(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SpecialMissionsEntry(
|
||||
specialMission: SpecialMissions,
|
||||
modifier: Modifier = Modifier,
|
||||
onClickMission: (Long) -> Unit = { },
|
||||
onClickCollect: (Long) -> 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) {
|
||||
{ onClickCollect(specialMission.id) }
|
||||
} else if (specialMission.status == SpecialMission.Status.UNAVAILABLE) {
|
||||
{ }
|
||||
} else {
|
||||
{ onClickMission(specialMission.id) }
|
||||
},
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,8 @@ fun TopBanner(
|
||||
onGearClick: (() -> Unit)? = null,
|
||||
onBackClick: (() -> Unit)? = null,
|
||||
onScanClick: (() -> Unit)? = null,
|
||||
onAdventureClick: (() -> Unit)? = null
|
||||
onAdventureClick: (() -> Unit)? = null,
|
||||
onModifyClick: (() -> Unit)? = null
|
||||
) {
|
||||
Box( // Use Box to overlay elements
|
||||
modifier = modifier
|
||||
@@ -37,16 +38,16 @@ 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"
|
||||
)
|
||||
}
|
||||
@@ -54,23 +55,34 @@ fun TopBanner(
|
||||
IconButton(
|
||||
onClick = onAdventureClick,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd) // Place gear icon at the end
|
||||
.align(Alignment.CenterEnd)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.baseline_fort_24), // Use a gear 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"
|
||||
)
|
||||
}
|
||||
@@ -78,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"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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
|
||||
@@ -18,23 +19,26 @@ interface AdventureDao {
|
||||
""")
|
||||
fun getAdventureCount(): Int
|
||||
|
||||
@Query("""
|
||||
@Query(
|
||||
"""
|
||||
SELECT
|
||||
uc.*,
|
||||
c.stage,
|
||||
c.attribute,
|
||||
c.sprite1 AS spriteIdle,
|
||||
c.spritesWidth AS spriteWidth,
|
||||
c.spritesHeight AS spriteHeight,
|
||||
s.spriteIdle1 AS spriteIdle,
|
||||
s.width AS spriteWidth,
|
||||
s.height AS spriteHeight,
|
||||
d.isBEm as isBemCard,
|
||||
a.finishesAdventure AS finishesAdventure,
|
||||
a.originalDuration AS originalTimeInMinutes
|
||||
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 Sprite s ON s.id = c.spriteId
|
||||
JOIN Card d ON c.cardId = d.id
|
||||
JOIN Adventure a ON uc.id = a.characterId
|
||||
""")
|
||||
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites>
|
||||
"""
|
||||
)
|
||||
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>>
|
||||
|
||||
@Query("""
|
||||
DELETE FROM Adventure
|
||||
|
||||
@@ -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,6 +3,7 @@ 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 {
|
||||
@@ -13,7 +14,7 @@ interface ItemDao {
|
||||
ORDER BY Items.itemIcon ASC
|
||||
"""
|
||||
)
|
||||
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities>
|
||||
fun getAllItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
@@ -22,7 +23,7 @@ interface ItemDao {
|
||||
WHERE quantity > 0
|
||||
"""
|
||||
)
|
||||
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
|
||||
fun getAllUserItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
@@ -31,7 +32,7 @@ interface ItemDao {
|
||||
WHERE Items.id = :itemId
|
||||
"""
|
||||
)
|
||||
fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities
|
||||
suspend fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities
|
||||
|
||||
@Query(
|
||||
"""
|
||||
@@ -40,7 +41,7 @@ interface ItemDao {
|
||||
WHERE id = :itemId
|
||||
"""
|
||||
)
|
||||
fun useItem(itemId: Long)
|
||||
suspend fun useItem(itemId: Long)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.github.nacabaro.vbhelper.daos
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface SpecialMissionDao {
|
||||
@Query("""
|
||||
UPDATE SpecialMissions SET
|
||||
missionType = "NONE",
|
||||
status = "UNAVAILABLE"
|
||||
WHERE id = :id
|
||||
""")
|
||||
suspend fun clearSpecialMission(id: Long)
|
||||
|
||||
@Query("""
|
||||
SELECT *
|
||||
FROM SpecialMissions
|
||||
WHERE id = :id
|
||||
""")
|
||||
fun getSpecialMission(id: Long): Flow<SpecialMissions>
|
||||
}
|
||||
@@ -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,11 +5,15 @@ import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||
import com.github.nacabaro.vbhelper.domain.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 {
|
||||
@@ -19,6 +23,9 @@ interface UserCharacterDao {
|
||||
@Insert
|
||||
fun insertBECharacterData(characterData: BECharacterData)
|
||||
|
||||
@Insert
|
||||
fun insertVBCharacterData(characterData: VBCharacterData)
|
||||
|
||||
@Upsert
|
||||
fun updateCharacter(character: UserCharacter)
|
||||
|
||||
@@ -28,19 +35,25 @@ 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(
|
||||
"""
|
||||
@@ -48,21 +61,24 @@ interface UserCharacterDao {
|
||||
uc.*,
|
||||
c.stage,
|
||||
c.attribute,
|
||||
c.sprite1 AS spriteIdle,
|
||||
c.spritesWidth AS spriteWidth,
|
||||
c.spritesHeight AS spriteHeight,
|
||||
c.name as nameSprite,
|
||||
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
|
||||
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(
|
||||
"""
|
||||
@@ -70,27 +86,37 @@ interface UserCharacterDao {
|
||||
uc.*,
|
||||
c.stage,
|
||||
c.attribute,
|
||||
c.sprite1 AS spriteIdle,
|
||||
c.spritesWidth AS spriteWidth,
|
||||
c.spritesHeight AS spriteHeight,
|
||||
c.name as nameSprite,
|
||||
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
|
||||
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 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(
|
||||
"""
|
||||
@@ -98,22 +124,26 @@ interface UserCharacterDao {
|
||||
uc.*,
|
||||
c.stage,
|
||||
c.attribute,
|
||||
c.sprite1 AS spriteIdle,
|
||||
c.spritesWidth AS spriteWidth,
|
||||
c.spritesHeight AS spriteHeight,
|
||||
c.name as nameSprite,
|
||||
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
|
||||
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)
|
||||
@@ -127,11 +157,81 @@ interface UserCharacterDao {
|
||||
@Query(
|
||||
"""
|
||||
SELECT c.*
|
||||
FROM Character c
|
||||
FROM CardCharacter c
|
||||
join UserCharacter uc on c.id = uc.charId
|
||||
where uc.id = :charId
|
||||
LIMIT 1
|
||||
"""
|
||||
)
|
||||
suspend fun getCharacterInfo(charId: Long): Character
|
||||
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>
|
||||
}
|
||||
@@ -3,40 +3,66 @@ 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
|
||||
|
||||
@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,
|
||||
Adventure::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
|
||||
)
|
||||
+2
-2
@@ -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?
|
||||
)
|
||||
+19
-14
@@ -1,39 +1,44 @@
|
||||
package com.github.nacabaro.vbhelper.domain.characters
|
||||
package com.github.nacabaro.vbhelper.domain.card
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
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 = ["dimId"],
|
||||
childColumns = ["cardId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = Sprite::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["spriteId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
/*
|
||||
* Character represents a character on a DIM card. There should only be one of these per dimId
|
||||
* Character represents a character on a card. There should only be one of these per dimId
|
||||
* and monIndex.
|
||||
* TODO: Customs will mean this should be unique per cardName and monIndex
|
||||
*/
|
||||
data class Character (
|
||||
data class CardCharacter (
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val dimId: Long,
|
||||
val monIndex: Int,
|
||||
val name: ByteArray,
|
||||
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 sprite1: ByteArray,
|
||||
val sprite2: ByteArray,
|
||||
val nameSprite: ByteArray,
|
||||
val nameWidth: Int,
|
||||
val nameHeight: Int,
|
||||
val spritesWidth: Int,
|
||||
val spritesHeight: 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
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
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 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?
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
+2
-2
@@ -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,12 +5,12 @@ 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
|
||||
@@ -24,7 +24,6 @@ data class UserCharacter (
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
var charId: Long,
|
||||
var ageInDays: Int,
|
||||
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
|
||||
var mood: Int,
|
||||
var vitalPoints: Int,
|
||||
var transformationCountdown: Int,
|
||||
|
||||
+19
-2
@@ -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,
|
||||
@@ -11,5 +18,6 @@ data class Items(
|
||||
val itemIcon: Int,
|
||||
val itemLength: Int,
|
||||
val price: Int,
|
||||
val quantity: 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,6 @@ object CharacterDtos {
|
||||
var stage: Int,
|
||||
var attribute: NfcCharacter.Attribute,
|
||||
var ageInDays: Int,
|
||||
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
|
||||
var mood: Int,
|
||||
var vitalPoints: Int,
|
||||
var transformationCountdown: Int,
|
||||
@@ -25,18 +24,23 @@ object CharacterDtos {
|
||||
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 isInAdventure: Boolean,
|
||||
val active: Boolean
|
||||
)
|
||||
|
||||
data class DiMInfo(
|
||||
val cardId: Int,
|
||||
val charId: Int
|
||||
data class CardCharacterInfo(
|
||||
val cardId: Long,
|
||||
val charId: Int,
|
||||
val stage: Int,
|
||||
val attribute: NfcCharacter.Attribute,
|
||||
val currentStage: Int
|
||||
)
|
||||
|
||||
data class TransformationHistory(
|
||||
@@ -48,12 +52,27 @@ object CharacterDtos {
|
||||
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,
|
||||
val discoveredOn: Long?
|
||||
)
|
||||
|
||||
data class AdventureCharacterWithSprites(
|
||||
@@ -62,7 +81,6 @@ object CharacterDtos {
|
||||
var stage: Int,
|
||||
var attribute: NfcCharacter.Attribute,
|
||||
var ageInDays: Int,
|
||||
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
|
||||
var mood: Int,
|
||||
var vitalPoints: Int,
|
||||
var transformationCountdown: Int,
|
||||
@@ -82,4 +100,29 @@ object CharacterDtos {
|
||||
val finishesAdventure: Long,
|
||||
val originalTimeInMinutes: Long
|
||||
)
|
||||
|
||||
data class EvolutionRequirementsWithSpritesAndObtained(
|
||||
val charaId: Long,
|
||||
val fromCharaId: Long,
|
||||
val spriteIdle: ByteArray,
|
||||
val spriteWidth: Int,
|
||||
val spriteHeight: Int,
|
||||
val discoveredOn: Long?,
|
||||
val requiredTrophies: Int,
|
||||
val requiredVitals: Int,
|
||||
val requiredBattles: Int,
|
||||
val requiredWinRate: Int,
|
||||
val changeTimerHours: Int,
|
||||
val requiredAdventureLevelCompleted: Int
|
||||
)
|
||||
|
||||
data class FusionsWithSpritesAndObtained(
|
||||
val charaId: Long,
|
||||
val fromCharaId: Long,
|
||||
val spriteIdle: ByteArray,
|
||||
val spriteWidth: Int,
|
||||
val spriteHeight: Int,
|
||||
val discoveredOn: Long?,
|
||||
val fusionAttribute: NfcCharacter.Attribute
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.github.nacabaro.vbhelper.dtos
|
||||
|
||||
import com.github.nacabaro.vbhelper.domain.items.ItemType
|
||||
|
||||
|
||||
object ItemDtos {
|
||||
data class ItemsWithQuantities(
|
||||
@@ -10,6 +12,7 @@ object ItemDtos {
|
||||
val itemLength: Int,
|
||||
val price: Int,
|
||||
val quantity: Int,
|
||||
val itemType: ItemType
|
||||
)
|
||||
|
||||
data class PurchasedItem(
|
||||
@@ -18,6 +21,7 @@ object ItemDtos {
|
||||
val itemDescription: String,
|
||||
val itemIcon: Int,
|
||||
val itemLength: Int,
|
||||
val itemAmount: Int
|
||||
val itemAmount: Int,
|
||||
val itemType: ItemType
|
||||
)
|
||||
}
|
||||
@@ -1,21 +1,31 @@
|
||||
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.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
|
||||
@@ -23,8 +33,12 @@ import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImp
|
||||
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,
|
||||
@@ -32,7 +46,9 @@ data class AppNavigationHandlers(
|
||||
val itemsScreenController: ItemsScreenControllerImpl,
|
||||
val adventureScreenController: AdventureScreenControllerImpl,
|
||||
val storageScreenController: StorageScreenControllerImpl,
|
||||
val homeScreenController: HomeScreenControllerImpl
|
||||
val homeScreenController: HomeScreenControllerImpl,
|
||||
val spriteViewerController: SpriteViewerControllerImpl,
|
||||
val cardScreenController: CardScreenControllerImpl
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -49,8 +65,19 @@ 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()
|
||||
@@ -70,17 +97,29 @@ fun AppNavigation(
|
||||
}
|
||||
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) {
|
||||
@@ -91,15 +130,16 @@ fun AppNavigation(
|
||||
}
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -131,6 +171,17 @@ fun AppNavigation(
|
||||
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) {
|
||||
@@ -26,8 +26,8 @@ fun BottomNavigationBar(navController: NavController) {
|
||||
|
||||
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,24 +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 Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure")
|
||||
object Credits : NavigationItems("Credits", R.drawable.baseline_data_24, "Credits")
|
||||
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
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-19
@@ -9,16 +9,16 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.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.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.navigation.NavController
|
||||
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
|
||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||
@@ -29,24 +29,25 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Instant
|
||||
import com.github.nacabaro.vbhelper.R
|
||||
|
||||
@Composable
|
||||
fun AdventureScreen(
|
||||
navController: NavController,
|
||||
storageScreenController: AdventureScreenControllerImpl
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val application = LocalContext.current.applicationContext as VBHelper
|
||||
val database = application.container.db
|
||||
val storageRepository = StorageRepository(database)
|
||||
val characterList = remember {
|
||||
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
|
||||
}
|
||||
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) {
|
||||
@@ -59,24 +60,17 @@ fun AdventureScreen(
|
||||
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(storageRepository) {
|
||||
coroutineScope.launch {
|
||||
characterList.value = storageRepository
|
||||
.getAdventureCharacters()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBanner(
|
||||
text = "Adventure",
|
||||
text = stringResource(R.string.adventure_title),
|
||||
onBackClick = {
|
||||
navController.popBackStack()
|
||||
}
|
||||
)
|
||||
}
|
||||
) { contentPadding ->
|
||||
if (characterList.value.isEmpty()) {
|
||||
if (characterList.isEmpty()) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
@@ -84,14 +78,14 @@ fun AdventureScreen(
|
||||
.padding(top = contentPadding.calculateTopPadding())
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Text(text = "Nothing to see here")
|
||||
Text(text = stringResource(R.string.adventure_empty_state))
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(top = contentPadding.calculateTopPadding())
|
||||
) {
|
||||
items(characterList.value) {
|
||||
items(characterList) {
|
||||
AdventureEntry(
|
||||
icon = BitmapData(
|
||||
bitmap = it.spriteIdle,
|
||||
@@ -102,8 +96,9 @@ fun AdventureScreen(
|
||||
onClick = {
|
||||
if (it.finishesAdventure < currentTime) {
|
||||
storageScreenController
|
||||
.getItemFromAdventure(it.id) { adventureResult ->
|
||||
.getItemFromAdventure(it.id) { adventureResult, generatedCurrency ->
|
||||
obtainedItem = adventureResult
|
||||
obtainedCurrency = generatedCurrency
|
||||
}
|
||||
} else {
|
||||
cancelAdventureDialog = it
|
||||
@@ -118,6 +113,7 @@ fun AdventureScreen(
|
||||
if (obtainedItem != null) {
|
||||
ObtainedItemDialog(
|
||||
obtainedItem = obtainedItem!!,
|
||||
obtainedCurrency = obtainedCurrency,
|
||||
onClickDismiss = {
|
||||
obtainedItem = null
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,6 +4,6 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
|
||||
interface AdventureScreenController {
|
||||
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
|
||||
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit)
|
||||
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem, Int) -> Unit)
|
||||
fun cancelAdventure(characterId: Long, onResult: () -> Unit)
|
||||
}
|
||||
+16
-3
@@ -6,6 +6,7 @@ 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
|
||||
@@ -37,19 +38,29 @@ class AdventureScreenControllerImpl(
|
||||
|
||||
override fun getItemFromAdventure(
|
||||
characterId: Long,
|
||||
onResult: (ItemDtos.PurchasedItem) -> Unit
|
||||
onResult: (ItemDtos.PurchasedItem, Int) -> Unit
|
||||
) {
|
||||
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
database
|
||||
.adventureDao()
|
||||
.deleteAdventure(characterId)
|
||||
|
||||
val generatedCurrency = generateRandomCurrency()
|
||||
|
||||
val generatedItem = generateItem(characterId)
|
||||
|
||||
onResult(generatedItem)
|
||||
onResult(generatedItem, generatedCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun generateRandomCurrency(): Int {
|
||||
val currentValue = application.container.currencyRepository.currencyValue.first()
|
||||
val random = (2..6).random() * 1000
|
||||
application.container.currencyRepository.setCurrencyValue(currentValue + random)
|
||||
|
||||
return random
|
||||
}
|
||||
|
||||
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
|
||||
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
database
|
||||
@@ -77,6 +88,7 @@ class AdventureScreenControllerImpl(
|
||||
val randomItem = database
|
||||
.itemDao()
|
||||
.getAllItems()
|
||||
.first()
|
||||
.random()
|
||||
|
||||
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()
|
||||
@@ -94,7 +106,8 @@ class AdventureScreenControllerImpl(
|
||||
itemName = randomItem.name,
|
||||
itemIcon = randomItem.itemIcon,
|
||||
itemLength = randomItem.itemLength,
|
||||
itemDescription = randomItem.description
|
||||
itemDescription = randomItem.description,
|
||||
itemType = randomItem.itemType
|
||||
)
|
||||
}
|
||||
}
|
||||
+122
@@ -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: -")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -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>>
|
||||
}
|
||||
+55
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
+38
-18
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+51
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+287
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
+191
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@ 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(
|
||||
@@ -19,26 +21,26 @@ fun BetaWarning(
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Card {
|
||||
Column (
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "This application is currently in alpha and it is not complete. Do not use to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!"
|
||||
text = stringResource(R.string.beta_warning_message_main)
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
Text(
|
||||
text = "Also, this application does not work yet with the original VB."
|
||||
text = stringResource(R.string.beta_warning_message_compatibility)
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
Text(
|
||||
text = "Thank you for your understanding and patience. Sincerely, the dev team."
|
||||
text = stringResource(R.string.beta_warning_message_thanks)
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
Button(
|
||||
onClick = onDismissRequest
|
||||
) {
|
||||
Text(text = "Dismiss")
|
||||
Text(text = stringResource(R.string.beta_warning_button_dismiss))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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
|
||||
@@ -19,6 +20,7 @@ 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
|
||||
@@ -28,11 +30,19 @@ 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(
|
||||
@@ -41,22 +51,59 @@ fun HomeScreen(
|
||||
) {
|
||||
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)
|
||||
|
||||
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) }
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) }
|
||||
var collectedCurrency by remember { mutableStateOf<Int?>(null) }
|
||||
|
||||
LaunchedEffect(true) {
|
||||
homeScreenController
|
||||
@@ -68,7 +115,7 @@ fun HomeScreen(
|
||||
Scaffold (
|
||||
topBar = {
|
||||
TopBanner(
|
||||
text = "VB Helper",
|
||||
text = stringResource(R.string.home_title),
|
||||
onScanClick = {
|
||||
navController.navigate(NavigationItems.Scan.route)
|
||||
},
|
||||
@@ -78,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,
|
||||
@@ -86,34 +133,60 @@ 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) {
|
||||
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) {
|
||||
} 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 },
|
||||
@@ -124,7 +197,7 @@ fun HomeScreen(
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "One of your characters has finished their adventure mission!",
|
||||
text = stringResource(R.string.home_adventure_mission_finished),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Button(
|
||||
@@ -135,7 +208,7 @@ fun HomeScreen(
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "Dismiss")
|
||||
Text(text = stringResource(R.string.beta_warning_button_dismiss))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -1,5 +1,8 @@
|
||||
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
|
||||
interface HomeScreenController {
|
||||
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
|
||||
fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit)
|
||||
}
|
||||
+54
@@ -2,9 +2,14 @@ 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,
|
||||
@@ -18,6 +23,7 @@ class HomeScreenControllerImpl(
|
||||
val adventureCharacters = database
|
||||
.adventureDao()
|
||||
.getAdventureCharacters()
|
||||
.first()
|
||||
|
||||
val finishedAdventureCharacters = adventureCharacters.filter { character ->
|
||||
character.finishesAdventure <= currentTime
|
||||
@@ -26,4 +32,52 @@ class HomeScreenControllerImpl(
|
||||
onCompletion(finishedAdventureCharacters.isNotEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit) {
|
||||
componentActivity.lifecycleScope.launch {
|
||||
val missionStatus = database
|
||||
.specialMissionDao()
|
||||
.getSpecialMission(missionId)
|
||||
.first()
|
||||
|
||||
database
|
||||
.specialMissionDao()
|
||||
.clearSpecialMission(missionId)
|
||||
|
||||
if (missionStatus.status == 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")
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.github.nacabaro.vbhelper.screens.homeScreens.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.github.nacabaro.vbhelper.R
|
||||
|
||||
@Composable
|
||||
fun DeleteSpecialMissionDialog(
|
||||
onClickDismiss: () -> Unit,
|
||||
onClickDelete: () -> Unit
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onClickDismiss
|
||||
) {
|
||||
Card {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.home_special_mission_delete_main),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
Row {
|
||||
Button(
|
||||
onClick = onClickDismiss,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Text(text = stringResource(R.string.home_special_mission_delete_dismiss))
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = onClickDelete,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Text(text = stringResource(R.string.home_special_mission_delete_remove))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -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
|
||||
@@ -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)
|
||||
+3
-1
@@ -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
|
||||
@@ -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
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
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.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.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
|
||||
import com.github.nacabaro.vbhelper.screens.homeScreens.dialogs.DeleteSpecialMissionDialog
|
||||
|
||||
|
||||
@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
|
||||
) {
|
||||
var selectedSpecialMissionId by remember { mutableStateOf<Long>(-1) }
|
||||
|
||||
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),
|
||||
onClickMission = { missionId ->
|
||||
selectedSpecialMissionId = missionId
|
||||
},
|
||||
onClickCollect = {
|
||||
homeScreenController
|
||||
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedSpecialMissionId.toInt() != -1) {
|
||||
DeleteSpecialMissionDialog(
|
||||
onClickDismiss = {
|
||||
selectedSpecialMissionId = -1
|
||||
},
|
||||
onClickDelete = {
|
||||
homeScreenController
|
||||
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
|
||||
selectedSpecialMissionId = -1
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+24
-5
@@ -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()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.github.nacabaro.vbhelper.R
|
||||
import com.github.nacabaro.vbhelper.domain.items.ItemType
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
fun ItemDialog(
|
||||
item: ItemDtos.ItemsWithQuantities,
|
||||
onClickCancel: () -> Unit,
|
||||
onClickUse: (() -> Unit)? = null,
|
||||
onClickPurchase: (() -> Unit)? = null,
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onClickCancel,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = true
|
||||
)
|
||||
) {
|
||||
Card (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row {
|
||||
Box(modifier = Modifier) {
|
||||
// Background image (full size)
|
||||
Icon(
|
||||
painter = painterResource(id = getIconResource(item.itemIcon)),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(96.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = getLengthResource(item.itemLength)),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
modifier = Modifier
|
||||
.size(64.dp) // Set the size of the overlay image
|
||||
.align(Alignment.BottomEnd) // Align to the top end (top-right corner)
|
||||
)
|
||||
}
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||
text = item.name,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
|
||||
text = item.description,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp)
|
||||
)
|
||||
if (onClickPurchase != null) {
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||
text = stringResource(
|
||||
R.string.item_dialog_costs_credits,
|
||||
item.price
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||
text = stringResource(
|
||||
R.string.item_dialog_you_have_quantity,
|
||||
item.quantity
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Row (
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
if (onClickUse != null) {
|
||||
Button(
|
||||
onClick = onClickUse
|
||||
) {
|
||||
Text(stringResource(R.string.item_dialog_use))
|
||||
}
|
||||
}
|
||||
|
||||
if (onClickPurchase != null) {
|
||||
Button(
|
||||
onClick = onClickPurchase
|
||||
) {
|
||||
Text(stringResource(R.string.item_dialog_purchase))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Button(
|
||||
onClick = onClickCancel
|
||||
) {
|
||||
Text(stringResource(R.string.item_dialog_cancel))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun PreviewItemDialog() {
|
||||
VBHelperTheme {
|
||||
ItemDialog(
|
||||
item = ItemDtos.ItemsWithQuantities(
|
||||
name = "AP Training x3 (60 min)",
|
||||
description = "Boosts AP during training (for 60 minutes)",
|
||||
itemIcon = R.drawable.baseline_attack_24,
|
||||
itemLength = R.drawable.baseline_60_min_timer,
|
||||
quantity = 19,
|
||||
id = 1,
|
||||
price = 500,
|
||||
itemType = ItemType.BEITEM
|
||||
),
|
||||
onClickUse = { },
|
||||
onClickCancel = { }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,24 @@
|
||||
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
import com.github.nacabaro.vbhelper.R
|
||||
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
|
||||
|
||||
@Composable
|
||||
fun ItemElement(
|
||||
itemIcon: Int,
|
||||
lengthIcon: Int,
|
||||
item: ItemDtos.ItemsWithQuantities,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit) = { }
|
||||
) {
|
||||
@@ -41,7 +29,7 @@ fun ItemElement(
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Icon(
|
||||
painter = painterResource(id = itemIcon),
|
||||
painter = painterResource(id = getIconResource(item.itemIcon)),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(96.dp)
|
||||
@@ -49,164 +37,14 @@ fun ItemElement(
|
||||
.padding(16.dp)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = lengthIcon),
|
||||
painter = painterResource(id = getLengthResource(item.itemLength)),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.surfaceTint,
|
||||
modifier = Modifier
|
||||
.size(48.dp) // Set the size of the overlay image
|
||||
.align(Alignment.TopStart) // Align to the top end (top-right corner)
|
||||
.size(48.dp)
|
||||
.align(Alignment.TopStart)
|
||||
.padding(8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ItemDialog(
|
||||
name: String,
|
||||
description: String,
|
||||
itemIcon: Int,
|
||||
lengthIcon: Int,
|
||||
amount: Int,
|
||||
onClickUse: () -> Unit,
|
||||
onClickCancel: () -> Unit
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onClickCancel,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = true
|
||||
)
|
||||
) {
|
||||
Card (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row {
|
||||
Box(modifier = Modifier) {
|
||||
// Background image (full size)
|
||||
Icon(
|
||||
painter = painterResource(id = itemIcon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(96.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = lengthIcon),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
modifier = Modifier
|
||||
.size(64.dp) // Set the size of the overlay image
|
||||
.align(Alignment.BottomEnd) // Align to the top end (top-right corner)
|
||||
)
|
||||
}
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||
text = name,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
|
||||
text = description,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp)
|
||||
)
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||
text = "You have $amount of this item",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
)
|
||||
Row (
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Button(
|
||||
onClick = onClickUse
|
||||
) {
|
||||
Text("Use item")
|
||||
}
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Button(
|
||||
onClick = onClickCancel
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getIconResource(index: Int): Int {
|
||||
return when (index) {
|
||||
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> R.drawable.baseline_agility_24
|
||||
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> R.drawable.baseline_attack_24
|
||||
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> R.drawable.baseline_shield_24
|
||||
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> R.drawable.baseline_trophy_24
|
||||
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> R.drawable.baseline_arrow_up_24
|
||||
6 -> R.drawable.baseline_timer_24
|
||||
7 -> R.drawable.baseline_rank_24
|
||||
8 -> R.drawable.baseline_vitals_24
|
||||
else -> R.drawable.baseline_question_mark_24
|
||||
}
|
||||
}
|
||||
|
||||
fun getLengthResource(index: Int): Int {
|
||||
return when (index) {
|
||||
15 -> R.drawable.baseline_15_min_timer
|
||||
30 -> R.drawable.baseline_30_min_timer
|
||||
60 -> R.drawable.baseline_60_min_timer
|
||||
-60 -> R.drawable.baseline_60_min_timer
|
||||
300 -> R.drawable.baseline_5_hour_timer
|
||||
600 -> R.drawable.baseline_10_hour_timer
|
||||
-720 -> R.drawable.baseline_12_hour_timer
|
||||
-1440 -> R.drawable.baseline_24_hour_timer
|
||||
6000 -> R.drawable.baseline_reset_24
|
||||
1000 -> R.drawable.baseline_single_arrow_up
|
||||
2500 -> R.drawable.baseline_double_arrow_up
|
||||
5000 -> R.drawable.baseline_triple_arrow_up
|
||||
9999 -> R.drawable.baseline_health_24
|
||||
-500 -> R.drawable.baseline_single_arrow_down
|
||||
-1000 -> R.drawable.baseline_double_arrow_down
|
||||
-2500 -> R.drawable.baseline_triple_arrow_down
|
||||
-9999 -> R.drawable.baseline_reset_24
|
||||
else -> R.drawable.baseline_question_mark_24
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun PreviewItemDialog() {
|
||||
VBHelperTheme {
|
||||
ItemDialog(
|
||||
name = "AP Training x3 (60 min)",
|
||||
description = "Boosts AP during training (for 60 minutes)",
|
||||
itemIcon = R.drawable.baseline_attack_24,
|
||||
lengthIcon = R.drawable.baseline_60_min_timer,
|
||||
onClickUse = { },
|
||||
onClickCancel = { },
|
||||
amount = 19
|
||||
)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user