Compare commits

..
Author SHA1 Message Date
nacabaro c8e035aee1 Fixes 2026-06-24 21:28:22 +00:00
nacabaro 5787069761 Revert 457R0 work, merged wrong branch
Revert "Merge pull request #53 from 457R0/main"

This reverts commit 8aa31e819b, reversing
changes made to af27fc4933.
2026-06-22 14:55:12 +00:00
nacabaroandGitHub c67dc4bd02 Merge pull request #52 from shiki8826/main
Japanese Translation
2026-06-13 00:51:40 +02:00
nacabaroandGitHub 8aa31e819b Merge pull request #53 from 457R0/main
This should be the correct one without problem-report
2026-06-13 00:50:49 +02:00
shiki8826 bd8883e5d1 日本語翻訳追加 2026-06-09 23:11:30 +09:00
shiki8826 b094d6ccd3 日本語翻訳追加 2026-06-09 22:57:47 +09:00
Taveon Nelson 6c257b2080 HCE Savepoint
This is HCE before companion merge
2026-05-26 03:36:45 -05:00
Taveon Nelson 04ea584c4f Delete problems-report.html
removing useless files
2026-05-26 03:20:04 -05:00
nacabaroandGitHub af27fc4933 Merge pull request #49 from Cantalapiedra/patch-1
Update README.md
2026-05-24 21:19:05 +02:00
nacabaroandGitHub 1fffab25bd Merge pull request #51 from lightheel/main
Merging battle system into repo.
2026-05-24 21:18:00 +02:00
Taveon Nelson 1a83527590 speeding up HCE 2026-05-18 17:28:24 -05:00
Taveon Nelson 431be5faea Battlescreen login loop fix 2026-05-18 05:46:40 -05:00
Taveon Nelson 9aa62f0d81 companion reup 2026-05-18 05:17:08 -05:00
Taveon Nelson 8fd4f6c414 VBH is the companion app now
Got tired of having to reinstall all three apps so i ported the companion apps features to vitalwear, now you only need VBH and Vitalwear.

I've also added emblems to items in icons so user isnt confused.
2026-05-18 04:57:57 -05:00
457R0 f829efae68 Overwrite with local version 2026-05-03 18:07:07 -05:00
Taveon Nelson 895b704c8d Make VBH-VW transfer DB owner via provider and shared transfer DAO 2026-04-21 18:57:38 -05:00
Taveon Nelson d40428ccfd Reduce duplicate HCE scans and suppress NFC redispatch 2026-04-21 15:00:14 -05:00
Taveon Nelson cd9a6d5d80 Add VitalWear HCE reader client 2026-04-21 14:41:06 -05:00
Taveon Nelson e0a086d9aa This patch enables transfer between vitalwear and VBH 2026-04-21 14:36:27 -05:00
Taveon Nelson e787ea4a19 Initial commit of VBHelper 2026-04-18 17:19:34 -05:00
Taveon Nelson 5273bae7b6 Update: changes and new files from local development 2026-04-18 07:24:06 -05:00
Carlos P CantalapiedraandGitHub 706f30ea49 Update README.md
Step 2 "VB Arena" should read "VB Helper"
2026-04-03 10:15:36 +02:00
lightheel 3f29d725ea Updated opponent list to show display name. 2026-03-06 15:16:43 -05:00
lightheel 977896d296 Merge bugfixes. 2026-03-06 12:20:28 -05:00
lightheel 654529c851 Merge remote-tracking branch 'upstream/main' 2026-03-06 10:56:32 -05:00
nacabaro 366f425539 Updated libraries
Moved library definitions to the catalog, and updated the catalog
2026-01-28 02:05:43 +01:00
nacabaro 37076a9bf0 VERSION UP 2026-01-25 20:00:55 +01:00
nacabaroandGitHub 8c99046edb Merge pull request #47 from nacabaro/specialMissions/fix
Fix Special Missions a bit more
2026-01-25 19:58:39 +01:00
nacabaro c990a495b7 Fix Special Missions a bit more
Now they should behave a bit better, and it will not allow the watch to have multiple missions of the same type, plus you can now delete special missions.
2026-01-25 19:58:03 +01:00
nacabaroandGitHub 0e61723db1 Merge pull request #46 from nacabaro/ui/cardIcon
Card icon in home screen
2026-01-25 17:53:30 +01:00
nacabaro 8815907563 Card icon in home screen
Together with character sprite
2026-01-25 17:35:54 +01:00
lightheel 566e2ec977 Battles now update wins/losses in room DB. 2026-01-21 22:53:42 -05:00
lightheel 65a7ccb221 Fixed resume battle max HP display bug. 2026-01-21 21:27:11 -05:00
lightheel 2201b7d0fe Setup resume/quit options if match gets disconnected before it finishes. 2026-01-21 20:23:51 -05:00
lightheel a9c354ad8a Updated to use Long instead of Int on playerID. 2026-01-19 22:21:05 -05:00
lightheel 9365bc0215 API calls now use X-Session-Token. 2026-01-19 22:16:09 -05:00
lightheel efa4bab144 Merge remote-tracking branch 'upstream/main' 2026-01-18 20:37:58 -05:00
nacabaroandGitHub 6cce33c5f6 Merge pull request #44 from jeffersoncarlospedroso/feature/add-multilanguage-support
Add multilanguage support (i18n) initial implementation
2025-12-14 12:31:27 +01:00
jeffersoncarlospedroso 16fbfae0c2 feat(i18n): continue translation work 2025-12-11 22:38:43 +00:00
jeffersoncarlospedroso 0d174f1550 Add multilanguage support (i18n) initial implementation 2025-12-10 23:06:37 +00:00
lightheel eb82c2afc1 Added BGM to battles. 2025-11-17 17:52:53 -05:00
lightheel c774fd1536 Adjusted auth to not force login every time app is reopened. 2025-11-17 17:34:58 -05:00
lightheel 54b2905196 Changed to use small attack sprites instead of large. 2025-11-17 17:25:57 -05:00
lightheel c4450296db Removed debugging logging. 2025-11-17 17:22:25 -05:00
lightheel d1908d629a Fixed logout when swapping from screen orientation. 2025-11-17 16:52:07 -05:00
lightheel 67b56b3990 Reduced logging. 2025-11-17 16:37:59 -05:00
lightheel b0c5d6375d Setup API calls to use User ID. 2025-11-17 15:06:42 -05:00
lightheel 29ff2805c3 Added NacaAuth for logging into Battles. 2025-11-17 14:31:18 -05:00
lightheel b4d509aad9 Swapped from using internal app storage to android device storage. 2025-11-17 11:11:58 -05:00
lightheel 44c6382356 Merge remote-tracking branch 'upstream/main' 2025-11-17 10:26:59 -05:00
nacabaro 489e27b038 More things
- Active characters display different
- Adventure missions award credits (same as special missions)
- Clicking on about will open the github page
- Corrected from arean to arena
- Removed rename from the settings (it was not used)
2025-11-16 21:59:08 +00:00
nacabaro 6be167bbed Might be incomplete uhhh 2025-11-16 21:13:28 +01:00
lightheel 0f1feb88b8 Update .gitignore 2025-10-19 17:36:20 -04:00
lightheel 5ddb8f5da9 Battle screen now filters opponents automatically based on active Digimon's stage. 2025-10-19 13:32:41 -04:00
lightheel 0875b114d5 Fixed HP desync with battle. 2025-10-19 13:09:14 -04:00
lightheel 61daad459b Setup player API call to pull from active Digi. 2025-10-19 12:55:57 -04:00
lightheel 14e941031c Updated API to send charaID instead of character name for match setup. 2025-10-19 12:14:32 -04:00
lightheel 952fd5a871 Added scrollable button list for opponents of all stages. 2025-10-19 12:02:14 -04:00
lightheel ac02205f76 Updated sprites to load from phone's internal storage. 2025-10-19 10:20:01 -04:00
lightheel 68ad57b78f Update .gitignore 2025-10-19 08:24:50 -04:00
lightheelandGitHub 07983d9403 Merge branch 'nacabaro:main' into main 2025-09-05 11:42:43 -04:00
lightheelandGitHub 9c581a5ebd Merge pull request #1 from lightheel/vb_battle_client
Vb battle client
2025-08-15 15:46:35 -04:00
lightheelandGitHub 0add667ef8 Merge branch 'nacabaro:main' into vb_battle_client 2025-08-15 15:44:42 -04:00
lightheel c122b71b46 Changed references to be correct. 2025-08-15 11:55:45 -04:00
lightheel 5ed7d117f5 Fixed background scaling issues in landscape view. 2025-08-08 13:20:40 -04:00
lightheel 16cd7abce8 Added multiple background sets for battle. 2025-08-08 12:37:32 -04:00
lightheel 6a6369ae9e Adjusted attack hit sprite location in landscape view. 2025-08-08 12:17:47 -04:00
lightheel 1bbaf66c24 Removed exit button from view until battle is over. 2025-08-08 09:24:30 -04:00
lightheel ba03be808e Removed attack bar and button from player view. 2025-08-08 07:51:51 -04:00
lightheel bb1c29bbb4 Moved sprites to load from external Android storage.
Setup landscape view for battle.
2025-08-08 07:44:36 -04:00
lightheel f0760f9ed0 Added gray boxes around HP 2025-08-07 11:23:32 -04:00
lightheel 61a2439f84 Adjusted horizontal sprite shake timing to sync with attack hit. 2025-08-06 19:19:58 -04:00
lightheel 5bb9fe5209 Delayed damage text to show up when attack sprite hits. 2025-08-06 18:59:08 -04:00
lightheel 26fc0ced56 Adjusted hit effect timing. 2025-08-06 18:53:23 -04:00
lightheel 371a850d45 Offset idle animation so player/enemy Digimon aren't in sync. 2025-08-06 18:38:11 -04:00
lightheel d833a89c17 Added hit sprites. 2025-08-06 18:03:03 -04:00
lightheel 28cb824bf3 Changed battle background to use all 3 layers. 2025-08-06 12:36:21 -04:00
lightheel 0a643053af Adjusted HP text color and size. 2025-08-06 12:21:30 -04:00
lightheel 5989f48355 Added battle background. 2025-08-06 11:51:37 -04:00
lightheel de3d312a32 Fixed pop up damage timing and positioning. 2025-08-06 10:49:30 -04:00
lightheel cd33d06ecf Added pop up damage number. 2025-08-06 10:25:26 -04:00
lightheel 6ea9946412 Attack sprites now shows on main battle screen. 2025-08-06 09:00:51 -04:00
lightheel ec3058b511 Adjusted dodge trigger. 2025-08-05 20:03:50 -04:00
lightheel 26842d1b1b Reduced attack button size. 2025-08-05 19:55:36 -04:00
lightheel b74b04cda9 Fixed sprite positioning on main screen. 2025-08-05 19:50:37 -04:00
lightheel 481c0b6d9a Adjusted BattlesScreen UI. 2025-08-05 19:42:41 -04:00
lightheel a3bebcf290 Adjusted animation timings. 2025-08-05 19:42:31 -04:00
lightheel b3cf823c3f Swapped to 3 panel battle. 2025-08-05 18:35:50 -04:00
lightheel 7843be7004 Fixed dodge animation. Sprites now move up then back to original location. 2025-08-05 17:35:44 -04:00
lightheel f0f1d9830e Added dodge and attack hit animations. 2025-08-05 16:14:17 -04:00
lightheel c404f4f436 Raised opponent HP bar. 2025-08-05 07:01:39 -04:00
lightheel 71ba5e0207 Fixed atk sprite import bug. Readjusted sprites to load from individual files instead of full spritesheets. 2025-08-05 06:54:55 -04:00
lightheel fb09350825 Created sprite and animation tester menu. 2025-08-04 14:26:24 -04:00
lightheel 615fb85204 Added sprite test button to battle. 2025-08-04 14:13:26 -04:00
lightheel 3687ff2c21 Updated idle animation to use 2 sprites instead of just 1. 2025-08-04 13:26:39 -04:00
lightheel c973030d9d Fixed animation mapping.
Was previously using _00, _01, _02, etc. number suffix at end of .json file.

