mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-10-01 07:31:37 +00:00
Compare commits
225
Commits
| 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 | ||
|
|
d46769b0cb | ||
|
|
fce05870c5 | ||
|
|
9f5f806786 | ||
|
|
d749af0a9c | ||
|
|
076f6bfacf | ||
|
|
a4f3b701e5 | ||
|
|
da134247b1 | ||
|
|
f7b3b7256a | ||
|
|
e36a700d9f | ||
|
|
fb6b6d2825 | ||
|
|
e17f6c23e4 | ||
|
|
409474b5d1 | ||
|
|
547318864b | ||
|
|
5e92895656 | ||
|
|
25500e9cb4 | ||
|
|
1a38cefb92 | ||
|
|
a974bd366e | ||
|
|
bb5f66d167 | ||
|
|
09394871e5 | ||
|
|
972a640394 | ||
|
|
35a7c9d959 | ||
|
|
b4a73031e7 | ||
|
|
3e41f82273 | ||
|
|
f3740d4816 | ||
|
|
305b776b52 | ||
|
|
c0a67e382b | ||
|
|
bc7a05ccd3 | ||
|
|
660dbaca19 | ||
|
|
b89dfb0df3 | ||
|
|
23e233227a | ||
|
|
542072c238 | ||
|
|
42dd87f0c4 | ||
|
|
635be60cdb | ||
|
|
88163684ca | ||
|
|
586d6c01ef | ||
|
|
5d996edc1e | ||
|
|
074ced1082 | ||
|
|
5a1d52aa1d | ||
|
|
98bd74c544 | ||
|
|
ce1cf3eddb | ||
|
|
0bd745ac86 | ||
|
|
0d999925b5 | ||
|
|
dd893a08da | ||
|
|
c5e8236e19 | ||
|
|
cac5198488 | ||
|
|
7f22650601 | ||
|
|
1088e5259a | ||
|
|
eabf8770cc | ||
|
|
da5e6c6628 | ||
|
|
4c0e2fafd0 | ||
|
|
4529925906 | ||
|
|
c456d455ef | ||
|
|
9871f0420f | ||
|
|
f4974c8705 | ||
|
|
5d6e3743f0 | ||
|
|
b91da136df | ||
|
|
e4366ce15c | ||
|
|
7582f944b5 | ||
|
|
a028611d8a | ||
|
|
5edd753da1 | ||
|
|
5691f998f2 | ||
|
|
4e2b9eb541 | ||
|
|
19fbed0ef2 | ||
|
|
82292823d3 | ||
|
|
01e82a6a44 | ||
|
|
1e73f52da5 | ||
|
|
d9e492eb0e | ||
|
|
5bd5fc3f74 | ||
|
|
3019560a26 | ||
|
|
a01d0eba47 | ||
|
|
bc02689850 |
+19
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
.gradle/
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# VBHelper
|
||||||
|
|
||||||
|
Application to interact with the Vital series, VB, VH, VBC and VBBE.
|
||||||
|
|
||||||
|
## 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.
|
||||||
+65
-11
@@ -1,20 +1,23 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
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 {
|
android {
|
||||||
namespace = "com.github.nacabaro.vbhelper"
|
namespace = "com.github.nacabaro.vbhelper"
|
||||||
compileSdk = 35
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.github.nacabaro.vbhelper"
|
applicationId = "com.github.nacabaro.vbhelper"
|
||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 35
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "Alpha 0.6.3"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -32,36 +35,87 @@ android {
|
|||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "11"
|
|
||||||
}
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lint {
|
||||||
|
baseline = file("lint-baseline.xml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget = JvmTarget.JVM_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
artifact = "com.google.protobuf:protoc:4.27.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates the java Protobuf-lite code for the Protobufs in this project. See
|
||||||
|
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
|
||||||
|
// for more information.
|
||||||
|
generateProtoTasks {
|
||||||
|
all().forEach { task ->
|
||||||
|
task.builtins {
|
||||||
|
create("java") {
|
||||||
|
option("lite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
implementation(project(":vb-nfc-reader"))
|
implementation(libs.vb.nfc.reader)
|
||||||
ksp(libs.androidx.room.compiler)
|
implementation(libs.dim.reader)
|
||||||
annotationProcessor(libs.androidx.room.compiler)
|
|
||||||
implementation(libs.androidx.core.ktx)
|
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.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.androidx.datastore)
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
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)
|
testImplementation(libs.junit)
|
||||||
|
|
||||||
|
ksp(libs.androidx.room.compiler)
|
||||||
|
annotationProcessor(libs.androidx.room.compiler)
|
||||||
|
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
implementation("androidx.navigation:navigation-compose:2.7.0")
|
implementation("androidx.navigation:navigation-compose:2.7.0")
|
||||||
implementation("com.google.android.material:material:1.2.0")
|
implementation("com.google.android.material:material:1.2.0")
|
||||||
|
implementation(libs.protobuf.javalite)
|
||||||
implementation("androidx.compose.material:material")
|
implementation("androidx.compose.material:material")
|
||||||
|
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-permission android:name="android.permission.NFC" />
|
||||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
<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
|
<application
|
||||||
android:name=".di.VBHelper"
|
android:name=".di.VBHelper"
|
||||||
@@ -15,6 +20,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.VBHelper"
|
android:theme="@style/Theme.VBHelper"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@@ -26,6 +32,19 @@
|
|||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<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="vbhelper" android:host="auth" />
|
||||||
|
</intent-filter>
|
||||||
|
<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>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.nacabaro.vbhelper
|
||||||
|
|
||||||
|
interface ActivityLifecycleListener {
|
||||||
|
fun onPause()
|
||||||
|
fun onResume()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun noOpInstance(): ActivityLifecycleListener {
|
||||||
|
return object: ActivityLifecycleListener {
|
||||||
|
override fun onPause() {}
|
||||||
|
|
||||||
|
override fun onResume() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,234 +1,115 @@
|
|||||||
package com.github.nacabaro.vbhelper
|
package com.github.nacabaro.vbhelper
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.nfc.NfcAdapter
|
|
||||||
import android.nfc.Tag
|
|
||||||
import android.nfc.tech.NfcA
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import com.github.nacabaro.vbhelper.navigation.AppNavigation
|
import com.github.nacabaro.vbhelper.navigation.AppNavigation
|
||||||
import com.github.cfogrady.vbnfc.CryptographicTransformer
|
|
||||||
import com.github.cfogrady.vbnfc.R
|
|
||||||
import com.github.cfogrady.vbnfc.TagCommunicator
|
|
||||||
import com.github.cfogrady.vbnfc.be.BENfcCharacter
|
|
||||||
import com.github.cfogrady.vbnfc.data.DeviceType
|
|
||||||
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
|
||||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryBECharacterData
|
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
|
||||||
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData
|
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
|
||||||
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHistory
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
|
||||||
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
|
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var nfcAdapter: NfcAdapter
|
private val onActivityLifecycleListeners = HashMap<String, ActivityLifecycleListener>()
|
||||||
private lateinit var deviceToCryptographicTransformers: Map<UShort, CryptographicTransformer>
|
|
||||||
|
|
||||||
private var nfcCharacter = MutableStateFlow<NfcCharacter?>(null)
|
private fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener) {
|
||||||
|
if( onActivityLifecycleListeners[key] != null) {
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
throw IllegalStateException("Key is already in use")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
deviceToCryptographicTransformers = getMapOfCryptographicTransformers()
|
|
||||||
|
|
||||||
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
|
||||||
if (maybeNfcAdapter == null) {
|
|
||||||
Toast.makeText(this, "No NFC on device!", Toast.LENGTH_SHORT).show()
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
nfcAdapter = maybeNfcAdapter
|
onActivityLifecycleListeners[key] = activityLifecycleListener
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unregisterActivityLifecycleListener(key: String) {
|
||||||
|
onActivityLifecycleListeners.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
val application = applicationContext as VBHelper
|
||||||
|
val scanScreenController = ScanScreenControllerImpl(
|
||||||
|
application.container.dataStoreSecretsRepository.secretsFlow,
|
||||||
|
this,
|
||||||
|
this::registerActivityLifecycleListener,
|
||||||
|
this::unregisterActivityLifecycleListener
|
||||||
|
)
|
||||||
|
val settingsScreenController = SettingsScreenControllerImpl(this)
|
||||||
|
val itemsScreenController = ItemsScreenControllerImpl(this)
|
||||||
|
val adventureScreenController = AdventureScreenControllerImpl(this)
|
||||||
|
val storageScreenController = StorageScreenControllerImpl(this)
|
||||||
|
val homeScreenController = HomeScreenControllerImpl(this)
|
||||||
|
val spriteViewerController = SpriteViewerControllerImpl(this)
|
||||||
|
val cardScreenController = CardScreenControllerImpl(this)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
VBHelperTheme {
|
VBHelperTheme {
|
||||||
MainApplication()
|
MainApplication(
|
||||||
|
scanScreenController = scanScreenController,
|
||||||
|
settingsScreenController = settingsScreenController,
|
||||||
|
itemsScreenController = itemsScreenController,
|
||||||
|
adventureScreenController = adventureScreenController,
|
||||||
|
homeScreenController = homeScreenController,
|
||||||
|
storageScreenController = storageScreenController,
|
||||||
|
spriteViewerController = spriteViewerController,
|
||||||
|
cardScreenController = cardScreenController
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i("MainActivity", "Activity onCreated")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
Log.i("MainActivity", "onPause")
|
||||||
|
for(activityListener in onActivityLifecycleListeners) {
|
||||||
|
activityListener.value.onPause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
Log.i("MainActivity", "Resume")
|
||||||
|
for(activityListener in onActivityLifecycleListeners) {
|
||||||
|
activityListener.value.onResume()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MainApplication() {
|
private fun MainApplication(
|
||||||
var isDoneReadingCharacter by remember { mutableStateOf(false) }
|
scanScreenController: ScanScreenControllerImpl,
|
||||||
val application = LocalContext.current.applicationContext as VBHelper
|
settingsScreenController: SettingsScreenControllerImpl,
|
||||||
val storageRepository = application.container.db
|
itemsScreenController: ItemsScreenControllerImpl,
|
||||||
|
adventureScreenController: AdventureScreenControllerImpl,
|
||||||
|
storageScreenController: StorageScreenControllerImpl,
|
||||||
|
homeScreenController: HomeScreenControllerImpl,
|
||||||
|
spriteViewerController: SpriteViewerControllerImpl,
|
||||||
|
cardScreenController: CardScreenControllerImpl
|
||||||
|
) {
|
||||||
AppNavigation(
|
AppNavigation(
|
||||||
isDoneReadingCharacter = isDoneReadingCharacter,
|
applicationNavigationHandlers = AppNavigationHandlers(
|
||||||
onClickRead = {
|
settingsScreenController,
|
||||||
handleTag {
|
scanScreenController,
|
||||||
val character = it.receiveCharacter()
|
itemsScreenController,
|
||||||
nfcCharacter.value = character
|
adventureScreenController,
|
||||||
addCharacterScannedIntoDatabase()
|
storageScreenController,
|
||||||
isDoneReadingCharacter = true
|
homeScreenController,
|
||||||
"Done reading character"
|
spriteViewerController,
|
||||||
}
|
cardScreenController
|
||||||
},
|
|
||||||
onClickScan = {
|
|
||||||
isDoneReadingCharacter = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
private fun getMapOfCryptographicTransformers(): Map<UShort, CryptographicTransformer> {
|
|
||||||
return mapOf(
|
|
||||||
Pair(DeviceType.VitalBraceletBEDeviceType,
|
|
||||||
CryptographicTransformer(readableHmacKey1 = resources.getString(R.string.password1),
|
|
||||||
readableHmacKey2 = resources.getString(R.string.password2),
|
|
||||||
aesKey = resources.getString(R.string.decryptionKey),
|
|
||||||
substitutionCipher = resources.getIntArray(R.array.substitutionArray))),
|
|
||||||
// Pair(DeviceType.VitalSeriesDeviceType,
|
|
||||||
// CryptographicTransformer(hmacKey1 = resources.getString(R.string.password1),
|
|
||||||
// hmacKey2 = resources.getString(R.string.password2),
|
|
||||||
// decryptionKey = resources.getString(R.string.decryptionKey),
|
|
||||||
// substitutionCipher = resources.getIntArray(R.array.substitutionArray))),
|
|
||||||
// Pair(DeviceType.VitalCharactersDeviceType,
|
|
||||||
// CryptographicTransformer(hmacKey1 = resources.getString(R.string.password1),
|
|
||||||
// hmacKey2 = resources.getString(R.string.password2),
|
|
||||||
// decryptionKey = resources.getString(R.string.decryptionKey),
|
|
||||||
// substitutionCipher = resources.getIntArray(R.array.substitutionArray)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
private fun showWirelessSettings() {
|
|
||||||
Toast.makeText(this, "NFC must be enabled", Toast.LENGTH_SHORT).show()
|
|
||||||
startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
private fun buildOnReadTag(handlerFunc: (TagCommunicator)->String): (Tag)->Unit {
|
|
||||||
return { tag->
|
|
||||||
val nfcData = NfcA.get(tag)
|
|
||||||
if (nfcData == null) {
|
|
||||||
runOnUiThread {
|
|
||||||
Toast.makeText(this, "Tag detected is not VB", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nfcData.connect()
|
|
||||||
nfcData.use {
|
|
||||||
val tagCommunicator = TagCommunicator.getInstance(nfcData, deviceToCryptographicTransformers)
|
|
||||||
val successText = handlerFunc(tagCommunicator)
|
|
||||||
runOnUiThread {
|
|
||||||
Toast.makeText(this, successText, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
private fun handleTag(handlerFunc: (TagCommunicator)->String) {
|
|
||||||
if (!nfcAdapter.isEnabled) {
|
|
||||||
showWirelessSettings()
|
|
||||||
} else {
|
|
||||||
val options = Bundle()
|
|
||||||
// Work around for some broken Nfc firmware implementations that poll the card too fast
|
|
||||||
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
|
|
||||||
nfcAdapter.enableReaderMode(this, buildOnReadTag(handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
|
|
||||||
options
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
if (nfcAdapter.isEnabled) {
|
|
||||||
nfcAdapter.disableReaderMode(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
/*
|
|
||||||
TODO:
|
|
||||||
- Make it able to detect the different model of watches
|
|
||||||
- Support for regular VB
|
|
||||||
|
|
||||||
The good news is that the theory behind inserting to the database should be working
|
|
||||||
now, it's a matter of implementing the functionality to parse dim/bem cards and use my
|
|
||||||
domain model.
|
|
||||||
*/
|
|
||||||
private fun addCharacterScannedIntoDatabase() {
|
|
||||||
val beCharacter = nfcCharacter as MutableStateFlow<BENfcCharacter?>
|
|
||||||
val temporaryCharacterData = TemporaryCharacterData(
|
|
||||||
dimId = nfcCharacter.value!!.dimId.toInt(),
|
|
||||||
charIndex = nfcCharacter.value!!.charIndex.toInt(),
|
|
||||||
stage = nfcCharacter.value!!.stage.toInt(),
|
|
||||||
attribute = nfcCharacter.value!!.attribute,
|
|
||||||
ageInDays = nfcCharacter.value!!.ageInDays.toInt(),
|
|
||||||
nextAdventureMissionStage = nfcCharacter.value!!.nextAdventureMissionStage.toInt(),
|
|
||||||
mood = nfcCharacter.value!!.mood.toInt(),
|
|
||||||
vitalPoints = nfcCharacter.value!!.vitalPoints.toInt(),
|
|
||||||
transformationCountdown = nfcCharacter.value!!.transformationCountdown.toInt(),
|
|
||||||
injuryStatus = nfcCharacter.value!!.injuryStatus,
|
|
||||||
trophies = nfcCharacter.value!!.trophies.toInt(),
|
|
||||||
currentPhaseBattlesWon = nfcCharacter.value!!.currentPhaseBattlesWon.toInt(),
|
|
||||||
currentPhaseBattlesLost = nfcCharacter.value!!.currentPhaseBattlesLost.toInt(),
|
|
||||||
totalBattlesWon = nfcCharacter.value!!.totalBattlesWon.toInt(),
|
|
||||||
totalBattlesLost = nfcCharacter.value!!.totalBattlesLost.toInt(),
|
|
||||||
activityLevel = nfcCharacter.value!!.activityLevel.toInt(),
|
|
||||||
heartRateCurrent = nfcCharacter.value!!.heartRateCurrent.toInt()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val application = applicationContext as VBHelper
|
|
||||||
val storageRepository = application.container.db
|
|
||||||
val characterId: Long = storageRepository
|
|
||||||
.temporaryMonsterDao()
|
|
||||||
.insertCharacterData(temporaryCharacterData)
|
|
||||||
|
|
||||||
val temporaryBECharacterData = TemporaryBECharacterData(
|
|
||||||
id = characterId,
|
|
||||||
trainingHp = beCharacter.value!!.trainingHp.toInt(),
|
|
||||||
trainingAp = beCharacter.value!!.trainingAp.toInt(),
|
|
||||||
trainingBp = beCharacter.value!!.trainingBp.toInt(),
|
|
||||||
remainingTrainingTimeInMinutes = beCharacter.value!!.remainingTrainingTimeInMinutes.toInt(),
|
|
||||||
itemEffectActivityLevelValue = beCharacter.value!!.itemEffectActivityLevelValue.toInt(),
|
|
||||||
itemEffectMentalStateValue = beCharacter.value!!.itemEffectMentalStateValue.toInt(),
|
|
||||||
itemEffectMentalStateMinutesRemaining = beCharacter.value!!.itemEffectMentalStateMinutesRemaining.toInt(),
|
|
||||||
itemEffectActivityLevelMinutesRemaining = beCharacter.value!!.itemEffectActivityLevelMinutesRemaining.toInt(),
|
|
||||||
itemEffectVitalPointsChangeValue = beCharacter.value!!.itemEffectVitalPointsChangeValue.toInt(),
|
|
||||||
itemEffectVitalPointsChangeMinutesRemaining = beCharacter.value!!.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
|
|
||||||
abilityRarity = beCharacter.value!!.abilityRarity,
|
|
||||||
abilityType = beCharacter.value!!.abilityType.toInt(),
|
|
||||||
abilityBranch = beCharacter.value!!.abilityBranch.toInt(),
|
|
||||||
abilityReset = beCharacter.value!!.abilityReset.toInt(),
|
|
||||||
rank = beCharacter.value!!.abilityReset.toInt(),
|
|
||||||
itemType = beCharacter.value!!.itemType.toInt(),
|
|
||||||
itemMultiplier = beCharacter.value!!.itemMultiplier.toInt(),
|
|
||||||
itemRemainingTime = beCharacter.value!!.itemRemainingTime.toInt(),
|
|
||||||
otp0 = "", //beCharacter.value!!.otp0.toString(),
|
|
||||||
otp1 = "", //beCharacter.value!!.otp1.toString(),
|
|
||||||
minorVersion = beCharacter.value!!.characterCreationFirmwareVersion.minorVersion.toInt(),
|
|
||||||
majorVersion = beCharacter.value!!.characterCreationFirmwareVersion.majorVersion.toInt(),
|
|
||||||
)
|
|
||||||
|
|
||||||
storageRepository
|
|
||||||
.temporaryMonsterDao()
|
|
||||||
.insertBECharacterData(temporaryBECharacterData)
|
|
||||||
|
|
||||||
val transformationHistoryWatch = beCharacter.value!!.transformationHistory
|
|
||||||
val domainTransformationHistory = transformationHistoryWatch.map { item ->
|
|
||||||
TemporaryTransformationHistory(
|
|
||||||
monId = characterId,
|
|
||||||
toCharIndex = item.toCharIndex.toInt(),
|
|
||||||
yearsSince1988 = item.yearsSince1988.toInt(),
|
|
||||||
month = item.month.toInt(),
|
|
||||||
day = item.day.toInt()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
storageRepository
|
|
||||||
.temporaryMonsterDao()
|
|
||||||
.insertTransformationHistory(*domainTransformationHistory.toTypedArray())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.components
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
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
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
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
|
||||||
|
|
||||||
|
Card(
|
||||||
|
shape = shape,
|
||||||
|
onClick = when (disabled) {
|
||||||
|
true -> { {} }
|
||||||
|
false -> onClick
|
||||||
|
},
|
||||||
|
modifier = modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp),
|
||||||
|
colors = cardColors
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.BottomCenter,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = "Icon",
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
colorFilter = when (obscure) {
|
||||||
|
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
|
||||||
|
false -> null
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (cardIcon != null) {
|
||||||
|
val bitmap = remember (icon.bitmap) { cardIcon.getBitmap() }
|
||||||
|
val iconBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val dpSize = (icon.width * iconSizeMultiplier /density).dp
|
||||||
|
|
||||||
|
Image(
|
||||||
|
bitmap = iconBitmap,
|
||||||
|
contentDescription = "Card icon",
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ItemDisplay(
|
||||||
|
icon: Int,
|
||||||
|
textValue: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
definition: String = "",
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
Card(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = androidx.compose.material.MaterialTheme.shapes.small,
|
||||||
|
onClick = {
|
||||||
|
Toast.makeText(context, definition, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(icon),
|
||||||
|
contentDescription = "Vitals",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(0.5f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = textValue,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.github.nacabaro.vbhelper.components
|
package com.github.nacabaro.vbhelper.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -20,10 +22,14 @@ fun TopBanner(
|
|||||||
text: String,
|
text: String,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onGearClick: (() -> Unit)? = null,
|
onGearClick: (() -> Unit)? = null,
|
||||||
onBackClick: (() -> Unit)? = null
|
onBackClick: (() -> Unit)? = null,
|
||||||
|
onScanClick: (() -> Unit)? = null,
|
||||||
|
onAdventureClick: (() -> Unit)? = null,
|
||||||
|
onModifyClick: (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
Box( // Use Box to overlay elements
|
Box( // Use Box to overlay elements
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -32,29 +38,62 @@ fun TopBanner(
|
|||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.Center) // Center the text
|
.align(Alignment.Center)
|
||||||
)
|
)
|
||||||
if (onGearClick != null) {
|
if (onGearClick != null) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onGearClick,
|
onClick = onGearClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterEnd) // Place gear icon at the end
|
.align(Alignment.CenterEnd)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.baseline_settings_24), // Use a gear icon
|
painter = painterResource(R.drawable.baseline_settings_24),
|
||||||
contentDescription = "Settings"
|
contentDescription = "Settings"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} else if (onAdventureClick != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onAdventureClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_fort_24),
|
||||||
|
contentDescription = "Adventure"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (onModifyClick != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onModifyClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_edit_24),
|
||||||
|
contentDescription = "Adventure"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (onBackClick != null) {
|
if (onScanClick != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onScanClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterStart)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_nfc_24),
|
||||||
|
contentDescription = "Scan"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (onBackClick != null) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBackClick,
|
onClick = onBackClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterStart) // Place gear icon at the end
|
.align(Alignment.CenterStart)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.baseline_arrow_back_24), // Use a gear icon
|
painter = painterResource(R.drawable.baseline_arrow_back_24),
|
||||||
contentDescription = "Settings"
|
contentDescription = "Settings"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
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.domain.device_data.TransformationHistory
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransformationHistoryCard(
|
||||||
|
transformationHistory: List<CharacterDtos.TransformationHistory>,
|
||||||
|
modifier: Modifier= Modifier
|
||||||
|
) {
|
||||||
|
Card (
|
||||||
|
shape = androidx.compose.material.MaterialTheme.shapes.small,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
LazyRow (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
items(transformationHistory) { transformation ->
|
||||||
|
TransformationHistoryItem(transformation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransformationHistoryItem(
|
||||||
|
transformation: CharacterDtos.TransformationHistory
|
||||||
|
) {
|
||||||
|
val bitmapData = BitmapData(
|
||||||
|
bitmap = transformation.spriteIdle,
|
||||||
|
width = transformation.spriteWidth,
|
||||||
|
height = transformation.spriteHeight
|
||||||
|
)
|
||||||
|
val bitmap = remember (bitmapData) { bitmapData.getBitmap() }
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (bitmap.width * 3 / density).dp
|
||||||
|
|
||||||
|
Box (
|
||||||
|
contentAlignment = Alignment.BottomCenter,
|
||||||
|
modifier = Modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.size((64*3/density).dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = "Transformation",
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface AdventureDao {
|
||||||
|
@Query("""
|
||||||
|
INSERT INTO Adventure (characterId, originalDuration, finishesAdventure)
|
||||||
|
VALUES (:characterId, :originalDuration, strftime('%s', 'now') + :timeInSeconds)
|
||||||
|
""")
|
||||||
|
fun insertNewAdventure(characterId: Long, originalDuration: Long, timeInSeconds: Long)
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT COUNT(*) FROM Adventure
|
||||||
|
""")
|
||||||
|
fun getAdventureCount(): Int
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.finishesAdventure AS finishesAdventure,
|
||||||
|
a.originalDuration AS originalTimeInMinutes
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN CardCharacter c ON uc.charId = c.id
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
JOIN Card d ON c.cardId = d.id
|
||||||
|
JOIN Adventure a ON uc.id = a.characterId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
DELETE FROM Adventure
|
||||||
|
WHERE characterId = :characterId
|
||||||
|
""")
|
||||||
|
suspend fun deleteAdventure(characterId: Long)
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CardDtos
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface CardAdventureDao {
|
||||||
|
@Query("""
|
||||||
|
INSERT INTO
|
||||||
|
CardAdventure (cardId, characterId, steps, bossAp, bossHp, bossDp, bossBp)
|
||||||
|
SELECT
|
||||||
|
:cardId,
|
||||||
|
cc.id,
|
||||||
|
:steps,
|
||||||
|
:bossAp,
|
||||||
|
:bossHp,
|
||||||
|
:bossDp,
|
||||||
|
:bossBp
|
||||||
|
FROM
|
||||||
|
CardCharacter cc
|
||||||
|
WHERE
|
||||||
|
cc.charaIndex = :characterId AND
|
||||||
|
cc.cardId = :cardId
|
||||||
|
""")
|
||||||
|
suspend fun insertNewAdventure(
|
||||||
|
cardId: Long,
|
||||||
|
characterId: Int,
|
||||||
|
steps: Int,
|
||||||
|
bossAp: Int,
|
||||||
|
bossHp: Int,
|
||||||
|
bossDp: Int,
|
||||||
|
bossBp: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT
|
||||||
|
cc.nameSprite as characterName,
|
||||||
|
cc.nameWidth as characterNameWidth,
|
||||||
|
cc.nameHeight as characterNameHeight,
|
||||||
|
s.spriteIdle1 as characterIdleSprite,
|
||||||
|
s.width as characterIdleSpriteWidth,
|
||||||
|
s.height as characterIdleSpriteHeight,
|
||||||
|
ca.bossAp as characterAp,
|
||||||
|
ca.bossBp as characterBp,
|
||||||
|
ca.bossDp as characterDp,
|
||||||
|
ca.bossHp as characterHp,
|
||||||
|
ca.steps as steps
|
||||||
|
FROM CardCharacter cc
|
||||||
|
JOIN Sprite s ON cc.spriteId = s.id
|
||||||
|
JOIN CardAdventure ca ON cc.id = ca.characterId
|
||||||
|
WHERE
|
||||||
|
cc.cardId = :cardId
|
||||||
|
""")
|
||||||
|
fun getAdventureForCard(
|
||||||
|
cardId: Long
|
||||||
|
): Flow<List<CardDtos.CardAdventureWithSprites>>
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.domain.card.Card
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CardDtos
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface CardDao {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
suspend fun insertNewCard(card: Card): Long
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Card WHERE cardId = :id")
|
||||||
|
fun getCardByCardId(id: Int): List<Card>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Card WHERE id = :id")
|
||||||
|
fun getCardById(id: Long): Card?
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT ca.*
|
||||||
|
FROM Card ca
|
||||||
|
JOIN CardCharacter ch ON ca.id = ch.cardId
|
||||||
|
JOIN UserCharacter uc ON ch.id = uc.charId
|
||||||
|
WHERE uc.id = :id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getCardByCharacterId(id: Long): Flow<Card>
|
||||||
|
|
||||||
|
@Query("UPDATE Card SET name = :newName WHERE id = :id")
|
||||||
|
suspend fun renameCard(id: Int, newName: String)
|
||||||
|
|
||||||
|
@Query("DELETE FROM Card WHERE id = :id")
|
||||||
|
suspend fun deleteCard(id: Long)
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT
|
||||||
|
c.logo as cardIcon,
|
||||||
|
c.logoWidth as cardIconWidth,
|
||||||
|
c.logoHeight as cardIconHeight
|
||||||
|
FROM Card c
|
||||||
|
JOIN CardCharacter cc ON cc.cardId = c.id
|
||||||
|
WHERE cc.id = :charaId
|
||||||
|
""")
|
||||||
|
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon>
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface CardFusionsDao {
|
||||||
|
@Query("""
|
||||||
|
INSERT INTO
|
||||||
|
CardFusions (
|
||||||
|
fromCharaId,
|
||||||
|
attribute,
|
||||||
|
toCharaId
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :fromCharaId),
|
||||||
|
:attribute,
|
||||||
|
(SELECT id FROM CardCharacter WHERE cardId = :cardId AND charaIndex = :toCharaId)
|
||||||
|
""")
|
||||||
|
suspend fun insertNewFusion(
|
||||||
|
cardId: Long,
|
||||||
|
fromCharaId: Int,
|
||||||
|
attribute: NfcCharacter.Attribute,
|
||||||
|
toCharaId: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT
|
||||||
|
cf.toCharaId as charaId,
|
||||||
|
cf.fromCharaId as fromCharaId,
|
||||||
|
s.spriteIdle1 as spriteIdle,
|
||||||
|
cc.attribute as attribute,
|
||||||
|
s.width as spriteWidth,
|
||||||
|
s.height as spriteHeight,
|
||||||
|
d.discoveredOn as discoveredOn,
|
||||||
|
cf.attribute as fusionAttribute
|
||||||
|
FROM CardFusions cf
|
||||||
|
JOIN CardCharacter cc ON cc.id = cf.toCharaId
|
||||||
|
JOIN Sprite s ON s.id = cc.id
|
||||||
|
LEFT JOIN Dex d ON d.id = cc.id
|
||||||
|
WHERE cf.fromCharaId = :charaId
|
||||||
|
ORDER BY cc.charaIndex
|
||||||
|
""")
|
||||||
|
fun getFusionsForCharacter(charaId: Long): Flow<List<CharacterDtos.FusionsWithSpritesAndObtained>>
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.domain.card.CardProgress
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface CardProgressDao {
|
||||||
|
@Query("""
|
||||||
|
UPDATE CardProgress
|
||||||
|
SET
|
||||||
|
currentStage = :currentStage,
|
||||||
|
unlocked = :unlocked
|
||||||
|
WHERE cardId = :cardId AND
|
||||||
|
currentStage < :currentStage
|
||||||
|
""")
|
||||||
|
fun updateCardProgress(currentStage: Int, cardId: Long, unlocked: Boolean)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT currentStage FROM CardProgress WHERE cardId = :cardId"
|
||||||
|
)
|
||||||
|
fun getCardProgress(cardId: Long): Flow<Int>
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insertCardProgress(cardProgress: CardProgress)
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
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.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: CardCharacter)
|
||||||
|
|
||||||
|
@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: Sprite)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
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.cardId = d.id
|
||||||
|
JOIN CardProgress cp ON d.id = cp.cardId
|
||||||
|
WHERE c.id = :charId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getCharacterInfo(charId: Long): CharacterDtos.CardCharacterInfo
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
INSERT INTO PossibleTransformations (charaId, requiredVitals, requiredTrophies, requiredBattles, requiredWinRate, changeTimerHours, requiredAdventureLevelCompleted, toCharaId)
|
||||||
|
SELECT
|
||||||
|
(SELECT id FROM CardCharacter WHERE charaIndex = :fromChraraIndex AND cardId = :cardId),
|
||||||
|
:requiredVitals,
|
||||||
|
:requiredTrophies,
|
||||||
|
:requiredBattles,
|
||||||
|
:requiredWinRate,
|
||||||
|
:changeTimerHours,
|
||||||
|
:requiredAdventureLevelCompleted,
|
||||||
|
(SELECT id FROM CardCharacter WHERE charaIndex = :toChraraIndex AND cardId = :cardId)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun insertPossibleTransformation(
|
||||||
|
fromChraraIndex: Int,
|
||||||
|
toChraraIndex: Int,
|
||||||
|
cardId: Long,
|
||||||
|
requiredVitals: Int,
|
||||||
|
requiredTrophies: Int,
|
||||||
|
requiredBattles: Int,
|
||||||
|
changeTimerHours: Int,
|
||||||
|
requiredWinRate: Int,
|
||||||
|
requiredAdventureLevelCompleted: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
pt.charaId as fromCharaId,
|
||||||
|
pt.toCharaId as charaId,
|
||||||
|
s.spriteIdle1 as spriteIdle,
|
||||||
|
s.width as spriteWidth,
|
||||||
|
s.height as spriteHeight,
|
||||||
|
d.discoveredOn as discoveredOn,
|
||||||
|
pt.requiredTrophies as requiredTrophies,
|
||||||
|
pt.requiredVitals as requiredVitals,
|
||||||
|
pt.requiredBattles as requiredBattles,
|
||||||
|
pt.requiredWinRate as requiredWinRate,
|
||||||
|
pt.changeTimerHours as changeTimerHours,
|
||||||
|
pt.requiredAdventureLevelCompleted as requiredAdventureLevelCompleted
|
||||||
|
FROM
|
||||||
|
PossibleTransformations pt
|
||||||
|
JOIN CardCharacter c on pt.toCharaId = c.id
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
LEFT JOIN Dex d ON d.id = pt.toCharaId
|
||||||
|
WHERE
|
||||||
|
pt.charaId = :characterId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getEvolutionRequirementsForCard(characterId: Long): Flow<List<CharacterDtos.EvolutionRequirementsWithSpritesAndObtained>>
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
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(
|
||||||
|
"""
|
||||||
|
INSERT OR IGNORE INTO Dex(id, discoveredOn)
|
||||||
|
VALUES (
|
||||||
|
(SELECT id FROM CardCharacter WHERE charaIndex = :charIndex AND cardId = :cardId),
|
||||||
|
:discoveredOn
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun insertCharacter(charIndex: Int, cardId: Long, discoveredOn: Long)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
c.id AS id,
|
||||||
|
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.cardId = :cardId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getSingleCardProgress(cardId: Long): Flow<List<CharacterDtos.CardCharaProgress>>
|
||||||
|
|
||||||
|
@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 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
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getCardsWithProgress(): Flow<List<CardDtos.CardProgress>>
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.daos
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.Query
|
|
||||||
import com.github.nacabaro.vbhelper.domain.Dim
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface DiMDao {
|
|
||||||
@Insert
|
|
||||||
suspend fun insertNewDim(dim: Dim)
|
|
||||||
|
|
||||||
@Query("SELECT * FROM Dim")
|
|
||||||
fun getAllDims(): Flow<List<Dim>>
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ItemDao {
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM Items
|
||||||
|
ORDER BY Items.itemIcon ASC
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getAllItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM Items
|
||||||
|
WHERE quantity > 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getAllUserItems(): Flow<List<ItemDtos.ItemsWithQuantities>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM Items
|
||||||
|
WHERE Items.id = :itemId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
UPDATE Items
|
||||||
|
SET quantity = quantity - 1
|
||||||
|
WHERE id = :itemId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun useItem(itemId: Long)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
UPDATE Items
|
||||||
|
SET quantity = quantity + :itemAmount
|
||||||
|
WHERE id = :itemId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun purchaseItem(itemId: Long, itemAmount: Int)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UserCharacterDao {
|
||||||
|
@Insert
|
||||||
|
fun insertCharacterData(characterData: UserCharacter): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insertBECharacterData(characterData: BECharacterData)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insertVBCharacterData(characterData: VBCharacterData)
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
fun updateCharacter(character: UserCharacter)
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
fun updateBECharacterData(characterData: BECharacterData)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
fun insertSpecialMissions(vararg specialMissions: SpecialMissions)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
c.id AS id,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.charaIndex AS monIndex,
|
||||||
|
t.transformationDate AS transformationDate
|
||||||
|
FROM TransformationHistory t
|
||||||
|
JOIN CardCharacter c ON c.id = t.stageId
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
WHERE monId = :monId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getTransformationHistory(monId: Long): Flow<List<CharacterDtos.TransformationHistory>>
|
||||||
|
|
||||||
|
@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
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.spriteIdle2 AS spriteIdle2,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.nameSprite as nameSprite,
|
||||||
|
c.nameWidth as nameSpriteWidth,
|
||||||
|
c.nameHeight as nameSpriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.characterId = uc.id as isInAdventure,
|
||||||
|
uc.isActive as active
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN CardCharacter c ON uc.charId = c.id
|
||||||
|
JOIN Card d ON c.cardId = d.id
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
LEFT JOIN Adventure a ON a.characterId = uc.id
|
||||||
|
WHERE uc.id = :id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getCharacterWithSprites(id: Long): CharacterDtos.CharacterWithSprites
|
||||||
|
|
||||||
|
@Query("SELECT * FROM UserCharacter WHERE id = :id")
|
||||||
|
suspend fun getCharacter(id: Long): UserCharacter
|
||||||
|
|
||||||
|
@Query("SELECT * FROM BECharacterData WHERE id = :id")
|
||||||
|
fun getBeData(id: Long): Flow<BECharacterData>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM VBCharacterData WHERE id = :id")
|
||||||
|
fun getVbData(id: Long): Flow<VBCharacterData>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
|
||||||
|
fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.spriteIdle2 AS spriteIdle2,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.nameSprite as nameSprite,
|
||||||
|
c.nameWidth as nameSpriteWidth,
|
||||||
|
c.nameHeight as nameSpriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.characterId as isInAdventure,
|
||||||
|
uc.isActive as active
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN 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
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?>
|
||||||
|
|
||||||
|
@Query("DELETE FROM UserCharacter WHERE id = :id")
|
||||||
|
fun deleteCharacterById(id: Long)
|
||||||
|
|
||||||
|
@Query("UPDATE UserCharacter SET isActive = 0 WHERE isActive = 1")
|
||||||
|
fun clearActiveCharacter()
|
||||||
|
|
||||||
|
@Query("UPDATE UserCharacter SET isActive = 1 WHERE id = :id")
|
||||||
|
fun setActiveCharacter(id: Long)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT c.*
|
||||||
|
FROM CardCharacter c
|
||||||
|
join UserCharacter uc on c.id = uc.charId
|
||||||
|
where uc.id = :charId
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getCharacterInfo(charId: Long): CardCharacter
|
||||||
|
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
INSERT INTO TransformationHistory(monId, stageId, transformationDate)
|
||||||
|
VALUES
|
||||||
|
(:monId,
|
||||||
|
(SELECT id FROM CardCharacter WHERE charaIndex = :stage AND cardId = :dimId),
|
||||||
|
:transformationDate)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun insertTransformation(monId: Long, stage: Int, dimId: Long, transformationDate: Long)
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
fun insertVitals(vararg vitalsHistory: VitalsHistory)
|
||||||
|
|
||||||
|
@Query("""SELECT * FROM VitalsHistory WHERE charId = :charId ORDER BY id ASC""")
|
||||||
|
suspend fun getVitalsHistory(charId: Long): List<VitalsHistory>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.spriteIdle2 AS spriteIdle2,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.nameSprite as nameSprite,
|
||||||
|
c.nameWidth as nameSpriteWidth,
|
||||||
|
c.nameHeight as nameSpriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.characterId = uc.id as isInAdventure,
|
||||||
|
uc.isActive as active
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN CardCharacter c ON uc.charId = c.id
|
||||||
|
JOIN Card d ON d.id = c.cardId
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
LEFT JOIN Adventure a ON a.characterId = uc.id
|
||||||
|
WHERE uc.characterType = "BEDevice"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getBECharacters(): List<CharacterDtos.CharacterWithSprites>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.spriteIdle2 AS spriteIdle2,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.nameSprite as nameSprite,
|
||||||
|
c.nameWidth as nameSpriteWidth,
|
||||||
|
c.nameHeight as nameSpriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.characterId = uc.id as isInAdventure,
|
||||||
|
uc.isActive as active
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN CardCharacter c ON uc.charId = c.id
|
||||||
|
JOIN Card d ON d.id = c.cardId
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
LEFT JOIN Adventure a ON a.characterId = uc.id
|
||||||
|
WHERE uc.characterType = "VBDevice"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getVBDimCharacters(): List<CharacterDtos.CharacterWithSprites>
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.daos
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.Query
|
|
||||||
import com.github.nacabaro.vbhelper.domain.UserMonsters
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface UserMonstersDao {
|
|
||||||
@Insert
|
|
||||||
fun insertUserMonsters(userMonsters: UserMonsters)
|
|
||||||
|
|
||||||
@Query("SELECT * FROM UserMonsters WHERE userId = :userId")
|
|
||||||
fun getUserMonsters(userId: Int): List<UserMonsters>
|
|
||||||
}
|
|
||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.daos
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHistory
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface UserMonstersTransformationHistoryDao {
|
|
||||||
@Insert
|
|
||||||
fun insertTransformations(vararg transformations: TemporaryTransformationHistory)
|
|
||||||
}
|
|
||||||
@@ -2,20 +2,67 @@ package com.github.nacabaro.vbhelper.database
|
|||||||
|
|
||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import com.github.nacabaro.vbhelper.temporary_daos.TemporaryMonsterDao
|
import com.github.nacabaro.vbhelper.daos.AdventureDao
|
||||||
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryBECharacterData
|
import com.github.nacabaro.vbhelper.daos.CardAdventureDao
|
||||||
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData
|
import com.github.nacabaro.vbhelper.daos.CharacterDao
|
||||||
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryTransformationHistory
|
import com.github.nacabaro.vbhelper.daos.DexDao
|
||||||
|
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.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(
|
@Database(
|
||||||
version = 1,
|
version = 1,
|
||||||
entities = [
|
entities = [
|
||||||
TemporaryCharacterData::class,
|
Card::class,
|
||||||
TemporaryBECharacterData::class,
|
CardProgress::class,
|
||||||
TemporaryTransformationHistory::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,
|
||||||
|
Background::class,
|
||||||
|
PossibleTransformations::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun temporaryMonsterDao(): TemporaryMonsterDao
|
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,7 +1,11 @@
|
|||||||
package com.github.nacabaro.vbhelper.di
|
package com.github.nacabaro.vbhelper.di
|
||||||
|
|
||||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
||||||
|
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||||
|
|
||||||
interface AppContainer {
|
interface AppContainer {
|
||||||
val db: AppDatabase
|
val db: AppDatabase
|
||||||
|
val dataStoreSecretsRepository: DataStoreSecretsRepository
|
||||||
|
val currencyRepository: CurrencyRepository
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,41 @@
|
|||||||
import android.content.Context
|
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 androidx.room.Room
|
||||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
import com.github.nacabaro.vbhelper.di.AppContainer
|
import com.github.nacabaro.vbhelper.di.AppContainer
|
||||||
|
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
||||||
|
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||||
|
import com.github.nacabaro.vbhelper.source.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 {
|
class DefaultAppContainer(private val context: Context) : AppContainer {
|
||||||
|
|
||||||
override val db: AppDatabase by lazy {
|
override val db: AppDatabase by lazy {
|
||||||
Room.databaseBuilder(
|
Room.databaseBuilder(
|
||||||
context = context,
|
context = context,
|
||||||
klass = AppDatabase::class.java,
|
klass = AppDatabase::class.java,
|
||||||
"internalDb"
|
"internalDb"
|
||||||
).build()
|
)
|
||||||
|
.createFromAsset("items.db")
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
|
||||||
|
|
||||||
|
override val currencyRepository = CurrencyRepository(context.currencyStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.domain
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class Dim(
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val stageCount: Int
|
|
||||||
)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.domain
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = User::class,
|
|
||||||
parentColumns = ["id"],
|
|
||||||
childColumns = ["userId"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
),
|
|
||||||
ForeignKey(
|
|
||||||
entity = Dim::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
|
|
||||||
)
|
|
||||||
@@ -7,13 +7,13 @@ import androidx.room.PrimaryKey
|
|||||||
@Entity(
|
@Entity(
|
||||||
foreignKeys = [
|
foreignKeys = [
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = Mon::class,
|
entity = Character::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["monId"],
|
childColumns = ["monId"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
),
|
),
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = Mon::class,
|
entity = Character::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["nextMon"],
|
childColumns = ["nextMon"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.domain
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = Dim::class,
|
|
||||||
parentColumns = ["id"],
|
|
||||||
childColumns = ["dimId"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
data class Mon (
|
|
||||||
@PrimaryKey val id: Int,
|
|
||||||
val dimId: Int,
|
|
||||||
val monIndex: Int,
|
|
||||||
val name: String,
|
|
||||||
val stage: Int, // These should be replaced with enums
|
|
||||||
val attribute: Int, // This one too
|
|
||||||
val baseHp: Int,
|
|
||||||
val baseBp: Int,
|
|
||||||
val baseAp: Int,
|
|
||||||
val evoTime: Int, // In minutes
|
|
||||||
)
|
|
||||||
@@ -13,7 +13,7 @@ import androidx.room.PrimaryKey
|
|||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
),
|
),
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = Mon::class,
|
entity = Character::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["monId"],
|
childColumns = ["monId"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.card
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class Card(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val id: Long = 0,
|
||||||
|
val cardId: Int,
|
||||||
|
val logo: ByteArray,
|
||||||
|
val logoWidth: Int,
|
||||||
|
val logoHeight: Int,
|
||||||
|
val name: String,
|
||||||
|
val stageCount: Int,
|
||||||
|
val isBEm: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.card
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = CardCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["characterId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
ForeignKey(
|
||||||
|
entity = Card::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["cardId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class CardAdventure(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
val cardId: Long,
|
||||||
|
val characterId: Long,
|
||||||
|
val steps: Int,
|
||||||
|
val bossHp: Int,
|
||||||
|
val bossAp: Int,
|
||||||
|
val bossDp: Int,
|
||||||
|
val bossBp: Int?
|
||||||
|
)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.card
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = Card::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["cardId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
ForeignKey(
|
||||||
|
entity = Sprite::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["spriteId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Character represents a character on a card. There should only be one of these per dimId
|
||||||
|
* and monIndex.
|
||||||
|
* TODO: Customs will mean this should be unique per cardName and monIndex
|
||||||
|
*/
|
||||||
|
data class CardCharacter (
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
val cardId: Long,
|
||||||
|
val spriteId: Long,
|
||||||
|
val charaIndex: Int,
|
||||||
|
val stage: Int, // These should be replaced with enums
|
||||||
|
val attribute: NfcCharacter.Attribute, // This one too
|
||||||
|
val baseHp: Int,
|
||||||
|
val baseBp: Int,
|
||||||
|
val baseAp: Int,
|
||||||
|
val nameSprite: ByteArray,
|
||||||
|
val nameWidth: Int,
|
||||||
|
val nameHeight: Int
|
||||||
|
)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.card
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = CardCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["fromCharaId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
ForeignKey(
|
||||||
|
entity = CardCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["toCharaId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class CardFusions(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long,
|
||||||
|
val fromCharaId: Long,
|
||||||
|
val attribute: NfcCharacter.Attribute,
|
||||||
|
val toCharaId: Long
|
||||||
|
)
|
||||||
@@ -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?
|
||||||
|
)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.characters
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = UserCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["characterId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class Adventure(
|
||||||
|
@PrimaryKey val characterId: Long,
|
||||||
|
val originalDuration: Long,
|
||||||
|
val finishesAdventure: Long
|
||||||
|
)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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 = CardCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["id"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class Dex(
|
||||||
|
@PrimaryKey val id: Long = 0,
|
||||||
|
val discoveredOn: Long
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
+7
-9
@@ -1,28 +1,26 @@
|
|||||||
package com.github.nacabaro.vbhelper.temporary_domain
|
package com.github.nacabaro.vbhelper.domain.device_data
|
||||||
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.github.cfogrady.vbnfc.be.BENfcCharacter
|
|
||||||
import com.github.cfogrady.vbnfc.be.FirmwareVersion
|
|
||||||
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
foreignKeys = [
|
foreignKeys = [
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = TemporaryCharacterData::class,
|
entity = UserCharacter::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["id"],
|
childColumns = ["id"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class TemporaryBECharacterData (
|
data class BECharacterData (
|
||||||
@PrimaryKey(autoGenerate = true) val id: Long,
|
@PrimaryKey(autoGenerate = true) val id: Long,
|
||||||
val trainingHp: Int,
|
val trainingHp: Int,
|
||||||
val trainingAp: Int,
|
val trainingAp: Int,
|
||||||
val trainingBp: Int,
|
val trainingBp: Int,
|
||||||
val remainingTrainingTimeInMinutes: Int,
|
var remainingTrainingTimeInMinutes: Int,
|
||||||
val itemEffectMentalStateValue: Int,
|
val itemEffectMentalStateValue: Int,
|
||||||
val itemEffectMentalStateMinutesRemaining: Int,
|
val itemEffectMentalStateMinutesRemaining: Int,
|
||||||
val itemEffectActivityLevelValue: Int,
|
val itemEffectActivityLevelValue: Int,
|
||||||
@@ -34,9 +32,9 @@ data class TemporaryBECharacterData (
|
|||||||
val abilityBranch: Int,
|
val abilityBranch: Int,
|
||||||
val abilityReset: Int,
|
val abilityReset: Int,
|
||||||
val rank: Int,
|
val rank: Int,
|
||||||
val itemType: Int,
|
var itemType: Int,
|
||||||
val itemMultiplier: Int,
|
var itemMultiplier: Int,
|
||||||
val itemRemainingTime: Int,
|
var itemRemainingTime: Int,
|
||||||
val otp0: String,
|
val otp0: String,
|
||||||
val otp1: String,
|
val otp1: String,
|
||||||
val minorVersion: Int,
|
val minorVersion: 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
|
||||||
|
)
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
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.card.CardCharacter
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = UserCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["monId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
ForeignKey(
|
||||||
|
entity = CardCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["stageId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class TransformationHistory (
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
val monId: Long,
|
||||||
|
val stageId: Long,
|
||||||
|
val transformationDate: Long
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.utils.DeviceType
|
||||||
|
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = CardCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["charId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* UserCharacter represents and instance of a character. The charId should map to a Character
|
||||||
|
*/
|
||||||
|
data class UserCharacter (
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
var charId: Long,
|
||||||
|
var ageInDays: Int,
|
||||||
|
var mood: Int,
|
||||||
|
var vitalPoints: Int,
|
||||||
|
var transformationCountdown: Int,
|
||||||
|
var injuryStatus: NfcCharacter.InjuryStatus,
|
||||||
|
var trophies: Int,
|
||||||
|
var currentPhaseBattlesWon: Int,
|
||||||
|
var currentPhaseBattlesLost: Int,
|
||||||
|
var totalBattlesWon: Int,
|
||||||
|
var totalBattlesLost: Int,
|
||||||
|
var activityLevel: Int,
|
||||||
|
var heartRateCurrent: Int,
|
||||||
|
var characterType: DeviceType,
|
||||||
|
var isActive: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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 = ["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
|
||||||
|
)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val itemIcon: Int,
|
||||||
|
val itemLength: Int,
|
||||||
|
val price: Int,
|
||||||
|
val quantity: Int,
|
||||||
|
val itemType: ItemType
|
||||||
|
)
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.dtos
|
||||||
|
|
||||||
|
object CardDtos {
|
||||||
|
data class CardProgress (
|
||||||
|
val cardId: Long,
|
||||||
|
val cardName: String,
|
||||||
|
val cardLogo: ByteArray,
|
||||||
|
val logoWidth: Int,
|
||||||
|
val logoHeight: Int,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.dtos
|
||||||
|
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.utils.DeviceType
|
||||||
|
|
||||||
|
|
||||||
|
object CharacterDtos {
|
||||||
|
data class CharacterWithSprites(
|
||||||
|
var id: Long = 0,
|
||||||
|
var charId: Long,
|
||||||
|
var stage: Int,
|
||||||
|
var attribute: NfcCharacter.Attribute,
|
||||||
|
var ageInDays: Int,
|
||||||
|
var mood: Int,
|
||||||
|
var vitalPoints: Int,
|
||||||
|
var transformationCountdown: Int,
|
||||||
|
var injuryStatus: NfcCharacter.InjuryStatus,
|
||||||
|
var trophies: Int,
|
||||||
|
var currentPhaseBattlesWon: Int,
|
||||||
|
var currentPhaseBattlesLost: Int,
|
||||||
|
var totalBattlesWon: Int,
|
||||||
|
var totalBattlesLost: Int,
|
||||||
|
var activityLevel: Int,
|
||||||
|
var heartRateCurrent: Int,
|
||||||
|
var characterType: DeviceType,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteIdle2: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
val nameSprite: ByteArray,
|
||||||
|
val nameSpriteWidth: Int,
|
||||||
|
val nameSpriteHeight: Int,
|
||||||
|
val isBemCard: Boolean,
|
||||||
|
val isInAdventure: Boolean,
|
||||||
|
val active: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CardCharacterInfo(
|
||||||
|
val cardId: Long,
|
||||||
|
val charId: Int,
|
||||||
|
val stage: Int,
|
||||||
|
val attribute: NfcCharacter.Attribute,
|
||||||
|
val currentStage: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TransformationHistory(
|
||||||
|
val id: Long,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
val monIndex: Int,
|
||||||
|
val transformationDate: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CardCharaProgress(
|
||||||
|
val id: Long,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
val nameSprite: ByteArray,
|
||||||
|
val nameSpriteWidth: Int,
|
||||||
|
val nameSpriteHeight: Int,
|
||||||
|
val discoveredOn: Long?,
|
||||||
|
val baseHp: Int,
|
||||||
|
val baseBp: Int,
|
||||||
|
val baseAp: Int,
|
||||||
|
val stage: Int,
|
||||||
|
val attribute: NfcCharacter.Attribute,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CardProgress(
|
||||||
|
val id: Long,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AdventureCharacterWithSprites(
|
||||||
|
var id: Long = 0,
|
||||||
|
var charId: Long,
|
||||||
|
var stage: Int,
|
||||||
|
var attribute: NfcCharacter.Attribute,
|
||||||
|
var ageInDays: Int,
|
||||||
|
var mood: Int,
|
||||||
|
var vitalPoints: Int,
|
||||||
|
var transformationCountdown: Int,
|
||||||
|
var injuryStatus: NfcCharacter.InjuryStatus,
|
||||||
|
var trophies: Int,
|
||||||
|
var currentPhaseBattlesWon: Int,
|
||||||
|
var currentPhaseBattlesLost: Int,
|
||||||
|
var totalBattlesWon: Int,
|
||||||
|
var totalBattlesLost: Int,
|
||||||
|
var activityLevel: Int,
|
||||||
|
var heartRateCurrent: Int,
|
||||||
|
var characterType: DeviceType,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
val isBemCard: Boolean,
|
||||||
|
val finishesAdventure: Long,
|
||||||
|
val originalTimeInMinutes: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.dtos
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.domain.items.ItemType
|
||||||
|
|
||||||
|
|
||||||
|
object ItemDtos {
|
||||||
|
data class ItemsWithQuantities(
|
||||||
|
val id: Long,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val itemIcon: Int,
|
||||||
|
val itemLength: Int,
|
||||||
|
val price: Int,
|
||||||
|
val quantity: Int,
|
||||||
|
val itemType: ItemType
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PurchasedItem(
|
||||||
|
val itemId: Long,
|
||||||
|
val itemName: String,
|
||||||
|
val itemDescription: String,
|
||||||
|
val itemIcon: Int,
|
||||||
|
val itemLength: Int,
|
||||||
|
val itemAmount: Int,
|
||||||
|
val itemType: ItemType
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.dtos
|
|
||||||
|
|
||||||
class MonsterDataCombined {
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,59 @@
|
|||||||
package com.github.nacabaro.vbhelper.navigation
|
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.foundation.layout.padding
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.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.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
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.BattlesScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.DexScreen
|
import com.github.nacabaro.vbhelper.screens.cardScreen.CardsScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.HomeScreen
|
import com.github.nacabaro.vbhelper.screens.cardScreen.CardViewScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.ScanScreen
|
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.SettingsScreen
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.StorageScreen
|
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.SpriteViewer
|
||||||
|
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ChooseCharacterScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.cardScreen.CardAdventureScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
|
||||||
|
data class AppNavigationHandlers(
|
||||||
|
val settingsScreenController: SettingsScreenControllerImpl,
|
||||||
|
val scanScreenController: ScanScreenControllerImpl,
|
||||||
|
val itemsScreenController: ItemsScreenControllerImpl,
|
||||||
|
val adventureScreenController: AdventureScreenControllerImpl,
|
||||||
|
val storageScreenController: StorageScreenControllerImpl,
|
||||||
|
val homeScreenController: HomeScreenControllerImpl,
|
||||||
|
val spriteViewerController: SpriteViewerControllerImpl,
|
||||||
|
val cardScreenController: CardScreenControllerImpl
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavigation(
|
fun AppNavigation(
|
||||||
onClickRead: () -> Unit,
|
applicationNavigationHandlers: AppNavigationHandlers,
|
||||||
onClickScan: () -> Unit,
|
|
||||||
isDoneReadingCharacter: Boolean
|
|
||||||
) {
|
) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
@@ -29,37 +64,124 @@ fun AppNavigation(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = BottomNavItem.Home.route,
|
startDestination = NavigationItems.Home.route,
|
||||||
|
enterTransition = {
|
||||||
|
fadeIn(
|
||||||
|
animationSpec = tween(200)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
exitTransition = {
|
||||||
|
fadeOut(
|
||||||
|
animationSpec = tween(200)
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(contentPadding)
|
.padding(contentPadding)
|
||||||
|
|
||||||
) {
|
) {
|
||||||
composable(BottomNavItem.Battles.route) {
|
composable(NavigationItems.Battles.route) {
|
||||||
BattlesScreen()
|
BattlesScreen()
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Home.route) {
|
composable(NavigationItems.Home.route) {
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
navController = navController
|
navController = navController,
|
||||||
|
homeScreenController = applicationNavigationHandlers.homeScreenController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Storage.route) {
|
composable(NavigationItems.Storage.route) {
|
||||||
StorageScreen()
|
StorageScreen(
|
||||||
|
navController = navController,
|
||||||
|
adventureScreenController = applicationNavigationHandlers.adventureScreenController,
|
||||||
|
storageScreenController = applicationNavigationHandlers.storageScreenController
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Scan.route) {
|
composable(NavigationItems.Scan.route) {
|
||||||
onClickScan()
|
val characterIdString = it.arguments?.getString("characterId")
|
||||||
|
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(
|
ScanScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
onClickRead = onClickRead,
|
scanScreenController = applicationNavigationHandlers.scanScreenController,
|
||||||
isDoneReadingCharacter = isDoneReadingCharacter
|
characterId = characterId,
|
||||||
|
launchedFromHomeScreen = launchedFromHomeScreen
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Dex.route) {
|
composable(NavigationItems.Dex.route) {
|
||||||
DexScreen()
|
CardsScreen(
|
||||||
|
navController = navController,
|
||||||
|
cardScreenController = applicationNavigationHandlers.cardScreenController
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Settings.route) {
|
composable(NavigationItems.Settings.route) {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
|
navController = navController,
|
||||||
|
settingsScreenController = applicationNavigationHandlers.settingsScreenController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Viewer.route) {
|
||||||
|
SpriteViewer(
|
||||||
|
navController = navController,
|
||||||
|
spriteViewerController = applicationNavigationHandlers.spriteViewerController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.CardView.route) {
|
||||||
|
val cardId = it.arguments?.getString("cardId")
|
||||||
|
if (cardId != null) {
|
||||||
|
CardViewScreen(
|
||||||
|
navController = navController,
|
||||||
|
cardId = cardId.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Items.route) {
|
||||||
|
ItemsScreen(
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable(NavigationItems.ApplyItem.route) {
|
||||||
|
val itemId = it.arguments?.getString("itemId")
|
||||||
|
if (itemId != null) {
|
||||||
|
ChooseCharacterScreen(
|
||||||
|
itemsScreenController = applicationNavigationHandlers
|
||||||
|
.itemsScreenController,
|
||||||
|
navController = navController,
|
||||||
|
itemId = itemId.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Adventure.route) {
|
||||||
|
AdventureScreen(
|
||||||
|
navController = navController,
|
||||||
|
storageScreenController = applicationNavigationHandlers
|
||||||
|
.adventureScreenController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Credits.route) {
|
||||||
|
CreditsScreen(
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.CardAdventure.route) {
|
||||||
|
val cardId = it.arguments?.getString("cardId")
|
||||||
|
if (cardId != null) {
|
||||||
|
CardAdventureScreen(
|
||||||
|
navController = navController,
|
||||||
|
cardId = cardId.toLong(),
|
||||||
|
cardScreenController = applicationNavigationHandlers
|
||||||
|
.cardScreenController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.navigation
|
|
||||||
|
|
||||||
import com.github.nacabaro.vbhelper.R
|
|
||||||
|
|
||||||
sealed class BottomNavItem (
|
|
||||||
var route: String,
|
|
||||||
var icon: Int,
|
|
||||||
var label: String
|
|
||||||
) {
|
|
||||||
object Scan : BottomNavItem("Scan", R.drawable.baseline_nfc_24, "Scan")
|
|
||||||
object Battles : BottomNavItem("Battle", R.drawable.baseline_swords_24, "Battle")
|
|
||||||
object Home : BottomNavItem("Home", R.drawable.baseline_cottage_24, "Home")
|
|
||||||
object Dex : BottomNavItem("Dex", R.drawable.baseline_menu_book_24, "Dex")
|
|
||||||
object Storage : BottomNavItem("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
|
|
||||||
object Settings : BottomNavItem("Settings", R.drawable.baseline_settings_24, "Settings")
|
|
||||||
}
|
|
||||||
@@ -8,25 +8,26 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomNavigationBar(navController: NavController) {
|
fun BottomNavigationBar(navController: NavController) {
|
||||||
val items = listOf(
|
val items = listOf(
|
||||||
BottomNavItem.Scan,
|
NavigationItems.Items,
|
||||||
BottomNavItem.Battles,
|
NavigationItems.Battles,
|
||||||
BottomNavItem.Home,
|
NavigationItems.Home,
|
||||||
BottomNavItem.Dex,
|
NavigationItems.Dex,
|
||||||
BottomNavItem.Storage,
|
NavigationItems.Storage,
|
||||||
)
|
)
|
||||||
|
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
val currentBackStackEntry = navController.currentBackStackEntryAsState()
|
val currentBackStackEntry = navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = currentBackStackEntry.value?.destination?.route
|
val currentRoute = currentBackStackEntry.value?.destination?.route
|
||||||
|
|
||||||
items.forEach { item ->
|
items.forEach { item ->
|
||||||
NavigationBarItem (
|
NavigationBarItem (
|
||||||
icon = { Icon(painter = painterResource(item.icon), contentDescription = item.label) },
|
icon = { Icon(painter = painterResource(item.icon), contentDescription = stringResource(item.label)) },
|
||||||
label = { Text(item.label) },
|
label = { Text(text = stringResource(item.label)) },
|
||||||
selected = currentRoute == item.route,
|
selected = currentRoute == item.route,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigate(item.route) {
|
navController.navigate(item.route) {
|
||||||
|
|||||||
@@ -0,0 +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(
|
||||||
|
val route: String,
|
||||||
|
@DrawableRes val icon: Int,
|
||||||
|
@StringRes val label: Int
|
||||||
|
) {
|
||||||
|
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,34 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
|
||||||
import com.github.nacabaro.vbhelper.navigation.BottomNavItem
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun HomeScreen(
|
|
||||||
navController: NavController
|
|
||||||
) {
|
|
||||||
Scaffold (
|
|
||||||
topBar = {
|
|
||||||
TopBanner(
|
|
||||||
text = "VB Helper",
|
|
||||||
onGearClick = {
|
|
||||||
navController.navigate(BottomNavItem.Settings.route)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { contentPadding ->
|
|
||||||
Box (
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = contentPadding.calculateTopPadding())
|
|
||||||
) {
|
|
||||||
Text("Home Screen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.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.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
|
||||||
import com.github.nacabaro.vbhelper.navigation.BottomNavItem
|
|
||||||
import com.github.nacabaro.vbhelper.screens.scanScreen.ReadingCharacterScreen
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ScanScreen(
|
|
||||||
navController: NavController,
|
|
||||||
onClickRead: () -> Unit,
|
|
||||||
isDoneReadingCharacter: Boolean
|
|
||||||
) {
|
|
||||||
var readingScreen by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (isDoneReadingCharacter) {
|
|
||||||
readingScreen = false
|
|
||||||
navController.navigate(BottomNavItem.Home.route)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readingScreen) {
|
|
||||||
ReadingCharacterScreen { readingScreen = false }
|
|
||||||
} else {
|
|
||||||
ChooseConnectOption(
|
|
||||||
onClickRead = {
|
|
||||||
readingScreen = true
|
|
||||||
onClickRead()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ChooseConnectOption(
|
|
||||||
onClickRead: () -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
topBar = { TopBanner(text = "Scan a Vital Bracelet") }
|
|
||||||
) { contentPadding ->
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(contentPadding)
|
|
||||||
) {
|
|
||||||
ScanButton(
|
|
||||||
text = "Vital Bracelet to App",
|
|
||||||
onClick = onClickRead
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
ScanButton(
|
|
||||||
text = "App to Vital Bracelet",
|
|
||||||
onClick = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ScanButton(
|
|
||||||
text: String,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
|
||||||
@Composable
|
|
||||||
fun ScanScreenPreview() {
|
|
||||||
ScanScreen(
|
|
||||||
navController = rememberNavController(),
|
|
||||||
onClickRead = { },
|
|
||||||
isDoneReadingCharacter = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SettingsScreen(
|
|
||||||
navController: NavController
|
|
||||||
) {
|
|
||||||
Scaffold (
|
|
||||||
topBar = {
|
|
||||||
TopBanner(
|
|
||||||
text = "Settings",
|
|
||||||
onBackClick = {
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) { contentPadding ->
|
|
||||||
Column (
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = contentPadding.calculateTopPadding())
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
SettingsSection("General")
|
|
||||||
SettingsEntry(title = "Import VB key", description = "Import standard vital bracelet keys") { }
|
|
||||||
SettingsEntry(title = "Import VB Characters key", description = "Import standard vital bracelet keys") { }
|
|
||||||
SettingsEntry(title = "Import VB BE key", description = "Import standard vital bracelet keys") { }
|
|
||||||
SettingsEntry(title = "Import transform functions", description = "Import standard vital bracelet keys") { }
|
|
||||||
SettingsEntry(title = "Import decryption key", description = "Import standard vital bracelet keys") { }
|
|
||||||
SettingsSection("DiM/BEm management")
|
|
||||||
SettingsEntry(title = "Import DiM card", description = "Import DiM card file") { }
|
|
||||||
SettingsEntry(title = "Import BEm card", description = "Import BEm card file") { }
|
|
||||||
SettingsSection("About and credits")
|
|
||||||
SettingsEntry(title = "Credits", description = "Credits") { }
|
|
||||||
SettingsEntry(title = "About", description = "About") { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SettingsEntry(
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { onClick() }
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Text(text = title)
|
|
||||||
Text(
|
|
||||||
text = description,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = MaterialTheme.colorScheme.outline
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SettingsSection(
|
|
||||||
title: String
|
|
||||||
) {
|
|
||||||
Box (
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 16.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.gestures.Orientation
|
|
||||||
import androidx.compose.foundation.gestures.scrollable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.CardElevation
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import com.github.nacabaro.vbhelper.R
|
|
||||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
|
||||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
|
||||||
import com.github.nacabaro.vbhelper.source.StorageRepository
|
|
||||||
import com.github.nacabaro.vbhelper.temporary_domain.TemporaryCharacterData
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StorageScreen() {
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val application = LocalContext.current.applicationContext as VBHelper
|
|
||||||
val storageRepository = StorageRepository(application.container.db)
|
|
||||||
val monList = remember { mutableStateListOf<TemporaryCharacterData>() }
|
|
||||||
|
|
||||||
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
|
|
||||||
|
|
||||||
LaunchedEffect(storageRepository) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
monList.clear()
|
|
||||||
monList.addAll(storageRepository.getAllCharacters())
|
|
||||||
Log.d("StorageScreen", "Updated data: $monList")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("StorageScreen", "monList: $monList")
|
|
||||||
|
|
||||||
Scaffold (
|
|
||||||
topBar = { TopBanner(text = "My Digimon") }
|
|
||||||
) { contentPadding ->
|
|
||||||
if (monList.isEmpty()) {
|
|
||||||
Column (
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(contentPadding)
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Nothing to see here",
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(3),
|
|
||||||
modifier = Modifier
|
|
||||||
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
|
|
||||||
.padding(top = contentPadding.calculateTopPadding())
|
|
||||||
) {
|
|
||||||
items(monList) { index ->
|
|
||||||
var showDialog by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
StorageEntry(
|
|
||||||
name = index.dimId.toString() + " - " + index.charIndex.toString(),
|
|
||||||
icon = R.drawable.ic_launcher_foreground,
|
|
||||||
onClick = { selectedCharacter = index.id },
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (selectedCharacter != null) {
|
|
||||||
StorageDialog(
|
|
||||||
characterId = selectedCharacter!!,
|
|
||||||
onDismissRequest = { selectedCharacter = null }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StorageEntry(
|
|
||||||
name: String,
|
|
||||||
icon: Int,
|
|
||||||
onClick: () -> Unit = {},
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Card(
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = icon),
|
|
||||||
contentDescription = name,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(64.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = name,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StorageDialog(
|
|
||||||
characterId: Long,
|
|
||||||
onDismissRequest: () -> Unit
|
|
||||||
) {
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val application = LocalContext.current.applicationContext as VBHelper
|
|
||||||
val storageRepository = StorageRepository(application.container.db)
|
|
||||||
val character = remember { mutableStateOf<TemporaryCharacterData?>(null) }
|
|
||||||
|
|
||||||
LaunchedEffect(storageRepository) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
character.value = storageRepository.getSingleCharacter(characterId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
properties = DialogProperties(
|
|
||||||
dismissOnBackPress = true,
|
|
||||||
dismissOnClickOutside = true
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Card(
|
|
||||||
shape = RoundedCornerShape(16.dp)
|
|
||||||
) {
|
|
||||||
Column (
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
if (character.value != null) {
|
|
||||||
Text(
|
|
||||||
text = character.value?.toString() ?: "Loading...",
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
onClick = onDismissRequest
|
|
||||||
) {
|
|
||||||
Text(text = "Close")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AdventureEntry(
|
||||||
|
icon: BitmapData,
|
||||||
|
timeLeft: Long,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val bitmap = remember (icon.bitmap) { icon.getBitmap() }
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (icon.width * 4 / density).dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(96.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = null,
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = when {
|
||||||
|
timeLeft < 0 -> "Adventure finished"
|
||||||
|
else -> "Time left: ${formatSeconds(timeLeft)}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatSeconds(totalSeconds: Long): String {
|
||||||
|
val hours = totalSeconds / 3600
|
||||||
|
val minutes = (totalSeconds % 3600) / 60
|
||||||
|
val seconds = totalSeconds % 60
|
||||||
|
|
||||||
|
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
|
||||||
|
}
|
||||||
+141
@@ -0,0 +1,141 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import java.time.Instant
|
||||||
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AdventureScreen(
|
||||||
|
navController: NavController,
|
||||||
|
storageScreenController: AdventureScreenControllerImpl
|
||||||
|
) {
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val database = application.container.db
|
||||||
|
val storageRepository = StorageRepository(database)
|
||||||
|
val characterList by storageRepository.getAdventureCharacters().collectAsState(emptyList())
|
||||||
|
|
||||||
|
var obtainedItem by remember {
|
||||||
|
mutableStateOf<ItemDtos.PurchasedItem?>(null)
|
||||||
|
}
|
||||||
|
var obtainedCurrency by remember {
|
||||||
|
mutableStateOf(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentTime by produceState(initialValue = Instant.now().epochSecond) {
|
||||||
|
while (true) {
|
||||||
|
value = Instant.now().epochSecond
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cancelAdventureDialog by remember {
|
||||||
|
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = stringResource(R.string.adventure_title),
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { contentPadding ->
|
||||||
|
if (characterList.isEmpty()) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.adventure_empty_state))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
items(characterList) {
|
||||||
|
AdventureEntry(
|
||||||
|
icon = BitmapData(
|
||||||
|
bitmap = it.spriteIdle,
|
||||||
|
width = it.spriteWidth,
|
||||||
|
height = it.spriteHeight
|
||||||
|
),
|
||||||
|
timeLeft = it.finishesAdventure - currentTime,
|
||||||
|
onClick = {
|
||||||
|
if (it.finishesAdventure < currentTime) {
|
||||||
|
storageScreenController
|
||||||
|
.getItemFromAdventure(it.id) { adventureResult, generatedCurrency ->
|
||||||
|
obtainedItem = adventureResult
|
||||||
|
obtainedCurrency = generatedCurrency
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cancelAdventureDialog = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obtainedItem != null) {
|
||||||
|
ObtainedItemDialog(
|
||||||
|
obtainedItem = obtainedItem!!,
|
||||||
|
obtainedCurrency = obtainedCurrency,
|
||||||
|
onClickDismiss = {
|
||||||
|
obtainedItem = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelAdventureDialog != null) {
|
||||||
|
CancelAdventureDialog(
|
||||||
|
characterSprite = BitmapData(
|
||||||
|
bitmap = cancelAdventureDialog!!.spriteIdle,
|
||||||
|
width = cancelAdventureDialog!!.spriteWidth,
|
||||||
|
height = cancelAdventureDialog!!.spriteHeight
|
||||||
|
),
|
||||||
|
onDismissRequest = {
|
||||||
|
cancelAdventureDialog = null
|
||||||
|
},
|
||||||
|
onClickConfirm = {
|
||||||
|
storageScreenController.cancelAdventure(cancelAdventureDialog!!.id) {
|
||||||
|
navController.navigate(NavigationItems.Storage.route)
|
||||||
|
}
|
||||||
|
cancelAdventureDialog = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
|
||||||
|
interface AdventureScreenController {
|
||||||
|
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
|
||||||
|
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem, Int) -> Unit)
|
||||||
|
fun cancelAdventure(characterId: Long, onResult: () -> Unit)
|
||||||
|
}
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class AdventureScreenControllerImpl(
|
||||||
|
private val componentActivity: ComponentActivity,
|
||||||
|
) : AdventureScreenController {
|
||||||
|
private val application = componentActivity.applicationContext as VBHelper
|
||||||
|
private val database = application.container.db
|
||||||
|
|
||||||
|
override fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) {
|
||||||
|
val finishesAdventureAt = timeInMinutes * 60
|
||||||
|
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val characterData = database
|
||||||
|
.userCharacterDao()
|
||||||
|
.getCharacter(characterId)
|
||||||
|
|
||||||
|
if (characterData.isActive) {
|
||||||
|
database
|
||||||
|
.userCharacterDao()
|
||||||
|
.clearActiveCharacter()
|
||||||
|
}
|
||||||
|
|
||||||
|
database
|
||||||
|
.adventureDao()
|
||||||
|
.insertNewAdventure(characterId, timeInMinutes, finishesAdventureAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemFromAdventure(
|
||||||
|
characterId: Long,
|
||||||
|
onResult: (ItemDtos.PurchasedItem, Int) -> Unit
|
||||||
|
) {
|
||||||
|
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
database
|
||||||
|
.adventureDao()
|
||||||
|
.deleteAdventure(characterId)
|
||||||
|
|
||||||
|
val generatedCurrency = generateRandomCurrency()
|
||||||
|
|
||||||
|
val generatedItem = generateItem(characterId)
|
||||||
|
|
||||||
|
onResult(generatedItem, generatedCurrency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun generateRandomCurrency(): Int {
|
||||||
|
val currentValue = application.container.currencyRepository.currencyValue.first()
|
||||||
|
val random = (2..6).random() * 1000
|
||||||
|
application.container.currencyRepository.setCurrencyValue(currentValue + random)
|
||||||
|
|
||||||
|
return random
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
|
||||||
|
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
database
|
||||||
|
.adventureDao()
|
||||||
|
.deleteAdventure(characterId)
|
||||||
|
|
||||||
|
componentActivity
|
||||||
|
.runOnUiThread {
|
||||||
|
Toast.makeText(
|
||||||
|
componentActivity,
|
||||||
|
"Adventure canceled",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
onResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun generateItem(characterId: Long): ItemDtos.PurchasedItem {
|
||||||
|
val character = database
|
||||||
|
.userCharacterDao()
|
||||||
|
.getCharacterInfo(characterId)
|
||||||
|
|
||||||
|
val randomItem = database
|
||||||
|
.itemDao()
|
||||||
|
.getAllItems()
|
||||||
|
.first()
|
||||||
|
.random()
|
||||||
|
|
||||||
|
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()
|
||||||
|
|
||||||
|
database
|
||||||
|
.itemDao()
|
||||||
|
.purchaseItem(
|
||||||
|
itemId = randomItem.id,
|
||||||
|
itemAmount = random
|
||||||
|
)
|
||||||
|
|
||||||
|
return ItemDtos.PurchasedItem(
|
||||||
|
itemId = randomItem.id,
|
||||||
|
itemAmount = random,
|
||||||
|
itemName = randomItem.name,
|
||||||
|
itemIcon = randomItem.itemIcon,
|
||||||
|
itemLength = randomItem.itemLength,
|
||||||
|
itemDescription = randomItem.description,
|
||||||
|
itemType = randomItem.itemType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CancelAdventureDialog(
|
||||||
|
characterSprite: BitmapData,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onClickConfirm: () -> Unit
|
||||||
|
) {
|
||||||
|
val bitmap = remember (characterSprite) { characterSprite.getBitmap() }
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (characterSprite.width * 4 / density).dp
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest
|
||||||
|
) {
|
||||||
|
Card {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = null,
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Are you sure you want to cancel this character's adventure?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClickConfirm
|
||||||
|
) {
|
||||||
|
Text(text = "Confirm")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.padding(4.dp))
|
||||||
|
Button(
|
||||||
|
onClick = onDismissRequest
|
||||||
|
) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
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.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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.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 com.github.nacabaro.vbhelper.R
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CardViewScreen(
|
||||||
|
navController: NavController,
|
||||||
|
cardId: Long
|
||||||
|
) {
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val dexRepository = DexRepository(application.container.db)
|
||||||
|
|
||||||
|
val characterList by dexRepository.getCharactersByCardId(cardId).collectAsState(emptyList())
|
||||||
|
|
||||||
|
val selectedCharacter = remember { mutableStateOf<CharacterDtos.CardCharaProgress?>(null) }
|
||||||
|
|
||||||
|
Scaffold (
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = stringResource(R.string.card_view_discovered_characters),
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
onAdventureClick = {
|
||||||
|
navController
|
||||||
|
.navigate(route = NavigationItems
|
||||||
|
.CardAdventure
|
||||||
|
.route
|
||||||
|
.replace(
|
||||||
|
"{cardId}",
|
||||||
|
cardId.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { contentPadding ->
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(3),
|
||||||
|
contentPadding = contentPadding
|
||||||
|
) {
|
||||||
|
items(characterList) { character ->
|
||||||
|
CharacterEntry(
|
||||||
|
onClick = {
|
||||||
|
selectedCharacter.value = character
|
||||||
|
},
|
||||||
|
obscure = character.discoveredOn == null,
|
||||||
|
icon = BitmapData(
|
||||||
|
bitmap = character.spriteIdle,
|
||||||
|
width = character.spriteWidth,
|
||||||
|
height = character.spriteHeight,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user