mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-10-01 15:41:36 +00:00
Compare commits
81
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39ba48d867 | ||
|
|
3f324ca141 | ||
|
|
1e28a8c249 | ||
|
|
d46769b0cb | ||
|
|
fce05870c5 | ||
|
|
d749af0a9c | ||
|
|
076f6bfacf | ||
|
|
a4f3b701e5 | ||
|
|
da134247b1 | ||
|
|
e36a700d9f | ||
|
|
fb6b6d2825 | ||
|
|
e17f6c23e4 | ||
|
|
409474b5d1 | ||
|
|
547318864b | ||
|
|
5e92895656 | ||
|
|
25500e9cb4 | ||
|
|
1a38cefb92 | ||
|
|
a974bd366e | ||
|
|
bb5f66d167 | ||
|
|
09394871e5 | ||
|
|
972a640394 | ||
|
|
35a7c9d959 | ||
|
|
b4a73031e7 | ||
|
|
3e41f82273 | ||
|
|
f3740d4816 | ||
|
|
305b776b52 | ||
|
|
c0a67e382b | ||
|
|
bc7a05ccd3 | ||
|
|
660dbaca19 | ||
|
|
b89dfb0df3 | ||
|
|
23e233227a | ||
|
|
542072c238 | ||
|
|
42dd87f0c4 | ||
|
|
635be60cdb | ||
|
|
88163684ca | ||
|
|
586d6c01ef | ||
|
|
5d996edc1e | ||
|
|
074ced1082 | ||
|
|
5a1d52aa1d | ||
|
|
98bd74c544 | ||
|
|
ce1cf3eddb | ||
|
|
0bd745ac86 | ||
|
|
0d999925b5 | ||
|
|
dd893a08da | ||
|
|
c5e8236e19 | ||
|
|
cac5198488 | ||
|
|
7f22650601 | ||
|
|
1088e5259a | ||
|
|
eabf8770cc | ||
|
|
da5e6c6628 | ||
|
|
4c0e2fafd0 | ||
|
|
4529925906 | ||
|
|
c456d455ef | ||
|
|
9871f0420f | ||
|
|
f4974c8705 | ||
|
|
5d6e3743f0 | ||
|
|
b91da136df | ||
|
|
e4366ce15c | ||
|
|
7582f944b5 | ||
|
|
a028611d8a | ||
|
|
5edd753da1 | ||
|
|
5691f998f2 | ||
|
|
4e2b9eb541 | ||
|
|
19fbed0ef2 | ||
|
|
82292823d3 | ||
|
|
01e82a6a44 | ||
|
|
1e73f52da5 | ||
|
|
d9e492eb0e | ||
|
|
5bd5fc3f74 | ||
|
|
3019560a26 | ||
|
|
a01d0eba47 | ||
|
|
bc02689850 | ||
|
|
e8f441ba2b | ||
|
|
59033e6459 | ||
|
|
4736202838 | ||
|
|
08e3b844a4 | ||
|
|
fbbb8f6ad1 | ||
|
|
859786c7f3 | ||
|
|
2f4bc9dba6 | ||
|
|
aa0f4241ea | ||
|
|
e9eedb7429 |
+12
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
.gradle/
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
app/src/main/res/values/keys.xml
|
||||||
|
|
||||||
|
app/src/test/resources/com/github/nacabaro/vbhelper/source/com.bandai.vitalbraceletarena.apk
|
||||||
|
|
||||||
|
app/src/test/resources/com/github/nacabaro/vbhelper/source/classes.dex
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# VBHelper
|
||||||
|
|
||||||
|
## Developer Setup
|
||||||
|
|
||||||
|
1. Clone vb-nfc-reader (https://github.com/cfogrady/lib-vb-nfc)
|
||||||
|
2. Run vb-nfc-reader/publishToMavenLocal gradle task in the lib-vb-nfc project.
|
||||||
|
3. Clone vb-dim-reader (https://github.com/cfogrady/vb-dim-reader)
|
||||||
|
4. Run publishToMavenLocal gradle task in the vb-dim-reader project.
|
||||||
|
5. Create res/values/keys.xml within the app module.
|
||||||
|
6. Populate with:
|
||||||
|
```
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="password1">beHmacKey1</string>
|
||||||
|
<string name="password2">beHmacKey2</string>
|
||||||
|
<string name="decryptionKey">aesKey</string>
|
||||||
|
<integer-array name="substitutionArray">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
<item>4</item>
|
||||||
|
<item>5</item>
|
||||||
|
<item>6</item>
|
||||||
|
<item>7</item>
|
||||||
|
<item>8</item>
|
||||||
|
<item>9</item>
|
||||||
|
<item>10</item>
|
||||||
|
<item>11</item>
|
||||||
|
<item>12</item>
|
||||||
|
<item>13</item>
|
||||||
|
<item>14</item>
|
||||||
|
<item>15</item>
|
||||||
|
</integer-array>
|
||||||
|
</resources>
|
||||||
|
```
|
||||||
|
7. Replace the values in the keys.xml file with those extracted from the original app.
|
||||||
|
8. Run
|
||||||
+30
-2
@@ -3,6 +3,7 @@ plugins {
|
|||||||
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.0.21-1.0.27"
|
||||||
|
id("com.google.protobuf")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -14,7 +15,7 @@ android {
|
|||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "Alpha 0.1"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -38,17 +39,43 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lint {
|
||||||
|
baseline = file("lint-baseline.xml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
artifact = "com.google.protobuf:protoc:4.27.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates the java Protobuf-lite code for the Protobufs in this project. See
|
||||||
|
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
|
||||||
|
// for more information.
|
||||||
|
generateProtoTasks {
|
||||||
|
all().forEach { task ->
|
||||||
|
task.builtins {
|
||||||
|
create("java") {
|
||||||
|
option("lite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.androidx.room.runtime)
|
implementation(libs.androidx.room.runtime)
|
||||||
implementation(project(":vb-nfc-reader"))
|
implementation(libs.vb.nfc.reader)
|
||||||
|
implementation(libs.dim.reader)
|
||||||
ksp(libs.androidx.room.compiler)
|
ksp(libs.androidx.room.compiler)
|
||||||
annotationProcessor(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.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
|
implementation(libs.androidx.datastore)
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
@@ -62,5 +89,6 @@ dependencies {
|
|||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
implementation("androidx.navigation:navigation-compose:2.7.0")
|
implementation("androidx.navigation:navigation-compose:2.7.0")
|
||||||
implementation("com.google.android.material:material:1.2.0")
|
implementation("com.google.android.material:material:1.2.0")
|
||||||
|
implementation(libs.protobuf.javalite)
|
||||||
implementation("androidx.compose.material:material")
|
implementation("androidx.compose.material:material")
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".di.VBHelper"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.nacabaro.vbhelper
|
||||||
|
|
||||||
|
interface ActivityLifecycleListener {
|
||||||
|
fun onPause()
|
||||||
|
fun onResume()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun noOpInstance(): ActivityLifecycleListener {
|
||||||
|
return object: ActivityLifecycleListener {
|
||||||
|
override fun onPause() {}
|
||||||
|
|
||||||
|
override fun onResume() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,141 +1,110 @@
|
|||||||
package com.github.nacabaro.vbhelper
|
package com.github.nacabaro.vbhelper
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.nfc.NfcAdapter
|
|
||||||
import android.nfc.Tag
|
|
||||||
import android.nfc.tech.NfcA
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import com.github.nacabaro.vbhelper.navigation.AppNavigation
|
import com.github.nacabaro.vbhelper.navigation.AppNavigation
|
||||||
import com.github.cfogrady.vbnfc.CryptographicTransformer
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
import com.github.cfogrady.vbnfc.TagCommunicator
|
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
|
||||||
import com.github.cfogrady.vbnfc.data.DeviceType
|
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
|
||||||
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
|
||||||
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
|
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var nfcAdapter: NfcAdapter
|
private val onActivityLifecycleListeners = HashMap<String, ActivityLifecycleListener>()
|
||||||
private lateinit var deviceToCryptographicTransformers: Map<UShort, CryptographicTransformer>
|
|
||||||
|
|
||||||
private var nfcCharacter = MutableStateFlow<NfcCharacter?>(null)
|
private fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener) {
|
||||||
|
if( onActivityLifecycleListeners[key] != null) {
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
throw IllegalStateException("Key is already in use")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
}
|
||||||
deviceToCryptographicTransformers = getMapOfCryptographicTransformers()
|
onActivityLifecycleListeners[key] = activityLifecycleListener
|
||||||
|
|
||||||
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
|
||||||
if (maybeNfcAdapter == null) {
|
|
||||||
Toast.makeText(this, "No NFC on device!", Toast.LENGTH_SHORT).show()
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
nfcAdapter = maybeNfcAdapter
|
|
||||||
|
|
||||||
|
private fun unregisterActivityLifecycleListener(key: String) {
|
||||||
|
onActivityLifecycleListeners.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
val application = applicationContext as VBHelper
|
||||||
|
val scanScreenController = ScanScreenControllerImpl(
|
||||||
|
application.container.dataStoreSecretsRepository.secretsFlow,
|
||||||
|
this,
|
||||||
|
this::registerActivityLifecycleListener,
|
||||||
|
this::unregisterActivityLifecycleListener
|
||||||
|
)
|
||||||
|
val settingsScreenController = SettingsScreenControllerImpl(this)
|
||||||
|
val itemsScreenController = ItemsScreenControllerImpl(this)
|
||||||
|
val adventureScreenController = AdventureScreenControllerImpl(this)
|
||||||
|
val storageScreenController = StorageScreenControllerImpl(this)
|
||||||
|
val homeScreenController = HomeScreenControllerImpl(this)
|
||||||
|
val spriteViewerController = SpriteViewerControllerImpl(this)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
VBHelperTheme {
|
VBHelperTheme {
|
||||||
MainApplication()
|
MainApplication(
|
||||||
|
scanScreenController = scanScreenController,
|
||||||
|
settingsScreenController = settingsScreenController,
|
||||||
|
itemsScreenController = itemsScreenController,
|
||||||
|
adventureScreenController = adventureScreenController,
|
||||||
|
homeScreenController = homeScreenController,
|
||||||
|
storageScreenController = storageScreenController,
|
||||||
|
spriteViewerController = spriteViewerController
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i("MainActivity", "Activity onCreated")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
Log.i("MainActivity", "onPause")
|
||||||
|
for(activityListener in onActivityLifecycleListeners) {
|
||||||
|
activityListener.value.onPause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
Log.i("MainActivity", "Resume")
|
||||||
|
for(activityListener in onActivityLifecycleListeners) {
|
||||||
|
activityListener.value.onResume()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MainApplication() {
|
private fun MainApplication(
|
||||||
var isDoneReadingCharacter by remember { mutableStateOf(false) }
|
scanScreenController: ScanScreenControllerImpl,
|
||||||
|
settingsScreenController: SettingsScreenControllerImpl,
|
||||||
|
itemsScreenController: ItemsScreenControllerImpl,
|
||||||
|
adventureScreenController: AdventureScreenControllerImpl,
|
||||||
|
storageScreenController: StorageScreenControllerImpl,
|
||||||
|
homeScreenController: HomeScreenControllerImpl,
|
||||||
|
spriteViewerController: SpriteViewerControllerImpl
|
||||||
|
) {
|
||||||
AppNavigation(
|
AppNavigation(
|
||||||
isDoneReadingCharacter = isDoneReadingCharacter,
|
applicationNavigationHandlers = AppNavigationHandlers(
|
||||||
onClickRead = {
|
settingsScreenController,
|
||||||
handleTag {
|
scanScreenController,
|
||||||
val character = it.receiveCharacter()
|
itemsScreenController,
|
||||||
nfcCharacter.value = character
|
adventureScreenController,
|
||||||
isDoneReadingCharacter = true
|
storageScreenController,
|
||||||
"Done reading character"
|
homeScreenController,
|
||||||
}
|
spriteViewerController
|
||||||
},
|
|
||||||
onClickScan = {
|
|
||||||
isDoneReadingCharacter = false
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
private fun getMapOfCryptographicTransformers(): Map<UShort, CryptographicTransformer> {
|
|
||||||
return mapOf(
|
|
||||||
Pair(DeviceType.VitalBraceletBEDeviceType,
|
|
||||||
CryptographicTransformer(readableHmacKey1 = resources.getString(com.github.cfogrady.vbnfc.R.string.password1),
|
|
||||||
readableHmacKey2 = resources.getString(com.github.cfogrady.vbnfc.R.string.password2),
|
|
||||||
aesKey = resources.getString(com.github.cfogrady.vbnfc.R.string.decryptionKey),
|
|
||||||
substitutionCipher = resources.getIntArray(com.github.cfogrady.vbnfc.R.array.substitutionArray))),
|
|
||||||
// Pair(DeviceType.VitalSeriesDeviceType,
|
|
||||||
// CryptographicTransformer(hmacKey1 = resources.getString(R.string.password1),
|
|
||||||
// hmacKey2 = resources.getString(R.string.password2),
|
|
||||||
// decryptionKey = resources.getString(R.string.decryptionKey),
|
|
||||||
// substitutionCipher = resources.getIntArray(R.array.substitutionArray))),
|
|
||||||
// Pair(DeviceType.VitalCharactersDeviceType,
|
|
||||||
// CryptographicTransformer(hmacKey1 = resources.getString(R.string.password1),
|
|
||||||
// hmacKey2 = resources.getString(R.string.password2),
|
|
||||||
// decryptionKey = resources.getString(R.string.decryptionKey),
|
|
||||||
// substitutionCipher = resources.getIntArray(R.array.substitutionArray)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
private fun showWirelessSettings() {
|
|
||||||
Toast.makeText(this, "NFC must be enabled", Toast.LENGTH_SHORT).show()
|
|
||||||
startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
private fun buildOnReadTag(handlerFunc: (TagCommunicator)->String): (Tag)->Unit {
|
|
||||||
return { tag->
|
|
||||||
val nfcData = NfcA.get(tag)
|
|
||||||
if (nfcData == null) {
|
|
||||||
runOnUiThread {
|
|
||||||
Toast.makeText(this, "Tag detected is not VB", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nfcData.connect()
|
|
||||||
nfcData.use {
|
|
||||||
val tagCommunicator = TagCommunicator.getInstance(nfcData, deviceToCryptographicTransformers)
|
|
||||||
val successText = handlerFunc(tagCommunicator)
|
|
||||||
runOnUiThread {
|
|
||||||
Toast.makeText(this, successText, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
private fun handleTag(handlerFunc: (TagCommunicator)->String) {
|
|
||||||
if (!nfcAdapter.isEnabled) {
|
|
||||||
showWirelessSettings()
|
|
||||||
} else {
|
|
||||||
val options = Bundle()
|
|
||||||
// Work around for some broken Nfc firmware implementations that poll the card too fast
|
|
||||||
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
|
|
||||||
nfcAdapter.enableReaderMode(this, buildOnReadTag(handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
|
|
||||||
options
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
if (nfcAdapter.isEnabled) {
|
|
||||||
nfcAdapter.disableReaderMode(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.components
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getObscuredBitmap
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CharacterEntry(
|
||||||
|
icon: BitmapData,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
obscure: Boolean = false,
|
||||||
|
disabled: Boolean = false,
|
||||||
|
shape: Shape = MaterialTheme.shapes.medium,
|
||||||
|
multiplier: Int = 4,
|
||||||
|
onClick: () -> Unit = { }
|
||||||
|
) {
|
||||||
|
val bitmap = remember (icon.bitmap) {
|
||||||
|
if(obscure) icon.getObscuredBitmap() else icon.getBitmap()
|
||||||
|
}
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (icon.width * multiplier / density).dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
shape = shape,
|
||||||
|
onClick = when (disabled) {
|
||||||
|
true -> { {} }
|
||||||
|
false -> onClick
|
||||||
|
},
|
||||||
|
modifier = modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.BottomCenter,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = "Icon",
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
colorFilter = when (obscure) {
|
||||||
|
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
|
||||||
|
false -> null
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ItemDisplay(
|
||||||
|
icon: Int,
|
||||||
|
textValue: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
definition: String = "",
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
Card(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = androidx.compose.material.MaterialTheme.shapes.small,
|
||||||
|
onClick = {
|
||||||
|
Toast.makeText(context, definition, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(icon),
|
||||||
|
contentDescription = "Vitals",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize(0.5f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = textValue,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DexDiMEntry(
|
||||||
|
name: String,
|
||||||
|
logo: BitmapData,
|
||||||
|
obtainedCharacters: Int,
|
||||||
|
totalCharacters: Int,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val bitmap = remember (logo.bitmap) { logo.getBitmap() }
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (logo.width * 4 / density).dp
|
||||||
|
|
||||||
|
Card (
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Image (
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = name,
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.size(dpSize)
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = name,
|
||||||
|
modifier = Modifier
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "$obtainedCharacters of $totalCharacters characters obtained",
|
||||||
|
fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
|
||||||
|
fontSize = MaterialTheme.typography.labelSmall.fontSize,
|
||||||
|
modifier = Modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,90 @@
|
|||||||
package com.github.nacabaro.vbhelper.components
|
package com.github.nacabaro.vbhelper.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
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.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TopBanner(
|
fun TopBanner(
|
||||||
text: String,
|
text: String,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
onGearClick: (() -> Unit)? = null,
|
||||||
|
onBackClick: (() -> Unit)? = null,
|
||||||
|
onScanClick: (() -> Unit)? = null,
|
||||||
|
onAdventureClick: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
Box( // Use Box to overlay elements
|
||||||
|
modifier = modifier
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
modifier = modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.align(Alignment.Center) // Center the text
|
||||||
.padding(16.dp)
|
)
|
||||||
|
if (onGearClick != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onGearClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd) // Place gear icon at the end
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_settings_24), // Use a gear icon
|
||||||
|
contentDescription = "Settings"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (onAdventureClick != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onAdventureClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd) // Place gear icon at the end
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_fort_24), // Use a gear icon
|
||||||
|
contentDescription = "Adventure"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onScanClick != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onScanClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterStart) // Place gear icon at the end
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_nfc_24), // Use a gear icon
|
||||||
|
contentDescription = "Scan"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (onBackClick != null) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onBackClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterStart) // Place gear icon at the end
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_arrow_back_24), // Use a gear icon
|
||||||
|
contentDescription = "Settings"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransformationHistoryCard(
|
||||||
|
transformationHistory: List<CharacterDtos.TransformationHistory>,
|
||||||
|
modifier: Modifier= Modifier
|
||||||
|
) {
|
||||||
|
Card (
|
||||||
|
shape = androidx.compose.material.MaterialTheme.shapes.small,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
LazyRow (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
items(transformationHistory) { transformation ->
|
||||||
|
TransformationHistoryItem(transformation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransformationHistoryItem(
|
||||||
|
transformation: CharacterDtos.TransformationHistory
|
||||||
|
) {
|
||||||
|
val bitmapData = BitmapData(
|
||||||
|
bitmap = transformation.spriteIdle,
|
||||||
|
width = transformation.spriteWidth,
|
||||||
|
height = transformation.spriteHeight
|
||||||
|
)
|
||||||
|
val bitmap = remember (bitmapData) { bitmapData.getBitmap() }
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (bitmap.width * 3 / density).dp
|
||||||
|
|
||||||
|
Box (
|
||||||
|
contentAlignment = Alignment.BottomCenter,
|
||||||
|
modifier = Modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.size((64*3/density).dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = "Transformation",
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface AdventureDao {
|
||||||
|
@Query("""
|
||||||
|
INSERT INTO Adventure (characterId, originalDuration, finishesAdventure)
|
||||||
|
VALUES (:characterId, :originalDuration, strftime('%s', 'now') + :timeInSeconds)
|
||||||
|
""")
|
||||||
|
fun insertNewAdventure(characterId: Long, originalDuration: Long, timeInSeconds: Long)
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT COUNT(*) FROM Adventure
|
||||||
|
""")
|
||||||
|
fun getAdventureCount(): Int
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.finishesAdventure AS finishesAdventure,
|
||||||
|
a.originalDuration AS originalTimeInMinutes
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN Character c ON uc.charId = c.id
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
JOIN Card d ON c.dimId = d.id
|
||||||
|
JOIN Adventure a ON uc.id = a.characterId
|
||||||
|
""")
|
||||||
|
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
DELETE FROM Adventure
|
||||||
|
WHERE characterId = :characterId
|
||||||
|
""")
|
||||||
|
suspend fun deleteAdventure(characterId: Long)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface CharacterDao {
|
||||||
|
@Insert
|
||||||
|
suspend fun insertCharacter(vararg characterData: Character)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Character")
|
||||||
|
suspend fun getAllCharacters(): List<Character>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Character WHERE dimId = :dimId")
|
||||||
|
suspend fun getCharacterByDimId(dimId: Int): List<Character>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Character WHERE monIndex = :monIndex AND dimId = :dimId LIMIT 1")
|
||||||
|
fun getCharacterByMonIndex(monIndex: Int, dimId: Long): Character
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insertSprite(vararg sprite: Sprite)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Sprite")
|
||||||
|
suspend fun getAllSprites(): List<Sprite>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
d.dimId as cardId,
|
||||||
|
c.monIndex as charId
|
||||||
|
FROM Character c
|
||||||
|
JOIN UserCharacter uc ON c.id = uc.charId
|
||||||
|
JOIN Card d ON c.dimId = d.id
|
||||||
|
WHERE uc.id = :charId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getCharacterInfo(charId: Long): CharacterDtos.DiMInfo
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
INSERT INTO TransformationHistory(monId, stageId, transformationDate)
|
||||||
|
VALUES
|
||||||
|
(:monId,
|
||||||
|
(SELECT id FROM Character WHERE monIndex = :stage AND dimId = :dimId),
|
||||||
|
:transformationDate)
|
||||||
|
""")
|
||||||
|
fun insertTransformation(monId: Long, stage: Int, dimId: Long, transformationDate: Long)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CardDtos
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DexDao {
|
||||||
|
@Query("""
|
||||||
|
INSERT OR IGNORE INTO Dex(id, discoveredOn)
|
||||||
|
VALUES (
|
||||||
|
(SELECT id FROM Character WHERE monIndex = :charIndex AND dimId = :cardId),
|
||||||
|
:discoveredOn
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
fun insertCharacter(charIndex: Int, cardId: Long, discoveredOn: Long)
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT
|
||||||
|
c.id AS id,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
d.discoveredOn AS discoveredOn
|
||||||
|
FROM Character c
|
||||||
|
JOIN Sprite s ON c.spriteId = s.id
|
||||||
|
LEFT JOIN dex d ON c.id = d.id
|
||||||
|
WHERE c.dimId = :cardId
|
||||||
|
""")
|
||||||
|
suspend fun getSingleCardProgress(cardId: Long): List<CharacterDtos.CardProgress>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT
|
||||||
|
c.id as cardId,
|
||||||
|
c.name as cardName,
|
||||||
|
c.logo as cardLogo,
|
||||||
|
c.logoWidth as logoWidth,
|
||||||
|
c.logoHeight as logoHeight,
|
||||||
|
(SELECT COUNT(*) FROM Character cc WHERE cc.dimId = c.id) AS totalCharacters,
|
||||||
|
(SELECT COUNT(*) FROM Dex d JOIN Character cc ON d.id = cc.id WHERE cc.dimId = c.id AND d.discoveredOn IS NOT NULL) AS obtainedCharacters
|
||||||
|
FROM Card c
|
||||||
|
""")
|
||||||
|
suspend fun getCardsWithProgress(): List<CardDtos.CardProgress>
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Card
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DiMDao {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
suspend fun insertNewDim(card: Card): Long
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Card")
|
||||||
|
suspend fun getAllDims(): List<Card>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Card WHERE dimId = :id")
|
||||||
|
fun getDimById(id: Int): Card?
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.github.nacabaro.vbhelper.domain.DimProgress
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DiMProgressDao {
|
||||||
|
@Upsert
|
||||||
|
suspend fun updateDimProgress(vararg dimProgress: DimProgress)
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ItemDao {
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM Items
|
||||||
|
ORDER BY Items.itemIcon ASC
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM Items
|
||||||
|
WHERE quantity > 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM Items
|
||||||
|
WHERE Items.id = :itemId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
UPDATE Items
|
||||||
|
SET quantity = quantity - 1
|
||||||
|
WHERE id = :itemId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun useItem(itemId: Long)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
UPDATE Items
|
||||||
|
SET quantity = quantity + :itemAmount
|
||||||
|
WHERE id = :itemId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun purchaseItem(itemId: Long, itemAmount: Int)
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface SpriteDao {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertSprite(sprite: Sprite): Long
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Sprite")
|
||||||
|
suspend fun getAllSprites(): List<Sprite>
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Upsert
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UserCharacterDao {
|
||||||
|
@Insert
|
||||||
|
fun insertCharacterData(characterData: UserCharacter): Long
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insertBECharacterData(characterData: BECharacterData)
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
fun updateCharacter(character: UserCharacter)
|
||||||
|
|
||||||
|
@Upsert
|
||||||
|
fun updateBECharacterData(characterData: BECharacterData)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertTransformationHistory(vararg transformationHistory: TransformationHistory)
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT
|
||||||
|
c.id AS id,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.monIndex AS monIndex,
|
||||||
|
t.transformationDate AS transformationDate
|
||||||
|
FROM TransformationHistory t
|
||||||
|
JOIN Character c ON c.id = t.stageId
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
WHERE monId = :monId
|
||||||
|
""")
|
||||||
|
fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.spriteIdle2 AS spriteIdle2,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.name as nameSprite,
|
||||||
|
c.nameWidth as nameSpriteWidth,
|
||||||
|
c.nameHeight as nameSpriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.characterId = uc.id as isInAdventure
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN Character c ON uc.charId = c.id
|
||||||
|
JOIN Card d ON d.id = c.dimId
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
LEFT JOIN Adventure a ON a.characterId = uc.id
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.spriteIdle2 AS spriteIdle2,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.name as nameSprite,
|
||||||
|
c.nameWidth as nameSpriteWidth,
|
||||||
|
c.nameHeight as nameSpriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.characterId = uc.id as isInAdventure
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN Character c ON uc.charId = c.id
|
||||||
|
JOIN Card d ON c.dimId = d.id
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
LEFT JOIN Adventure a ON a.characterId = uc.id
|
||||||
|
WHERE uc.id = :id
|
||||||
|
""")
|
||||||
|
suspend fun getCharacterWithSprites(id: Long): CharacterDtos.CharacterWithSprites
|
||||||
|
|
||||||
|
@Query("SELECT * FROM UserCharacter WHERE id = :id")
|
||||||
|
suspend fun getCharacter(id: Long): UserCharacter
|
||||||
|
|
||||||
|
@Query("SELECT * FROM BECharacterData WHERE id = :id")
|
||||||
|
suspend fun getBeData(id: Long): BECharacterData
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
uc.*,
|
||||||
|
c.stage,
|
||||||
|
c.attribute,
|
||||||
|
s.spriteIdle1 AS spriteIdle,
|
||||||
|
s.spriteIdle2 AS spriteIdle2,
|
||||||
|
s.width AS spriteWidth,
|
||||||
|
s.height AS spriteHeight,
|
||||||
|
c.name as nameSprite,
|
||||||
|
c.nameWidth as nameSpriteWidth,
|
||||||
|
c.nameHeight as nameSpriteHeight,
|
||||||
|
d.isBEm as isBemCard,
|
||||||
|
a.characterId as isInAdventure
|
||||||
|
FROM UserCharacter uc
|
||||||
|
JOIN Character c ON uc.charId = c.id
|
||||||
|
JOIN Card d ON c.dimId = d.id
|
||||||
|
JOIN Sprite s ON s.id = c.spriteId
|
||||||
|
LEFT JOIN Adventure a ON a.characterId = uc.id
|
||||||
|
WHERE uc.isActive = 1
|
||||||
|
LIMIT 1
|
||||||
|
""")
|
||||||
|
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites?
|
||||||
|
|
||||||
|
@Query("DELETE FROM UserCharacter WHERE id = :id")
|
||||||
|
fun deleteCharacterById(id: Long)
|
||||||
|
|
||||||
|
@Query("UPDATE UserCharacter SET isActive = 0 WHERE isActive = 1")
|
||||||
|
fun clearActiveCharacter()
|
||||||
|
|
||||||
|
@Query("UPDATE UserCharacter SET isActive = 1 WHERE id = :id")
|
||||||
|
fun setActiveCharacter(id: Long)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT c.*
|
||||||
|
FROM Character c
|
||||||
|
join UserCharacter uc on c.id = uc.charId
|
||||||
|
where uc.id = :charId
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun getCharacterInfo(charId: Long): Character
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.database
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.daos.AdventureDao
|
||||||
|
import com.github.nacabaro.vbhelper.daos.CharacterDao
|
||||||
|
import com.github.nacabaro.vbhelper.daos.DexDao
|
||||||
|
import com.github.nacabaro.vbhelper.daos.DiMDao
|
||||||
|
import com.github.nacabaro.vbhelper.daos.ItemDao
|
||||||
|
import com.github.nacabaro.vbhelper.daos.SpriteDao
|
||||||
|
import com.github.nacabaro.vbhelper.daos.UserCharacterDao
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Card
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Adventure
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Dex
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.domain.items.Items
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
version = 1,
|
||||||
|
entities = [
|
||||||
|
Card::class,
|
||||||
|
Character::class,
|
||||||
|
Sprite::class,
|
||||||
|
UserCharacter::class,
|
||||||
|
BECharacterData::class,
|
||||||
|
TransformationHistory::class,
|
||||||
|
Dex::class,
|
||||||
|
Items::class,
|
||||||
|
Adventure::class
|
||||||
|
]
|
||||||
|
)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun dimDao(): DiMDao
|
||||||
|
abstract fun characterDao(): CharacterDao
|
||||||
|
abstract fun userCharacterDao(): UserCharacterDao
|
||||||
|
abstract fun dexDao(): DexDao
|
||||||
|
abstract fun itemDao(): ItemDao
|
||||||
|
abstract fun adventureDao(): AdventureDao
|
||||||
|
abstract fun spriteDao(): SpriteDao
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.di
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||||
|
|
||||||
|
interface AppContainer {
|
||||||
|
val db: AppDatabase
|
||||||
|
val dataStoreSecretsRepository: DataStoreSecretsRepository
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.dataStore
|
||||||
|
import androidx.room.Room
|
||||||
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.di.AppContainer
|
||||||
|
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||||
|
import com.github.nacabaro.vbhelper.source.SecretsSerializer
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
|
||||||
|
private const val SECRETS_DATA_STORE_NAME = "secrets.pb"
|
||||||
|
|
||||||
|
val Context.secretsStore: DataStore<Secrets> by dataStore(
|
||||||
|
fileName = SECRETS_DATA_STORE_NAME,
|
||||||
|
serializer = SecretsSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
class DefaultAppContainer(private val context: Context) : AppContainer {
|
||||||
|
|
||||||
|
override val db: AppDatabase by lazy {
|
||||||
|
Room.databaseBuilder(
|
||||||
|
context = context,
|
||||||
|
klass = AppDatabase::class.java,
|
||||||
|
"internalDb"
|
||||||
|
)
|
||||||
|
.createFromAsset("items.db")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.di
|
||||||
|
|
||||||
|
import DefaultAppContainer
|
||||||
|
import android.app.Application
|
||||||
|
|
||||||
|
class VBHelper : Application() {
|
||||||
|
lateinit var container: DefaultAppContainer
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
container = DefaultAppContainer(applicationContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.domain
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class Dim(
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val stageCount: Int
|
|
||||||
)
|
|
||||||
@@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.domain
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Card
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
foreignKeys = [
|
foreignKeys = [
|
||||||
@@ -13,7 +14,7 @@ import androidx.room.PrimaryKey
|
|||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
),
|
),
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = Dim::class,
|
entity = Card::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["dimId"],
|
childColumns = ["dimId"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ package com.github.nacabaro.vbhelper.domain
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
foreignKeys = [
|
foreignKeys = [
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = Mon::class,
|
entity = Character::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["monId"],
|
childColumns = ["monId"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
),
|
),
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = Mon::class,
|
entity = Character::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["nextMon"],
|
childColumns = ["nextMon"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.domain
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = Dim::class,
|
|
||||||
parentColumns = ["id"],
|
|
||||||
childColumns = ["dimId"],
|
|
||||||
onDelete = ForeignKey.CASCADE
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
data class Mon (
|
|
||||||
@PrimaryKey val id: Int,
|
|
||||||
val dimId: Int,
|
|
||||||
val monIndex: Int,
|
|
||||||
val name: String,
|
|
||||||
val stage: Int, // These should be replaced with enums
|
|
||||||
val attribute: Int, // This one too
|
|
||||||
val baseHp: Int,
|
|
||||||
val baseBp: Int,
|
|
||||||
val baseAp: Int,
|
|
||||||
val evoTime: Int, // In minutes
|
|
||||||
)
|
|
||||||
@@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.domain
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
foreignKeys = [
|
foreignKeys = [
|
||||||
@@ -13,7 +14,7 @@ import androidx.room.PrimaryKey
|
|||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
),
|
),
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = Mon::class,
|
entity = Character::class,
|
||||||
parentColumns = ["id"],
|
parentColumns = ["id"],
|
||||||
childColumns = ["monId"],
|
childColumns = ["monId"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.characters
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = UserCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["characterId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class Adventure(
|
||||||
|
@PrimaryKey val characterId: Long,
|
||||||
|
val originalDuration: Long,
|
||||||
|
val finishesAdventure: Long
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.characters
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class Card(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val id: Long = 0,
|
||||||
|
val dimId: Int,
|
||||||
|
val logo: ByteArray,
|
||||||
|
val logoWidth: Int,
|
||||||
|
val logoHeight: Int,
|
||||||
|
val name: String,
|
||||||
|
val stageCount: Int,
|
||||||
|
val isBEm: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.characters
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = Card::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["dimId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
ForeignKey(
|
||||||
|
entity = Sprite::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["spriteId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Character represents a character on a DIM card. There should only be one of these per dimId
|
||||||
|
* and monIndex.
|
||||||
|
* TODO: Customs will mean this should be unique per cardName and monIndex
|
||||||
|
*/
|
||||||
|
data class Character (
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
val dimId: Long,
|
||||||
|
val spriteId: Long,
|
||||||
|
val monIndex: Int,
|
||||||
|
val name: ByteArray,
|
||||||
|
val stage: Int, // These should be replaced with enums
|
||||||
|
val attribute: NfcCharacter.Attribute, // This one too
|
||||||
|
val baseHp: Int,
|
||||||
|
val baseBp: Int,
|
||||||
|
val baseAp: Int,
|
||||||
|
val nameWidth: Int,
|
||||||
|
val nameHeight: Int
|
||||||
|
)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.characters
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = Character::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["id"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class Dex(
|
||||||
|
@PrimaryKey val id: Long = 0,
|
||||||
|
val discoveredOn: Long
|
||||||
|
)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.characters
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class Sprite(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id : Int = 0,
|
||||||
|
val spriteIdle1: ByteArray,
|
||||||
|
val spriteIdle2: ByteArray,
|
||||||
|
val spriteWalk1: ByteArray,
|
||||||
|
val spriteWalk2: ByteArray,
|
||||||
|
val spriteRun1: ByteArray,
|
||||||
|
val spriteRun2: ByteArray,
|
||||||
|
val spriteTrain1: ByteArray,
|
||||||
|
val spriteTrain2: ByteArray,
|
||||||
|
val spriteHappy: ByteArray,
|
||||||
|
val spriteSleep: ByteArray,
|
||||||
|
val spriteAttack: ByteArray,
|
||||||
|
val spriteDodge: ByteArray,
|
||||||
|
val width: Int,
|
||||||
|
val height: Int
|
||||||
|
)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.device_data
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = UserCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["id"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class BECharacterData (
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long,
|
||||||
|
val trainingHp: Int,
|
||||||
|
val trainingAp: Int,
|
||||||
|
val trainingBp: Int,
|
||||||
|
var remainingTrainingTimeInMinutes: Int,
|
||||||
|
val itemEffectMentalStateValue: Int,
|
||||||
|
val itemEffectMentalStateMinutesRemaining: Int,
|
||||||
|
val itemEffectActivityLevelValue: Int,
|
||||||
|
val itemEffectActivityLevelMinutesRemaining: Int,
|
||||||
|
val itemEffectVitalPointsChangeValue: Int,
|
||||||
|
val itemEffectVitalPointsChangeMinutesRemaining: Int,
|
||||||
|
val abilityRarity: NfcCharacter.AbilityRarity,
|
||||||
|
val abilityType: Int,
|
||||||
|
val abilityBranch: Int,
|
||||||
|
val abilityReset: Int,
|
||||||
|
val rank: Int,
|
||||||
|
var itemType: Int,
|
||||||
|
var itemMultiplier: Int,
|
||||||
|
var itemRemainingTime: Int,
|
||||||
|
val otp0: String,
|
||||||
|
val otp1: String,
|
||||||
|
val minorVersion: Int,
|
||||||
|
val majorVersion: Int,
|
||||||
|
)
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.device_data
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = UserCharacter::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["monId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
),
|
||||||
|
ForeignKey(
|
||||||
|
entity = Character::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["stageId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class TransformationHistory (
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
val monId: Long,
|
||||||
|
val stageId: Long,
|
||||||
|
val transformationDate: Long
|
||||||
|
)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.device_data
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.utils.DeviceType
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = Character::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["charId"],
|
||||||
|
onDelete = ForeignKey.CASCADE
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* UserCharacter represents and instance of a character. The charId should map to a Character
|
||||||
|
*/
|
||||||
|
data class UserCharacter (
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
var charId: Long,
|
||||||
|
var ageInDays: Int,
|
||||||
|
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
|
||||||
|
var mood: Int,
|
||||||
|
var vitalPoints: Int,
|
||||||
|
var transformationCountdown: Int,
|
||||||
|
var injuryStatus: NfcCharacter.InjuryStatus,
|
||||||
|
var trophies: Int,
|
||||||
|
var currentPhaseBattlesWon: Int,
|
||||||
|
var currentPhaseBattlesLost: Int,
|
||||||
|
var totalBattlesWon: Int,
|
||||||
|
var totalBattlesLost: Int,
|
||||||
|
var activityLevel: Int,
|
||||||
|
var heartRateCurrent: Int,
|
||||||
|
var characterType: DeviceType,
|
||||||
|
var isActive: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.device_data
|
||||||
|
|
||||||
|
class VBCharacterData {
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.items
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class Items(
|
||||||
|
@PrimaryKey val id: Long,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val itemIcon: Int,
|
||||||
|
val itemLength: Int,
|
||||||
|
val price: Int,
|
||||||
|
val quantity: Int
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.dtos
|
||||||
|
|
||||||
|
object CardDtos {
|
||||||
|
data class CardProgress (
|
||||||
|
val cardId: Long,
|
||||||
|
val cardName: String,
|
||||||
|
val cardLogo: ByteArray,
|
||||||
|
val logoWidth: Int,
|
||||||
|
val logoHeight: Int,
|
||||||
|
val totalCharacters: Int,
|
||||||
|
val obtainedCharacters: Int,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.dtos
|
||||||
|
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.utils.DeviceType
|
||||||
|
|
||||||
|
|
||||||
|
object CharacterDtos {
|
||||||
|
data class CharacterWithSprites(
|
||||||
|
var id: Long = 0,
|
||||||
|
var charId: Long,
|
||||||
|
var stage: Int,
|
||||||
|
var attribute: NfcCharacter.Attribute,
|
||||||
|
var ageInDays: Int,
|
||||||
|
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
|
||||||
|
var mood: Int,
|
||||||
|
var vitalPoints: Int,
|
||||||
|
var transformationCountdown: Int,
|
||||||
|
var injuryStatus: NfcCharacter.InjuryStatus,
|
||||||
|
var trophies: Int,
|
||||||
|
var currentPhaseBattlesWon: Int,
|
||||||
|
var currentPhaseBattlesLost: Int,
|
||||||
|
var totalBattlesWon: Int,
|
||||||
|
var totalBattlesLost: Int,
|
||||||
|
var activityLevel: Int,
|
||||||
|
var heartRateCurrent: Int,
|
||||||
|
var characterType: DeviceType,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteIdle2: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
val nameSprite: ByteArray,
|
||||||
|
val nameSpriteWidth: Int,
|
||||||
|
val nameSpriteHeight: Int,
|
||||||
|
val isBemCard: Boolean,
|
||||||
|
val isInAdventure: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DiMInfo(
|
||||||
|
val cardId: Int,
|
||||||
|
val charId: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TransformationHistory(
|
||||||
|
val id: Long,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
val monIndex: Int,
|
||||||
|
val transformationDate: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CardProgress(
|
||||||
|
val id: Long,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
val discoveredOn: Long?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AdventureCharacterWithSprites(
|
||||||
|
var id: Long = 0,
|
||||||
|
var charId: Long,
|
||||||
|
var stage: Int,
|
||||||
|
var attribute: NfcCharacter.Attribute,
|
||||||
|
var ageInDays: Int,
|
||||||
|
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
|
||||||
|
var mood: Int,
|
||||||
|
var vitalPoints: Int,
|
||||||
|
var transformationCountdown: Int,
|
||||||
|
var injuryStatus: NfcCharacter.InjuryStatus,
|
||||||
|
var trophies: Int,
|
||||||
|
var currentPhaseBattlesWon: Int,
|
||||||
|
var currentPhaseBattlesLost: Int,
|
||||||
|
var totalBattlesWon: Int,
|
||||||
|
var totalBattlesLost: Int,
|
||||||
|
var activityLevel: Int,
|
||||||
|
var heartRateCurrent: Int,
|
||||||
|
var characterType: DeviceType,
|
||||||
|
val spriteIdle: ByteArray,
|
||||||
|
val spriteWidth: Int,
|
||||||
|
val spriteHeight: Int,
|
||||||
|
val isBemCard: Boolean,
|
||||||
|
val finishesAdventure: Long,
|
||||||
|
val originalTimeInMinutes: Long
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.dtos
|
||||||
|
|
||||||
|
|
||||||
|
object ItemDtos {
|
||||||
|
data class ItemsWithQuantities(
|
||||||
|
val id: Long,
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val itemIcon: Int,
|
||||||
|
val itemLength: Int,
|
||||||
|
val price: Int,
|
||||||
|
val quantity: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PurchasedItem(
|
||||||
|
val itemId: Long,
|
||||||
|
val itemName: String,
|
||||||
|
val itemDescription: String,
|
||||||
|
val itemIcon: Int,
|
||||||
|
val itemLength: Int,
|
||||||
|
val itemAmount: Int
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,15 +9,37 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.github.nacabaro.vbhelper.screens.BattlesScreen
|
import com.github.nacabaro.vbhelper.screens.BattlesScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.DexScreen
|
import com.github.nacabaro.vbhelper.screens.DexScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.HomeScreen
|
import com.github.nacabaro.vbhelper.screens.DiMScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.ScanScreen
|
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen
|
||||||
import com.github.nacabaro.vbhelper.screens.StorageScreen
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewer
|
||||||
|
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ChooseCharacterScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen
|
||||||
|
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
|
||||||
|
|
||||||
|
data class AppNavigationHandlers(
|
||||||
|
val settingsScreenController: SettingsScreenControllerImpl,
|
||||||
|
val scanScreenController: ScanScreenControllerImpl,
|
||||||
|
val itemsScreenController: ItemsScreenControllerImpl,
|
||||||
|
val adventureScreenController: AdventureScreenControllerImpl,
|
||||||
|
val storageScreenController: StorageScreenControllerImpl,
|
||||||
|
val homeScreenController: HomeScreenControllerImpl,
|
||||||
|
val spriteViewerController: SpriteViewerControllerImpl
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavigation(
|
fun AppNavigation(
|
||||||
onClickRead: () -> Unit,
|
applicationNavigationHandlers: AppNavigationHandlers,
|
||||||
onClickScan: () -> Unit,
|
|
||||||
isDoneReadingCharacter: Boolean
|
|
||||||
) {
|
) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
@@ -28,29 +50,89 @@ fun AppNavigation(
|
|||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = BottomNavItem.Home.route,
|
startDestination = NavigationItems.Home.route,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(contentPadding)
|
.padding(contentPadding)
|
||||||
) {
|
) {
|
||||||
composable(BottomNavItem.Battles.route) {
|
composable(NavigationItems.Battles.route) {
|
||||||
BattlesScreen()
|
BattlesScreen()
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Home.route) {
|
composable(NavigationItems.Home.route) {
|
||||||
HomeScreen()
|
HomeScreen(
|
||||||
}
|
|
||||||
composable(BottomNavItem.Storage.route) {
|
|
||||||
StorageScreen()
|
|
||||||
}
|
|
||||||
composable(BottomNavItem.Scan.route) {
|
|
||||||
onClickScan()
|
|
||||||
ScanScreen(
|
|
||||||
navController = navController,
|
navController = navController,
|
||||||
onClickRead = onClickRead,
|
homeScreenController = applicationNavigationHandlers.homeScreenController
|
||||||
isDoneReadingCharacter = isDoneReadingCharacter
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Dex.route) {
|
composable(NavigationItems.Storage.route) {
|
||||||
DexScreen()
|
StorageScreen(
|
||||||
|
navController = navController,
|
||||||
|
adventureScreenController = applicationNavigationHandlers.adventureScreenController,
|
||||||
|
storageScreenController = applicationNavigationHandlers.storageScreenController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Scan.route) {
|
||||||
|
val characterIdString = it.arguments?.getString("characterId")
|
||||||
|
val characterId = characterIdString?.toLongOrNull()
|
||||||
|
|
||||||
|
ScanScreen(
|
||||||
|
navController = navController,
|
||||||
|
scanScreenController = applicationNavigationHandlers.scanScreenController,
|
||||||
|
characterId = characterId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Dex.route) {
|
||||||
|
DexScreen(
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Settings.route) {
|
||||||
|
SettingsScreen(
|
||||||
|
navController = navController,
|
||||||
|
settingsScreenController = applicationNavigationHandlers.settingsScreenController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Viewer.route) {
|
||||||
|
SpriteViewer(
|
||||||
|
navController = navController,
|
||||||
|
spriteViewerController = applicationNavigationHandlers.spriteViewerController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.CardView.route) {
|
||||||
|
val cardId = it.arguments?.getString("cardId")
|
||||||
|
if (cardId != null) {
|
||||||
|
DiMScreen(
|
||||||
|
navController = navController,
|
||||||
|
dimId = cardId.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Items.route) {
|
||||||
|
ItemsScreen(
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.ApplyItem.route) {
|
||||||
|
val itemId = it.arguments?.getString("itemId")
|
||||||
|
if (itemId != null) {
|
||||||
|
ChooseCharacterScreen(
|
||||||
|
itemsScreenController = applicationNavigationHandlers
|
||||||
|
.itemsScreenController,
|
||||||
|
navController = navController,
|
||||||
|
itemId = itemId.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Adventure.route) {
|
||||||
|
AdventureScreen(
|
||||||
|
navController = navController,
|
||||||
|
storageScreenController = applicationNavigationHandlers
|
||||||
|
.adventureScreenController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(NavigationItems.Credits.route) {
|
||||||
|
CreditsScreen(
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.navigation
|
|
||||||
|
|
||||||
import com.github.nacabaro.vbhelper.R
|
|
||||||
|
|
||||||
sealed class BottomNavItem (
|
|
||||||
var route: String,
|
|
||||||
var icon: Int,
|
|
||||||
var label: String
|
|
||||||
) {
|
|
||||||
object Scan : BottomNavItem("Scan", R.drawable.baseline_nfc_24, "Scan")
|
|
||||||
object Battles : BottomNavItem("Battles", R.drawable.baseline_swords_24, "Battles")
|
|
||||||
object Home : BottomNavItem("Home", R.drawable.baseline_cottage_24, "Home")
|
|
||||||
object Dex : BottomNavItem("Dex", R.drawable.baseline_menu_book_24, "Dex")
|
|
||||||
object Storage : BottomNavItem("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
|
|
||||||
}
|
|
||||||
@@ -13,12 +13,13 @@ import androidx.navigation.compose.currentBackStackEntryAsState
|
|||||||
@Composable
|
@Composable
|
||||||
fun BottomNavigationBar(navController: NavController) {
|
fun BottomNavigationBar(navController: NavController) {
|
||||||
val items = listOf(
|
val items = listOf(
|
||||||
BottomNavItem.Scan,
|
NavigationItems.Items,
|
||||||
BottomNavItem.Battles,
|
NavigationItems.Battles,
|
||||||
BottomNavItem.Home,
|
NavigationItems.Home,
|
||||||
BottomNavItem.Dex,
|
NavigationItems.Dex,
|
||||||
BottomNavItem.Storage,
|
NavigationItems.Storage,
|
||||||
)
|
)
|
||||||
|
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
val currentBackStackEntry = navController.currentBackStackEntryAsState()
|
val currentBackStackEntry = navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = currentBackStackEntry.value?.destination?.route
|
val currentRoute = currentBackStackEntry.value?.destination?.route
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.navigation
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
|
||||||
|
sealed class NavigationItems (
|
||||||
|
var route: String,
|
||||||
|
var icon: Int,
|
||||||
|
var label: String
|
||||||
|
) {
|
||||||
|
object Scan : NavigationItems("Scan/{characterId}", R.drawable.baseline_nfc_24, "Scan")
|
||||||
|
object Battles : NavigationItems("Battle", R.drawable.baseline_swords_24, "Battle")
|
||||||
|
object Home : NavigationItems("Home", R.drawable.baseline_cottage_24, "Home")
|
||||||
|
object Dex : NavigationItems("Dex", R.drawable.baseline_menu_book_24, "Dex")
|
||||||
|
object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
|
||||||
|
object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings")
|
||||||
|
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer")
|
||||||
|
object CardView : NavigationItems("Card/{cardId}", R.drawable.baseline_image_24, "Card")
|
||||||
|
object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items")
|
||||||
|
object MyItems : NavigationItems("MyItems", R.drawable.baseline_data_24, "My items")
|
||||||
|
object ItemsStore : NavigationItems("ItemsStore", R.drawable.baseline_data_24, "Items store")
|
||||||
|
object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item")
|
||||||
|
object Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure")
|
||||||
|
object Credits : NavigationItems("Credits", R.drawable.baseline_data_24, "Credits")
|
||||||
|
}
|
||||||
@@ -1,80 +1,84 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens
|
package com.github.nacabaro.vbhelper.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.github.nacabaro.vbhelper.R
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.components.DexDiMEntry
|
||||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Card
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CardDtos
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
import com.github.nacabaro.vbhelper.source.DexRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DexScreen() {
|
fun DexScreen(
|
||||||
|
navController: NavController
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val dexRepository = DexRepository(application.container.db)
|
||||||
|
|
||||||
|
val cardList = remember { mutableStateOf<List<CardDtos.CardProgress>>(emptyList()) }
|
||||||
|
|
||||||
|
LaunchedEffect(dexRepository) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
val newDimList = dexRepository.getAllDims()
|
||||||
|
cardList.value = newDimList // Replace the entire list atomically
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold (
|
Scaffold (
|
||||||
topBar = { TopBanner("Discovered Digimon") }
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "Discovered characters",
|
||||||
|
onGearClick = {
|
||||||
|
navController.navigate(NavigationItems.Viewer.route)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
LazyColumn (
|
LazyColumn (
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = contentPadding.calculateTopPadding())
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
) {
|
) {
|
||||||
items(100) { i ->
|
items(cardList.value) {
|
||||||
DexDiMEntry(
|
DexDiMEntry(
|
||||||
name = "Digimon $i",
|
name = it.cardName,
|
||||||
icon = R.drawable.baseline_egg_24,
|
logo = BitmapData(
|
||||||
onClick = {},
|
bitmap = it.cardLogo,
|
||||||
|
width = it.logoWidth,
|
||||||
|
height = it.logoHeight
|
||||||
|
),
|
||||||
|
onClick = {
|
||||||
|
navController
|
||||||
|
.navigate(
|
||||||
|
NavigationItems
|
||||||
|
.CardView.route
|
||||||
|
.replace("{cardId}", "${it.cardId}")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
obtainedCharacters = it.obtainedCharacters,
|
||||||
|
totalCharacters = it.totalCharacters,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(8.dp)
|
||||||
vertical = 8.dp,
|
|
||||||
horizontal = 16.dp
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DexDiMEntry(
|
|
||||||
name: String,
|
|
||||||
icon: Int,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Card (
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
modifier = modifier,
|
|
||||||
onClick = onClick
|
|
||||||
) {
|
|
||||||
Row (
|
|
||||||
horizontalArrangement = Arrangement.Start,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
Image (
|
|
||||||
painter = painterResource(id = icon),
|
|
||||||
contentDescription = name,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(64.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = name,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.components.CharacterEntry
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.source.DexRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DiMScreen(
|
||||||
|
navController: NavController,
|
||||||
|
dimId: Long
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val dexRepository = DexRepository(application.container.db)
|
||||||
|
|
||||||
|
val characterList = remember { mutableStateOf<List<CharacterDtos.CardProgress>>(emptyList()) }
|
||||||
|
|
||||||
|
LaunchedEffect(dexRepository) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
val newCharacterList = dexRepository.getCharactersByDimId(dimId)
|
||||||
|
characterList.value = newCharacterList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold (
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "Discovered characters",
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { contentPadding ->
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(3),
|
||||||
|
contentPadding = contentPadding
|
||||||
|
) {
|
||||||
|
items(characterList.value) { character ->
|
||||||
|
CharacterEntry(
|
||||||
|
onClick = { },
|
||||||
|
obscure = character.discoveredOn == null,
|
||||||
|
icon = BitmapData(
|
||||||
|
bitmap = character.spriteIdle,
|
||||||
|
width = character.spriteWidth,
|
||||||
|
height = character.spriteHeight,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens
|
|
||||||
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun HomeScreen() {
|
|
||||||
Text("Home Screen")
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
|
||||||
import com.github.nacabaro.vbhelper.navigation.BottomNavItem
|
|
||||||
import com.github.nacabaro.vbhelper.screens.scanScreen.ReadingCharacterScreen
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ScanScreen(
|
|
||||||
navController: NavController,
|
|
||||||
onClickRead: () -> Unit,
|
|
||||||
isDoneReadingCharacter: Boolean
|
|
||||||
) {
|
|
||||||
var readingScreen by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (isDoneReadingCharacter) {
|
|
||||||
readingScreen = false
|
|
||||||
navController.navigate(BottomNavItem.Home.route)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readingScreen) {
|
|
||||||
ReadingCharacterScreen { readingScreen = false }
|
|
||||||
} else {
|
|
||||||
ChooseConnectOption(
|
|
||||||
onClickRead = {
|
|
||||||
readingScreen = true
|
|
||||||
onClickRead()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ChooseConnectOption(
|
|
||||||
onClickRead: () -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
topBar = { TopBanner(text = "Scan a Vital Bracelet") }
|
|
||||||
) { contentPadding ->
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(contentPadding)
|
|
||||||
) {
|
|
||||||
ScanButton(
|
|
||||||
text = "Vital Bracelet to App",
|
|
||||||
onClick = onClickRead
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
ScanButton(
|
|
||||||
text = "App to Vital Bracelet",
|
|
||||||
onClick = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ScanButton(
|
|
||||||
text: String,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
|
||||||
@Composable
|
|
||||||
fun ScanScreenPreview() {
|
|
||||||
ScanScreen(
|
|
||||||
navController = rememberNavController(),
|
|
||||||
onClickRead = { },
|
|
||||||
isDoneReadingCharacter = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package com.github.nacabaro.vbhelper.screens
|
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.gestures.Orientation
|
|
||||||
import androidx.compose.foundation.gestures.scrollable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.github.nacabaro.vbhelper.R
|
|
||||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StorageScreen() {
|
|
||||||
Scaffold (
|
|
||||||
topBar = { TopBanner(text = "My Digimon") }
|
|
||||||
) { contentPadding ->
|
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(3),
|
|
||||||
modifier = Modifier
|
|
||||||
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
|
|
||||||
.padding(top = contentPadding.calculateTopPadding())
|
|
||||||
) {
|
|
||||||
items(100) { i ->
|
|
||||||
StorageEntry(
|
|
||||||
name = "Digimon $i",
|
|
||||||
icon = R.drawable.baseline_question_mark_24
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StorageEntry(
|
|
||||||
name: String,
|
|
||||||
icon: Int,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Card (
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
modifier = modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
Column (
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = icon),
|
|
||||||
contentDescription = name,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(64.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = name,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AdventureEntry(
|
||||||
|
icon: BitmapData,
|
||||||
|
timeLeft: Long,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val bitmap = remember (icon.bitmap) { icon.getBitmap() }
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (icon.width * 4 / density).dp
|
||||||
|
|
||||||
|
Card(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.height(96.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = null,
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = when {
|
||||||
|
timeLeft < 0 -> "Adventure finished"
|
||||||
|
else -> "Time left: ${formatSeconds(timeLeft)}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatSeconds(totalSeconds: Long): String {
|
||||||
|
val hours = totalSeconds / 3600
|
||||||
|
val minutes = (totalSeconds % 3600) / 60
|
||||||
|
val seconds = totalSeconds % 60
|
||||||
|
|
||||||
|
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
|
||||||
|
}
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AdventureScreen(
|
||||||
|
navController: NavController,
|
||||||
|
storageScreenController: AdventureScreenControllerImpl
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val database = application.container.db
|
||||||
|
val storageRepository = StorageRepository(database)
|
||||||
|
val characterList = remember {
|
||||||
|
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
|
||||||
|
}
|
||||||
|
var obtainedItem by remember {
|
||||||
|
mutableStateOf<ItemDtos.PurchasedItem?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentTime by produceState(initialValue = Instant.now().epochSecond) {
|
||||||
|
while (true) {
|
||||||
|
value = Instant.now().epochSecond
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cancelAdventureDialog by remember {
|
||||||
|
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(storageRepository) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
characterList.value = storageRepository
|
||||||
|
.getAdventureCharacters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "Adventure",
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { contentPadding ->
|
||||||
|
if (characterList.value.isEmpty()) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Text(text = "Nothing to see here")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
items(characterList.value) {
|
||||||
|
AdventureEntry(
|
||||||
|
icon = BitmapData(
|
||||||
|
bitmap = it.spriteIdle,
|
||||||
|
width = it.spriteWidth,
|
||||||
|
height = it.spriteHeight
|
||||||
|
),
|
||||||
|
timeLeft = it.finishesAdventure - currentTime,
|
||||||
|
onClick = {
|
||||||
|
if (it.finishesAdventure < currentTime) {
|
||||||
|
storageScreenController
|
||||||
|
.getItemFromAdventure(it.id) { adventureResult ->
|
||||||
|
obtainedItem = adventureResult
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cancelAdventureDialog = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obtainedItem != null) {
|
||||||
|
ObtainedItemDialog(
|
||||||
|
obtainedItem = obtainedItem!!,
|
||||||
|
onClickDismiss = {
|
||||||
|
obtainedItem = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelAdventureDialog != null) {
|
||||||
|
CancelAdventureDialog(
|
||||||
|
characterSprite = BitmapData(
|
||||||
|
bitmap = cancelAdventureDialog!!.spriteIdle,
|
||||||
|
width = cancelAdventureDialog!!.spriteWidth,
|
||||||
|
height = cancelAdventureDialog!!.spriteHeight
|
||||||
|
),
|
||||||
|
onDismissRequest = {
|
||||||
|
cancelAdventureDialog = null
|
||||||
|
},
|
||||||
|
onClickConfirm = {
|
||||||
|
storageScreenController.cancelAdventure(cancelAdventureDialog!!.id) {
|
||||||
|
navController.navigate(NavigationItems.Storage.route)
|
||||||
|
}
|
||||||
|
cancelAdventureDialog = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
|
||||||
|
interface AdventureScreenController {
|
||||||
|
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
|
||||||
|
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit)
|
||||||
|
fun cancelAdventure(characterId: Long, onResult: () -> Unit)
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class AdventureScreenControllerImpl(
|
||||||
|
private val componentActivity: ComponentActivity,
|
||||||
|
) : AdventureScreenController {
|
||||||
|
private val application = componentActivity.applicationContext as VBHelper
|
||||||
|
private val database = application.container.db
|
||||||
|
|
||||||
|
override fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) {
|
||||||
|
val finishesAdventureAt = timeInMinutes * 60
|
||||||
|
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val characterData = database
|
||||||
|
.userCharacterDao()
|
||||||
|
.getCharacter(characterId)
|
||||||
|
|
||||||
|
if (characterData.isActive) {
|
||||||
|
database
|
||||||
|
.userCharacterDao()
|
||||||
|
.clearActiveCharacter()
|
||||||
|
}
|
||||||
|
|
||||||
|
database
|
||||||
|
.adventureDao()
|
||||||
|
.insertNewAdventure(characterId, timeInMinutes, finishesAdventureAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemFromAdventure(
|
||||||
|
characterId: Long,
|
||||||
|
onResult: (ItemDtos.PurchasedItem) -> Unit
|
||||||
|
) {
|
||||||
|
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
database
|
||||||
|
.adventureDao()
|
||||||
|
.deleteAdventure(characterId)
|
||||||
|
|
||||||
|
val generatedItem = generateItem(characterId)
|
||||||
|
|
||||||
|
onResult(generatedItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
|
||||||
|
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
database
|
||||||
|
.adventureDao()
|
||||||
|
.deleteAdventure(characterId)
|
||||||
|
|
||||||
|
componentActivity
|
||||||
|
.runOnUiThread {
|
||||||
|
Toast.makeText(
|
||||||
|
componentActivity,
|
||||||
|
"Adventure canceled",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
onResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun generateItem(characterId: Long): ItemDtos.PurchasedItem {
|
||||||
|
val character = database
|
||||||
|
.userCharacterDao()
|
||||||
|
.getCharacterInfo(characterId)
|
||||||
|
|
||||||
|
val randomItem = database
|
||||||
|
.itemDao()
|
||||||
|
.getAllItems()
|
||||||
|
.random()
|
||||||
|
|
||||||
|
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()
|
||||||
|
|
||||||
|
database
|
||||||
|
.itemDao()
|
||||||
|
.purchaseItem(
|
||||||
|
itemId = randomItem.id,
|
||||||
|
itemAmount = random
|
||||||
|
)
|
||||||
|
|
||||||
|
return ItemDtos.PurchasedItem(
|
||||||
|
itemId = randomItem.id,
|
||||||
|
itemAmount = random,
|
||||||
|
itemName = randomItem.name,
|
||||||
|
itemIcon = randomItem.itemIcon,
|
||||||
|
itemLength = randomItem.itemLength,
|
||||||
|
itemDescription = randomItem.description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CancelAdventureDialog(
|
||||||
|
characterSprite: BitmapData,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onClickConfirm: () -> Unit
|
||||||
|
) {
|
||||||
|
val bitmap = remember (characterSprite) { characterSprite.getBitmap() }
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (characterSprite.width * 4 / density).dp
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest
|
||||||
|
) {
|
||||||
|
Card {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = null,
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Are you sure you want to cancel this character's adventure?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClickConfirm
|
||||||
|
) {
|
||||||
|
Text(text = "Confirm")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.padding(4.dp))
|
||||||
|
Button(
|
||||||
|
onClick = onDismissRequest
|
||||||
|
) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
import com.github.nacabaro.vbhelper.components.CharacterEntry
|
||||||
|
import com.github.nacabaro.vbhelper.components.ItemDisplay
|
||||||
|
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.getIconResource
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BEBEmHomeScreen(
|
||||||
|
activeMon: CharacterDtos.CharacterWithSprites,
|
||||||
|
beData: BECharacterData,
|
||||||
|
transformationHistory: List<CharacterDtos.TransformationHistory>,
|
||||||
|
contentPadding: PaddingValues
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
.verticalScroll(state = rememberScrollState())
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
CharacterEntry(
|
||||||
|
icon = BitmapData(
|
||||||
|
bitmap = activeMon.spriteIdle,
|
||||||
|
width = activeMon.spriteWidth,
|
||||||
|
height = activeMon.spriteHeight
|
||||||
|
),
|
||||||
|
multiplier = 8,
|
||||||
|
shape = androidx.compose.material.MaterialTheme.shapes.small,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
)
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(0.5f)
|
||||||
|
.aspectRatio(0.5f)
|
||||||
|
) {
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_vitals_24,
|
||||||
|
textValue = activeMon.vitalPoints.toString(),
|
||||||
|
definition = "Vitals",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(0.5f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_trophy_24,
|
||||||
|
textValue = activeMon.trophies.toString(),
|
||||||
|
definition = "Trophies",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(0.5f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_mood_24,
|
||||||
|
textValue = activeMon.mood.toString(),
|
||||||
|
definition = "Mood",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
val timeInHours = (beData.remainingTrainingTimeInMinutes / 60)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_timer_24,
|
||||||
|
textValue = "$timeInHours h",
|
||||||
|
definition = "Training limit",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_rank_24,
|
||||||
|
textValue = beData.rank.toString(),
|
||||||
|
definition = "Rank",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
val transformationCountdownInHours = activeMon.transformationCountdown / 60
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_next_24,
|
||||||
|
textValue = when (transformationCountdownInHours) {
|
||||||
|
0 -> "${activeMon.transformationCountdown} m"
|
||||||
|
else -> "$transformationCountdownInHours h"
|
||||||
|
},
|
||||||
|
definition = "Next timer",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_swords_24,
|
||||||
|
textValue = when {
|
||||||
|
activeMon.totalBattlesLost == 0 -> "0.00 %"
|
||||||
|
else -> {
|
||||||
|
val battleWinPercentage = activeMon.totalBattlesWon.toFloat() / (activeMon.totalBattlesWon + activeMon.totalBattlesLost).toFloat()
|
||||||
|
String.format(Locale.getDefault(), "%.2f", battleWinPercentage * 100) + " %" // Specify locale
|
||||||
|
}
|
||||||
|
},
|
||||||
|
definition = "Total battle win %",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_swords_24,
|
||||||
|
textValue = when {
|
||||||
|
activeMon.totalBattlesLost == 0 -> "0.00 %"
|
||||||
|
else -> {
|
||||||
|
val battleWinPercentage = activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
|
||||||
|
String.format(Locale.getDefault(), "%.2f", battleWinPercentage * 100) + " %" // Specify locale
|
||||||
|
}
|
||||||
|
},
|
||||||
|
definition = "Current phase win %",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
if (beData.itemRemainingTime != 0) {
|
||||||
|
ItemDisplay(
|
||||||
|
icon = getIconResource(beData.itemType),
|
||||||
|
textValue = "${beData.itemRemainingTime} m",
|
||||||
|
definition = when (beData.itemType) {
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> "PP Training"
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> "HP Training"
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> "AP Training"
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> "BP Training"
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> "All Training"
|
||||||
|
else -> ""
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
TransformationHistoryCard(
|
||||||
|
transformationHistory = transformationHistory,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_health_24,
|
||||||
|
textValue = "+${beData.trainingHp}",
|
||||||
|
definition = "Training HP",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_agility_24,
|
||||||
|
textValue = "+${beData.trainingBp}",
|
||||||
|
definition = "Training BP",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_attack_24,
|
||||||
|
textValue = "+${beData.trainingAp}",
|
||||||
|
definition = "Training AP",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import java.util.Locale
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
import com.github.nacabaro.vbhelper.components.CharacterEntry
|
||||||
|
import com.github.nacabaro.vbhelper.components.ItemDisplay
|
||||||
|
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.getIconResource
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import kotlin.text.format
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BEDiMHomeScreen(
|
||||||
|
activeMon: CharacterDtos.CharacterWithSprites,
|
||||||
|
beData: BECharacterData,
|
||||||
|
transformationHistory: List<CharacterDtos.TransformationHistory>,
|
||||||
|
contentPadding: PaddingValues
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
.verticalScroll(state = rememberScrollState())
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
CharacterEntry(
|
||||||
|
icon = BitmapData(
|
||||||
|
bitmap = activeMon.spriteIdle,
|
||||||
|
width = activeMon.spriteWidth,
|
||||||
|
height = activeMon.spriteHeight
|
||||||
|
),
|
||||||
|
multiplier = 8,
|
||||||
|
shape = androidx.compose.material.MaterialTheme.shapes.small,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(0.5f)
|
||||||
|
.aspectRatio(0.5f)
|
||||||
|
) {
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_vitals_24,
|
||||||
|
textValue = activeMon.vitalPoints.toString(),
|
||||||
|
definition = "Vitals",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(0.5f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_trophy_24,
|
||||||
|
textValue = activeMon.trophies.toString(),
|
||||||
|
definition = "Trophies",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(0.5f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_mood_24,
|
||||||
|
textValue = activeMon.mood.toString(),
|
||||||
|
definition = "Mood",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
val timeInHours = (beData.remainingTrainingTimeInMinutes / 60)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_timer_24,
|
||||||
|
textValue = "$timeInHours h",
|
||||||
|
definition = "Training limit",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
// Maybe get rid of this?
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_rank_24,
|
||||||
|
textValue = beData.rank.toString(),
|
||||||
|
definition = "Rank",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
val transformationCountdownInHours = activeMon.transformationCountdown / 60
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_next_24,
|
||||||
|
textValue = when (transformationCountdownInHours) {
|
||||||
|
0 -> "${activeMon.transformationCountdown} m"
|
||||||
|
else -> "$transformationCountdownInHours h"
|
||||||
|
},
|
||||||
|
definition = "Next timer",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_swords_24,
|
||||||
|
textValue = when {
|
||||||
|
activeMon.totalBattlesLost == 0 -> "0.00 %"
|
||||||
|
else -> {
|
||||||
|
val battleWinPercentage =
|
||||||
|
activeMon.totalBattlesWon.toFloat() / (activeMon.totalBattlesWon + activeMon.totalBattlesLost).toFloat()
|
||||||
|
String.format(
|
||||||
|
Locale.getDefault(),
|
||||||
|
"%.2f",
|
||||||
|
battleWinPercentage * 100
|
||||||
|
) + " %" // Specify locale
|
||||||
|
}
|
||||||
|
},
|
||||||
|
definition = "Total battle win %",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
ItemDisplay(
|
||||||
|
icon = R.drawable.baseline_swords_24,
|
||||||
|
textValue = when {
|
||||||
|
activeMon.totalBattlesLost == 0 -> "0.00 %"
|
||||||
|
else -> {
|
||||||
|
val battleWinPercentage =
|
||||||
|
activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
|
||||||
|
String.format(
|
||||||
|
Locale.getDefault(),
|
||||||
|
"%.2f",
|
||||||
|
battleWinPercentage * 100
|
||||||
|
) + " %" // Specify locale
|
||||||
|
}
|
||||||
|
},
|
||||||
|
definition = "Current phase win %",
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
if (beData.itemRemainingTime != 0) {
|
||||||
|
ItemDisplay(
|
||||||
|
icon = getIconResource(beData.itemType),
|
||||||
|
textValue = "${beData.itemRemainingTime} m",
|
||||||
|
definition = when (beData.itemType) {
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> "PP Training"
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> "HP Training"
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> "AP Training"
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> "BP Training"
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> "All Training"
|
||||||
|
else -> ""
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
TransformationHistoryCard(
|
||||||
|
transformationHistory = transformationHistory,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BetaWarning(
|
||||||
|
onDismissRequest: () -> Unit
|
||||||
|
) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest
|
||||||
|
) {
|
||||||
|
Card {
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "This application is currently in alpha and it is not complete. Do not use to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!"
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.padding(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Also, this application does not work yet with the original VB."
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.padding(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Thank you for your understanding and patience. Sincerely, the dev team."
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.padding(8.dp))
|
||||||
|
Button(
|
||||||
|
onClick = onDismissRequest
|
||||||
|
) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.utils.DeviceType
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HomeScreen(
|
||||||
|
navController: NavController,
|
||||||
|
homeScreenController: HomeScreenControllerImpl
|
||||||
|
) {
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val storageRepository = StorageRepository(application.container.db)
|
||||||
|
val activeMon = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
|
||||||
|
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
|
||||||
|
val beData = remember { mutableStateOf<BECharacterData?>(null) }
|
||||||
|
val vbData = remember { mutableStateOf<VBCharacterData?>(null) }
|
||||||
|
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var betaWarning by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
|
LaunchedEffect(storageRepository, activeMon) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
activeMon.value = storageRepository.getActiveCharacter()
|
||||||
|
if (activeMon.value != null) {
|
||||||
|
beData.value = storageRepository.getCharacterBeData(activeMon.value!!.id)
|
||||||
|
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(true) {
|
||||||
|
homeScreenController
|
||||||
|
.didAdventureMissionsFinish {
|
||||||
|
adventureMissionsFinished = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold (
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "VB Helper",
|
||||||
|
onScanClick = {
|
||||||
|
navController.navigate(NavigationItems.Scan.route)
|
||||||
|
},
|
||||||
|
onGearClick = {
|
||||||
|
navController.navigate(NavigationItems.Settings.route)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { contentPadding ->
|
||||||
|
if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) {
|
||||||
|
Column (
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
Text(text = "Nothing to see here")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (activeMon.value!!.isBemCard) {
|
||||||
|
BEBEmHomeScreen(
|
||||||
|
activeMon = activeMon.value!!,
|
||||||
|
beData = beData.value!!,
|
||||||
|
transformationHistory = transformationHistory.value!!,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adventureMissionsFinished) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { adventureMissionsFinished = false },
|
||||||
|
) {
|
||||||
|
Card {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "One of your characters has finished their adventure mission!",
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
adventureMissionsFinished = false
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (betaWarning) {
|
||||||
|
BetaWarning {
|
||||||
|
betaWarning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||||
|
|
||||||
|
interface HomeScreenController {
|
||||||
|
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class HomeScreenControllerImpl(
|
||||||
|
private val componentActivity: ComponentActivity,
|
||||||
|
): HomeScreenController {
|
||||||
|
private val application = componentActivity.applicationContext as VBHelper
|
||||||
|
private val database = application.container.db
|
||||||
|
|
||||||
|
override fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit) {
|
||||||
|
componentActivity.lifecycleScope.launch {
|
||||||
|
val currentTime = Instant.now().epochSecond
|
||||||
|
val adventureCharacters = database
|
||||||
|
.adventureDao()
|
||||||
|
.getAdventureCharacters()
|
||||||
|
|
||||||
|
val finishedAdventureCharacters = adventureCharacters.filter { character ->
|
||||||
|
character.finishesAdventure <= currentTime
|
||||||
|
}
|
||||||
|
|
||||||
|
onCompletion(finishedAdventureCharacters.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VBDiMHomeScreen(
|
||||||
|
activeMon: CharacterDtos.CharacterWithSprites,
|
||||||
|
vbData: VBCharacterData,
|
||||||
|
transformationHistory: List<CharacterDtos.TransformationHistory>,
|
||||||
|
contentPadding: PaddingValues
|
||||||
|
) {
|
||||||
|
TODO("Not implemented yet")
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.components.CharacterEntry
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChooseCharacterScreen(
|
||||||
|
navController: NavController,
|
||||||
|
itemsScreenController: ItemsScreenControllerImpl,
|
||||||
|
itemId: Long
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val storageRepository = StorageRepository(application.container.db)
|
||||||
|
val characterList = remember {
|
||||||
|
mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(storageRepository) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
characterList.value = storageRepository.getAllCharacters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect (selectedCharacter) {
|
||||||
|
if (selectedCharacter != null) {
|
||||||
|
itemsScreenController.applyItem(itemId, selectedCharacter!!) {
|
||||||
|
Toast.makeText(
|
||||||
|
application.applicationContext,
|
||||||
|
"Item applied!",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "Choose character",
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { contentPadding ->
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(3),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
items(characterList.value) {
|
||||||
|
CharacterEntry(
|
||||||
|
icon = BitmapData(
|
||||||
|
bitmap = it.spriteIdle,
|
||||||
|
width = it.spriteWidth,
|
||||||
|
height = it.spriteHeight
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
selectedCharacter = it.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ItemElement(
|
||||||
|
itemIcon: Int,
|
||||||
|
lengthIcon: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: (() -> Unit) = { }
|
||||||
|
) {
|
||||||
|
Card (
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = itemIcon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(96.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.padding(16.dp)
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = lengthIcon),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.surfaceTint,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp) // Set the size of the overlay image
|
||||||
|
.align(Alignment.TopStart) // Align to the top end (top-right corner)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ItemDialog(
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
itemIcon: Int,
|
||||||
|
lengthIcon: Int,
|
||||||
|
amount: Int,
|
||||||
|
onClickUse: () -> Unit,
|
||||||
|
onClickCancel: () -> Unit
|
||||||
|
) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onClickCancel,
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = true,
|
||||||
|
dismissOnClickOutside = true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Card (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Box(modifier = Modifier) {
|
||||||
|
// Background image (full size)
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = itemIcon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(96.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = lengthIcon),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.outline,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp) // Set the size of the overlay image
|
||||||
|
.align(Alignment.BottomEnd) // Align to the top end (top-right corner)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||||
|
text = name,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||||
|
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
|
||||||
|
text = description,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
|
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||||
|
text = "You have $amount of this item",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
Row (
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClickUse
|
||||||
|
) {
|
||||||
|
Text("Use item")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.size(8.dp))
|
||||||
|
Button(
|
||||||
|
onClick = onClickCancel
|
||||||
|
) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIconResource(index: Int): Int {
|
||||||
|
return when (index) {
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.PPTraining.id -> R.drawable.baseline_agility_24
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.APTraining.id -> R.drawable.baseline_attack_24
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.HPTraining.id -> R.drawable.baseline_shield_24
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.BPTraining.id -> R.drawable.baseline_trophy_24
|
||||||
|
ItemsScreenControllerImpl.ItemTypes.AllTraining.id -> R.drawable.baseline_arrow_up_24
|
||||||
|
6 -> R.drawable.baseline_timer_24
|
||||||
|
7 -> R.drawable.baseline_rank_24
|
||||||
|
8 -> R.drawable.baseline_vitals_24
|
||||||
|
else -> R.drawable.baseline_question_mark_24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLengthResource(index: Int): Int {
|
||||||
|
return when (index) {
|
||||||
|
15 -> R.drawable.baseline_15_min_timer
|
||||||
|
30 -> R.drawable.baseline_30_min_timer
|
||||||
|
60 -> R.drawable.baseline_60_min_timer
|
||||||
|
-60 -> R.drawable.baseline_60_min_timer
|
||||||
|
300 -> R.drawable.baseline_5_hour_timer
|
||||||
|
600 -> R.drawable.baseline_10_hour_timer
|
||||||
|
-720 -> R.drawable.baseline_12_hour_timer
|
||||||
|
-1440 -> R.drawable.baseline_24_hour_timer
|
||||||
|
6000 -> R.drawable.baseline_reset_24
|
||||||
|
1000 -> R.drawable.baseline_single_arrow_up
|
||||||
|
2500 -> R.drawable.baseline_double_arrow_up
|
||||||
|
5000 -> R.drawable.baseline_triple_arrow_up
|
||||||
|
9999 -> R.drawable.baseline_health_24
|
||||||
|
-500 -> R.drawable.baseline_single_arrow_down
|
||||||
|
-1000 -> R.drawable.baseline_double_arrow_down
|
||||||
|
-2500 -> R.drawable.baseline_triple_arrow_down
|
||||||
|
-9999 -> R.drawable.baseline_reset_24
|
||||||
|
else -> R.drawable.baseline_question_mark_24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
fun PreviewItemDialog() {
|
||||||
|
VBHelperTheme {
|
||||||
|
ItemDialog(
|
||||||
|
name = "AP Training x3 (60 min)",
|
||||||
|
description = "Boosts AP during training (for 60 minutes)",
|
||||||
|
itemIcon = R.drawable.baseline_attack_24,
|
||||||
|
lengthIcon = R.drawable.baseline_60_min_timer,
|
||||||
|
onClickUse = { },
|
||||||
|
onClickCancel = { },
|
||||||
|
amount = 19
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ItemsScreen(
|
||||||
|
navController: NavController,
|
||||||
|
) {
|
||||||
|
var selectedTabItem by remember { mutableStateOf(0) }
|
||||||
|
val items = listOf(
|
||||||
|
NavigationItems.MyItems,
|
||||||
|
NavigationItems.ItemsStore
|
||||||
|
)
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
Column {
|
||||||
|
TopBanner("Items")
|
||||||
|
TabRow(
|
||||||
|
selectedTabIndex = selectedTabItem,
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
items.forEachIndexed { index, item ->
|
||||||
|
Tab(
|
||||||
|
text = { Text(item.label) },
|
||||||
|
selected = selectedTabItem == index,
|
||||||
|
onClick = { selectedTabItem = index }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { contentPadding ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
when (selectedTabItem) {
|
||||||
|
0 -> MyItems(navController)
|
||||||
|
1 -> ItemsStore(navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||||
|
|
||||||
|
|
||||||
|
interface ItemsScreenController {
|
||||||
|
fun applyItem(itemId: Long, characterId: Long, onCompletion: () -> Unit)
|
||||||
|
}
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
import com.github.nacabaro.vbhelper.utils.DeviceType
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class ItemsScreenControllerImpl (
|
||||||
|
private val context: ComponentActivity,
|
||||||
|
): ItemsScreenController {
|
||||||
|
private var database: AppDatabase
|
||||||
|
|
||||||
|
enum class ItemTypes(val id: Int) {
|
||||||
|
PPTraining(1),
|
||||||
|
HPTraining(2),
|
||||||
|
APTraining(3),
|
||||||
|
BPTraining(4),
|
||||||
|
AllTraining(5),
|
||||||
|
EvoTimer(6),
|
||||||
|
LimitTimer(7),
|
||||||
|
Vitals(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val application = context.applicationContext as VBHelper
|
||||||
|
database = application.container.db
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyItem(itemId: Long, characterId: Long, onCompletion: () -> Unit) {
|
||||||
|
context.lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val item = getItem(itemId)
|
||||||
|
val characterData = database.userCharacterDao().getCharacter(characterId)
|
||||||
|
val beCharacterData: BECharacterData
|
||||||
|
//var vbCharacterData: VBCharacterData
|
||||||
|
|
||||||
|
if (characterData.characterType == DeviceType.BEDevice) {
|
||||||
|
beCharacterData = database.userCharacterDao().getBeData(characterId)
|
||||||
|
} else {
|
||||||
|
TODO("Not implemented")
|
||||||
|
//vbCharacterData = database.userCharacterDao().getVbData(characterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.itemIcon in 1 .. 5 && characterData.characterType == DeviceType.BEDevice) {
|
||||||
|
beCharacterData.itemType = item.itemIcon
|
||||||
|
beCharacterData.itemMultiplier = 3
|
||||||
|
beCharacterData.itemRemainingTime = item.itemLength
|
||||||
|
|
||||||
|
database
|
||||||
|
.userCharacterDao()
|
||||||
|
.updateBECharacterData(beCharacterData)
|
||||||
|
|
||||||
|
} else if (item.itemIcon == ItemTypes.EvoTimer.id) {
|
||||||
|
characterData.transformationCountdown += item.itemLength
|
||||||
|
if (characterData.transformationCountdown < 0) {
|
||||||
|
characterData.transformationCountdown = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// VB does not like it when the transformationCountdown is 0
|
||||||
|
if (characterData.characterType == DeviceType.VBDevice &&
|
||||||
|
characterData.transformationCountdown <= 0 ) {
|
||||||
|
characterData.transformationCountdown = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
database
|
||||||
|
.userCharacterDao()
|
||||||
|
.updateCharacter(characterData)
|
||||||
|
|
||||||
|
} else if (item.itemIcon == ItemTypes.LimitTimer.id) {
|
||||||
|
beCharacterData.remainingTrainingTimeInMinutes += item.itemLength
|
||||||
|
if (beCharacterData.remainingTrainingTimeInMinutes > 6000) {
|
||||||
|
beCharacterData.remainingTrainingTimeInMinutes = 6000
|
||||||
|
}
|
||||||
|
|
||||||
|
database
|
||||||
|
.userCharacterDao()
|
||||||
|
.updateBECharacterData(beCharacterData)
|
||||||
|
|
||||||
|
} else if (item.itemIcon == ItemTypes.Vitals.id) {
|
||||||
|
characterData.vitalPoints += item.itemLength
|
||||||
|
if (characterData.vitalPoints < 0) {
|
||||||
|
characterData.vitalPoints = 0
|
||||||
|
} else if (characterData.vitalPoints > 9999) {
|
||||||
|
characterData.vitalPoints = 9999
|
||||||
|
}
|
||||||
|
|
||||||
|
database
|
||||||
|
.userCharacterDao()
|
||||||
|
.updateCharacter(characterData)
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeItem(item.id)
|
||||||
|
|
||||||
|
context.runOnUiThread {
|
||||||
|
onCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities {
|
||||||
|
return database
|
||||||
|
.itemDao()
|
||||||
|
.getItem(itemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun consumeItem(itemId: Long) {
|
||||||
|
database
|
||||||
|
.itemDao()
|
||||||
|
.useItem(itemId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
import com.github.nacabaro.vbhelper.source.ItemsRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ItemsStore(
|
||||||
|
navController: NavController
|
||||||
|
) {
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val itemsRepository = ItemsRepository(application.container.db)
|
||||||
|
val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) }
|
||||||
|
var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(itemsRepository) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
myItems.value = itemsRepository.getAllItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myItems.value.isEmpty()) {
|
||||||
|
Text("No items")
|
||||||
|
} else {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(3),
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
items(myItems.value) { index ->
|
||||||
|
ItemElement(
|
||||||
|
itemIcon = getIconResource(index.itemIcon),
|
||||||
|
lengthIcon = getLengthResource(index.itemLength),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp),
|
||||||
|
onClick = {
|
||||||
|
selectedElementIndex = myItems.value.indexOf(index)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedElementIndex != null) {
|
||||||
|
ItemDialog(
|
||||||
|
name = myItems.value[selectedElementIndex!!].name,
|
||||||
|
description = myItems.value[selectedElementIndex!!].description,
|
||||||
|
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
|
||||||
|
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
|
||||||
|
amount = myItems.value[selectedElementIndex!!].quantity,
|
||||||
|
onClickUse = { },
|
||||||
|
onClickCancel = { selectedElementIndex = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
import com.github.nacabaro.vbhelper.source.ItemsRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MyItems(
|
||||||
|
navController: NavController,
|
||||||
|
) {
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val itemsRepository = ItemsRepository(application.container.db)
|
||||||
|
val myItems = remember { mutableStateOf(emptyList<ItemDtos.ItemsWithQuantities>()) }
|
||||||
|
var selectedElementIndex by remember { mutableStateOf<Int?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(itemsRepository) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
myItems.value = itemsRepository.getUserItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myItems.value.isEmpty()) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Text("No items")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(3),
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
items(myItems.value) { index ->
|
||||||
|
ItemElement(
|
||||||
|
itemIcon = getIconResource(index.itemIcon),
|
||||||
|
lengthIcon = getLengthResource(index.itemLength),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp),
|
||||||
|
onClick = {
|
||||||
|
selectedElementIndex = myItems.value.indexOf(index)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedElementIndex != null) {
|
||||||
|
ItemDialog(
|
||||||
|
name = myItems.value[selectedElementIndex!!].name,
|
||||||
|
description = myItems.value[selectedElementIndex!!].description,
|
||||||
|
itemIcon = getIconResource(myItems.value[selectedElementIndex!!].itemIcon),
|
||||||
|
lengthIcon = getLengthResource(myItems.value[selectedElementIndex!!].itemLength),
|
||||||
|
amount = myItems.value[selectedElementIndex!!].quantity,
|
||||||
|
onClickUse = {
|
||||||
|
navController
|
||||||
|
.navigate(
|
||||||
|
NavigationItems
|
||||||
|
.ApplyItem.route
|
||||||
|
.replace(
|
||||||
|
"{itemId}",
|
||||||
|
myItems.value[selectedElementIndex!!].id.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
selectedElementIndex = null
|
||||||
|
},
|
||||||
|
onClickCancel = { selectedElementIndex = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ObtainedItemDialog(
|
||||||
|
obtainedItem: ItemDtos.PurchasedItem,
|
||||||
|
onClickDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onClickDismiss
|
||||||
|
) {
|
||||||
|
Card {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Box(modifier = Modifier) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = getIconResource(obtainedItem.itemIcon)),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(96.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = getLengthResource(obtainedItem.itemLength)),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.outline,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||||
|
text = obtainedItem.itemName,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||||
|
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
|
||||||
|
text = obtainedItem.itemDescription,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(4.dp)
|
||||||
|
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||||
|
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||||
|
text = "You have obtained ${obtainedItem.itemAmount} of this item",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(5.dp)
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
onClick = onClickDismiss,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -15,11 +15,12 @@ import com.github.nacabaro.vbhelper.components.TopBanner
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReadingCharacterScreen(
|
fun ReadingCharacterScreen(
|
||||||
|
topBannerText: String,
|
||||||
onClickCancel: () -> Unit,
|
onClickCancel: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold (
|
Scaffold (
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBanner("Reading Character")
|
TopBanner(topBannerText)
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Column (
|
Column (
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.scanScreen
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
import com.github.nacabaro.vbhelper.source.isMissingSecrets
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import com.github.nacabaro.vbhelper.utils.characterToNfc
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScanScreen(
|
||||||
|
navController: NavController,
|
||||||
|
characterId: Long?,
|
||||||
|
scanScreenController: ScanScreenController,
|
||||||
|
) {
|
||||||
|
val secrets by scanScreenController.secretsFlow.collectAsState(null)
|
||||||
|
var readingScreen by remember { mutableStateOf(false) }
|
||||||
|
var writingScreen by remember { mutableStateOf(false) }
|
||||||
|
var isDoneReadingCharacter by remember { mutableStateOf(false) }
|
||||||
|
var isDoneSendingCard by remember { mutableStateOf(false) }
|
||||||
|
var isDoneWritingCharacter by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val storageRepository = StorageRepository(application.container.db)
|
||||||
|
var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
LaunchedEffect(storageRepository) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if(characterId != null && nfcCharacter == null) {
|
||||||
|
nfcCharacter = characterToNfc(context, characterId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(readingScreen || writingScreen, isDoneSendingCard) {
|
||||||
|
if(readingScreen) {
|
||||||
|
scanScreenController.registerActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER, object: ActivityLifecycleListener {
|
||||||
|
override fun onPause() {
|
||||||
|
scanScreenController.cancelRead()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
scanScreenController.onClickRead(secrets!!) {
|
||||||
|
isDoneReadingCharacter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
scanScreenController.onClickRead(secrets!!) {
|
||||||
|
isDoneReadingCharacter = true
|
||||||
|
}
|
||||||
|
} else if (writingScreen) {
|
||||||
|
scanScreenController.registerActivityLifecycleListener(
|
||||||
|
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
|
||||||
|
object : ActivityLifecycleListener {
|
||||||
|
override fun onPause() {
|
||||||
|
scanScreenController.cancelRead()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
if (!isDoneSendingCard) {
|
||||||
|
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
|
||||||
|
isDoneSendingCard = true
|
||||||
|
}
|
||||||
|
} else if (!isDoneWritingCharacter) {
|
||||||
|
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
|
||||||
|
isDoneWritingCharacter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!isDoneSendingCard) {
|
||||||
|
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
|
||||||
|
isDoneSendingCard = true
|
||||||
|
}
|
||||||
|
} else if (!isDoneWritingCharacter) {
|
||||||
|
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
|
||||||
|
isDoneWritingCharacter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDispose {
|
||||||
|
if(readingScreen || writingScreen) {
|
||||||
|
scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
|
||||||
|
scanScreenController.cancelRead()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDoneReadingCharacter) {
|
||||||
|
readingScreen = false
|
||||||
|
navController.navigate(NavigationItems.Home.route)
|
||||||
|
} else if (isDoneSendingCard && isDoneWritingCharacter) {
|
||||||
|
writingScreen = false
|
||||||
|
navController.navigate(NavigationItems.Home.route)
|
||||||
|
LaunchedEffect(storageRepository) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
storageRepository
|
||||||
|
.deleteCharacter(characterId!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readingScreen) {
|
||||||
|
ReadingCharacterScreen("Reading character") {
|
||||||
|
readingScreen = false
|
||||||
|
scanScreenController.cancelRead()
|
||||||
|
}
|
||||||
|
} else if (writingScreen) {
|
||||||
|
if (!isDoneSendingCard) {
|
||||||
|
ReadingCharacterScreen("Sending card") {
|
||||||
|
writingScreen = false
|
||||||
|
scanScreenController.cancelRead()
|
||||||
|
}
|
||||||
|
} else if (!isDoneWritingCharacter) {
|
||||||
|
ReadingCharacterScreen("Writing character") {
|
||||||
|
isDoneSendingCard = false
|
||||||
|
writingScreen = false
|
||||||
|
scanScreenController.cancelRead()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ChooseConnectOption(
|
||||||
|
onClickRead = when {
|
||||||
|
characterId != null -> null
|
||||||
|
else -> {
|
||||||
|
{
|
||||||
|
if(secrets == null) {
|
||||||
|
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
|
||||||
|
} else if(secrets?.isMissingSecrets() == true) {
|
||||||
|
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
readingScreen = true // kicks off nfc adapter in DisposableEffect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClickWrite = when {
|
||||||
|
nfcCharacter == null -> null
|
||||||
|
else -> {
|
||||||
|
{
|
||||||
|
if(secrets == null) {
|
||||||
|
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
|
||||||
|
} else if(secrets?.isMissingSecrets() == true) {
|
||||||
|
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
writingScreen = true // kicks off nfc adapter in DisposableEffect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChooseConnectOption(
|
||||||
|
onClickRead: (() -> Unit)? = null,
|
||||||
|
onClickWrite: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { TopBanner(text = "Scan a Vital Bracelet") }
|
||||||
|
) { contentPadding ->
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(contentPadding)
|
||||||
|
) {
|
||||||
|
ScanButton(
|
||||||
|
text = "Vital Bracelet to App",
|
||||||
|
disabled = onClickRead == null,
|
||||||
|
onClick = onClickRead?: { },
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
ScanButton(
|
||||||
|
text = "App to Vital Bracelet",
|
||||||
|
disabled = onClickWrite == null,
|
||||||
|
onClick = onClickWrite?: { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScanButton(
|
||||||
|
text: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
disabled: Boolean = false,
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = !disabled,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun ScanScreenPreview() {
|
||||||
|
ScanScreen(
|
||||||
|
navController = rememberNavController(),
|
||||||
|
scanScreenController = object: ScanScreenController {
|
||||||
|
override val secretsFlow = MutableStateFlow<Secrets>(Secrets.getDefaultInstance())
|
||||||
|
override fun unregisterActivityLifecycleListener(key: String) { }
|
||||||
|
override fun registerActivityLifecycleListener(
|
||||||
|
key: String,
|
||||||
|
activityLifecycleListener: ActivityLifecycleListener
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {}
|
||||||
|
override fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
|
||||||
|
override fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
|
||||||
|
override fun cancelRead() {}
|
||||||
|
},
|
||||||
|
characterId = null
|
||||||
|
)
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.scanScreen
|
||||||
|
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface ScanScreenController {
|
||||||
|
val secretsFlow: Flow<Secrets>
|
||||||
|
fun onClickRead(secrets: Secrets, onComplete: ()->Unit)
|
||||||
|
fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit)
|
||||||
|
fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit)
|
||||||
|
|
||||||
|
fun cancelRead()
|
||||||
|
|
||||||
|
fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener)
|
||||||
|
fun unregisterActivityLifecycleListener(key: String)
|
||||||
|
}
|
||||||
+251
@@ -0,0 +1,251 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.scanScreen
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.nfc.NfcAdapter
|
||||||
|
import android.nfc.Tag
|
||||||
|
import android.nfc.tech.NfcA
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.github.cfogrady.vbnfc.TagCommunicator
|
||||||
|
import com.github.cfogrady.vbnfc.be.BENfcCharacter
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.source.getCryptographicTransformerMap
|
||||||
|
import com.github.nacabaro.vbhelper.source.isMissingSecrets
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import com.github.nacabaro.vbhelper.utils.DeviceType
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.GregorianCalendar
|
||||||
|
|
||||||
|
class ScanScreenControllerImpl(
|
||||||
|
override val secretsFlow: Flow<Secrets>,
|
||||||
|
private val componentActivity: ComponentActivity,
|
||||||
|
private val registerActivityLifecycleListener: (String, ActivityLifecycleListener)->Unit,
|
||||||
|
private val unregisterActivityLifecycleListener: (String)->Unit,
|
||||||
|
): ScanScreenController {
|
||||||
|
|
||||||
|
private val nfcAdapter: NfcAdapter
|
||||||
|
|
||||||
|
init {
|
||||||
|
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity)
|
||||||
|
if (maybeNfcAdapter == null) {
|
||||||
|
Toast.makeText(componentActivity, "No NFC on device!", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
nfcAdapter = maybeNfcAdapter
|
||||||
|
checkSecrets()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {
|
||||||
|
handleTag(secrets) { tagCommunicator ->
|
||||||
|
val character = tagCommunicator.receiveCharacter()
|
||||||
|
val resultMessage = addCharacterScannedIntoDatabase(character)
|
||||||
|
onComplete.invoke()
|
||||||
|
resultMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelRead() {
|
||||||
|
if(nfcAdapter.isEnabled) {
|
||||||
|
nfcAdapter.disableReaderMode(componentActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerActivityLifecycleListener(
|
||||||
|
key: String,
|
||||||
|
activityLifecycleListener: ActivityLifecycleListener
|
||||||
|
) {
|
||||||
|
registerActivityLifecycleListener.invoke(key, activityLifecycleListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unregisterActivityLifecycleListener(key: String) {
|
||||||
|
unregisterActivityLifecycleListener.invoke(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
||||||
|
private fun handleTag(secrets: Secrets, handlerFunc: (TagCommunicator)->String) {
|
||||||
|
if (!nfcAdapter.isEnabled) {
|
||||||
|
showWirelessSettings()
|
||||||
|
} else {
|
||||||
|
val options = Bundle()
|
||||||
|
// Work around for some broken Nfc firmware implementations that poll the card too fast
|
||||||
|
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
|
||||||
|
nfcAdapter.enableReaderMode(componentActivity, buildOnReadTag(secrets, handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
||||||
|
private fun buildOnReadTag(secrets: Secrets, handlerFunc: (TagCommunicator)->String): (Tag)->Unit {
|
||||||
|
return { tag->
|
||||||
|
val nfcData = NfcA.get(tag)
|
||||||
|
if (nfcData == null) {
|
||||||
|
componentActivity.runOnUiThread {
|
||||||
|
Toast.makeText(componentActivity, "Tag detected is not VB", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nfcData.connect()
|
||||||
|
nfcData.use {
|
||||||
|
val tagCommunicator = TagCommunicator.getInstance(nfcData, secrets.getCryptographicTransformerMap())
|
||||||
|
val successText = handlerFunc(tagCommunicator)
|
||||||
|
componentActivity.runOnUiThread {
|
||||||
|
Toast.makeText(componentActivity, successText, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkSecrets() {
|
||||||
|
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) {
|
||||||
|
componentActivity.runOnUiThread {
|
||||||
|
Toast.makeText(componentActivity, "Missing Secrets. Go to settings and import Vital Arena APK", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickWrite(
|
||||||
|
secrets: Secrets,
|
||||||
|
nfcCharacter: NfcCharacter,
|
||||||
|
onComplete: () -> Unit
|
||||||
|
) {
|
||||||
|
handleTag(secrets) { tagCommunicator ->
|
||||||
|
try {
|
||||||
|
tagCommunicator.sendCharacter(nfcCharacter)
|
||||||
|
onComplete.invoke()
|
||||||
|
"Sent character successfully!"
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e("TAG", e.stackTraceToString())
|
||||||
|
"Whoops"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickCheckCard(
|
||||||
|
secrets: Secrets,
|
||||||
|
nfcCharacter: NfcCharacter,
|
||||||
|
onComplete: () -> Unit
|
||||||
|
) {
|
||||||
|
handleTag(secrets) { tagCommunicator ->
|
||||||
|
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
|
||||||
|
onComplete.invoke()
|
||||||
|
"Sent DIM successfully!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
||||||
|
private fun showWirelessSettings() {
|
||||||
|
Toast.makeText(componentActivity, "NFC must be enabled", Toast.LENGTH_SHORT).show()
|
||||||
|
componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addCharacterScannedIntoDatabase(nfcCharacter: NfcCharacter): String {
|
||||||
|
val application = componentActivity.applicationContext as VBHelper
|
||||||
|
val storageRepository = application.container.db
|
||||||
|
|
||||||
|
val dimData = storageRepository
|
||||||
|
.dimDao()
|
||||||
|
.getDimById(nfcCharacter.dimId.toInt())
|
||||||
|
|
||||||
|
if (dimData == null) return "Card not found"
|
||||||
|
|
||||||
|
val cardCharData = storageRepository
|
||||||
|
.characterDao()
|
||||||
|
.getCharacterByMonIndex(nfcCharacter.charIndex.toInt(), dimData.id)
|
||||||
|
|
||||||
|
val characterData = UserCharacter(
|
||||||
|
charId = cardCharData.id,
|
||||||
|
ageInDays = nfcCharacter.ageInDays.toInt(),
|
||||||
|
nextAdventureMissionStage = nfcCharacter.nextAdventureMissionStage.toInt(),
|
||||||
|
mood = nfcCharacter.mood.toInt(),
|
||||||
|
vitalPoints = nfcCharacter.vitalPoints.toInt(),
|
||||||
|
transformationCountdown = nfcCharacter.transformationCountdownInMinutes.toInt(),
|
||||||
|
injuryStatus = nfcCharacter.injuryStatus,
|
||||||
|
trophies = nfcCharacter.trophies.toInt(),
|
||||||
|
currentPhaseBattlesWon = nfcCharacter.currentPhaseBattlesWon.toInt(),
|
||||||
|
currentPhaseBattlesLost = nfcCharacter.currentPhaseBattlesLost.toInt(),
|
||||||
|
totalBattlesWon = nfcCharacter.totalBattlesWon.toInt(),
|
||||||
|
totalBattlesLost = nfcCharacter.totalBattlesLost.toInt(),
|
||||||
|
activityLevel = nfcCharacter.activityLevel.toInt(),
|
||||||
|
heartRateCurrent = nfcCharacter.heartRateCurrent.toInt(),
|
||||||
|
characterType = when (nfcCharacter) {
|
||||||
|
is BENfcCharacter -> DeviceType.BEDevice
|
||||||
|
else -> DeviceType.VBDevice
|
||||||
|
},
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
|
||||||
|
storageRepository
|
||||||
|
.userCharacterDao()
|
||||||
|
.clearActiveCharacter()
|
||||||
|
|
||||||
|
val characterId: Long = storageRepository
|
||||||
|
.userCharacterDao()
|
||||||
|
.insertCharacterData(characterData)
|
||||||
|
|
||||||
|
if (nfcCharacter is BENfcCharacter) {
|
||||||
|
val extraCharacterData = BECharacterData(
|
||||||
|
id = characterId,
|
||||||
|
trainingHp = nfcCharacter.trainingHp.toInt(),
|
||||||
|
trainingAp = nfcCharacter.trainingAp.toInt(),
|
||||||
|
trainingBp = nfcCharacter.trainingBp.toInt(),
|
||||||
|
remainingTrainingTimeInMinutes = nfcCharacter.remainingTrainingTimeInMinutes.toInt(),
|
||||||
|
itemEffectActivityLevelValue = nfcCharacter.itemEffectActivityLevelValue.toInt(),
|
||||||
|
itemEffectMentalStateValue = nfcCharacter.itemEffectMentalStateValue.toInt(),
|
||||||
|
itemEffectMentalStateMinutesRemaining = nfcCharacter.itemEffectMentalStateMinutesRemaining.toInt(),
|
||||||
|
itemEffectActivityLevelMinutesRemaining = nfcCharacter.itemEffectActivityLevelMinutesRemaining.toInt(),
|
||||||
|
itemEffectVitalPointsChangeValue = nfcCharacter.itemEffectVitalPointsChangeValue.toInt(),
|
||||||
|
itemEffectVitalPointsChangeMinutesRemaining = nfcCharacter.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
|
||||||
|
abilityRarity = nfcCharacter.abilityRarity,
|
||||||
|
abilityType = nfcCharacter.abilityType.toInt(),
|
||||||
|
abilityBranch = nfcCharacter.abilityBranch.toInt(),
|
||||||
|
abilityReset = nfcCharacter.abilityReset.toInt(),
|
||||||
|
rank = nfcCharacter.abilityReset.toInt(),
|
||||||
|
itemType = nfcCharacter.itemType.toInt(),
|
||||||
|
itemMultiplier = nfcCharacter.itemMultiplier.toInt(),
|
||||||
|
itemRemainingTime = nfcCharacter.itemRemainingTime.toInt(),
|
||||||
|
otp0 = "", //nfcCharacter.value!!.otp0.toString(),
|
||||||
|
otp1 = "", //nfcCharacter.value!!.otp1.toString(),
|
||||||
|
minorVersion = nfcCharacter.characterCreationFirmwareVersion.minorVersion.toInt(),
|
||||||
|
majorVersion = nfcCharacter.characterCreationFirmwareVersion.majorVersion.toInt(),
|
||||||
|
)
|
||||||
|
|
||||||
|
storageRepository
|
||||||
|
.userCharacterDao()
|
||||||
|
.insertBECharacterData(extraCharacterData)
|
||||||
|
|
||||||
|
val transformationHistoryWatch = nfcCharacter.transformationHistory
|
||||||
|
transformationHistoryWatch.map { item ->
|
||||||
|
if (item.toCharIndex.toInt() != 255) {
|
||||||
|
val date = GregorianCalendar(item.year.toInt(), item.month.toInt(), item.day.toInt())
|
||||||
|
.time
|
||||||
|
.time
|
||||||
|
|
||||||
|
storageRepository
|
||||||
|
.characterDao()
|
||||||
|
.insertTransformation(characterId, item.toCharIndex.toInt(), dimData.id, date)
|
||||||
|
|
||||||
|
storageRepository
|
||||||
|
.dexDao()
|
||||||
|
.insertCharacter(item.toCharIndex.toInt(), dimData.id, date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (nfcCharacter is VBNfcCharacter) {
|
||||||
|
return "Not implemented yet"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Done reading character!"
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.settingsScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CreditsScreen(
|
||||||
|
navController: NavController
|
||||||
|
) {
|
||||||
|
Scaffold (
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "Credits",
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) { contentPadding ->
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
SettingsSection("Reverse engineering")
|
||||||
|
SettingsEntry(title = "cyanic", description = "Reversed the firmware and helped us during development.") { }
|
||||||
|
SettingsSection("Application development")
|
||||||
|
SettingsEntry(title = "cfogrady", description = "Developed vb-lib-nfc and part of this application.") { }
|
||||||
|
SettingsEntry(title = "nacabaro", description = "Developed this application.") { }
|
||||||
|
SettingsEntry(title = "lightheel", description = "Developing the battling part for this application, including server. Still in the works.") { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.settingsScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreen(
|
||||||
|
navController: NavController,
|
||||||
|
settingsScreenController: SettingsScreenControllerImpl,
|
||||||
|
) {
|
||||||
|
Scaffold (
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "Settings",
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) { contentPadding ->
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
SettingsSection("NFC Communication")
|
||||||
|
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
|
||||||
|
settingsScreenController.onClickImportApk()
|
||||||
|
}
|
||||||
|
SettingsSection("DiM/BEm management")
|
||||||
|
SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") {
|
||||||
|
settingsScreenController.onClickImportCard()
|
||||||
|
}
|
||||||
|
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
|
||||||
|
SettingsSection("About and credits")
|
||||||
|
SettingsEntry(title = "Credits", description = "Credits") {
|
||||||
|
navController.navigate(NavigationItems.Credits.route)
|
||||||
|
}
|
||||||
|
SettingsEntry(title = "About", description = "About") { }
|
||||||
|
SettingsSection("Data management")
|
||||||
|
SettingsEntry(title = "Export data", description = "Export application database") {
|
||||||
|
settingsScreenController.onClickOpenDirectory()
|
||||||
|
}
|
||||||
|
SettingsEntry(title = "Import data", description = "Import application database") {
|
||||||
|
settingsScreenController.onClickImportDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsEntry(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onClick() }
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = title)
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsSection(
|
||||||
|
title: String
|
||||||
|
) {
|
||||||
|
Box (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.settingsScreen
|
||||||
|
|
||||||
|
interface SettingsScreenController {
|
||||||
|
fun onClickOpenDirectory()
|
||||||
|
fun onClickImportDatabase()
|
||||||
|
fun onClickImportApk()
|
||||||
|
fun onClickImportCard()
|
||||||
|
}
|
||||||
+351
@@ -0,0 +1,351 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.settingsScreen
|
||||||
|
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import com.github.cfogrady.vb.dim.card.BemCard
|
||||||
|
import com.github.cfogrady.vb.dim.card.DimCard
|
||||||
|
import com.github.cfogrady.vb.dim.card.DimReader
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Card
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Character
|
||||||
|
import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
|
||||||
|
import com.github.nacabaro.vbhelper.source.SecretsImporter
|
||||||
|
import com.github.nacabaro.vbhelper.source.SecretsRepository
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsScreenControllerImpl(
|
||||||
|
private val context: ComponentActivity,
|
||||||
|
): SettingsScreenController {
|
||||||
|
private val roomDbName = "internalDb"
|
||||||
|
private val filePickerLauncher: ActivityResultLauncher<String>
|
||||||
|
private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>>
|
||||||
|
private val filePickerApk: ActivityResultLauncher<Array<String>>
|
||||||
|
private val filePickerCard: ActivityResultLauncher<Array<String>>
|
||||||
|
private val secretsImporter: SecretsImporter = ApkSecretsImporter()
|
||||||
|
private val application = context.applicationContext as VBHelper
|
||||||
|
private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository
|
||||||
|
private val database: AppDatabase = application.container.db
|
||||||
|
|
||||||
|
init {
|
||||||
|
filePickerLauncher = context.registerForActivityResult(
|
||||||
|
ActivityResultContracts.CreateDocument("application/octet-stream")
|
||||||
|
) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
exportDatabase(uri)
|
||||||
|
} else {
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filePickerOpenerLauncher = context.registerForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
importDatabase(uri)
|
||||||
|
} else {
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filePickerApk = context.registerForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
importApk(uri)
|
||||||
|
} else {
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "APK import cancelled", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filePickerCard = context.registerForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
importCard(uri)
|
||||||
|
} else {
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Card import cancelled", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickOpenDirectory() {
|
||||||
|
filePickerLauncher.launch("My application data.vbhelper")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickImportDatabase() {
|
||||||
|
filePickerOpenerLauncher.launch(arrayOf("application/octet-stream"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickImportApk() {
|
||||||
|
filePickerApk.launch(arrayOf("*/*"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickImportCard() {
|
||||||
|
filePickerCard.launch(arrayOf("*/*"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importCard(uri: Uri) {
|
||||||
|
context.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val contentResolver = context.contentResolver
|
||||||
|
val inputStream = contentResolver.openInputStream(uri)
|
||||||
|
inputStream.use { fileReader ->
|
||||||
|
val dimReader = DimReader()
|
||||||
|
val card = dimReader.readCard(fileReader, false)
|
||||||
|
|
||||||
|
val cardModel = Card(
|
||||||
|
dimId = card.header.dimId,
|
||||||
|
logo = card.spriteData.sprites[0].pixelData,
|
||||||
|
name = card.spriteData.text, // TODO Make user write card name// TODO Make user write card name
|
||||||
|
stageCount = card.adventureLevels.levels.size,
|
||||||
|
logoHeight = card.spriteData.sprites[0].height,
|
||||||
|
logoWidth = card.spriteData.sprites[0].width,
|
||||||
|
isBEm = card is BemCard
|
||||||
|
)
|
||||||
|
|
||||||
|
val dimId = database
|
||||||
|
.dimDao()
|
||||||
|
.insertNewDim(cardModel)
|
||||||
|
|
||||||
|
val characters = card.characterStats.characterEntries
|
||||||
|
|
||||||
|
var spriteCounter = when (card is BemCard) {
|
||||||
|
true -> 54
|
||||||
|
false -> 10
|
||||||
|
}
|
||||||
|
|
||||||
|
val domainCharacters = mutableListOf<Character>()
|
||||||
|
|
||||||
|
for (index in 0 until characters.size) {
|
||||||
|
var domainSprite: Sprite? = null;
|
||||||
|
|
||||||
|
if (index < 2 && card is DimCard) {
|
||||||
|
domainSprite = Sprite(
|
||||||
|
width = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.width,
|
||||||
|
height = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.height,
|
||||||
|
spriteIdle1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
|
||||||
|
spriteIdle2 = card.spriteData.sprites[spriteCounter + 2].pixelData,
|
||||||
|
spriteWalk1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
|
||||||
|
spriteWalk2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
|
||||||
|
spriteRun1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
|
||||||
|
spriteRun2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
|
||||||
|
spriteTrain1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
|
||||||
|
spriteTrain2 = card.spriteData.sprites[spriteCounter + 3].pixelData,
|
||||||
|
spriteHappy = card.spriteData.sprites[spriteCounter + 4].pixelData,
|
||||||
|
spriteSleep = card.spriteData.sprites[spriteCounter + 5].pixelData,
|
||||||
|
spriteAttack = card.spriteData.sprites[spriteCounter + 2].pixelData,
|
||||||
|
spriteDodge = card.spriteData.sprites[spriteCounter + 3].pixelData
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
domainSprite = Sprite(
|
||||||
|
width = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.width,
|
||||||
|
height = card.spriteData.sprites[spriteCounter + 1].spriteDimensions.height,
|
||||||
|
spriteIdle1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
|
||||||
|
spriteIdle2 = card.spriteData.sprites[spriteCounter + 2].pixelData,
|
||||||
|
spriteWalk1 = card.spriteData.sprites[spriteCounter + 3].pixelData,
|
||||||
|
spriteWalk2 = card.spriteData.sprites[spriteCounter + 4].pixelData,
|
||||||
|
spriteRun1 = card.spriteData.sprites[spriteCounter + 5].pixelData,
|
||||||
|
spriteRun2 = card.spriteData.sprites[spriteCounter + 6].pixelData,
|
||||||
|
spriteTrain1 = card.spriteData.sprites[spriteCounter + 7].pixelData,
|
||||||
|
spriteTrain2 = card.spriteData.sprites[spriteCounter + 8].pixelData,
|
||||||
|
spriteHappy = card.spriteData.sprites[spriteCounter + 9].pixelData,
|
||||||
|
spriteSleep = card.spriteData.sprites[spriteCounter + 10].pixelData,
|
||||||
|
spriteAttack = card.spriteData.sprites[spriteCounter + 11].pixelData,
|
||||||
|
spriteDodge = card.spriteData.sprites[spriteCounter + 12].pixelData
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val spriteId = database
|
||||||
|
.spriteDao()
|
||||||
|
.insertSprite(domainSprite)
|
||||||
|
|
||||||
|
domainCharacters.add(
|
||||||
|
Character(
|
||||||
|
dimId = dimId,
|
||||||
|
spriteId = spriteId,
|
||||||
|
monIndex = index,
|
||||||
|
name = card.spriteData.sprites[spriteCounter].pixelData,
|
||||||
|
stage = characters[index].stage,
|
||||||
|
attribute = NfcCharacter.Attribute.entries[characters[index].attribute],
|
||||||
|
baseHp = characters[index].hp,
|
||||||
|
baseBp = characters[index].dp,
|
||||||
|
baseAp = characters[index].ap,
|
||||||
|
nameWidth = card.spriteData.sprites[spriteCounter].spriteDimensions.width,
|
||||||
|
nameHeight = card.spriteData.sprites[spriteCounter].spriteDimensions.height,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
spriteCounter += if (card is BemCard) {
|
||||||
|
14
|
||||||
|
} else {
|
||||||
|
when (index) {
|
||||||
|
0 -> 6
|
||||||
|
1 -> 7
|
||||||
|
else -> 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
database
|
||||||
|
.characterDao()
|
||||||
|
.insertCharacter(*domainCharacters.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
inputStream?.close()
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Import successful!", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportDatabase(destinationUri: Uri) {
|
||||||
|
context.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val dbFile = File(context.getDatabasePath(roomDbName).absolutePath)
|
||||||
|
if (!dbFile.exists()) {
|
||||||
|
throw IllegalStateException("Database file does not exist!")
|
||||||
|
}
|
||||||
|
|
||||||
|
application.container.db.close()
|
||||||
|
|
||||||
|
context.contentResolver.openOutputStream(destinationUri)?.use { outputStream ->
|
||||||
|
dbFile.inputStream().use { inputStream ->
|
||||||
|
copyFile(inputStream, outputStream)
|
||||||
|
}
|
||||||
|
} ?: throw IllegalArgumentException("Unable to open destination Uri for writing")
|
||||||
|
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Database exported successfully!", Toast.LENGTH_SHORT).show()
|
||||||
|
Toast.makeText(context, "Closing application to avoid changes.", Toast.LENGTH_LONG).show()
|
||||||
|
context.finishAffinity()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("ScanScreenController", "Error exporting database $e")
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importDatabase(sourceUri: Uri) {
|
||||||
|
context.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) {
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
application.container.db.close()
|
||||||
|
|
||||||
|
val dbPath = context.getDatabasePath(roomDbName)
|
||||||
|
val shmFile = File(dbPath.parent, "$roomDbName-shm")
|
||||||
|
val walFile = File(dbPath.parent, "$roomDbName-wal")
|
||||||
|
|
||||||
|
// Delete existing database files
|
||||||
|
if (dbPath.exists()) dbPath.delete()
|
||||||
|
if (shmFile.exists()) shmFile.delete()
|
||||||
|
if (walFile.exists()) walFile.delete()
|
||||||
|
|
||||||
|
val dbFile = File(dbPath.absolutePath)
|
||||||
|
|
||||||
|
context.contentResolver.openInputStream(sourceUri)?.use { inputStream ->
|
||||||
|
dbFile.outputStream().use { outputStream ->
|
||||||
|
copyFile(inputStream, outputStream)
|
||||||
|
}
|
||||||
|
} ?: throw IllegalArgumentException("Unable to open source Uri for reading")
|
||||||
|
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Database imported successfully!", Toast.LENGTH_SHORT).show()
|
||||||
|
Toast.makeText(context, "Reopen the app to finish import process!", Toast.LENGTH_LONG).show()
|
||||||
|
context.finishAffinity()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("ScanScreenController", "Error importing database $e")
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileNameFromUri(uri: Uri): String? {
|
||||||
|
var fileName: String? = null
|
||||||
|
val cursor = context.contentResolver.query(uri, null, null, null, null)
|
||||||
|
cursor?.use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val nameIndex = it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
|
||||||
|
fileName = it.getString(nameIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyFile(inputStream: InputStream, outputStream: OutputStream) {
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var bytesRead: Int
|
||||||
|
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead)
|
||||||
|
}
|
||||||
|
outputStream.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importApk(uri: Uri) {
|
||||||
|
context.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
context.contentResolver.openInputStream(uri).use {
|
||||||
|
if(it == null) {
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Selected file is empty!",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
val secrets: Secrets?
|
||||||
|
try {
|
||||||
|
secrets = secretsImporter.importSecrets(it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Secrets import failed. Please only select the official Vital Arena App 2.1.0 APK.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
context.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
secretsRepository.updateSecrets(secrets)
|
||||||
|
}.invokeOnCompletion {
|
||||||
|
context.runOnUiThread {
|
||||||
|
Toast.makeText(context, "Secrets successfully imported. Connections with devices are now possible.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.spriteViewer
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SpriteViewer(
|
||||||
|
navController: NavController,
|
||||||
|
spriteViewerController: SpriteViewerController
|
||||||
|
) {
|
||||||
|
val spriteList = remember { mutableStateListOf<Bitmap>() }
|
||||||
|
|
||||||
|
Log.d("SpriteViewer", "spriteList: $spriteList")
|
||||||
|
|
||||||
|
LaunchedEffect(spriteViewerController) {
|
||||||
|
val sprites = spriteViewerController.getAllSprites()
|
||||||
|
val bitmapData = spriteViewerController.convertToBitmap(sprites)
|
||||||
|
spriteList.addAll(bitmapData)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold (
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "Sprite viewer",
|
||||||
|
onBackClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) { contentPadding ->
|
||||||
|
LazyColumn (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
items(spriteList) { sprite ->
|
||||||
|
val imageBitmap = remember(sprite) { sprite.asImageBitmap() }
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = "Sprite",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(256.dp)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.spriteViewer
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
||||||
|
|
||||||
|
interface SpriteViewerController {
|
||||||
|
suspend fun getAllSprites(): List<Sprite>
|
||||||
|
fun convertToBitmap(sprites: List<Sprite>): List<Bitmap>
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.spriteViewer
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Sprite
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
class SpriteViewerControllerImpl(
|
||||||
|
private val context: ComponentActivity
|
||||||
|
) : SpriteViewerController {
|
||||||
|
override suspend fun getAllSprites(): List<Sprite> {
|
||||||
|
val applicationContext = context.applicationContext as VBHelper
|
||||||
|
val db = applicationContext.container.db
|
||||||
|
val sprites = db.spriteDao().getAllSprites()
|
||||||
|
return sprites
|
||||||
|
}
|
||||||
|
|
||||||
|
// I don't like this, chief
|
||||||
|
override fun convertToBitmap(sprites: List<Sprite>): List<Bitmap> {
|
||||||
|
val bitmapList = mutableListOf<Bitmap>()
|
||||||
|
|
||||||
|
for (sprite in sprites) {
|
||||||
|
Log.d("SpriteViewer", "sprite: $sprite")
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle1))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteIdle2))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk1))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteWalk2))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun1))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteRun2))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain1))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteTrain2))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteHappy))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteSleep))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteAttack))
|
||||||
|
})
|
||||||
|
bitmapList.add(Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
|
||||||
|
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.spriteDodge))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmapList
|
||||||
|
}
|
||||||
|
}
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import com.github.nacabaro.vbhelper.R
|
||||||
|
|
||||||
|
fun getAdventureTime(time: Int): String {
|
||||||
|
return when (time) {
|
||||||
|
360 -> "6 hours"
|
||||||
|
720 -> "12 hours"
|
||||||
|
1440 -> "24 hours"
|
||||||
|
else -> "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StorageAdventureTimeDialog(
|
||||||
|
onClickSendToAdventure: (time: Long) -> Unit,
|
||||||
|
onDismissRequest: () -> Unit
|
||||||
|
) {
|
||||||
|
val times = arrayOf(360, 720, 1440)
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
var itemPosition by remember { mutableIntStateOf(-1) }
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest
|
||||||
|
) {
|
||||||
|
Card {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(256.dp)
|
||||||
|
.clickable(true) {
|
||||||
|
expanded = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = when (itemPosition) {
|
||||||
|
-1 -> "Choose time"
|
||||||
|
else -> getAdventureTime(times[itemPosition])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.baseline_single_arrow_down),
|
||||||
|
contentDescription = "Show more"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false },
|
||||||
|
modifier = Modifier
|
||||||
|
.width(256.dp)
|
||||||
|
) {
|
||||||
|
times.forEach { time ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(getAdventureTime(time)) },
|
||||||
|
onClick = {
|
||||||
|
itemPosition = times.indexOf(time)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
if (itemPosition != -1) {
|
||||||
|
onClickSendToAdventure(times[itemPosition].toLong())
|
||||||
|
onDismissRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(text = "Send on adventure")
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
onClick = onDismissRequest
|
||||||
|
) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StorageDialog(
|
||||||
|
characterId: Long,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onSendToBracelet: () -> Unit,
|
||||||
|
onClickSetActive: () -> Unit,
|
||||||
|
onClickSendToAdventure: (time: Long) -> Unit
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val storageRepository = StorageRepository(application.container.db)
|
||||||
|
val character = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
|
||||||
|
val characterSprite = remember { mutableStateOf<BitmapData?>(null) }
|
||||||
|
val characterName = remember { mutableStateOf<BitmapData?>(null) }
|
||||||
|
var onSendToAdventureClicked by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(storageRepository) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
character.value = storageRepository.getSingleCharacter(characterId)
|
||||||
|
characterSprite.value = BitmapData(
|
||||||
|
bitmap = character.value!!.spriteIdle,
|
||||||
|
width = character.value!!.spriteWidth,
|
||||||
|
height = character.value!!.spriteHeight
|
||||||
|
)
|
||||||
|
characterName.value = BitmapData(
|
||||||
|
bitmap = character.value!!.nameSprite,
|
||||||
|
width = character.value!!.nameSpriteWidth,
|
||||||
|
height = character.value!!.nameSpriteHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = true,
|
||||||
|
dismissOnClickOutside = true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
if (character.value != null &&
|
||||||
|
characterSprite.value != null &&
|
||||||
|
characterName.value != null
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
val bitmap = remember (characterSprite.value!!) { characterSprite.value!!.getBitmap() }
|
||||||
|
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||||
|
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||||
|
val dpSize = (characterSprite.value!!.width * 4 / density).dp
|
||||||
|
Image(
|
||||||
|
bitmap = imageBitmap,
|
||||||
|
contentDescription = "Character image",
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(dpSize)
|
||||||
|
)
|
||||||
|
val nameBitmap = remember (characterName.value!!) { characterName.value!!.getBitmap() }
|
||||||
|
val nameImageBitmap = remember(nameBitmap) { nameBitmap.asImageBitmap() }
|
||||||
|
val nameDpSize = (characterName.value!!.width * 4 / density).dp
|
||||||
|
Image(
|
||||||
|
bitmap = nameImageBitmap,
|
||||||
|
contentDescription = "Character image",
|
||||||
|
filterQuality = FilterQuality.None,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(nameDpSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onSendToBracelet,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(text = "Send to watch")
|
||||||
|
}
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
onClick = onClickSetActive,
|
||||||
|
) {
|
||||||
|
Text(text = "Set active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onSendToAdventureClicked = true
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = "Send on adventure")
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
onClick = onDismissRequest
|
||||||
|
) {
|
||||||
|
Text(text = "Close")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onSendToAdventureClicked) {
|
||||||
|
StorageAdventureTimeDialog(
|
||||||
|
onClickSendToAdventure = { time ->
|
||||||
|
onClickSendToAdventure(time)
|
||||||
|
},
|
||||||
|
onDismissRequest = { onSendToAdventureClicked = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
|
import androidx.compose.foundation.gestures.scrollable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.github.nacabaro.vbhelper.components.CharacterEntry
|
||||||
|
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||||
|
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StorageScreen(
|
||||||
|
navController: NavController,
|
||||||
|
storageScreenController: StorageScreenControllerImpl,
|
||||||
|
adventureScreenController: AdventureScreenControllerImpl
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val application = LocalContext.current.applicationContext as VBHelper
|
||||||
|
val storageRepository = StorageRepository(application.container.db)
|
||||||
|
val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
|
||||||
|
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(storageRepository, selectedCharacter) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
val characterList = storageRepository.getAllCharacters()
|
||||||
|
monList.value = characterList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold (
|
||||||
|
topBar = {
|
||||||
|
TopBanner(
|
||||||
|
text = "My characters",
|
||||||
|
onAdventureClick = {
|
||||||
|
navController.navigate(NavigationItems.Adventure.route)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { contentPadding ->
|
||||||
|
if (monList.value.isEmpty()) {
|
||||||
|
Column (
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Nothing to see here",
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(3),
|
||||||
|
modifier = Modifier
|
||||||
|
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
|
||||||
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
items(monList.value) { index ->
|
||||||
|
CharacterEntry(
|
||||||
|
icon = BitmapData(
|
||||||
|
bitmap = index.spriteIdle,
|
||||||
|
width = index.spriteWidth,
|
||||||
|
height = index.spriteHeight
|
||||||
|
),
|
||||||
|
onClick = {
|
||||||
|
if (!index.isInAdventure) {
|
||||||
|
selectedCharacter = index.id
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
application,
|
||||||
|
"This character is in an adventure",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
navController.navigate(
|
||||||
|
NavigationItems.Adventure.route
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCharacter != null) {
|
||||||
|
StorageDialog(
|
||||||
|
characterId = selectedCharacter!!,
|
||||||
|
onDismissRequest = { selectedCharacter = null },
|
||||||
|
onClickSetActive = {
|
||||||
|
storageScreenController
|
||||||
|
.setActive(selectedCharacter!!) {
|
||||||
|
selectedCharacter = null
|
||||||
|
navController.navigate(NavigationItems.Home.route)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSendToBracelet = {
|
||||||
|
navController.navigate(
|
||||||
|
NavigationItems.Scan.route.replace(
|
||||||
|
"{characterId}",
|
||||||
|
selectedCharacter.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClickSendToAdventure = { time ->
|
||||||
|
adventureScreenController
|
||||||
|
.sendCharacterToAdventure(
|
||||||
|
characterId = selectedCharacter!!,
|
||||||
|
timeInMinutes = time
|
||||||
|
)
|
||||||
|
selectedCharacter = null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||||
|
|
||||||
|
interface StorageScreenController {
|
||||||
|
fun setActive(characterId: Long, onCompletion: () -> Unit)
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class StorageScreenControllerImpl(
|
||||||
|
private val componentActivity: ComponentActivity
|
||||||
|
): StorageScreenController {
|
||||||
|
private val application = componentActivity.applicationContext as VBHelper
|
||||||
|
private val database = application.container.db
|
||||||
|
|
||||||
|
override fun setActive(characterId: Long, onCompletion: () -> Unit) {
|
||||||
|
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
database.userCharacterDao().clearActiveCharacter()
|
||||||
|
database.userCharacterDao().setActiveCharacter(characterId)
|
||||||
|
|
||||||
|
componentActivity.runOnUiThread {
|
||||||
|
Toast.makeText(
|
||||||
|
componentActivity,
|
||||||
|
"Active character updated!",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
onCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
class ApkSecretsImporter(private val dexFileSecretsImporter: SecretsImporter = DexFileSecretsImporter()): SecretsImporter {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEX_FILE = "classes.dex"
|
||||||
|
}
|
||||||
|
|
||||||
|
// importSecrets imports the secrets from the apk input stream, and validates them.
|
||||||
|
override fun importSecrets(inputStream: InputStream): Secrets {
|
||||||
|
ZipInputStream(inputStream).use { zip ->
|
||||||
|
var zipEntry = zip.nextEntry
|
||||||
|
while(zipEntry != null) {
|
||||||
|
println("Zip Entry: ${zipEntry.name}")
|
||||||
|
if(zipEntry.name == DEX_FILE) {
|
||||||
|
return dexFileSecretsImporter.importSecrets(zip)
|
||||||
|
}
|
||||||
|
zipEntry = zip.nextEntry
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("File `$DEX_FILE` is missing from apk!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import com.github.cfogrady.vbnfc.CryptographicTransformer
|
||||||
|
import com.github.cfogrady.vbnfc.data.DeviceType
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets.HmacKeys
|
||||||
|
import kotlinx.coroutines.flow.single
|
||||||
|
|
||||||
|
class DataStoreSecretsRepository(
|
||||||
|
private val secretsDataStore: DataStore<Secrets>,
|
||||||
|
): SecretsRepository {
|
||||||
|
override val secretsFlow = secretsDataStore.data
|
||||||
|
|
||||||
|
override suspend fun updateSecrets(secrets: Secrets) {
|
||||||
|
secretsDataStore.updateData {
|
||||||
|
secrets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getSecrets(): Secrets {
|
||||||
|
return secretsFlow.single()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Secrets.getHmacKeys(deviceTypeId: UShort): HmacKeys {
|
||||||
|
return when(deviceTypeId) {
|
||||||
|
DeviceType.VitalBraceletBEDeviceType -> this.beHmacKeys
|
||||||
|
DeviceType.VitalCharactersDeviceType -> this.vbcHmacKeys
|
||||||
|
DeviceType.VitalSeriesDeviceType -> this.vbdmHmacKeys
|
||||||
|
else -> throw IllegalArgumentException("Unknown DeviceTypeId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Secrets.getCryptographicTransformerMap(): Map<UShort, CryptographicTransformer> {
|
||||||
|
val cipher = this.vbCipherList.toIntArray()
|
||||||
|
val beCipher = this.beCipherList.toIntArray()
|
||||||
|
val vbdmHmacKeys = this.getHmacKeys(DeviceType.VitalSeriesDeviceType)
|
||||||
|
val vbcHmacKeys = this.getHmacKeys(DeviceType.VitalCharactersDeviceType)
|
||||||
|
val beHmacKeys = this.getHmacKeys(DeviceType.VitalBraceletBEDeviceType)
|
||||||
|
return mapOf(
|
||||||
|
Pair(DeviceType.VitalSeriesDeviceType, CryptographicTransformer(vbdmHmacKeys.hmacKey1, vbdmHmacKeys.hmacKey2, this.aesKey, cipher)),
|
||||||
|
Pair(DeviceType.VitalCharactersDeviceType, CryptographicTransformer(vbcHmacKeys.hmacKey1, vbcHmacKeys.hmacKey2, this.aesKey, cipher)),
|
||||||
|
Pair(DeviceType.VitalBraceletBEDeviceType, CryptographicTransformer(beHmacKeys.hmacKey1, beHmacKeys.hmacKey2, this.aesKey, beCipher)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Secrets.isMissingSecrets(): Boolean {
|
||||||
|
return this.aesKey.length != 24 ||
|
||||||
|
this.vbCipherList.size != 16 ||
|
||||||
|
this.beCipherList.size != 16 ||
|
||||||
|
this.vbdmHmacKeys.isMissingKey() ||
|
||||||
|
this.vbcHmacKeys.isMissingKey() ||
|
||||||
|
this.beHmacKeys.isMissingKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HmacKeys.isMissingKey(): Boolean {
|
||||||
|
return this.hmacKey1.length != 24 || this.hmacKey2.length != 24
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import com.github.cfogrady.vbnfc.CryptographicTransformer
|
||||||
|
import com.github.cfogrady.vbnfc.data.DeviceType
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets.HmacKeys
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.InvalidKeyException
|
||||||
|
|
||||||
|
|
||||||
|
class DexFileSecretsImporter: SecretsImporter {
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val VBDM_SUBSTITUTION_CIPHER_IDX = 1080145
|
||||||
|
const val BE_SUBSTITUTION_CIPHER_IDX = 1080217
|
||||||
|
|
||||||
|
const val VBDM_HMAC_KEY_2_IDX = 1249063
|
||||||
|
const val VBDM_HMAC_KEY_1_IDX = 1494074
|
||||||
|
const val VBC_HMAC_KEY_1_IDX = 1241640
|
||||||
|
const val VBC_HMAC_KEY_2_IDX = 1466955
|
||||||
|
const val BE_HMAC_KEY_1_IDX = 1580157
|
||||||
|
const val BE_HMAC_KEY_2_IDX = 1593759
|
||||||
|
const val AES_KEY_IDX = 1277527
|
||||||
|
|
||||||
|
val TEST_TAG = byteArrayOf(0x34, 0x01, 0x10, 0xff.toByte(), 0xf5.toByte(), 0x00, 0xa2.toByte())
|
||||||
|
const val BE_TEST_TAG_PASSWORD = "be29a87e"
|
||||||
|
const val VBDM_TEST_TAG_PASSWORD = "6ea33673"
|
||||||
|
const val VBC_TEST_TAG_PASSWORD = "a71dfb22"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun importSecrets(inputStream: InputStream): Secrets {
|
||||||
|
val deviceToSecrets = readSecrets(inputStream)
|
||||||
|
verifySecretCorrectness(deviceToSecrets)
|
||||||
|
return deviceToSecrets
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readSecrets(inputStream: InputStream): Secrets {
|
||||||
|
val dexFile = inputStream.readBytes()
|
||||||
|
val byteOrder = ByteOrder.BIG_ENDIAN
|
||||||
|
val vbdmSubstitutionCipher = dexFile.sliceArray(VBDM_SUBSTITUTION_CIPHER_IDX until VBDM_SUBSTITUTION_CIPHER_IDX+(16*4)).toIntArray(byteOrder)
|
||||||
|
val beSubstitutionCipher = dexFile.sliceArray(BE_SUBSTITUTION_CIPHER_IDX until BE_SUBSTITUTION_CIPHER_IDX+(16*4)).toIntArray(byteOrder)
|
||||||
|
val aesKey = dexFile.sliceArray(AES_KEY_IDX until AES_KEY_IDX+24).toString(StandardCharsets.UTF_8)
|
||||||
|
return Secrets.newBuilder()
|
||||||
|
.setAesKey(aesKey)
|
||||||
|
.addAllVbCipher(vbdmSubstitutionCipher.toList())
|
||||||
|
.addAllBeCipher(beSubstitutionCipher.toList())
|
||||||
|
.setVbdmHmacKeys(getHmac(dexFile, VBDM_HMAC_KEY_1_IDX, VBDM_HMAC_KEY_2_IDX))
|
||||||
|
.setVbcHmacKeys(getHmac(dexFile, VBC_HMAC_KEY_1_IDX, VBC_HMAC_KEY_2_IDX))
|
||||||
|
.setBeHmacKeys(getHmac(dexFile, BE_HMAC_KEY_1_IDX, BE_HMAC_KEY_2_IDX))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHmac(dexFile: ByteArray, hmacKeyIdx1: Int, hmacKeyIdx2: Int): HmacKeys {
|
||||||
|
val hmacKey1 = dexFile.sliceArray(hmacKeyIdx1 until hmacKeyIdx1+24).toString(StandardCharsets.UTF_8)
|
||||||
|
val hmacKey2 = dexFile.sliceArray(hmacKeyIdx2 until hmacKeyIdx2+24).toString(StandardCharsets.UTF_8)
|
||||||
|
return HmacKeys.newBuilder()
|
||||||
|
.setHmacKey1(hmacKey1)
|
||||||
|
.setHmacKey2(hmacKey2)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
private fun verifySecretCorrectness(secrets: Secrets) {
|
||||||
|
val deviceToCryptographicTransformers = secrets.getCryptographicTransformerMap()
|
||||||
|
for (keyValue in deviceToCryptographicTransformers) {
|
||||||
|
when(keyValue.key) {
|
||||||
|
DeviceType.VitalBraceletBEDeviceType -> assertBuildsCorrectPassword(keyValue.value, BE_TEST_TAG_PASSWORD)
|
||||||
|
DeviceType.VitalCharactersDeviceType -> assertBuildsCorrectPassword(keyValue.value, VBC_TEST_TAG_PASSWORD)
|
||||||
|
DeviceType.VitalSeriesDeviceType -> assertBuildsCorrectPassword(keyValue.value, VBDM_TEST_TAG_PASSWORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
private fun assertBuildsCorrectPassword(cryptographicTransformer: CryptographicTransformer, expectedPassword: String) {
|
||||||
|
val result = cryptographicTransformer.createNfcPassword(
|
||||||
|
TEST_TAG
|
||||||
|
)
|
||||||
|
if( result.toHexString() != expectedPassword) {
|
||||||
|
throw InvalidKeyException("Secrets were loaded, but were unsuccessful at generating the test password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toIntArray(byteOrder: ByteOrder): IntArray {
|
||||||
|
require(this.size % 4 == 0) { "Number of bytes must be multiple of 4 to convert into 32-bit words" }
|
||||||
|
val values = IntArray(this.size / 4)
|
||||||
|
ByteBuffer.wrap(this).order(byteOrder).asIntBuffer()[values]
|
||||||
|
return values
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.domain.characters.Card
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CardDtos
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
|
||||||
|
class DexRepository (
|
||||||
|
private val db: AppDatabase
|
||||||
|
) {
|
||||||
|
suspend fun getAllDims(): List<CardDtos.CardProgress> {
|
||||||
|
return db.dexDao().getCardsWithProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCharactersByDimId(cardId: Long): List<CharacterDtos.CardProgress> {
|
||||||
|
return db.dexDao().getSingleCardProgress(cardId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.domain.items.Items
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||||
|
|
||||||
|
class ItemsRepository(
|
||||||
|
private val db: AppDatabase
|
||||||
|
) {
|
||||||
|
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities> {
|
||||||
|
return db.itemDao().getAllItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUserItems(): List<ItemDtos.ItemsWithQuantities> {
|
||||||
|
return db.itemDao().getAllUserItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
fun interface SecretsImporter {
|
||||||
|
fun importSecrets(inputStream: InputStream): Secrets
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface SecretsRepository {
|
||||||
|
val secretsFlow: Flow<Secrets>
|
||||||
|
|
||||||
|
suspend fun getSecrets(): Secrets
|
||||||
|
suspend fun updateSecrets(secrets: Secrets)
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import androidx.datastore.core.CorruptionException
|
||||||
|
import androidx.datastore.core.Serializer
|
||||||
|
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
object SecretsSerializer: Serializer<Secrets> {
|
||||||
|
override val defaultValue = Secrets.getDefaultInstance()
|
||||||
|
|
||||||
|
override suspend fun readFrom(input: InputStream): Secrets {
|
||||||
|
try {
|
||||||
|
return Secrets.parseFrom(input)
|
||||||
|
} catch (exception: InvalidProtocolBufferException) {
|
||||||
|
throw CorruptionException("Cannot read proto.", exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun writeTo(t: Secrets, output: OutputStream) {
|
||||||
|
t.writeTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
|
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
|
||||||
|
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||||
|
|
||||||
|
class StorageRepository (
|
||||||
|
private val db: AppDatabase
|
||||||
|
) {
|
||||||
|
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> {
|
||||||
|
return db.userCharacterDao().getAllCharacters()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSingleCharacter(id: Long): CharacterDtos.CharacterWithSprites {
|
||||||
|
return db.userCharacterDao().getCharacterWithSprites(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCharacterBeData(id: Long): BECharacterData {
|
||||||
|
return db.userCharacterDao().getBeData(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTransformationHistory(characterId: Long): List<CharacterDtos.TransformationHistory>? {
|
||||||
|
return db.userCharacterDao().getTransformationHistory(characterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCharacterData(id: Long): CharacterDtos.DiMInfo {
|
||||||
|
return db.characterDao().getCharacterInfo(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? {
|
||||||
|
return db.userCharacterDao().getActiveCharacter()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteCharacter(id: Long) {
|
||||||
|
return db.userCharacterDao().deleteCharacterById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> {
|
||||||
|
return db.adventureDao().getAdventureCharacters()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.utils
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import com.github.cfogrady.vb.dim.sprite.SpriteData
|
||||||
|
|
||||||
|
// simple, but smooth
|
||||||
|
data class BitmapData (
|
||||||
|
val bitmap: ByteArray,
|
||||||
|
val width: Int,
|
||||||
|
val height: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
fun BitmapData.getBitmap(): Bitmap {
|
||||||
|
return Bitmap.createBitmap(createARGBIntArray(), this.width, this.height, Bitmap.Config.HARDWARE)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ARGBMasks {
|
||||||
|
const val ALPHA = (0xFF shl 24)
|
||||||
|
const val RED = (0xFF shl 16)
|
||||||
|
const val GREEN = (0xFF shl 8)
|
||||||
|
const val BLUE = 0xFF
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const val BLACK = ARGBMasks.ALPHA
|
||||||
|
|
||||||
|
fun BitmapData.getObscuredBitmap(): Bitmap {
|
||||||
|
val argbPixels = createARGBIntArray()
|
||||||
|
for(i in argbPixels.indices) {
|
||||||
|
val currentPixel = argbPixels[i]
|
||||||
|
if( currentPixel and ARGBMasks.ALPHA != 0) {
|
||||||
|
// non transparent pixel
|
||||||
|
argbPixels[i] = BLACK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Bitmap.createBitmap(argbPixels, this.width, this.height, Bitmap.Config.HARDWARE)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun BitmapData.createARGBIntArray(): IntArray {
|
||||||
|
// hack to get it into correct format by relying on the DIM Sprites methods since we haven't changed the raw pixel data at this point.
|
||||||
|
val bytes = SpriteData.Sprite.builder().width(this.width).height(this.height).pixelData(this.bitmap).build()
|
||||||
|
.get24BitRGB()
|
||||||
|
val result = IntArray(this.width*this.height)
|
||||||
|
for(i in result.indices) {
|
||||||
|
val originalIndex = i*3
|
||||||
|
val red = bytes[originalIndex].toUInt() and 0xFFu
|
||||||
|
val green = bytes[originalIndex+1].toUInt() and 0xFFu
|
||||||
|
val blue = bytes[originalIndex+2].toUInt() and 0xFFu
|
||||||
|
val alpha = if(red == 0u && blue == 0u && green == 0xFFu) 0 else 0xFF
|
||||||
|
result[i] = (alpha shl 24) or (red shl 16).toInt() or (green shl 8).toInt() or blue.toInt()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun List<BitmapData>.getBitmaps(): List<Bitmap> {
|
||||||
|
return this.map{it.getBitmap()}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.icu.util.Calendar
|
||||||
|
import android.icu.util.GregorianCalendar
|
||||||
|
import com.github.cfogrady.vbnfc.be.BENfcCharacter
|
||||||
|
import com.github.cfogrady.vbnfc.be.FirmwareVersion
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||||
|
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
suspend fun characterToNfc(context: Context, characterId: Long): NfcCharacter? {
|
||||||
|
val app = context.applicationContext as VBHelper
|
||||||
|
val database = app.container.db
|
||||||
|
val storageRepository = StorageRepository(database)
|
||||||
|
val userCharacter = storageRepository.getSingleCharacter(characterId)
|
||||||
|
val characterInfo = storageRepository.getCharacterData(characterId)
|
||||||
|
|
||||||
|
if (userCharacter.characterType == DeviceType.BEDevice) {
|
||||||
|
val beData = storageRepository.getCharacterBeData(characterId)
|
||||||
|
val transformationHistory = storageRepository
|
||||||
|
.getTransformationHistory(characterId)!!
|
||||||
|
.map {
|
||||||
|
val date = Date(it.transformationDate)
|
||||||
|
val calendar = GregorianCalendar()
|
||||||
|
calendar.time = date
|
||||||
|
|
||||||
|
NfcCharacter.Transformation(
|
||||||
|
toCharIndex = it.monIndex.toUByte(),
|
||||||
|
year = calendar
|
||||||
|
.get(Calendar.YEAR)
|
||||||
|
.toUShort(),
|
||||||
|
month = calendar
|
||||||
|
.get(Calendar.MONTH)
|
||||||
|
.toUByte(),
|
||||||
|
day = calendar
|
||||||
|
.get(Calendar.DAY_OF_MONTH)
|
||||||
|
.toUByte()
|
||||||
|
)
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
val paddedTransformationArray = padTransformationArray(transformationHistory)
|
||||||
|
|
||||||
|
val nfcData = BENfcCharacter(
|
||||||
|
dimId = characterInfo.cardId.toUShort(),
|
||||||
|
charIndex = characterInfo.charId.toUShort(),
|
||||||
|
stage = userCharacter.stage.toByte(),
|
||||||
|
attribute = userCharacter.attribute,
|
||||||
|
ageInDays = userCharacter.ageInDays.toByte(),
|
||||||
|
nextAdventureMissionStage = userCharacter.nextAdventureMissionStage.toByte(),
|
||||||
|
mood = userCharacter.mood.toByte(),
|
||||||
|
vitalPoints = userCharacter.vitalPoints.toUShort(),
|
||||||
|
itemEffectMentalStateValue = beData.itemEffectMentalStateValue.toByte(),
|
||||||
|
itemEffectMentalStateMinutesRemaining = beData.itemEffectMentalStateMinutesRemaining.toByte(),
|
||||||
|
itemEffectActivityLevelValue = beData.itemEffectActivityLevelValue.toByte(),
|
||||||
|
itemEffectActivityLevelMinutesRemaining = beData.itemEffectActivityLevelMinutesRemaining.toByte(),
|
||||||
|
itemEffectVitalPointsChangeValue = beData.itemEffectVitalPointsChangeValue.toByte(),
|
||||||
|
itemEffectVitalPointsChangeMinutesRemaining = beData.itemEffectVitalPointsChangeMinutesRemaining.toByte(),
|
||||||
|
transformationCountdownInMinutes = userCharacter.transformationCountdown.toUShort(),
|
||||||
|
injuryStatus = userCharacter.injuryStatus,
|
||||||
|
trainingPp = userCharacter.trophies.toUShort(),
|
||||||
|
currentPhaseBattlesWon = userCharacter.currentPhaseBattlesWon.toUShort(),
|
||||||
|
currentPhaseBattlesLost = userCharacter.currentPhaseBattlesLost.toUShort(),
|
||||||
|
totalBattlesWon = userCharacter.totalBattlesWon.toUShort(),
|
||||||
|
totalBattlesLost = userCharacter.totalBattlesLost.toUShort(),
|
||||||
|
activityLevel = userCharacter.activityLevel.toByte(),
|
||||||
|
heartRateCurrent = userCharacter.heartRateCurrent.toUByte(),
|
||||||
|
transformationHistory = paddedTransformationArray,
|
||||||
|
vitalHistory = Array(7) {
|
||||||
|
NfcCharacter.DailyVitals(0u, 0u, 0u, 0u)
|
||||||
|
},
|
||||||
|
appReserved1 = ByteArray(12) {0},
|
||||||
|
appReserved2 = Array(3) {0u},
|
||||||
|
trainingHp = beData.trainingHp.toUShort(),
|
||||||
|
trainingAp = beData.trainingAp.toUShort(),
|
||||||
|
trainingBp = beData.trainingBp.toUShort(),
|
||||||
|
remainingTrainingTimeInMinutes = beData.remainingTrainingTimeInMinutes.toUShort(),
|
||||||
|
abilityRarity = beData.abilityRarity,
|
||||||
|
abilityType = beData.abilityType.toUShort(),
|
||||||
|
abilityBranch = beData.abilityBranch.toUShort(),
|
||||||
|
abilityReset = beData.abilityReset.toByte(),
|
||||||
|
rank = beData.rank.toByte(),
|
||||||
|
itemType = beData.itemType.toByte(),
|
||||||
|
itemMultiplier = beData.itemMultiplier.toByte(),
|
||||||
|
itemRemainingTime = beData.itemRemainingTime.toByte(),
|
||||||
|
otp0 = byteArrayOf(8),
|
||||||
|
otp1 = byteArrayOf(8),
|
||||||
|
characterCreationFirmwareVersion = FirmwareVersion(
|
||||||
|
minorVersion = beData.minorVersion.toByte(),
|
||||||
|
majorVersion = beData.majorVersion.toByte()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return nfcData
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.utils
|
||||||
|
|
||||||
|
enum class DeviceType {
|
||||||
|
VBDevice,
|
||||||
|
BEDevice
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.utils
|
||||||
|
|
||||||
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
|
||||||
|
fun padTransformationArray(
|
||||||
|
transformationArray: Array<NfcCharacter.Transformation>
|
||||||
|
): Array<NfcCharacter.Transformation> {
|
||||||
|
if (transformationArray.size >= 8) {
|
||||||
|
return transformationArray
|
||||||
|
}
|
||||||
|
|
||||||
|
val paddedArray = Array(8) {
|
||||||
|
NfcCharacter.Transformation(
|
||||||
|
toCharIndex = 255u,
|
||||||
|
year = 65535u,
|
||||||
|
month = 255u,
|
||||||
|
day = 255u
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(transformationArray, 0, paddedArray, 0, transformationArray.size)
|
||||||
|
return paddedArray
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option java_package = "com.github.nacabaro.vbhelper.source.proto";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
|
||||||
|
message Secrets {
|
||||||
|
string aes_key = 1;
|
||||||
|
repeated int32 vb_cipher = 2;
|
||||||
|
repeated int32 be_cipher = 3;
|
||||||
|
|
||||||
|
message HmacKeys {
|
||||||
|
string hmac_key_1 = 1;
|
||||||
|
string hmac_key_2 = 2;
|
||||||
|
}
|
||||||
|
HmacKeys vbdm_hmac_keys = 4;
|
||||||
|
HmacKeys vbc_hmac_keys = 5;
|
||||||
|
HmacKeys be_hmac_keys = 6;
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user