Actual sprite index is stored as the m_Name field inside this file.
2025-08-04 13:23:04 -04:00
lightheel 9f7e452850 Added Digimon idle animation. 2025-08-04 13:00:16 -04:00
lightheel 37e5efa874 Started setting up sprite animations. 2025-08-04 12:57:35 -04:00
lightheel c5cebd8213 Fixed bug where enemy attack was showing as player attack sprite for a moment before animation. 2025-08-04 12:21:41 -04:00
lightheel cfa52bce9b Fixed enemy attack sprite positioning. 2025-08-04 12:14:29 -04:00
lightheel acd990d32b Adjusted player attack sprite positioning. 2025-08-04 12:06:46 -04:00
lightheel c8690152bc Fixed player attack sprite on enemy screen.
Was showing enemy attack sprite.
2025-08-04 12:02:41 -04:00
lightheel eff2fadb55 Enemy attack sprite now shows on player screen. 2025-08-04 11:32:41 -04:00
lightheel 8f790eea41 Fixed player Digimon positioning. 2025-08-04 11:25:01 -04:00
lightheel 9d0e68fb8a Re-added missing UI pieces (HP bar, HP numbers, & exit button). 2025-08-04 11:22:22 -04:00
lightheel 31fab9dba4 Fixed damage application timing. 2025-08-04 11:13:01 -04:00
lightheel 7af8e00e6f Re-added missing exit button and health bars. 2025-08-04 11:02:51 -04:00
lightheel 4e12962c05 Fixed sprite naming bug. 2025-08-04 10:48:21 -04:00
lightheel b3a4ced28d Commented out crit bar progress to stop log noise. 2025-08-04 10:48:06 -04:00
lightheel d7b10b1ae8 Update BattlesScreen.kt 2025-08-04 10:04:30 -04:00
lightheel 942d843601 Create ArenaBattleSystem.kt 2025-08-04 10:04:05 -04:00
lightheel 3b762d6195 Updated battle loop. Started to fix attack animations. 2025-08-03 15:25:37 -04:00
lightheel 023af17b23 Updated BattleScreen to perform full battle loop with API calls. 2025-08-03 13:23:46 -04:00
lightheel 2901bcf0da Updated attack button to actually perform stage 1 API call. 2025-08-03 12:39:03 -04:00
lightheel 266658342a Fixed sprite sizing. 2025-08-03 12:14:24 -04:00
lightheel a4b159da45 Sprites now load correctly for Digimon and attacks. 2025-08-03 11:37:43 -04:00
lightheel fa8546f283 Updated file and sprite managers to use new file structure. 2025-08-03 11:29:40 -04:00
lightheel d86ee00109 Update .gitignore 2025-08-03 11:29:23 -04:00
lightheel 6c9d057917 Updated client for attack battle loop. 2025-08-02 07:49:43 -04:00
lightheel a044d24f5f Updated BattleScreen.kt. Now starts battle and allow clicking of first attack button. 2025-08-02 06:26:27 -04:00
lightheel 1d21155198 Create AttackSpriteManager.kt 2025-08-02 06:26:01 -04:00
lightheel 09ee139add Create BattleSpriteManager.kt 2025-08-02 06:25:59 -04:00
lightheel 8d9c507645 Create SpriteFileManager.kt 2025-08-02 06:25:57 -04:00
lightheel c947e2519c Create AttackSpriteImage.kt 2025-08-02 06:25:54 -04:00
lightheel 9bcbe85b7f Create SpriteImage.kt 2025-08-02 06:25:48 -04:00
lightheel a72718273c Added imports to remove build errors. Started added battle screens, logic, and animations. 2025-08-01 17:04:35 -04:00
lightheel bd0cc46398 Setup API calls for stage 0 on Digimon name button click. 2025-08-01 15:53:40 -04:00
lightheel a011ae39a4 First push on branch. 2025-08-01 15:42:37 -04:00
85 changed files with 8717 additions and 330 deletions
+9
View File
@@ -10,3 +10,12 @@ 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/com.bandai.vitalbraceletarena.apk
app/src/test/resources/com/github/nacabaro/vbhelper/source/classes.dex 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
build/
+1 -1
View File
@@ -26,7 +26,7 @@ App also comes with a dex that will update every time a new character is added,
2. Once the files are extracted, look for an APK called `com.bandai.vitalbraceletarena.apk`. Copy it somewhere else, you will need it. 2. Once the files are extracted, look for an APK called `com.bandai.vitalbraceletarena.apk`. Copy it somewhere else, you will need it.
2. Install an APK release for VB Arena. You will find the releases [here](http://github.com/nacabaro/vbhelper/releases). Download the latest release and install its APK. 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. 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.
+36 -10
View File
@@ -1,21 +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.2"
id("com.google.protobuf") 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 = "Alpha 0.6.1" versionName = "Alpha 0.6.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -33,9 +35,7 @@ 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
} }
@@ -45,6 +45,12 @@ android {
} }
} }
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
protobuf { protobuf {
protoc { protoc {
artifact = "com.google.protobuf:protoc:4.27.0" artifact = "com.google.protobuf:protoc:4.27.0"
@@ -68,10 +74,13 @@ dependencies {
implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.runtime)
implementation(libs.vb.nfc.reader) implementation(libs.vb.nfc.reader)
implementation(libs.dim.reader) implementation(libs.dim.reader)
ksp(libs.androidx.room.compiler)
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))
@@ -80,11 +89,21 @@ dependencies {
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")
@@ -92,4 +111,11 @@ dependencies {
implementation(libs.protobuf.javalite) implementation(libs.protobuf.javalite)
implementation("androidx.compose.material:material") implementation("androidx.compose.material:material")
implementation("androidx.datastore:datastore-preferences:1.1.7") 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")
} }
File diff suppressed because it is too large Load Diff
+19
View File
@@ -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>
@@ -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
)
}
}
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -35,20 +36,27 @@ import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.utils.getObscuredBitmap import com.github.nacabaro.vbhelper.utils.getObscuredBitmap
import androidx.compose.ui.res.stringResource
@Composable @Composable
fun CharacterEntry( fun CharacterEntry(
icon: BitmapData, icon: BitmapData,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
cardIcon: BitmapData? = null,
obscure: Boolean = false, obscure: Boolean = false,
disabled: Boolean = false, disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium, shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 4, multiplier: Int = 4,
cardColors: CardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
),
onClick: () -> Unit = { } onClick: () -> Unit = { }
) { ) {
val bitmap = remember (icon.bitmap) { val bitmap = remember (icon.bitmap) {
if(obscure) icon.getObscuredBitmap() else icon.getBitmap() if(obscure) icon.getObscuredBitmap() else icon.getBitmap()
} }
val iconSizeMultiplier = 3
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() } val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * multiplier / density).dp val dpSize = (icon.width * multiplier / density).dp
@@ -61,7 +69,8 @@ fun CharacterEntry(
}, },
modifier = modifier modifier = modifier
.aspectRatio(1f) .aspectRatio(1f)
.padding(8.dp) .padding(8.dp),
colors = cardColors
) { ) {
Box( Box(
contentAlignment = Alignment.BottomCenter, contentAlignment = Alignment.BottomCenter,
@@ -79,7 +88,24 @@ fun CharacterEntry(
}, },
modifier = Modifier modifier = Modifier
.size(dpSize) .size(dpSize)
.align(Alignment.BottomCenter)
) )
if (cardIcon != null) {
val bitmap = remember (icon.bitmap) { cardIcon.getBitmap() }
val iconBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val dpSize = (icon.width * iconSizeMultiplier /density).dp
Image(
bitmap = iconBitmap,
contentDescription = "Card icon",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
.align(Alignment.BottomEnd)
.padding(8.dp)
)
}
} }
} }
} }
@@ -126,14 +152,27 @@ fun ItemDisplay(
fun SpecialMissionsEntry( fun SpecialMissionsEntry(
specialMission: SpecialMissions, specialMission: SpecialMissions,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClickCard: () -> Unit = { }, onClickMission: (Long) -> Unit = { },
onClickCollect: (Long) -> Unit = { }
) { ) {
val textValue = when (specialMission.missionType) { val textValue = when (specialMission.missionType) {
SpecialMission.Type.NONE -> "No mission selected" SpecialMission.Type.NONE -> stringResource(R.string.special_mission_none)
SpecialMission.Type.STEPS -> "Walk ${specialMission.goal} steps" SpecialMission.Type.STEPS -> stringResource(
SpecialMission.Type.BATTLES -> "Battle ${specialMission.goal} times" R.string.special_mission_steps,
SpecialMission.Type.WINS -> "Win ${specialMission.goal} battles" specialMission.goal
SpecialMission.Type.VITALS -> "Earn ${specialMission.goal} vitals" )
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles,
specialMission.goal
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins,
specialMission.goal
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals,
specialMission.goal
)
} }
val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) { val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) {
@@ -144,10 +183,22 @@ fun SpecialMissionsEntry(
val completion = when (specialMission.missionType) { val completion = when (specialMission.missionType) {
SpecialMission.Type.NONE -> "" SpecialMission.Type.NONE -> ""
SpecialMission.Type.STEPS -> "Walked $progress steps" SpecialMission.Type.STEPS -> stringResource(
SpecialMission.Type.BATTLES -> "Battled $progress times" R.string.special_mission_steps_progress,
SpecialMission.Type.WINS -> "Won $progress battles" progress
SpecialMission.Type.VITALS -> "Earned $progress vitals" )
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles_progress,
progress
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins_progress,
progress
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals_progress,
progress
)
} }
val icon = when (specialMission.missionType) { val icon = when (specialMission.missionType) {
@@ -169,10 +220,12 @@ fun SpecialMissionsEntry(
Card( Card(
modifier = modifier, modifier = modifier,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
onClick = if (specialMission.status == SpecialMission.Status.COMPLETED || specialMission.status == SpecialMission.Status.FAILED) { onClick = if (specialMission.status == SpecialMission.Status.COMPLETED) {
onClickCard { onClickCollect(specialMission.id) }
} else if (specialMission.status == SpecialMission.Status.UNAVAILABLE) {
{ }
} else { } else {
{ } { onClickMission(specialMission.id) }
}, },
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = color containerColor = color
@@ -188,7 +241,7 @@ fun SpecialMissionsEntry(
) { ) {
Icon( Icon(
painter = painterResource(icon), painter = painterResource(icon),
contentDescription = "Vitals", contentDescription = stringResource(R.string.special_mission_icon_content_description),
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.padding(16.dp) .padding(16.dp)
@@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao @Dao
@@ -37,7 +38,7 @@ interface AdventureDao {
JOIN Adventure a ON uc.id = a.characterId JOIN Adventure a ON uc.id = a.characterId
""" """
) )
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>>
@Query(""" @Query("""
DELETE FROM Adventure DELETE FROM Adventure
@@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@@ -34,4 +35,15 @@ interface CardDao {
@Query("DELETE FROM Card WHERE id = :id") @Query("DELETE FROM Card WHERE id = :id")
suspend fun deleteCard(id: Long) suspend fun deleteCard(id: Long)
@Query("""
SELECT
c.logo as cardIcon,
c.logoWidth as cardIconWidth,
c.logoHeight as cardIconHeight
FROM Card c
JOIN CardCharacter cc ON cc.cardId = c.id
WHERE cc.id = :charaId
""")
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon>
} }
@@ -2,6 +2,8 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface SpecialMissionDao { interface SpecialMissionDao {
@@ -12,4 +14,11 @@ interface SpecialMissionDao {
WHERE id = :id WHERE id = :id
""") """)
suspend fun clearSpecialMission(id: Long) suspend fun clearSpecialMission(id: Long)
@Query("""
SELECT *
FROM SpecialMissions
WHERE id = :id
""")
fun getSpecialMission(id: Long): Flow<SpecialMissions>
} }
@@ -53,7 +53,7 @@ interface UserCharacterDao {
WHERE monId = :monId WHERE monId = :monId
""" """
) )
suspend fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>? fun getTransformationHistory(monId: Long): Flow<List<CharacterDtos.TransformationHistory>>
@Query( @Query(
""" """
@@ -69,7 +69,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId JOIN Card d ON d.id = c.cardId
@@ -93,7 +94,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = d.id JOIN Card d ON c.cardId = d.id
@@ -108,13 +110,13 @@ interface UserCharacterDao {
suspend fun getCharacter(id: Long): UserCharacter suspend fun getCharacter(id: Long): UserCharacter
@Query("SELECT * FROM BECharacterData WHERE id = :id") @Query("SELECT * FROM BECharacterData WHERE id = :id")
suspend fun getBeData(id: Long): BECharacterData fun getBeData(id: Long): Flow<BECharacterData>
@Query("SELECT * FROM VBCharacterData WHERE id = :id") @Query("SELECT * FROM VBCharacterData WHERE id = :id")
suspend fun getVbData(id: Long): VBCharacterData fun getVbData(id: Long): Flow<VBCharacterData>
@Query("SELECT * FROM SpecialMissions WHERE characterId = :id") @Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
suspend fun getSpecialMissions(id: Long): List<SpecialMissions> fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>>
@Query( @Query(
""" """
@@ -130,7 +132,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId as isInAdventure a.characterId as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = d.id JOIN Card d ON c.cardId = d.id
@@ -140,7 +143,7 @@ interface UserCharacterDao {
LIMIT 1 LIMIT 1
""" """
) )
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?>
@Query("DELETE FROM UserCharacter WHERE id = :id") @Query("DELETE FROM UserCharacter WHERE id = :id")
fun deleteCharacterById(id: Long) fun deleteCharacterById(id: Long)
@@ -194,7 +197,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId JOIN Card d ON d.id = c.cardId
@@ -219,7 +223,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth, c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight, c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard, d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId JOIN Card d ON d.id = c.cardId
@@ -24,4 +24,10 @@ object CardDtos {
val characterHp: Int, val characterHp: Int,
val steps: Int, val steps: Int,
) )
data class CardIcon (
val cardIcon: ByteArray,
val cardIconWidth: Int,
val cardIconHeight: Int
)
} }
@@ -31,7 +31,8 @@ object CharacterDtos {
val nameSpriteWidth: Int, val nameSpriteWidth: Int,
val nameSpriteHeight: Int, val nameSpriteHeight: Int,
val isBemCard: Boolean, val isBemCard: Boolean,
val isInAdventure: Boolean val isInAdventure: Boolean,
val active: Boolean
) )
data class CardCharacterInfo( data class CardCharacterInfo(
@@ -1,13 +1,12 @@
package com.github.nacabaro.vbhelper.navigation package com.github.nacabaro.vbhelper.navigation
import android.util.Log
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -99,20 +98,14 @@ fun AppNavigation(
composable(NavigationItems.Scan.route) { composable(NavigationItems.Scan.route) {
val characterIdString = it.arguments?.getString("characterId") val characterIdString = it.arguments?.getString("characterId")
var characterId by remember { mutableStateOf(characterIdString?.toLongOrNull()) } var characterId by remember { mutableStateOf(characterIdString?.toLongOrNull()) }
Log.d("ScanScreen", "characterId: $characterId")
val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null) val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null)
if (characterId == null) { if (characterId == null) {
val context = LocalContext.current.applicationContext as VBHelper val context = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(context.container.db) val storageRepository = StorageRepository(context.container.db)
val characterData by storageRepository.getActiveCharacter().collectAsState(null)
LaunchedEffect(characterId) { if (characterData != null) {
if (characterId == null) { characterId = characterData!!.id
val characterData = storageRepository.getActiveCharacter()
if (characterData != null) {
characterId = characterData.id
}
}
} }
} }
@@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.compose.ui.res.stringResource
@Composable @Composable
fun BottomNavigationBar(navController: NavController) { fun BottomNavigationBar(navController: NavController) {
@@ -26,8 +26,8 @@ fun BottomNavigationBar(navController: NavController) {
items.forEach { item -> items.forEach { item ->
NavigationBarItem ( NavigationBarItem (
icon = { Icon(painter = painterResource(item.icon), contentDescription = item.label) }, icon = { Icon(painter = painterResource(item.icon), contentDescription = stringResource(item.label)) },
label = { Text(item.label) }, label = { Text(text = stringResource(item.label)) },
selected = currentRoute == item.route, selected = currentRoute == item.route,
onClick = { onClick = {
navController.navigate(item.route) { navController.navigate(item.route) {
@@ -1,25 +1,101 @@
package com.github.nacabaro.vbhelper.navigation package com.github.nacabaro.vbhelper.navigation
import com.github.nacabaro.vbhelper.R import com.github.nacabaro.vbhelper.R
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
sealed class NavigationItems ( sealed class NavigationItems(
var route: String, val route: String,
var icon: Int, @DrawableRes val icon: Int,
var label: String @StringRes val label: Int
) { ) {
object Scan : NavigationItems("Scan/{characterId}", R.drawable.baseline_nfc_24, "Scan") object Scan : NavigationItems(
object Battles : NavigationItems("Battle", R.drawable.baseline_swords_24, "Battle") "Scan/{characterId}",
object Home : NavigationItems("Home", R.drawable.baseline_cottage_24, "Home") R.drawable.baseline_nfc_24,
object Dex : NavigationItems("Dex", R.drawable.baseline_menu_book_24, "Dex") R.string.nav_scan
object CardAdventure : NavigationItems("CardAdventure/{cardId}", R.drawable.baseline_fort_24, "Card adventure") )
object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings") object Battles : NavigationItems(
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer") "Battle",
object CardView : NavigationItems("Card/{cardId}", R.drawable.baseline_image_24, "Card") R.drawable.baseline_swords_24,
object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items") R.string.nav_battle
object MyItems : NavigationItems("MyItems", R.drawable.baseline_data_24, "My items") )
object ItemsStore : NavigationItems("ItemsStore", R.drawable.baseline_data_24, "Items store")
object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item") object Home : NavigationItems(
object Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure") "Home",
object Credits : NavigationItems("Credits", R.drawable.baseline_data_24, "Credits") 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
@@ -9,16 +9,16 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
@@ -29,24 +29,25 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.Instant import java.time.Instant
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun AdventureScreen( fun AdventureScreen(
navController: NavController, navController: NavController,
storageScreenController: AdventureScreenControllerImpl storageScreenController: AdventureScreenControllerImpl
) { ) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db val database = application.container.db
val storageRepository = StorageRepository(database) val storageRepository = StorageRepository(database)
val characterList = remember { val characterList by storageRepository.getAdventureCharacters().collectAsState(emptyList())
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
}
var obtainedItem by remember { var obtainedItem by remember {
mutableStateOf<ItemDtos.PurchasedItem?>(null) mutableStateOf<ItemDtos.PurchasedItem?>(null)
} }
var obtainedCurrency by remember {
mutableStateOf(0)
}
val currentTime by produceState(initialValue = Instant.now().epochSecond) { val currentTime by produceState(initialValue = Instant.now().epochSecond) {
while (true) { while (true) {
@@ -59,24 +60,17 @@ fun AdventureScreen(
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null) mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
} }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository
.getAdventureCharacters()
}
}
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Adventure", text = stringResource(R.string.adventure_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
) )
} }
) { contentPadding -> ) { contentPadding ->
if (characterList.value.isEmpty()) { if (characterList.isEmpty()) {
Column( Column(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@@ -84,14 +78,14 @@ fun AdventureScreen(
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
) { ) {
Text(text = "Nothing to see here") Text(text = stringResource(R.string.adventure_empty_state))
} }
} else { } else {
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(characterList.value) { items(characterList) {
AdventureEntry( AdventureEntry(
icon = BitmapData( icon = BitmapData(
bitmap = it.spriteIdle, bitmap = it.spriteIdle,
@@ -102,8 +96,9 @@ fun AdventureScreen(
onClick = { onClick = {
if (it.finishesAdventure < currentTime) { if (it.finishesAdventure < currentTime) {
storageScreenController storageScreenController
.getItemFromAdventure(it.id) { adventureResult -> .getItemFromAdventure(it.id) { adventureResult, generatedCurrency ->
obtainedItem = adventureResult obtainedItem = adventureResult
obtainedCurrency = generatedCurrency
} }
} else { } else {
cancelAdventureDialog = it cancelAdventureDialog = it
@@ -118,6 +113,7 @@ fun AdventureScreen(
if (obtainedItem != null) { if (obtainedItem != null) {
ObtainedItemDialog( ObtainedItemDialog(
obtainedItem = obtainedItem!!, obtainedItem = obtainedItem!!,
obtainedCurrency = obtainedCurrency,
onClickDismiss = { onClickDismiss = {
obtainedItem = null obtainedItem = null
} }
@@ -4,6 +4,6 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface AdventureScreenController { interface AdventureScreenController {
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit) fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem, Int) -> Unit)
fun cancelAdventure(characterId: Long, onResult: () -> Unit) fun cancelAdventure(characterId: Long, onResult: () -> Unit)
} }
@@ -38,19 +38,29 @@ class AdventureScreenControllerImpl(
override fun getItemFromAdventure( override fun getItemFromAdventure(
characterId: Long, characterId: Long,
onResult: (ItemDtos.PurchasedItem) -> Unit onResult: (ItemDtos.PurchasedItem, Int) -> Unit
) { ) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database database
.adventureDao() .adventureDao()
.deleteAdventure(characterId) .deleteAdventure(characterId)
val generatedCurrency = generateRandomCurrency()
val generatedItem = generateItem(characterId) val generatedItem = generateItem(characterId)
onResult(generatedItem) onResult(generatedItem, generatedCurrency)
} }
} }
private suspend fun generateRandomCurrency(): Int {
val currentValue = application.container.currencyRepository.currencyValue.first()
val random = (2..6).random() * 1000
application.container.currencyRepository.setCurrencyValue(currentValue + random)
return random
}
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) { override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database database
@@ -11,6 +11,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardAdventureScreen( fun CardAdventureScreen(
@@ -28,7 +31,7 @@ fun CardAdventureScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "Adventure missions", text = stringResource(R.string.card_adventure_missions_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -24,6 +24,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap import com.github.nacabaro.vbhelper.utils.getBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardEntry( fun CardEntry(
@@ -75,7 +78,11 @@ fun CardEntry(
modifier = Modifier modifier = Modifier
) )
Text( Text(
text = "$obtainedCharacters of $totalCharacters characters obtained", text = stringResource(
R.string.card_entry_characters_obtained,
obtainedCharacters,
totalCharacters
),
fontFamily = MaterialTheme.typography.labelSmall.fontFamily, fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize, fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier modifier = Modifier
@@ -91,7 +98,7 @@ fun CardEntry(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Edit, imageVector = Icons.Default.Edit,
contentDescription = "Edit" contentDescription = stringResource(R.string.card_entry_edit)
) )
} }
IconButton( IconButton(
@@ -99,7 +106,7 @@ fun CardEntry(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Delete, imageVector = Icons.Default.Delete,
contentDescription = "Delete" contentDescription = stringResource(R.string.card_entry_delete)
) )
} }
} }
@@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
@@ -19,6 +20,8 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.DexCharaDetailsDialog import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.DexCharaDetailsDialog
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardViewScreen( fun CardViewScreen(
@@ -35,7 +38,7 @@ fun CardViewScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "Discovered characters", text = stringResource(R.string.card_view_discovered_characters),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
}, },
@@ -24,6 +24,8 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CardsScreen( fun CardsScreen(
@@ -43,7 +45,7 @@ fun CardsScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "My cards", text = stringResource(R.string.cards_my_cards_title),
onModifyClick = { onModifyClick = {
modifyCards = !modifyCards modifyCards = !modifyCards
} }
@@ -33,7 +33,8 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap import com.github.nacabaro.vbhelper.utils.getImageBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun DexCharaDetailsDialog( fun DexCharaDetailsDialog(
@@ -119,7 +120,7 @@ fun DexCharaDetailsDialog(
) { ) {
Image( Image(
bitmap = charaImageBitmapData.imageBitmap, bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon", contentDescription = stringResource(R.string.dex_chara_icon_description),
modifier = Modifier modifier = Modifier
.size(charaImageBitmapData.dpWidth) .size(charaImageBitmapData.dpWidth)
.padding(8.dp), .padding(8.dp),
@@ -138,7 +139,7 @@ fun DexCharaDetailsDialog(
Column { Column {
Image( Image(
bitmap = nameImageBitmapData.imageBitmap, bitmap = nameImageBitmapData.imageBitmap,
contentDescription = "Icon", contentDescription = stringResource(R.string.dex_chara_name_icon_description),
modifier = Modifier modifier = Modifier
.width(nameImageBitmapData.dpWidth) .width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight), .height(nameImageBitmapData.dpHeight),
@@ -147,17 +148,28 @@ fun DexCharaDetailsDialog(
Spacer(modifier = Modifier.padding(4.dp)) Spacer(modifier = Modifier.padding(4.dp))
if (currentChara.baseHp != 65535) { if (currentChara.baseHp != 65535) {
Text( Text(
text = "HP: ${currentChara.baseHp}, BP: ${currentChara.baseBp}, AP: ${currentChara.baseAp}" text = stringResource(
R.string.dex_chara_stats,
currentChara.baseHp,
currentChara.baseBp,
currentChara.baseAp
)
)
Text(
text = stringResource(
R.string.dex_chara_stage_attribute,
romanNumeralsStage,
currentChara.attribute.toString().substring(0, 2)
)
) )
Text(text = "Stg: ${romanNumeralsStage}, Atr: ${currentChara.attribute.toString().substring(0, 2)}")
} }
} }
} else { } else {
Column { Column {
Text(text = "????????????????") Text(stringResource(R.string.dex_chara_unknown_name))
Spacer(modifier = Modifier.padding(4.dp)) Spacer(modifier = Modifier.padding(4.dp))
Text(text = "Stg: -, Atr: -") Text(stringResource(R.string.dex_chara_stage_attribute_unknown))
Text(text = "HP: -, BP: -, AP: -") Text(stringResource(R.string.dex_chara_stats_unknown))
} }
} }
} }
@@ -198,7 +210,7 @@ fun DexCharaDetailsDialog(
) { ) {
Image( Image(
bitmap = selectedCharaImageBitmap.imageBitmap, bitmap = selectedCharaImageBitmap.imageBitmap,
contentDescription = "Icon", contentDescription = stringResource(R.string.dex_chara_icon_description),
modifier = Modifier modifier = Modifier
.size(selectedCharaImageBitmap.dpWidth) .size(selectedCharaImageBitmap.dpWidth)
.padding(8.dp), .padding(8.dp),
@@ -214,8 +226,22 @@ fun DexCharaDetailsDialog(
.padding(16.dp) .padding(16.dp)
) )
Column { Column {
Text("Tr: ${it.requiredTrophies}; Bt: ${it.requiredBattles}; Vr: ${it.requiredVitals}; Wr: ${it.requiredWinRate}%; Ct: ${it.changeTimerHours}h") Text(
Text("AdvLvl ${it.requiredAdventureLevelCompleted + 1}") text = stringResource(
R.string.dex_chara_requirements,
it.requiredTrophies,
it.requiredBattles,
it.requiredVitals,
it.requiredWinRate,
it.changeTimerHours
)
)
Text(
text = stringResource(
R.string.dex_chara_adventure_level,
it.requiredAdventureLevelCompleted + 1
)
)
} }
} }
} }
@@ -229,7 +255,7 @@ fun DexCharaDetailsDialog(
showFusions = true showFusions = true
} }
) { ) {
Text("Fusions") Text(stringResource(R.string.dex_chara_fusions_button))
} }
} }
@@ -241,7 +267,7 @@ fun DexCharaDetailsDialog(
Button( Button(
onClick = onClickClose onClick = onClickClose
) { ) {
Text("Close") Text(stringResource(R.string.dex_chara_close_button))
} }
} }
} }
@@ -8,8 +8,10 @@ import androidx.compose.material3.Card
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun BetaWarning( fun BetaWarning(
@@ -19,26 +21,26 @@ fun BetaWarning(
onDismissRequest = onDismissRequest onDismissRequest = onDismissRequest
) { ) {
Card { Card {
Column ( Column(
modifier = Modifier modifier = Modifier
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "This application is currently in alpha and it is not complete. Do not use to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!" text = stringResource(R.string.beta_warning_message_main)
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Text( Text(
text = "Application should work now with the original VB and the VH." text = stringResource(R.string.beta_warning_message_compatibility)
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Text( Text(
text = "Thank you for your understanding and patience. Sincerely, the dev team." text = stringResource(R.string.beta_warning_message_thanks)
) )
Spacer(modifier = Modifier.padding(8.dp)) Spacer(modifier = Modifier.padding(8.dp))
Button( Button(
onClick = onDismissRequest onClick = onDismissRequest
) { ) {
Text(text = "Dismiss") Text(text = stringResource(R.string.beta_warning_button_dismiss))
} }
} }
} }
@@ -11,6 +11,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -19,6 +20,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
@@ -27,9 +29,7 @@ import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEBEmHomeScreen import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEBEmHomeScreen
@@ -37,8 +37,12 @@ import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.VBDiMHomeScreen import com.github.nacabaro.vbhelper.screens.homeScreens.screens.VBDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers import com.github.nacabaro.vbhelper.R
import kotlinx.coroutines.withContext import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.source.CardRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.flowOf
import kotlin.collections.emptyList
@Composable @Composable
fun HomeScreen( fun HomeScreen(
@@ -47,28 +51,59 @@ fun HomeScreen(
) { ) {
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db) val storageRepository = StorageRepository(application.container.db)
val activeMon = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) } val cardRepository = CardRepository(application.container.db)
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
val beData = remember { mutableStateOf<BECharacterData?>(null) } val activeMon by storageRepository
val vbData = remember { mutableStateOf<VBCharacterData?>(null) } .getActiveCharacter()
val vbSpecialMissions = remember { mutableStateOf<List<SpecialMissions>>(emptyList()) } .collectAsState(initial = null)
val cardIconData by (
activeMon
?.let { chara ->
cardRepository.getCardIconByCharaId(chara.charId)
}
?: flowOf<CardDtos.CardIcon?>(null)
).collectAsState(initial = null)
val transformationHistory by (
activeMon
?.let { chara ->
storageRepository.getTransformationHistory(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbSpecialMissions by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getSpecialMissions(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbData by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getCharacterVbData(chara.id)
}
?: flowOf<VBCharacterData?>(null)
).collectAsState(initial = null)
val beData by (
activeMon
?.takeIf { it.characterType == DeviceType.BEDevice }
?.let { chara ->
storageRepository.getCharacterBeData(chara.id)
}
?: flowOf<BECharacterData?>(null)
).collectAsState(initial = null)
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) } var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
var betaWarning by rememberSaveable { mutableStateOf(true) } var betaWarning by rememberSaveable { mutableStateOf(true) }
var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) } var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) }
var collectedCurrency by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(storageRepository, activeMon, collectedItem) {
withContext(Dispatchers.IO) {
activeMon.value = storageRepository.getActiveCharacter()
if (activeMon.value != null && activeMon.value!!.characterType == DeviceType.BEDevice) {
beData.value = storageRepository.getCharacterBeData(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
} else if (activeMon.value != null && activeMon.value!!.characterType == DeviceType.VBDevice) {
vbData.value = storageRepository.getCharacterVbData(activeMon.value!!.id)
vbSpecialMissions.value = storageRepository.getSpecialMissions(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
}
}
}
LaunchedEffect(true) { LaunchedEffect(true) {
homeScreenController homeScreenController
@@ -80,7 +115,7 @@ fun HomeScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "VB Helper", text = stringResource(R.string.home_title),
onScanClick = { onScanClick = {
navController.navigate(NavigationItems.Scan.route) navController.navigate(NavigationItems.Scan.route)
}, },
@@ -90,7 +125,7 @@ fun HomeScreen(
) )
} }
) { contentPadding -> ) { contentPadding ->
if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) { if (activeMon == null || (beData == null && vbData == null) || cardIconData == null || transformationHistory.isEmpty()) {
Column ( Column (
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@@ -98,34 +133,44 @@ fun HomeScreen(
.fillMaxSize() .fillMaxSize()
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
Text(text = "Nothing to see here") Text(text = stringResource(R.string.adventure_empty_state))
} }
} else { } else {
if (activeMon.value!!.isBemCard) { val cardIcon = BitmapData(
bitmap = cardIconData!!.cardIcon,
width = cardIconData!!.cardIconWidth,
height = cardIconData!!.cardIconHeight
)
if (activeMon!!.isBemCard && beData != null) {
BEBEmHomeScreen( BEBEmHomeScreen(
activeMon = activeMon.value!!, activeMon = activeMon!!,
beData = beData.value!!, beData = beData!!,
transformationHistory = transformationHistory.value!!, transformationHistory = transformationHistory,
contentPadding = contentPadding
)
} else if (!activeMon.value!!.isBemCard && activeMon.value!!.characterType == DeviceType.BEDevice) {
BEDiMHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
)
} else {
VBDiMHomeScreen(
activeMon = activeMon.value!!,
vbData = vbData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding, contentPadding = contentPadding,
specialMissions = vbSpecialMissions.value, cardIcon = cardIcon
)
} else if (!activeMon!!.isBemCard && activeMon!!.characterType == DeviceType.BEDevice && beData != null) {
BEDiMHomeScreen(
activeMon = activeMon!!,
beData = beData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
cardIcon = cardIcon
)
} else if (vbData != null) {
VBDiMHomeScreen(
activeMon = activeMon!!,
vbData = vbData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
specialMissions = vbSpecialMissions,
homeScreenController = homeScreenController, homeScreenController = homeScreenController,
onClickCollect = { item -> onClickCollect = { item, currency ->
collectedItem = item collectedItem = item
} collectedCurrency = currency
},
cardIcon = cardIcon
) )
} }
} }
@@ -134,8 +179,10 @@ fun HomeScreen(
if (collectedItem != null) { if (collectedItem != null) {
ObtainedItemDialog( ObtainedItemDialog(
obtainedItem = collectedItem!!, obtainedItem = collectedItem!!,
obtainedCurrency = collectedCurrency!!,
onClickDismiss = { onClickDismiss = {
collectedItem = null collectedItem = null
collectedCurrency = null
} }
) )
} }
@@ -150,7 +197,7 @@ fun HomeScreen(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "One of your characters has finished their adventure mission!", text = stringResource(R.string.home_adventure_mission_finished),
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Button( Button(
@@ -161,7 +208,7 @@ fun HomeScreen(
.padding(8.dp) .padding(8.dp)
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = "Dismiss") Text(text = stringResource(R.string.beta_warning_button_dismiss))
} }
} }
} }
@@ -1,9 +1,8 @@
package com.github.nacabaro.vbhelper.screens.homeScreens package com.github.nacabaro.vbhelper.screens.homeScreens
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface HomeScreenController { interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit) fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?) -> Unit) fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit)
} }
@@ -23,6 +23,7 @@ class HomeScreenControllerImpl(
val adventureCharacters = database val adventureCharacters = database
.adventureDao() .adventureDao()
.getAdventureCharacters() .getAdventureCharacters()
.first()
val finishedAdventureCharacters = adventureCharacters.filter { character -> val finishedAdventureCharacters = adventureCharacters.filter { character ->
character.finishesAdventure <= currentTime character.finishesAdventure <= currentTime
@@ -32,13 +33,18 @@ class HomeScreenControllerImpl(
} }
} }
override fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?) -> Unit) { override fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit) {
componentActivity.lifecycleScope.launch { componentActivity.lifecycleScope.launch {
val missionStatus = database
.specialMissionDao()
.getSpecialMission(missionId)
.first()
database database
.specialMissionDao() .specialMissionDao()
.clearSpecialMission(missionId) .clearSpecialMission(missionId)
if (missionCompletion == SpecialMission.Status.COMPLETED) { if (missionStatus.status == SpecialMission.Status.COMPLETED) {
val randomItem = database val randomItem = database
.itemDao() .itemDao()
.getAllItems() .getAllItems()
@@ -64,11 +70,14 @@ class HomeScreenControllerImpl(
itemType = randomItem.itemType itemType = randomItem.itemType
) )
onCleared(purchasedItem) val randomAmount = (2..6).random() * 1000
} else { val currentCurrency = application.container.currencyRepository.currencyValue.first()
onCleared(null) application.container.currencyRepository.setCurrencyValue(currentCurrency + randomAmount)
}
onCleared(purchasedItem, randomAmount)
} else {
onCleared(null, null)
}
} }
} }
} }
@@ -0,0 +1,56 @@
package com.github.nacabaro.vbhelper.screens.homeScreens.dialogs
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable
fun DeleteSpecialMissionDialog(
onClickDismiss: () -> Unit,
onClickDelete: () -> Unit
) {
Dialog(
onDismissRequest = onClickDismiss
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_special_mission_delete_main),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.padding(8.dp))
Row {
Button(
onClick = onClickDismiss,
modifier = Modifier
.padding(8.dp)
) {
Text(text = stringResource(R.string.home_special_mission_delete_dismiss))
}
Button(
onClick = onClickDelete,
modifier = Modifier
.padding(8.dp)
) {
Text(text = stringResource(R.string.home_special_mission_delete_remove))
}
}
}
}
}
}
@@ -26,6 +26,7 @@ import java.util.Locale
fun BEBEmHomeScreen( fun BEBEmHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
beData: BECharacterData, beData: BECharacterData,
cardIcon: BitmapData,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues contentPadding: PaddingValues
) { ) {
@@ -46,6 +47,7 @@ fun BEBEmHomeScreen(
), ),
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
cardIcon = cardIcon,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -26,6 +26,7 @@ import kotlin.text.format
@Composable @Composable
fun BEDiMHomeScreen( fun BEDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
beData: BECharacterData, beData: BECharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues contentPadding: PaddingValues
@@ -45,6 +46,7 @@ fun BEDiMHomeScreen(
width = activeMon.spriteWidth, width = activeMon.spriteWidth,
height = activeMon.spriteHeight height = activeMon.spriteHeight
), ),
cardIcon = cardIcon,
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier modifier = Modifier
@@ -10,6 +10,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -25,17 +29,23 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import java.util.Locale import java.util.Locale
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.screens.homeScreens.dialogs.DeleteSpecialMissionDialog
@Composable @Composable
fun VBDiMHomeScreen( fun VBDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites, activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
vbData: VBCharacterData, vbData: VBCharacterData,
specialMissions: List<SpecialMissions>, specialMissions: List<SpecialMissions>,
homeScreenController: HomeScreenControllerImpl, homeScreenController: HomeScreenControllerImpl,
transformationHistory: List<CharacterDtos.TransformationHistory>, transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onClickCollect: (ItemDtos.PurchasedItem?) -> Unit onClickCollect: (ItemDtos.PurchasedItem?, Int?) -> Unit
) { ) {
var selectedSpecialMissionId by remember { mutableStateOf<Long>(-1) }
Column( Column(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
@@ -51,6 +61,7 @@ fun VBDiMHomeScreen(
width = activeMon.spriteWidth, width = activeMon.spriteWidth,
height = activeMon.spriteHeight height = activeMon.spriteHeight
), ),
cardIcon = cardIcon,
multiplier = 8, multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small, shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier modifier = Modifier
@@ -65,7 +76,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_vitals_24, icon = R.drawable.baseline_vitals_24,
textValue = activeMon.vitalPoints.toString(), textValue = activeMon.vitalPoints.toString(),
definition = "Vitals", definition = stringResource(R.string.home_vbdim_vitals),
modifier = Modifier modifier = Modifier
.weight(0.5f) .weight(0.5f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -74,7 +85,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_trophy_24, icon = R.drawable.baseline_trophy_24,
textValue = activeMon.trophies.toString(), textValue = activeMon.trophies.toString(),
definition = "Trophies", definition = stringResource(R.string.home_vbdim_trophies),
modifier = Modifier modifier = Modifier
.weight(0.5f) .weight(0.5f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -89,7 +100,7 @@ fun VBDiMHomeScreen(
ItemDisplay( ItemDisplay(
icon = R.drawable.baseline_mood_24, icon = R.drawable.baseline_mood_24,
textValue = activeMon.mood.toString(), textValue = activeMon.mood.toString(),
definition = "Mood", definition = stringResource(R.string.home_vbdim_mood),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -102,7 +113,7 @@ fun VBDiMHomeScreen(
0 -> "${activeMon.transformationCountdown} m" 0 -> "${activeMon.transformationCountdown} m"
else -> "$transformationCountdownInHours h" else -> "$transformationCountdownInHours h"
}, },
definition = "Next timer", definition = stringResource(R.string.home_vbdim_next_timer),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -122,7 +133,7 @@ fun VBDiMHomeScreen(
) + " %" // Specify locale ) + " %" // Specify locale
} }
}, },
definition = "Total battle win %", definition = stringResource(R.string.home_vbdim_total_battle_win),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -142,7 +153,7 @@ fun VBDiMHomeScreen(
) + " %" // Specify locale ) + " %" // Specify locale
} }
}, },
definition = "Current phase win %", definition = stringResource(R.string.home_vbdim_current_phase_win),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.aspectRatio(1f) .aspectRatio(1f)
@@ -165,7 +176,7 @@ fun VBDiMHomeScreen(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = "Special missions", text = stringResource(R.string.home_vbdim_special_missions),
fontSize = 24.sp fontSize = 24.sp
) )
} }
@@ -179,11 +190,28 @@ fun VBDiMHomeScreen(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(8.dp), .padding(8.dp),
) { onClickMission = { missionId ->
homeScreenController selectedSpecialMissionId = missionId
.clearSpecialMission(mission.id, mission.status, onClickCollect) },
} onClickCollect = {
homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
}
)
} }
} }
} }
if (selectedSpecialMissionId.toInt() != -1) {
DeleteSpecialMissionDialog(
onClickDismiss = {
selectedSpecialMissionId = -1
},
onClickDelete = {
homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
selectedSpecialMissionId = -1
}
)
}
} }
@@ -15,6 +15,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
@@ -26,7 +27,7 @@ import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ChooseCharacterScreen( fun ChooseCharacterScreen(
@@ -69,7 +70,7 @@ fun ChooseCharacterScreen(
itemsScreenController.applyItem(itemId, selectedCharacter!!) { itemsScreenController.applyItem(itemId, selectedCharacter!!) {
Toast.makeText( Toast.makeText(
application.applicationContext, application.applicationContext,
"Item applied!", application.getString(R.string.choose_character_item_applied),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.popBackStack() navController.popBackStack()
@@ -80,7 +81,7 @@ fun ChooseCharacterScreen(
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Choose character", text = stringResource(R.string.choose_character_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -27,6 +28,8 @@ import com.github.nacabaro.vbhelper.domain.items.ItemType
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@Composable @Composable
fun ItemDialog( fun ItemDialog(
item: ItemDtos.ItemsWithQuantities, item: ItemDtos.ItemsWithQuantities,
@@ -95,7 +98,10 @@ fun ItemDialog(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize, fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily, fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "Costs ${item.price} credits", text = stringResource(
R.string.item_dialog_costs_credits,
item.price
),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) )
@@ -104,7 +110,10 @@ fun ItemDialog(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize, fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily, fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have ${item.quantity} of this item", text = stringResource(
R.string.item_dialog_you_have_quantity,
item.quantity
),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) )
@@ -118,7 +127,7 @@ fun ItemDialog(
Button( Button(
onClick = onClickUse onClick = onClickUse
) { ) {
Text("Use item") Text(stringResource(R.string.item_dialog_use))
} }
} }
@@ -126,7 +135,7 @@ fun ItemDialog(
Button( Button(
onClick = onClickPurchase onClick = onClickPurchase
) { ) {
Text("Purchase") Text(stringResource(R.string.item_dialog_purchase))
} }
} }
@@ -134,7 +143,7 @@ fun ItemDialog(
Button( Button(
onClick = onClickCancel onClick = onClickCancel
) { ) {
Text("Cancel") Text(stringResource(R.string.item_dialog_cancel))
} }
} }
} }
@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ItemElement( fun ItemElement(
@@ -17,6 +17,9 @@ import androidx.compose.ui.Modifier
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ItemsScreen( fun ItemsScreen(
@@ -30,14 +33,14 @@ fun ItemsScreen(
Scaffold( Scaffold(
topBar = { topBar = {
Column { Column {
TopBanner("Items") TopBanner(text = stringResource(R.string.items_title))
TabRow( TabRow(
selectedTabIndex = selectedTabItem, selectedTabIndex = selectedTabItem,
modifier = Modifier modifier = Modifier
) { ) {
items.forEachIndexed { index, item -> items.forEachIndexed { index, item ->
Tab( Tab(
text = { Text(item.label) }, text = { Text(text = stringResource(item.label)) },
selected = selectedTabItem == index, selected = selectedTabItem == index,
onClick = { selectedTabItem = index } onClick = { selectedTabItem = index }
) )
@@ -11,6 +11,8 @@ import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.utils.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -52,9 +54,16 @@ class ItemsScreenControllerImpl (
var vbCharacterData: VBCharacterData? = null var vbCharacterData: VBCharacterData? = null
if (characterData.characterType == DeviceType.BEDevice) { if (characterData.characterType == DeviceType.BEDevice) {
beCharacterData = database.userCharacterDao().getBeData(characterId) beCharacterData = database
.userCharacterDao()
.getBeData(characterId)
.firstOrNull()
} else if (characterData.characterType == DeviceType.VBDevice) { } else if (characterData.characterType == DeviceType.VBDevice) {
vbCharacterData = database.userCharacterDao().getVbData(characterId) vbCharacterData = database
.userCharacterDao()
.getVbData(characterId)
.firstOrNull()
} }
if ( if (
@@ -154,32 +163,34 @@ class ItemsScreenControllerImpl (
else -> 0 else -> 0
} }
val specialMissionSlot = when (itemIcon) {
ItemTypes.Step8k.id -> 0
ItemTypes.Step4k.id -> 0
ItemTypes.Vitals1000.id -> 1
ItemTypes.Vitals250.id -> 1
ItemTypes.Battle20.id -> 2
ItemTypes.Battle5.id -> 2
ItemTypes.Win10.id -> 3
ItemTypes.Win4.id -> 3
else -> 0
}
val availableSpecialMissions = database val availableSpecialMissions = database
.userCharacterDao() .userCharacterDao()
.getSpecialMissions(characterId) .getSpecialMissions(characterId)
.first()
var firstUnavailableMissionSlot: Long = 0 var newSpecialMission = availableSpecialMissions[specialMissionSlot]
var watchId = 0 newSpecialMission = SpecialMissions(
id = newSpecialMission.id,
for ((index, mission) in availableSpecialMissions.withIndex()) { characterId = newSpecialMission.characterId,
if (
mission.status == SpecialMission.Status.UNAVAILABLE
) {
firstUnavailableMissionSlot = mission.id
watchId = index + 1
}
}
val newSpecialMission = SpecialMissions(
id = firstUnavailableMissionSlot,
characterId = characterId,
missionType = specialMissionType,
goal = specialMissionGoal, goal = specialMissionGoal,
timeLimitInMinutes = itemLength, watchId = newSpecialMission.watchId,
watchId = watchId,
status = SpecialMission.Status.AVAILABLE,
progress = 0, progress = 0,
timeElapsedInMinutes = 0 status = SpecialMission.Status.AVAILABLE,
timeElapsedInMinutes = 0,
timeLimitInMinutes = itemLength,
missionType = specialMissionType
) )
database database
@@ -21,6 +21,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
@@ -30,6 +31,7 @@ import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.ItemsRepository import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ItemsStore( fun ItemsStore(
@@ -52,7 +54,7 @@ fun ItemsStore(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Text("No items") Text(stringResource(R.string.items_no_items))
} }
} else { } else {
Column() { Column() {
@@ -62,7 +64,10 @@ fun ItemsStore(
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text( Text(
text = "${currentCurrency.value} credits", text = stringResource(
R.string.items_store_credits,
currentCurrency.value
),
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(8.dp)
) )
@@ -94,14 +99,15 @@ fun ItemsStore(
scope.launch { scope.launch {
Toast.makeText( Toast.makeText(
application.applicationContext, application.applicationContext,
purchaseItem( application.getString(
application.container.db, purchaseItem(
myItems[selectedElementIndex!!], application.container.db,
currencyRepository myItems[selectedElementIndex!!],
currencyRepository
)
), ),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show( ).show()
)
} }
}, },
onClickCancel = { selectedElementIndex = null } onClickCancel = { selectedElementIndex = null }
@@ -113,9 +119,9 @@ suspend fun purchaseItem(
db: AppDatabase, db: AppDatabase,
item: ItemDtos.ItemsWithQuantities, item: ItemDtos.ItemsWithQuantities,
currencyRepository: CurrencyRepository currencyRepository: CurrencyRepository
): String { ): Int {
if (currencyRepository.currencyValue.first() < item.price) { return if (currencyRepository.currencyValue.first() < item.price) {
return "Not enough credits" R.string.items_not_enough_credits
} else { } else {
db db
.itemDao() .itemDao()
@@ -129,6 +135,6 @@ suspend fun purchaseItem(
currencyRepository.currencyValue.first() - item.price currencyRepository.currencyValue.first() - item.price
) )
return "Purchase successful!" R.string.items_purchase_success
} }
} }
@@ -17,11 +17,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.ItemsRepository import com.github.nacabaro.vbhelper.source.ItemsRepository
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun MyItems( fun MyItems(
@@ -39,7 +42,7 @@ fun MyItems(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Text("No items") Text(stringResource(R.string.items_no_items))
} }
} else { } else {
LazyVerticalGrid( LazyVerticalGrid(
@@ -19,10 +19,13 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.dtos.ItemDtos import com.github.nacabaro.vbhelper.dtos.ItemDtos
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ObtainedItemDialog( fun ObtainedItemDialog(
obtainedItem: ItemDtos.PurchasedItem, obtainedItem: ItemDtos.PurchasedItem,
obtainedCurrency: Int,
onClickDismiss: () -> Unit onClickDismiss: () -> Unit
) { ) {
Dialog( Dialog(
@@ -81,17 +84,32 @@ fun ObtainedItemDialog(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize, fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily, fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have obtained ${obtainedItem.itemAmount} of this item", text = stringResource(
R.string.obtained_item_you_have,
obtainedItem.itemAmount
),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(5.dp) .padding(top = 4.dp)
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.obtained_item_you_also_got_credits,
obtainedCurrency
),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
) )
Button( Button(
onClick = onClickDismiss, onClick = onClickDismiss,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = "Dismiss") Text(text = stringResource(R.string.obtained_item_dismiss))
} }
} }
} }
@@ -12,10 +12,12 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ChooseConnectOption( fun ChooseConnectOption(
@@ -26,7 +28,7 @@ fun ChooseConnectOption(
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Scan a Vital Bracelet", text = stringResource(R.string.scan_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -41,13 +43,13 @@ fun ChooseConnectOption(
.padding(contentPadding) .padding(contentPadding)
) { ) {
ScanButton( ScanButton(
text = "Vital Bracelet to App", text = stringResource(R.string.scan_vb_to_app),
disabled = onClickRead == null, disabled = onClickRead == null,
onClick = onClickRead?: { }, onClick = onClickRead?: { },
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
ScanButton( ScanButton(
text = "App to Vital Bracelet", text = stringResource(R.string.scan_app_to_vb),
disabled = onClickWrite == null, disabled = onClickWrite == null,
onClick = onClickWrite?: { }, onClick = onClickWrite?: { },
) )
@@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
@@ -25,6 +26,7 @@ import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.github.nacabaro.vbhelper.R
const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER" const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER"
@@ -87,15 +89,18 @@ fun ScanScreen(
} }
) )
} else { } else {
val secretsNotInitialized = stringResource(id = R.string.scan_secrets_not_initialized)
val secretsNotImported = stringResource(id = R.string.scan_secrets_not_imported)
ChooseConnectOption( ChooseConnectOption(
onClickRead = when { onClickRead = when {
!launchedFromHomeScreen -> null !launchedFromHomeScreen -> null
else -> { else -> {
{ {
if(secrets == null) { if (secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show() Toast.makeText(context, secretsNotInitialized, Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) { } else if (secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show() Toast.makeText(context, secretsNotImported, Toast.LENGTH_SHORT).show()
} else { } else {
readingScreen = true // kicks off nfc adapter in DisposableEffect readingScreen = true // kicks off nfc adapter in DisposableEffect
} }
@@ -106,10 +111,10 @@ fun ScanScreen(
nfcCharacter == null -> null nfcCharacter == null -> null
else -> { else -> {
{ {
if(secrets == null) { if (secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show() Toast.makeText(context, secretsNotInitialized, Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) { } else if (secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show() Toast.makeText(context, secretsNotImported, Toast.LENGTH_SHORT).show()
} else { } else {
writingScreen = true // kicks off nfc adapter in DisposableEffect writingScreen = true // kicks off nfc adapter in DisposableEffect
} }
@@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
class ScanScreenControllerImpl( class ScanScreenControllerImpl(
override val secretsFlow: Flow<Secrets>, override val secretsFlow: Flow<Secrets>,
@@ -38,7 +39,7 @@ class ScanScreenControllerImpl(
init { init {
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity) val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity)
if (maybeNfcAdapter == null) { if (maybeNfcAdapter == null) {
Toast.makeText(componentActivity, "No NFC on device!", Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_no_nfc_on_device), Toast.LENGTH_SHORT).show()
} }
nfcAdapter = maybeNfcAdapter nfcAdapter = maybeNfcAdapter
checkSecrets() checkSecrets()
@@ -94,7 +95,7 @@ class ScanScreenControllerImpl(
val nfcData = NfcA.get(tag) val nfcData = NfcA.get(tag)
if (nfcData == null) { if (nfcData == null) {
componentActivity.runOnUiThread { componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Tag detected is not VB", Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_tag_not_vb), Toast.LENGTH_SHORT).show()
} }
} }
nfcData.connect() nfcData.connect()
@@ -112,7 +113,7 @@ class ScanScreenControllerImpl(
componentActivity.lifecycleScope.launch(Dispatchers.IO) { componentActivity.lifecycleScope.launch(Dispatchers.IO) {
if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) { if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) {
componentActivity.runOnUiThread { componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Missing Secrets. Go to settings and import Vital Arena APK", Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_missing_secrets), Toast.LENGTH_SHORT).show()
} }
} }
} }
@@ -135,10 +136,10 @@ class ScanScreenControllerImpl(
tagCommunicator.sendCharacter(castNfcCharacter) tagCommunicator.sendCharacter(castNfcCharacter)
} }
onComplete.invoke() onComplete.invoke()
"Sent character successfully!" componentActivity.getString(R.string.scan_sent_character_success)
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e("TAG", e.stackTraceToString()) Log.e("TAG", e.stackTraceToString())
"Whoops" componentActivity.getString(R.string.scan_error_generic)
} }
} }
} }
@@ -151,13 +152,13 @@ class ScanScreenControllerImpl(
handleTag(secrets) { tagCommunicator -> handleTag(secrets) { tagCommunicator ->
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId) tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
onComplete.invoke() onComplete.invoke()
"Sent DIM successfully!" componentActivity.getString(R.string.scan_sent_dim_success)
} }
} }
// EXTRACTED DIRECTLY FROM EXAMPLE APP // EXTRACTED DIRECTLY FROM EXAMPLE APP
private fun showWirelessSettings() { private fun showWirelessSettings() {
Toast.makeText(componentActivity, "NFC must be enabled", Toast.LENGTH_SHORT).show() Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_nfc_must_be_enabled), Toast.LENGTH_SHORT).show()
componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS)) componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
} }
@@ -55,6 +55,7 @@ class ToNfcConverter(
val vbData = database val vbData = database
.userCharacterDao() .userCharacterDao()
.getVbData(characterId) .getVbData(characterId)
.first()
val paddedTransformationArray = generateTransformationHistory(characterId, 9) val paddedTransformationArray = generateTransformationHistory(characterId, 9)
@@ -116,6 +117,7 @@ class ToNfcConverter(
val specialMissions = database val specialMissions = database
.userCharacterDao() .userCharacterDao()
.getSpecialMissions(characterId) .getSpecialMissions(characterId)
.first()
val watchSpecialMissions = specialMissions.map { val watchSpecialMissions = specialMissions.map {
SpecialMission( SpecialMission(
@@ -175,6 +177,7 @@ class ToNfcConverter(
val beData = database val beData = database
.userCharacterDao() .userCharacterDao()
.getBeData(characterId) .getBeData(characterId)
.first()
val paddedTransformationArray = generateTransformationHistory(characterId) val paddedTransformationArray = generateTransformationHistory(characterId)
@@ -237,7 +240,8 @@ class ToNfcConverter(
): Array<NfcCharacter.Transformation> { ): Array<NfcCharacter.Transformation> {
val transformationHistory = database val transformationHistory = database
.userCharacterDao() .userCharacterDao()
.getTransformationHistory(characterId)!! .getTransformationHistory(characterId)
.first()
.map { .map {
val date = Date(it.transformationDate) val date = Date(it.transformationDate)
val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC")) val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC"))
@@ -10,8 +10,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ActionScreen( fun ActionScreen(
@@ -33,12 +35,12 @@ fun ActionScreen(
.padding(innerPadding) .padding(innerPadding)
.fillMaxSize() .fillMaxSize()
) { ) {
Text("Place your Vital Bracelet near the reader...") Text(stringResource(R.string.action_place_near_reader))
Button( Button(
onClick = onClickCancel, onClick = onClickCancel,
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) { ) {
Text("Cancel") Text(stringResource(R.string.action_cancel))
} }
} }
} }
@@ -11,9 +11,11 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ReadCharacterScreen( fun ReadCharacterScreen(
@@ -23,7 +25,7 @@ fun ReadCharacterScreen(
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Read character", text = stringResource(R.string.read_character_title),
onBackClick = onClickCancel onBackClick = onClickCancel
) )
} }
@@ -36,12 +38,12 @@ fun ReadCharacterScreen(
.fillMaxSize() .fillMaxSize()
) { ) {
Text( Text(
text = "Prepare your device!", text = stringResource(R.string.read_character_prepare_device),
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
Text( Text(
text = "Go to connect and when ready press confirm!", text = stringResource(R.string.read_character_go_to_connect),
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
@@ -52,7 +54,7 @@ fun ReadCharacterScreen(
Button( Button(
onClick = onClickConfirm, onClick = onClickConfirm,
) { ) {
Text("Confirm") Text(stringResource(R.string.read_character_confirm))
} }
} }
} }
@@ -7,11 +7,14 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.ActivityLifecycleListener import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun ReadingScreen( fun ReadingScreen(
@@ -92,7 +95,7 @@ fun ReadingScreen(
} }
if (readingScreen) { if (readingScreen) {
ActionScreen("Reading character") { ActionScreen(topBannerText = stringResource(R.string.reading_character_title),) {
readingScreen = false readingScreen = false
scanScreenController.cancelRead() scanScreenController.cancelRead()
onCancel() onCancel()
@@ -24,6 +24,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
@@ -31,6 +32,7 @@ import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun WriteCardScreen( fun WriteCardScreen(
@@ -55,7 +57,7 @@ fun WriteCardScreen(
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Writing card details", text = stringResource(R.string.write_card_title),
onBackClick = onClickCancel onBackClick = onClickCancel
) )
} }
@@ -101,7 +103,9 @@ fun WriteCardScreen(
) { ) {
Image( Image(
bitmap = charaImageBitmapData.imageBitmap, bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon", contentDescription = stringResource(
R.string.write_card_icon_description
),
modifier = Modifier modifier = Modifier
.size(charaImageBitmapData.dpWidth) .size(charaImageBitmapData.dpWidth)
.padding(8.dp), .padding(8.dp),
@@ -115,8 +119,14 @@ fun WriteCardScreen(
) )
Column { Column {
Text("Get your device Ready!") Text(stringResource(R.string.write_card_device_ready))
Text("You will need ${cardDetails.name} card!") Text(
stringResource(
R.string.write_card_required_card,
cardDetails.name
)
)
} }
} }
@@ -125,7 +135,7 @@ fun WriteCardScreen(
Button( Button(
onClick = onClickConfirm, onClick = onClickConfirm,
) { ) {
Text("Confirm") Text(stringResource(R.string.write_card_confirm))
} }
} }
} }
@@ -25,6 +25,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
@@ -32,6 +33,8 @@ import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun WriteCharacterScreen( fun WriteCharacterScreen(
@@ -56,7 +59,7 @@ fun WriteCharacterScreen(
Scaffold( Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Writing character", text = stringResource(R.string.write_character_title),
onBackClick = onClickCancel onBackClick = onClickCancel
) )
} }
@@ -102,7 +105,7 @@ fun WriteCharacterScreen(
) { ) {
Image( Image(
bitmap = charaImageBitmapData.imageBitmap, bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon", contentDescription = stringResource(R.string.write_character_icon_description),
modifier = Modifier modifier = Modifier
.size(charaImageBitmapData.dpWidth) .size(charaImageBitmapData.dpWidth)
.padding(8.dp), .padding(8.dp),
@@ -116,8 +119,8 @@ fun WriteCharacterScreen(
) )
Column { Column {
Text("Card installed successfully!!") Text(stringResource(R.string.write_character_success))
Text("Wait until your device is ready, then tap 'Confirm'") Text(stringResource(R.string.write_character_wait_ready))
} }
} }
@@ -128,7 +131,7 @@ fun WriteCharacterScreen(
Button( Button(
onClick = onClickConfirm, onClick = onClickConfirm,
) { ) {
Text("Confirm") Text(stringResource(R.string.write_character_confirm))
} }
} }
} }
@@ -17,6 +17,8 @@ import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun WritingScreen( fun WritingScreen(
@@ -97,7 +99,7 @@ fun WritingScreen(
) )
} else if (!isDoneSendingCard) { } else if (!isDoneSendingCard) {
writing = true writing = true
ActionScreen("Sending card") { ActionScreen( topBannerText = stringResource(R.string.sending_card_title)) {
scanScreenController.cancelRead() scanScreenController.cancelRead()
onCancel() onCancel()
} }
@@ -115,7 +117,7 @@ fun WritingScreen(
) )
} else if (!isDoneWritingCharacter) { } else if (!isDoneWritingCharacter) {
writing = true writing = true
ActionScreen("Writing character") { ActionScreen(topBannerText = stringResource(R.string.writing_character_action_title)) {
isDoneSendingCard = false isDoneSendingCard = false
scanScreenController.cancelRead() scanScreenController.cancelRead()
onCancel() onCancel()
@@ -6,8 +6,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun CreditsScreen( fun CreditsScreen(
@@ -16,7 +18,7 @@ fun CreditsScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "Credits", text = stringResource(R.string.credits_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -29,13 +31,13 @@ fun CreditsScreen(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
SettingsSection("Reverse engineering") SettingsSection(stringResource(R.string.credits_section_reverse_engineering))
SettingsEntry(title = "cyanic", description = "Reversed the firmware and helped us during development.") { } SettingsEntry(title = "cyanic", description = stringResource(R.string.credits_cyanic_description)) { }
SettingsSection("Application development") SettingsSection(stringResource(R.string.credits_section_app_development))
SettingsEntry(title = "cfogrady", description = "Developed vb-lib-nfc and part of this application.") { } SettingsEntry(title = "cfogrady", description = stringResource(R.string.credits_cfogrady_description)) { }
SettingsEntry(title = "nacabaro", description = "Developed this application.") { } SettingsEntry(title = "nacabaro", description = stringResource(R.string.credits_nacabaro_description)) { }
SettingsEntry(title = "lightheel", description = "Developing the battling part for this application, including server. Still in the works.") { } SettingsEntry(title = "lightheel", description = stringResource(R.string.credits_lightheel_description)) { }
SettingsEntry(title = "shvstrz", description = "Designing the app icon in SVG.") { } SettingsEntry(title = "shvstrz", description = stringResource(R.string.credits_shvstrz_description)) { }
} }
} }
} }
@@ -1,5 +1,7 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -13,22 +15,28 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
navController: NavController, navController: NavController,
settingsScreenController: SettingsScreenControllerImpl, settingsScreenController: SettingsScreenControllerImpl,
) { ) {
Scaffold ( val context = LocalContext.current
Scaffold(
topBar = { topBar = {
TopBanner( TopBanner(
text = "Settings", text = stringResource(R.string.settings_title),
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -37,31 +45,57 @@ fun SettingsScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { contentPadding -> ) { contentPadding ->
Column ( Column(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
SettingsSection("NFC Communication") SettingsSection(title = stringResource(R.string.settings_section_nfc))
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") { SettingsEntry(
title = stringResource(R.string.settings_import_apk_title),
description = stringResource(R.string.settings_import_apk_desc)
) {
settingsScreenController.onClickImportApk() settingsScreenController.onClickImportApk()
} }
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") { SettingsSection(title = stringResource(R.string.settings_section_dim_bem))
SettingsEntry(
title = stringResource(R.string.settings_import_card_title),
description = stringResource(R.string.settings_import_card_desc)
) {
settingsScreenController.onClickImportCard() settingsScreenController.onClickImportCard()
} }
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection("About and credits") SettingsSection(title = stringResource(R.string.settings_section_about))
SettingsEntry(title = "Credits", description = "Credits") { SettingsEntry(
title = stringResource(R.string.settings_credits_title),
description = stringResource(R.string.settings_credits_desc)
) {
navController.navigate(NavigationItems.Credits.route) navController.navigate(NavigationItems.Credits.route)
} }
SettingsEntry(title = "About", description = "About") { } SettingsEntry(
SettingsSection("Data management") title = stringResource(R.string.settings_about_title),
SettingsEntry(title = "Export data", description = "Export application database") { description = stringResource(R.string.settings_about_desc)
) {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://github.com/nacabaro/vbhelper/")
)
context.startActivity(browserIntent)
}
SettingsSection(title = stringResource(R.string.settings_section_data))
SettingsEntry(
title = stringResource(R.string.settings_export_data_title),
description = stringResource(R.string.settings_export_data_desc)
) {
settingsScreenController.onClickOpenDirectory() settingsScreenController.onClickOpenDirectory()
} }
SettingsEntry(title = "Import data", description = "Import application database") { SettingsEntry(
title = stringResource(R.string.settings_import_data_title),
description = stringResource(R.string.settings_import_data_desc)
) {
settingsScreenController.onClickImportDatabase() settingsScreenController.onClickImportDatabase()
} }
} }
@@ -93,9 +127,9 @@ fun SettingsEntry(
fun SettingsSection( fun SettingsSection(
title: String title: String
) { ) {
Box ( Box(
modifier = Modifier modifier = Modifier
.padding(start = 16.dp) .padding(start = 16.dp, top = 16.dp, bottom = 4.dp)
) { ) {
Text( Text(
text = title, text = title,
@@ -33,6 +33,9 @@ import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap import com.github.nacabaro.vbhelper.utils.getBitmap
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
fun StorageDialog( fun StorageDialog(
@@ -94,7 +97,7 @@ fun StorageDialog(
val dpSize = (characterSprite.value!!.width * 4 / density).dp val dpSize = (characterSprite.value!!.width * 4 / density).dp
Image( Image(
bitmap = imageBitmap, bitmap = imageBitmap,
contentDescription = "Character image", contentDescription = stringResource(R.string.storage_character_image_description),
filterQuality = FilterQuality.None, filterQuality = FilterQuality.None,
modifier = Modifier modifier = Modifier
.size(dpSize) .size(dpSize)
@@ -104,7 +107,7 @@ fun StorageDialog(
val nameDpSize = (characterName.value!!.width * 4 / density).dp val nameDpSize = (characterName.value!!.width * 4 / density).dp
Image( Image(
bitmap = nameImageBitmap, bitmap = nameImageBitmap,
contentDescription = "Character image", contentDescription = stringResource(R.string.storage_character_image_description),
filterQuality = FilterQuality.None, filterQuality = FilterQuality.None,
modifier = Modifier modifier = Modifier
.size(nameDpSize) .size(nameDpSize)
@@ -121,7 +124,7 @@ fun StorageDialog(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
) { ) {
Text(text = "Send to watch") Text(text = stringResource(R.string.storage_send_to_watch))
} }
Spacer( Spacer(
modifier = Modifier modifier = Modifier
@@ -130,7 +133,7 @@ fun StorageDialog(
Button( Button(
onClick = onClickSetActive, onClick = onClickSetActive,
) { ) {
Text(text = "Set active") Text(text = stringResource(R.string.storage_set_active))
} }
} }
Button( Button(
@@ -140,21 +143,21 @@ fun StorageDialog(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {
Text(text = "Send on adventure") Text(text = stringResource(R.string.storage_send_on_adventure))
} }
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
onClick = onClickDelete onClick = onClickDelete
) { ) {
Text(text = "Delete character") Text(text = stringResource(R.string.storage_delete_character))
} }
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
onClick = onDismissRequest onClick = onDismissRequest
) { ) {
Text(text = "Close") Text(text = stringResource(R.string.storage_close))
} }
} }
} }
@@ -12,6 +12,8 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -32,6 +34,8 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable @Composable
@@ -49,7 +53,7 @@ fun StorageScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "My characters", text = stringResource(R.string.storage_my_characters_title),
onAdventureClick = { onAdventureClick = {
navController.navigate(NavigationItems.Adventure.route) navController.navigate(NavigationItems.Adventure.route)
} }
@@ -65,7 +69,7 @@ fun StorageScreen(
.fillMaxSize() .fillMaxSize()
) { ) {
Text( Text(
text = "Nothing to see here", text = stringResource(R.string.storage_nothing_to_see_here),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier modifier = Modifier
) )
@@ -90,7 +94,7 @@ fun StorageScreen(
} else { } else {
Toast.makeText( Toast.makeText(
application, application,
"This character is in an adventure", application.getString(R.string.storage_in_adventure_toast),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
navController.navigate( navController.navigate(
@@ -98,6 +102,16 @@ fun StorageScreen(
) )
} }
}, },
cardColors = if (index.active) {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
)
} else {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
)
}
) )
} }
} }
@@ -0,0 +1,66 @@
package com.github.nacabaro.vbhelper.source
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class AuthRepository(
private val dataStore: DataStore<Preferences>
) {
private companion object {
val IS_AUTHENTICATED = booleanPreferencesKey("is_authenticated")
val AUTH_TOKEN = stringPreferencesKey("auth_token") // Nacatech token (for re-authentication)
val SESSION_TOKEN = stringPreferencesKey("session_token") // Session token (for API calls)
val USER_ID = longPreferencesKey("user_id")
}
val isAuthenticated: Flow<Boolean> = dataStore.data
.map { preferences ->
preferences[IS_AUTHENTICATED] ?: false
}
val authToken: Flow<String?> = dataStore.data
.map { preferences ->
preferences[AUTH_TOKEN]
}
val sessionToken: Flow<String?> = dataStore.data
.map { preferences ->
preferences[SESSION_TOKEN]
}
val userId: Flow<Long?> = dataStore.data
.map { preferences ->
preferences[USER_ID]
}
suspend fun setAuthenticated(isAuthenticated: Boolean, nacatechToken: String? = null, sessionToken: String? = null, userId: Long? = null) {
dataStore.edit { preferences ->
preferences[IS_AUTHENTICATED] = isAuthenticated
if (nacatechToken != null) {
preferences[AUTH_TOKEN] = nacatechToken
}
if (sessionToken != null) {
preferences[SESSION_TOKEN] = sessionToken
}
if (userId != null) {
preferences[USER_ID] = userId
}
}
}
suspend fun logout() {
dataStore.edit { preferences ->
preferences[IS_AUTHENTICATED] = false
preferences.remove(AUTH_TOKEN)
preferences.remove(SESSION_TOKEN)
preferences.remove(USER_ID)
}
}
}
@@ -0,0 +1,15 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
class CardRepository (
private val db: AppDatabase
) {
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon> {
return db
.cardDao()
.getCardIconByCharaId(charaId = charaId)
}
}
@@ -19,19 +19,19 @@ class StorageRepository (
return db.userCharacterDao().getCharacterWithSprites(id) return db.userCharacterDao().getCharacterWithSprites(id)
} }
suspend fun getCharacterBeData(id: Long): BECharacterData { fun getCharacterBeData(id: Long): Flow<BECharacterData> {
return db.userCharacterDao().getBeData(id) return db.userCharacterDao().getBeData(id)
} }
suspend fun getTransformationHistory(characterId: Long): List<CharacterDtos.TransformationHistory>? { fun getTransformationHistory(characterId: Long): Flow<List<CharacterDtos.TransformationHistory>> {
return db.userCharacterDao().getTransformationHistory(characterId) return db.userCharacterDao().getTransformationHistory(characterId)
} }
suspend fun getCharacterVbData(id: Long): VBCharacterData { fun getCharacterVbData(id: Long): Flow<VBCharacterData> {
return db.userCharacterDao().getVbData(id) return db.userCharacterDao().getVbData(id)
} }
suspend fun getSpecialMissions(id: Long): List<SpecialMissions> { fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>> {
return db.userCharacterDao().getSpecialMissions(id) return db.userCharacterDao().getSpecialMissions(id)
} }
@@ -39,7 +39,7 @@ class StorageRepository (
return db.itemDao().getItem(id) return db.itemDao().getItem(id)
} }
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? { fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?> {
return db.userCharacterDao().getActiveCharacter() return db.userCharacterDao().getActiveCharacter()
} }
@@ -47,7 +47,7 @@ class StorageRepository (
return db.userCharacterDao().deleteCharacterById(id) return db.userCharacterDao().deleteCharacterById(id)
} }
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> { fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>> {
return db.adventureDao().getAdventureCharacters() return db.adventureDao().getAdventureCharacters()
} }
+146
View File
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">VBHelper</string>
<string name="beta_warning_button_dismiss">閉じる</string>
<string name="nav_scan">スキャン</string>
<string name="nav_battle">バトル</string>
<string name="nav_home">ホーム</string>
<string name="nav_card_adventure">アドベンチャー</string>
<string name="nav_storage">ストレージ</string>
<string name="nav_settings">設定</string>
<string name="nav_viewer">閲覧</string>
<string name="nav_card">カード</string>
<string name="nav_items">アイテム</string>
<string name="nav_my_items">所持アイテム</string>
<string name="nav_items_store">ストア</string>
<string name="nav_apply_item">使用</string>
<string name="nav_adventure">アドベンチャー</string>
<string name="nav_credits">クレジット</string>
<string name="adventure_title">アドベンチャー</string>
<string name="adventure_empty_state">何もありません。</string>
<string name="home_title">VB Helper</string>
<string name="scan_secrets_not_imported">" APKの読み込みがされていません。設定から読み込をして下さい。 "</string>
<string name="scan_title">デバイスをスキャン</string>
<string name="scan_sent_dim_success">DIMの転送に成功しました。</string>
<string name="scan_nfc_must_be_enabled">NFC機能を有効にして下さい。</string>
<string name="settings_title">設定</string>
<string name="scan_missing_secrets">必要なデータが不足しています。設定を開き、Vital ArenaのAPKを読み込んでください。</string>
<string name="scan_sent_character_success">転送が成功しました。</string>
<string name="scan_error_generic">エラー</string>
<string name="settings_about_desc">このアプリについて</string>
<string name="settings_export_data_title">データベースの書き出し</string>
<string name="settings_export_data_desc">" データベースを書き出します。"</string>
<string name="settings_import_data_title">データベースの読み込み</string>
<string name="settings_import_data_desc">" データベースを読み込みます。"</string>
<string name="credits_title">クレジット</string>
<string name="battles_coming_soon">Coming soon</string>
<string name="cards_my_cards_title">カードリスト</string>
<string name="writing_character_action_title">キャラクター情報書き込み中</string>
<string name="items_title">アイテム</string>
<string name="items_no_items">アイテムはありません。</string>
<string name="items_store_credits">%1$d クレジット</string>
<string name="scan_vb_to_app">デバイスからアプリへ</string>
<string name="scan_app_to_vb">アプリからデバイスへ</string>
<string name="read_character_title">キャラクターを読み込みます。</string>
<string name="read_character_prepare_device">バイタルブレスを準備してください。</string>
<string name="read_character_go_to_connect">準備が出来たら転送を押してください。</string>
<string name="read_character_confirm">転送</string>
<string name="card_adventure_missions_title">アドベンチャーミッション</string>
<string name="card_entry_characters_obtained">%1$d / %2$d のキャラクターを取得しました。</string>
<string name="card_entry_edit">編集</string>
<string name="card_entry_delete">削除</string>
<string name="home_vbdim_current_phase_win">フェーズ勝率 %</string>
<string name="home_vbdim_special_missions">スペシャルミッション</string>
<string name="storage_delete_character">削除</string>
<string name="storage_close">閉じる</string>
<string name="storage_character_image_description">キャラクター画像</string>
<string name="storage_send_to_watch">転送</string>
<string name="storage_set_active">選択</string>
<string name="storage_send_on_adventure">アドベンチャーへ出発</string>
<string name="scan_no_nfc_on_device">NFC機能が見つかりません。</string>
<string name="scan_tag_not_vb">検出されたタグはバイタルブレスでは使用出来ません。</string>
<string name="settings_section_nfc">NFC</string>
<string name="settings_section_dim_bem">DiM / BEm</string>
<string name="settings_section_data">データ管理</string>
<string name="settings_import_apk_title">アプリの読み込み</string>
<string name="settings_import_apk_desc">" Vital Arena 2.1.0のAPKファイルを読み込みします。"</string>
<string name="settings_import_card_title">カードの読み込み</string>
<string name="settings_section_about">概要 / クレジット</string>
<string name="dex_chara_fusions_button">ジョグレス</string>
<string name="dex_chara_close_button">閉じる</string>
<string name="settings_import_card_desc">" Dim / BEm カードファイルを読み込みます。"</string>
<string name="settings_credits_title">クレジット</string>
<string name="settings_credits_desc">開発者について</string>
<string name="settings_about_title">概要</string>
<string name="beta_warning_message_main">" このアプリは現在アルファ版です。今後のアップデートにより保存されているデータの全てが削除される可能性がある為、大切なデータの保存はしないようにお願い致します。ご不便をおかけして申し訳ございません。 "</string>
<string name="beta_warning_message_compatibility">" このアプリはオリジナルのVBおよびVHで動作するようになりました。 "</string>
<string name="beta_warning_message_thanks">" ご理解とご協力に感謝いたします。\n 開発チーム一同"</string>
<string name="home_vbdim_vitals">バイタル</string>
<string name="home_adventure_mission_finished">" 1人のキャラクターがアドベンチャーミッションを完了しました。 "</string>
<string name="scan_secrets_not_initialized">" 初期化が出来ませんでした。もう一度試して下さい。 "</string>
<string name="home_special_mission_delete_main">現在の進捗状況が失われますが、本当にこのスペシャルミッションを削除しますか?</string>
<string name="home_special_mission_delete_dismiss">閉じる</string>
<string name="home_special_mission_delete_remove">削除</string>
<string name="item_dialog_use">アイテム使用</string>
<string name="item_dialog_purchase">購入</string>
<string name="item_dialog_cancel">キャンセル</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\\?\\?\\?\\?\\?\\?\\?\\?\\?\\?\\?\\?\\?\\?\\?</string>
<string name="battles_online_title">オンラインバトル</string>
<string name="obtained_item_you_also_got_credits">%1$d クレジットを獲得しました。</string>
<string name="obtained_item_dismiss">閉じる</string>
<string name="nav_dex">図鑑</string>
<string name="action_place_near_reader">バイタルブレス本体を近くに置いて下さい。</string>
<string name="action_cancel">キャンセル</string>
<string name="card_view_discovered_characters">解放済みキャラクター</string>
<string name="dex_chara_icon_description">キャラクターアイコン</string>
<string name="dex_chara_name_icon_description">名前</string>
<string name="credits_section_reverse_engineering">リバースエンジニアリング</string>
<string name="credits_section_app_development">アプリケーション開発</string>
<string name="credits_cyanic_description">" ファームウェアの解析を行い、開発をサポートしてくれました。"</string>
<string name="credits_cfogrady_description">" vb-lib-nfc開発およびアプリの開発に協力して頂きました。"</string>
<string name="credits_nacabaro_description">" アプリの開発をしました。"</string>
<string name="credits_lightheel_description">" アプリのサーバー含む対戦機能を開発して頂きました。現在も開発を継続して頂いています。"</string>
<string name="credits_shvstrz_description">" アプリのアイコンデザインをして頂きました。"</string>
<string name="write_card_title">カード情報書き込み中</string>
<string name="write_card_icon_description">カードアイコン</string>
<string name="write_card_device_ready">デバイスの準備をして下さい。</string>
<string name="write_card_required_card">%1$s カードが必要です。</string>
<string name="write_card_confirm">転送</string>
<string name="write_character_success">" カードの読み込みが完了しました。"</string>
<string name="write_character_wait_ready">" デバイスの準備をしてから確認を押して下さい。"</string>
<string name="write_character_confirm">確認</string>
<string name="home_vbdim_trophies">トロフィー</string>
<string name="items_purchase_success">購入しました。</string>
<string name="obtained_item_you_have">%1$d 個入手しました。</string>
<string name="choose_character_title">キャラクターを選択</string>
<string name="choose_character_item_applied">アイテムを使用しました。</string>
<string name="item_dialog_costs_credits">%1$d クレジット</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">" Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh "</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="storage_my_characters_title">キャラクターリスト</string>
<string name="storage_nothing_to_see_here">表示するデータはありません。</string>
<string name="storage_in_adventure_toast">このキャラクターは冒険中です。</string>
<string name="home_vbdim_next_timer">次のタイマー</string>
<string name="home_vbdim_total_battle_win">総勝率 %</string>
<string name="special_mission_none">ミッションが選択されていません。</string>
<string name="special_mission_steps">%1$d 歩</string>
<string name="write_character_title">キャラクター情報書き込み中</string>
<string name="write_character_icon_description">キャラクターアイコン</string>
<string name="items_not_enough_credits">クレジットが足りません。</string>
<string name="special_mission_vitals_progress">獲得バイタル値 %1$d</string>
<string name="special_mission_icon_content_description">スペシャルミッションアイコン</string>
<string name="reading_character_title">キャラクター情報読み込み中</string>
<string name="sending_card_title">カード情報送信中</string>
<string name="item_dialog_you_have_quantity">所持数: %1$d</string>
<string name="home_vbdim_mood">メンタル</string>
<string name="special_mission_battles">バトル %1$d 回</string>
<string name="special_mission_wins">勝利 %1$d 回</string>
<string name="special_mission_vitals">バイタル値 %1$d</string>
<string name="special_mission_steps_progress">歩数 %1$d 歩</string>
<string name="special_mission_battles_progress">バトル %1$d 回</string>
<string name="special_mission_wins_progress">勝利 %1$d 回</string>
</resources>
+214
View File
@@ -0,0 +1,214 @@
<resources>
<string name="app_name">VBHelper</string>
<string name="beta_warning_message_main">
Este aplicativo ainda está em versão alfa e não está completo. Não o use para armazenar personagens importantes para você, pois futuras atualizações podem apagar todos os seus personagens. Desculpe pelo transtorno!
</string>
<string name="beta_warning_message_compatibility">
O aplicativo agora deve funcionar com o VB original e com o VH.
</string>
<string name="beta_warning_message_thanks">
Obrigado pela compreensão e pela paciência. Atenciosamente, a equipe de desenvolvimento.
</string>
<string name="beta_warning_button_dismiss">Fechar</string>
<string name="nav_scan">Escanear</string>
<string name="nav_battle">Batalhas</string>
<string name="nav_home">Início</string>
<string name="nav_dex">Dex</string>
<string name="nav_card_adventure">Aventura do card</string>
<string name="nav_storage">Armazenamento</string>
<string name="nav_settings">Configurações</string>
<string name="nav_viewer">Visualizador</string>
<string name="nav_card">Card</string>
<string name="nav_items">Itens</string>
<string name="nav_my_items">Meus itens</string>
<string name="nav_items_store">Loja de itens</string>
<string name="nav_apply_item">Aplicar item</string>
<string name="nav_adventure">Aventura</string>
<string name="nav_credits">Créditos</string>
<string name="adventure_title">Aventura</string>
<string name="adventure_empty_state">Nada para ver aqui</string>
<string name="home_title">VB Helper</string>
<string name="home_adventure_mission_finished">
Um dos seus personagens terminou a missão de aventura!
</string>
<string name="scan_secrets_not_initialized">
Os segredos ainda não foram inicializados. Tente novamente.
</string>
<string name="scan_secrets_not_imported">
Os segredos ainda não foram importados. Vá em Configurações e importe o APK.
</string>
<string name="scan_title">Escanear um Vital Bracelet</string>
<string name="scan_vb_to_app">Vital Bracelet para o app</string>
<string name="scan_app_to_vb">App para o Vital Bracelet</string>
<string name="scan_no_nfc_on_device">O dispositivo não possui NFC!</string>
<string name="scan_tag_not_vb">A tag detectada não é do Vital Bracelet.</string>
<string name="scan_missing_secrets">" Segredos ausentes. Vá em Configurações e importe o APK do Vital Arena. "</string>
<string name="scan_sent_character_success">Personagem enviado com sucesso!</string>
<string name="scan_error_generic">Ops!</string>
<string name="scan_sent_dim_success">DIM enviado com sucesso!</string>
<string name="scan_nfc_must_be_enabled">O NFC precisa estar ativado.</string>
<string name="settings_title">Configurações</string>
<string name="settings_section_nfc">Comunicação NFC</string>
<string name="settings_section_dim_bem">Gerenciamento de DiM/BEm</string>
<string name="settings_section_about">Sobre e créditos</string>
<string name="settings_section_data">Gerenciamento de dados</string>
<string name="settings_import_apk_title">Importar APK</string>
<string name="settings_import_apk_desc">
Importar segredos do APK do Vital Arena 2.1.0
</string>
<string name="settings_import_card_title">Importar card</string>
<string name="settings_import_card_desc">
Importar arquivo de card DiM/BEm
</string>
<string name="settings_credits_title">Créditos</string>
<string name="settings_credits_desc">Créditos</string>
<string name="settings_about_title">Sobre</string>
<string name="settings_about_desc">Sobre</string>
<string name="settings_export_data_title">Exportar dados</string>
<string name="settings_export_data_desc">
Exportar banco de dados do aplicativo
</string>
<string name="settings_import_data_title">Importar dados</string>
<string name="settings_import_data_desc">
Importar banco de dados do aplicativo
</string>
<string name="credits_title">Créditos</string>
<string name="credits_section_reverse_engineering">Engenharia reversa</string>
<string name="credits_section_app_development">Desenvolvimento do aplicativo</string>
<string name="credits_cyanic_description">" Reverteu o firmware e nos ajudou durante o desenvolvimento. "</string>
<string name="credits_cfogrady_description">
Desenvolveu vb-lib-nfc e parte deste aplicativo.
</string>
<string name="credits_nacabaro_description">
Desenvolveu este aplicativo.
</string>
<string name="credits_lightheel_description">
Está desenvolvendo a parte de batalhas deste aplicativo, incluindo o servidor. Ainda em desenvolvimento.
</string>
<string name="credits_shvstrz_description">
Responsável pelo design do ícone do aplicativo em SVG.
</string>
<string name="action_place_near_reader">Aproxime o seu Vital Bracelet do leitor...</string>
<string name="action_cancel">Cancelar</string>
<string name="read_character_title">Ler personagem</string>
<string name="read_character_prepare_device">Prepare seu dispositivo!</string>
<string name="read_character_go_to_connect">Vá em conectar e, quando estiver pronto, pressione confirmar!</string>
<string name="read_character_confirm">Confirmar</string>
<string name="reading_character_title">Lendo personagem</string>
<string name="write_card_title">Escrevendo detalhes do cartão</string>
<string name="write_card_icon_description">Ícone do cartão</string>
<string name="write_card_device_ready">Prepare o seu dispositivo!</string>
<string name="write_card_required_card">Você vai precisar do cartão %1$s!</string>
<string name="write_card_confirm">Confirmar</string>
<string name="write_character_title">Escrevendo personagem</string>
<string name="write_character_icon_description">Ícone do personagem</string>
<string name="write_character_success">" Cartão instalado com sucesso!! "</string>
<string name="write_character_wait_ready">
Aguarde até que seu dispositivo esteja pronto e então toque em Confirmar
</string>
<string name="write_character_confirm">Confirmar</string>
<string name="sending_card_title">Enviando cartão</string>
<string name="writing_character_action_title">Escrevendo personagem</string>
<string name="items_title">Itens</string>
<string name="items_no_items">Nenhum item</string>
<string name="items_store_credits">%1$d créditos</string>
<string name="items_not_enough_credits">Créditos insuficientes</string>
<string name="items_purchase_success">Compra realizada com sucesso!</string>
<string name="obtained_item_you_have">Você obteve %1$d deste item</string>
<string name="obtained_item_you_also_got_credits">Você também recebeu %1$d créditos</string>
<string name="obtained_item_dismiss">Fechar</string>
<string name="choose_character_title">Escolher personagem</string>
<string name="choose_character_item_applied">Item aplicado!</string>
<string name="item_dialog_costs_credits">Custa %1$d créditos</string>
<string name="item_dialog_you_have_quantity">Você tem %1$d deste item</string>
<string name="item_dialog_use">Usar item</string>
<string name="item_dialog_purchase">Comprar</string>
<string name="item_dialog_cancel">Cancelar</string>
<string name="battles_online_title">Batalhas online</string>
<string name="battles_coming_soon">Em breve</string>
<string name="cards_my_cards_title">Meus cards</string>
<string name="card_view_discovered_characters">Personagens descobertos</string>
<string name="dex_chara_icon_description">Ícone do personagem</string>
<string name="dex_chara_name_icon_description">Nome do personagem</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">
Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh
</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="dex_chara_fusions_button">Fusões</string>
<string name="dex_chara_close_button">Fechar</string>
<string name="card_adventure_missions_title">Missões de aventura</string>
<string name="card_entry_characters_obtained">%1$d de %2$d personagens obtidos</string>
<string name="card_entry_edit">Editar card</string>
<string name="card_entry_delete">Excluir card</string>
<string name="storage_my_characters_title">Meus personagens</string>
<string name="storage_nothing_to_see_here">Nada para ver aqui</string>
<string name="storage_in_adventure_toast">Este personagem está em uma aventura</string>
<string name="storage_character_image_description">Imagem do personagem</string>
<string name="storage_send_to_watch">Enviar para o relógio</string>
<string name="storage_set_active">Definir como ativo</string>
<string name="storage_send_on_adventure">Enviar para aventura</string>
<string name="storage_delete_character">Excluir personagem</string>
<string name="storage_close">Fechar</string>
<string name="home_vbdim_vitals">Vitais</string>
<string name="home_vbdim_trophies">Troféus</string>
<string name="home_vbdim_mood">Humor</string>
<string name="home_vbdim_next_timer">Próximo timer</string>
<string name="home_vbdim_total_battle_win">% vitórias totais</string>
<string name="home_vbdim_current_phase_win">% vitórias fase atual</string>
<string name="home_vbdim_special_missions">Missões especiais</string>
<string name="special_mission_none">Nenhuma missão selecionada</string>
<string name="special_mission_steps">Ande %1$d passos</string>
<string name="special_mission_battles">Lute %1$d vezes</string>
<string name="special_mission_wins">Vença %1$d batalhas</string>
<string name="special_mission_vitals">Ganhe %1$d vitals</string>
<string name="special_mission_steps_progress">Andou %1$d passos</string>
<string name="special_mission_battles_progress">Lutou %1$d vezes</string>
<string name="special_mission_wins_progress">Venceu %1$d batalhas</string>
<string name="special_mission_vitals_progress">Ganhou %1$d vitals</string>
<string name="special_mission_icon_content_description">Ícone de missão especial</string>
</resources>
+216
View File
@@ -1,3 +1,219 @@
<resources> <resources>
<string name="app_name">VBHelper</string> <string name="app_name">VBHelper</string>
<string name="beta_warning_message_main">
This application is currently in alpha and it is not complete. Do not use it to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!
</string>
<string name="beta_warning_message_compatibility">
The application should now work with the original VB and the VH.
</string>
<string name="beta_warning_message_thanks">
Thank you for your understanding and patience. Sincerely, the dev team.
</string>
<string name="beta_warning_button_dismiss">
Dismiss
</string>
<string name="nav_scan">Scan</string>
<string name="nav_battle">Battle</string>
<string name="nav_home">Home</string>
<string name="nav_dex">Dex</string>
<string name="nav_card_adventure">Card adventure</string>
<string name="nav_storage">Storage</string>
<string name="nav_settings">Settings</string>
<string name="nav_viewer">Viewer</string>
<string name="nav_card">Card</string>
<string name="nav_items">Items</string>
<string name="nav_my_items">My items</string>
<string name="nav_items_store">Items store</string>
<string name="nav_apply_item">Apply item</string>
<string name="nav_adventure">Adventure</string>
<string name="nav_credits">Credits</string>
<string name="adventure_title">Adventure</string>
<string name="adventure_empty_state">Nothing to see here</string>
<string name="home_title">VB Helper</string>
<string name="home_adventure_mission_finished">" One of your characters has finished their adventure mission! "</string>
<string name="scan_secrets_not_initialized">" Secrets is not yet initialized. Try again. "</string>
<string name="scan_secrets_not_imported">
Secrets not yet imported. Go to Settings and Import APK.
</string>
<string name="scan_title">Scan a Vital Bracelet</string>
<string name="scan_vb_to_app">Vital Bracelet to App</string>
<string name="scan_app_to_vb">App to Vital Bracelet</string>
<string name="scan_no_nfc_on_device">No NFC on device!</string>
<string name="scan_tag_not_vb">Tag detected is not VB</string>
<string name="scan_missing_secrets">" Missing Secrets. Go to settings and import Vital Arena APK. "</string>
<string name="scan_sent_character_success">Sent character successfully!</string>
<string name="scan_error_generic">Whoops</string>
<string name="scan_sent_dim_success">Sent DIM successfully!</string>
<string name="scan_nfc_must_be_enabled">NFC must be enabled</string>
<string name="settings_title">Settings</string>
<string name="settings_section_nfc">NFC Communication</string>
<string name="settings_section_dim_bem">DiM/BEm management</string>
<string name="settings_section_about">About and credits</string>
<string name="settings_section_data">Data management</string>
<string name="settings_import_apk_title">Import APK</string>
<string name="settings_import_apk_desc">
Import Secrets From Vital Arena 2.1.0 APK
</string>
<string name="settings_import_card_title">Import card</string>
<string name="settings_import_card_desc">
Import DiM/BEm card file
</string>
<string name="settings_credits_title">Credits</string>
<string name="settings_credits_desc">Credits</string>
<string name="settings_about_title">About</string>
<string name="settings_about_desc">About</string>
<string name="settings_export_data_title">Export data</string>
<string name="settings_export_data_desc">
Export application database
</string>
<string name="settings_import_data_title">Import data</string>
<string name="settings_import_data_desc">
Import application database
</string>
<string name="credits_title">Credits</string>
<string name="credits_section_reverse_engineering">Reverse engineering</string>
<string name="credits_section_app_development">Application development</string>
<string name="credits_cyanic_description">" Reversed the firmware and helped us during development. "</string>
<string name="credits_cfogrady_description">
Developed vb-lib-nfc and part of this application.
</string>
<string name="credits_nacabaro_description">
Developed this application.
</string>
<string name="credits_lightheel_description">
Developing the battling part for this application, including server. Still in the works.
</string>
<string name="credits_shvstrz_description">
Designing the app icon in SVG.
</string>
<string name="action_place_near_reader">Place your Vital Bracelet near the reader...</string>
<string name="action_cancel">Cancel</string>
<string name="read_character_title">Read character</string>
<string name="read_character_prepare_device">Prepare your device!</string>
<string name="read_character_go_to_connect">Go to connect and when ready press confirm!</string>
<string name="read_character_confirm">Confirm</string>
<string name="reading_character_title">Reading character</string>
<string name="write_card_title">Writing card details</string>
<string name="write_card_icon_description">Card icon</string>
<string name="write_card_device_ready">Get your device ready!</string>
<string name="write_card_required_card">You will need %1$s card!</string>
<string name="write_card_confirm">Confirm</string>
<string name="write_character_title">Writing character</string>
<string name="write_character_icon_description">Character icon</string>
<string name="write_character_success">" Card installed successfully!! "</string>
<string name="write_character_wait_ready">
Wait until your device is ready, then tap Confirm
</string>
<string name="write_character_confirm">Confirm</string>
<string name="sending_card_title">Sending card</string>
<string name="writing_character_action_title">Writing character</string>
<string name="items_title">Items</string>
<string name="items_no_items">No items</string>
<string name="items_store_credits">%1$d credits</string>
<string name="items_not_enough_credits">Not enough credits</string>
<string name="items_purchase_success">Purchase successful!</string>
<string name="obtained_item_you_have">You have obtained %1$d of this item</string>
<string name="obtained_item_you_also_got_credits">You also got %1$d credits</string>
<string name="obtained_item_dismiss">Dismiss</string>
<string name="choose_character_title">Choose character</string>
<string name="choose_character_item_applied">Item applied!</string>
<string name="item_dialog_costs_credits">Costs %1$d credits</string>
<string name="item_dialog_you_have_quantity">You have %1$d of this item</string>
<string name="item_dialog_use">Use item</string>
<string name="item_dialog_purchase">Purchase</string>
<string name="item_dialog_cancel">Cancel</string>
<string name="battles_online_title">Online battles</string>
<string name="battles_coming_soon">Coming soon</string>
<string name="cards_my_cards_title">My cards</string>
<string name="card_view_discovered_characters">Discovered characters</string>
<string name="dex_chara_icon_description">Character icon</string>
<string name="dex_chara_name_icon_description">Character name</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">
Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh
</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="dex_chara_fusions_button">Fusions</string>
<string name="dex_chara_close_button">Close</string>
<string name="card_adventure_missions_title">Adventure missions</string>
<string name="card_entry_characters_obtained">%1$d of %2$d characters obtained</string>
<string name="card_entry_edit">Edit card</string>
<string name="card_entry_delete">Delete card</string>
<string name="storage_my_characters_title">My characters</string>
<string name="storage_nothing_to_see_here">Nothing to see here</string>
<string name="storage_in_adventure_toast">This character is in an adventure</string>
<string name="storage_character_image_description">Character image</string>
<string name="storage_send_to_watch">Send to watch</string>
<string name="storage_set_active">Set active</string>
<string name="storage_send_on_adventure">Send on adventure</string>
<string name="storage_delete_character">Delete character</string>
<string name="storage_close">Close</string>
<string name="home_vbdim_vitals">Vitals</string>
<string name="home_vbdim_trophies">Trophies</string>
<string name="home_vbdim_mood">Mood</string>
<string name="home_vbdim_next_timer">Next timer</string>
<string name="home_vbdim_total_battle_win">Total battle win %</string>
<string name="home_vbdim_current_phase_win">Current phase win %</string>
<string name="home_vbdim_special_missions">Special missions</string>
<string name="special_mission_none">No mission selected</string>
<string name="special_mission_steps">Walk %1$d steps</string>
<string name="special_mission_battles">Battle %1$d times</string>
<string name="special_mission_wins">Win %1$d battles</string>
<string name="special_mission_vitals">Earn %1$d vitals</string>
<string name="special_mission_steps_progress">Walked %1$d steps</string>
<string name="special_mission_battles_progress">Battled %1$d times</string>
<string name="special_mission_wins_progress">Won %1$d battles</string>
<string name="special_mission_vitals_progress">Earned %1$d vitals</string>
<string name="special_mission_icon_content_description">Special mission icon</string>
<string name="home_special_mission_delete_main">Are you sure you want to delete this special mission with this progress?</string>
<string name="home_special_mission_delete_dismiss">Dismiss</string>
<string name="home_special_mission_delete_remove">Remove</string>
</resources> </resources>
+10
View File
@@ -21,3 +21,13 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.defaults.buildfeatures.resvalues=true
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
android.enableAppCompileTimeRClass=false
android.usesSdkInManifest.disallowed=false
android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
android.r8.optimizedResourceShrinking=false
android.builtInKotlin=false
android.newDsl=false
+21 -12
View File
@@ -1,24 +1,32 @@
[versions] [versions]
agp = "8.13.1" agp = "9.2.1"
datastore = "1.1.2" datastore = "1.2.1"
kotlin = "2.0.0" datastorePreferences = "1.2.1"
coreKtx = "1.15.0" kotlin = "2.4.0"
coreKtx = "1.19.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" junitVersion = "1.3.0"
espressoCore = "3.6.1" espressoCore = "3.7.0"
lifecycleRuntimeKtx = "2.8.7" lifecycleRuntimeKtx = "2.11.0"
activityCompose = "1.10.0" activityCompose = "1.13.0"
composeBom = "2025.01.00" composeBom = "2026.06.00"
protobufGradlePlugin = "0.9.4" material = "1.14.0"
protobufJavalite = "4.27.0" navigationCompose = "2.9.8"
roomRuntime = "2.6.1" protobufGradlePlugin = "0.10.0"
protobufJavalite = "4.35.1"
roomRuntime = "2.8.4"
vbNfcReader = "0.2.0-SNAPSHOT" vbNfcReader = "0.2.0-SNAPSHOT"
dimReader = "2.1.0" dimReader = "2.1.0"
[libraries] [libraries]
androidx-compose-material = { module = "androidx.compose.material:material" }
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
@@ -33,6 +41,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
material = { module = "com.google.android.material:material", version.ref = "material" }
protobuf-gradle-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobufGradlePlugin" } protobuf-gradle-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobufGradlePlugin" }
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" } protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" }
vb-nfc-reader = { module = "com.github.cfogrady:vb-nfc-reader", version.ref = "vbNfcReader" } vb-nfc-reader = { module = "com.github.cfogrady:vb-nfc-reader", version.ref = "vbNfcReader" }
+1 -1
View File
@@ -1,6 +1,6 @@
#Tue Dec 24 10:55:57 UTC 2024 #Tue Dec 24 10:55:57 UTC 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists