Compare commits

...
Author SHA1 Message Date
Nacho 39ba48d867 Forgot to remove the dependency to SpriteRepo 2025-07-27 19:46:07 +02:00
Nacho 3f324ca141 SpriteRepo is not needed anymore 2025-07-27 19:44:12 +02:00
Nacho 1e28a8c249 Application now imports all the sprites related to the character into the internal database. 2025-07-27 19:42:25 +02:00
Nacho d46769b0cb Added credits for the app in preparation for version 0.1 2025-07-26 22:26:41 +02:00
Nacho fce05870c5 This should allow the app to build, otherwise it won't or it will crash 2025-07-26 14:23:53 +02:00
nacabaroandGitHub d749af0a9c Merge pull request #27 from cfogrady/FixStageAndAttributeFromCharacter
Fix stage and attribute coming from character instead of UserCharacter
2025-04-08 15:35:13 +02:00
Christopher O'Grady 076f6bfacf Fix stage and attribute coming from character instead of UserCharacter 2025-04-05 20:31:03 -04:00
nacabaroandGitHub a4f3b701e5 Merge pull request #26 from cfogrady/RemoveDuplicatedData
Remove duplicated stage and attribute from UserCharacter
2025-03-22 00:34:07 +01:00
Christopher O'Grady da134247b1 Remove duplicated stage and attribute from UserCharacter
These fields are on the Character entity.
2025-03-19 15:33:52 -04:00
nacabaroandGitHub e36a700d9f Merge pull request #25 from nacabaro/database/adventure
Adventure mode
2025-01-26 02:01:24 +01:00
Nacho fb6b6d2825 While I'm at it...
- Resized some buttons in the dialogs
- Added a dismiss button in get item dialog after adventure and in the send on adventure choose time dialogs
- Moved the export/import data lower in the settings screen
2025-01-26 01:59:43 +01:00
Nacho e17f6c23e4 Renaming terms and other bits and bobs
- Changed adventure database so that the original intended time is also stored with the final time. This will come useful once the algorithm for determining which object to give is made.
2025-01-26 01:40:32 +01:00
Nacho 409474b5d1 Phew
- Added adventure screen, dialogs, components and controllers needed to rock.
- Modified DTOs so that it is possible to tell if a character is in adventure or not
- Updated layout of items database
- Fixed an issue with importing bems where the character name would be read incorrectly
- Also added a ton of checks
2025-01-26 01:25:25 +01:00
nacabaroandGitHub 547318864b Merge pull request #24 from nacabaro/cleanup
Bigger icons plus material obscurity
2025-01-21 13:35:43 +01:00
Nacho 5e92895656 Made item icons a bit smaller in order to be similarly sized to character icons 2025-01-21 13:34:49 +01:00
Nacho 25500e9cb4 Made character icons bigger and also made the obscured characters follow material guidelines, as being pure black can be difficult to see in dark theme 2025-01-21 13:28:13 +01:00
nacabaroandGitHub 1a38cefb92 Merge pull request #23 from nacabaro/cleanup
Cleanup MainActivity
2025-01-21 13:12:38 +01:00
Nacho a974bd366e Moved read character from watch to its corresponding controller 2025-01-21 13:02:45 +01:00
Nacho bb5f66d167 Moved import card logic outside of the MainActivity 2025-01-21 12:42:52 +01:00
nacabaroandGitHub 09394871e5 Merge pull request #22 from nacabaro/database/items
Add items functionality
2025-01-20 15:49:22 +01:00
Nacho 972a640394 Added missing icons, ready to merge! 2025-01-20 15:46:53 +01:00
Nacho 35a7c9d959 Slowdowns and UI
- Current item can be seen from a character when clicked on it, you can also see how much time left does that item have on that character.
- Fixed slowdown issue in the items screen. Also fixed this issue in the storage screen as it'd also affect it if there are a lot of characters in the storage.
2025-01-20 15:23:40 +01:00
Nacho b4a73031e7 Working items
- Click on use item > then choose the mon you want to apply the item to
2025-01-20 14:45:28 +01:00
Nacho 3e41f82273 Forgot to push the updated db 2025-01-20 00:29:07 +01:00
Nacho f3740d4816 Here comes the train (with more things)
- Updated available items
- Created ItemsScreenController
- Moved the ItemsScreen to keep code organized
2025-01-20 00:06:42 +01:00
Nacho 305b776b52 Things
- Added items store and a way to switch between the store and your items
- Added an items dialog, click on it to see details of the item (description, amount) and use it
- Added items store, it lists all the items available
2025-01-19 13:41:50 +01:00
Nacho c0a67e382b Things items related
- Added a MyItems screen
- Added an ItemElement placeholder
- Included database with all the items in the app (not final)
- Also included some demo items

As of now items don't do anything, i'm still making the UI
2025-01-18 23:44:38 +01:00
nacabaroandGitHub bc7a05ccd3 Merge pull request #21 from nacabaro/db/data_export
Forgot to include the settingsscreencontroller interface my bad
2025-01-18 16:02:29 +01:00
Nacho 660dbaca19 Forgot to include the settingsscreencontroller interface my bad 2025-01-18 16:01:42 +01:00
nacabaroandGitHub b89dfb0df3 Merge pull request #20 from nacabaro/db/data_export
Added data importing/exporting
2025-01-18 12:11:58 +01:00
Nacho 23e233227a Finished refactoring the old SettingsScreenController.kt 2025-01-18 12:05:10 +01:00
Nacho 542072c238 Forgot to close DB before importing process
This will clear the database lock file before closing the application, ensuring that during the second startup the application does not see anything weird with the data. Not closing the database before import can lead to weird behavior.
2025-01-18 01:05:45 +01:00
Nacho 42dd87f0c4 Data export working
During export the database will be closed, otherwise the WAL will ruin the export. data will be saved as a .vbhelper file, then the app will be closed to avoid making any changes to the DB
For import, we check if the extension is .vbhelper, otherwise we can corrupt the database, IMPORTANT. Next we delete everything related to RoomDB and we swap it with the new files. Finally the app will be closed to ensure RoomDB is running on the new DB.

Finally, I still have to reintegrate the importApk functionality with the NewSettingsScreenController and get rid of the old one.
2025-01-18 00:45:16 +01:00
nacabaroandGitHub 635be60cdb Merge pull request #19 from nacabaro/dex/functionality
Dex stuff
2025-01-17 21:21:35 +01:00
81 changed files with 3173 additions and 720 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ android {
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionName = "Alpha 0.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Binary file not shown.
@@ -1,45 +1,25 @@
package com.github.nacabaro.vbhelper
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vb.dim.card.BemCard
import com.github.cfogrady.vb.dim.card.DimReader
import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.characters.Dex
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.SettingsScreenController
import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
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.utils.DeviceType
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.util.GregorianCalendar
class MainActivity : ComponentActivity() {
private var nfcCharacter = MutableStateFlow<NfcCharacter?>(null)
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
private val onActivityLifecycleListeners = HashMap<String, ActivityLifecycleListener>()
private fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener) {
@@ -54,26 +34,38 @@ class MainActivity : ComponentActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
registerFileActivityResult()
val application = applicationContext as VBHelper
val settingsScreenController = SettingsScreenController.Factory(this, ApkSecretsImporter(), application.container.dataStoreSecretsRepository)
.buildSettingScreenHandlers()
val scanScreenController = ScanScreenControllerImpl(
application.container.dataStoreSecretsRepository.secretsFlow,
this::handleReceivedNfcCharacter,
this,
this::registerActivityLifecycleListener,
this::unregisterActivityLifecycleListener)
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)
enableEdgeToEdge()
setContent {
VBHelperTheme {
MainApplication(settingsScreenController, scanScreenController)
MainApplication(
scanScreenController = scanScreenController,
settingsScreenController = settingsScreenController,
itemsScreenController = itemsScreenController,
adventureScreenController = adventureScreenController,
homeScreenController = homeScreenController,
storageScreenController = storageScreenController,
spriteViewerController = spriteViewerController
)
}
}
Log.i("MainActivity", "Activity onCreated")
}
@@ -93,232 +85,26 @@ class MainActivity : ComponentActivity() {
}
}
private fun registerFileActivityResult() {
activityResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
lifecycleScope.launch {
val application = applicationContext as VBHelper
val storageRepository = application.container.db
if (it.resultCode != RESULT_OK) {
Toast.makeText(applicationContext, "Import operation cancelled.", Toast.LENGTH_SHORT).show()
}
val contentResolver = applicationContext.contentResolver
val inputStream = contentResolver.openInputStream(it.data!!.data!!)
inputStream.use { fileReader ->
val dimReader = DimReader()
val card = dimReader.readCard(fileReader, false)
Log.i("MainActivity", "Card name: ${card is BemCard}")
val cardModel = Card(
dimId = card.header.dimId,
logo = card.spriteData.sprites[0].pixelData,
name = card.spriteData.text, // TODO Make user write card name
stageCount = card.adventureLevels.levels.size,
logoHeight = card.spriteData.sprites[0].height,
logoWidth = card.spriteData.sprites[0].width,
isBEm = card is BemCard
)
val dimId = storageRepository
.dimDao()
.insertNewDim(cardModel)
val characters = card.characterStats.characterEntries
var spriteCounter = when (card is BemCard) {
true -> 55
false -> 10
}
val domainCharacters = mutableListOf<Character>()
for (index in 0 until characters.size) {
domainCharacters.add(
Character(
dimId = dimId,
monIndex = index,
name = card.spriteData.sprites[spriteCounter].pixelData,
stage = characters[index].stage,
attribute = characters[index].attribute,
baseHp = characters[index].hp,
baseBp = characters[index].dp,
baseAp = characters[index].ap,
sprite1 = card.spriteData.sprites[spriteCounter + 1].pixelData,
sprite2 = card.spriteData.sprites[spriteCounter + 2].pixelData,
nameWidth = card.spriteData.sprites[spriteCounter].width,
nameHeight = card.spriteData.sprites[spriteCounter].height,
spritesWidth = card.spriteData.sprites[spriteCounter + 1].width,
spritesHeight = card.spriteData.sprites[spriteCounter + 1].height
)
)
// TODO: Improve this
if (card is BemCard) {
spriteCounter += 14
} else {
when (index) {
0 -> spriteCounter += 6
1 -> spriteCounter += 7
else -> spriteCounter += 14
}
}
}
storageRepository
.characterDao()
.insertCharacter(*domainCharacters.toTypedArray())
val sprites = card.spriteData.sprites.map { sprite ->
Sprites(
id = 0,
sprite = sprite.pixelData,
width = sprite.width,
height = sprite.height
)
}
storageRepository
.characterDao()
.insertSprite(*sprites.toTypedArray())
}
inputStream?.close()
Toast.makeText(applicationContext, "Import successful!", Toast.LENGTH_SHORT).show()
}
}
}
@Composable
private fun MainApplication(settingsScreenController: SettingsScreenController, scanScreenController: ScanScreenControllerImpl) {
private fun MainApplication(
scanScreenController: ScanScreenControllerImpl,
settingsScreenController: SettingsScreenControllerImpl,
itemsScreenController: ItemsScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl,
storageScreenController: StorageScreenControllerImpl,
homeScreenController: HomeScreenControllerImpl,
spriteViewerController: SpriteViewerControllerImpl
) {
AppNavigation(
applicationNavigationHandlers = AppNavigationHandlers(settingsScreenController, scanScreenController),
onClickImportCard = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
activityResultLauncher.launch(intent)
}
)
}
private fun handleReceivedNfcCharacter(character: NfcCharacter): String {
nfcCharacter.value = character
val importStatus = addCharacterScannedIntoDatabase()
return importStatus
}
//
/*
TODO:
- Support for regular VB
The good news is that the theory behind inserting to the database should be working
now, it's a matter of implementing the functionality to parse dim/bem cards and use my
domain model.
*/
private fun addCharacterScannedIntoDatabase(): String {
val application = applicationContext as VBHelper
val storageRepository = application.container.db
val dimData = storageRepository
.dimDao()
.getDimById(nfcCharacter.value!!.dimId.toInt())
if (dimData == null) return "Card not found"
val cardCharData = storageRepository
.characterDao()
.getCharacterByMonIndex(nfcCharacter.value!!.charIndex.toInt(), dimData.id)
val characterData = UserCharacter(
charId = cardCharData.id,
stage = nfcCharacter.value!!.stage.toInt(),
attribute = nfcCharacter.value!!.attribute,
ageInDays = nfcCharacter.value!!.ageInDays.toInt(),
nextAdventureMissionStage = nfcCharacter.value!!.nextAdventureMissionStage.toInt(),
mood = nfcCharacter.value!!.mood.toInt(),
vitalPoints = nfcCharacter.value!!.vitalPoints.toInt(),
transformationCountdown = nfcCharacter.value!!.transformationCountdownInMinutes.toInt(),
injuryStatus = nfcCharacter.value!!.injuryStatus,
trophies = nfcCharacter.value!!.trophies.toInt(),
currentPhaseBattlesWon = nfcCharacter.value!!.currentPhaseBattlesWon.toInt(),
currentPhaseBattlesLost = nfcCharacter.value!!.currentPhaseBattlesLost.toInt(),
totalBattlesWon = nfcCharacter.value!!.totalBattlesWon.toInt(),
totalBattlesLost = nfcCharacter.value!!.totalBattlesLost.toInt(),
activityLevel = nfcCharacter.value!!.activityLevel.toInt(),
heartRateCurrent = nfcCharacter.value!!.heartRateCurrent.toInt(),
characterType = when (nfcCharacter.value) {
is BENfcCharacter -> DeviceType.BEDevice
else -> DeviceType.VBDevice
},
isActive = true
)
storageRepository
.userCharacterDao()
.clearActiveCharacter()
val characterId: Long = storageRepository
.userCharacterDao()
.insertCharacterData(characterData)
if (nfcCharacter.value is BENfcCharacter) {
val beCharacter = nfcCharacter.value as BENfcCharacter
val extraCharacterData = BECharacterData(
id = characterId,
trainingHp = beCharacter.trainingHp.toInt(),
trainingAp = beCharacter.trainingAp.toInt(),
trainingBp = beCharacter.trainingBp.toInt(),
remainingTrainingTimeInMinutes = beCharacter.remainingTrainingTimeInMinutes.toInt(),
itemEffectActivityLevelValue = beCharacter.itemEffectActivityLevelValue.toInt(),
itemEffectMentalStateValue = beCharacter.itemEffectMentalStateValue.toInt(),
itemEffectMentalStateMinutesRemaining = beCharacter.itemEffectMentalStateMinutesRemaining.toInt(),
itemEffectActivityLevelMinutesRemaining = beCharacter.itemEffectActivityLevelMinutesRemaining.toInt(),
itemEffectVitalPointsChangeValue = beCharacter.itemEffectVitalPointsChangeValue.toInt(),
itemEffectVitalPointsChangeMinutesRemaining = beCharacter.itemEffectVitalPointsChangeMinutesRemaining.toInt(),
abilityRarity = beCharacter.abilityRarity,
abilityType = beCharacter.abilityType.toInt(),
abilityBranch = beCharacter.abilityBranch.toInt(),
abilityReset = beCharacter.abilityReset.toInt(),
rank = beCharacter.abilityReset.toInt(),
itemType = beCharacter.itemType.toInt(),
itemMultiplier = beCharacter.itemMultiplier.toInt(),
itemRemainingTime = beCharacter.itemRemainingTime.toInt(),
otp0 = "", //beCharacter.value!!.otp0.toString(),
otp1 = "", //beCharacter.value!!.otp1.toString(),
minorVersion = beCharacter.characterCreationFirmwareVersion.minorVersion.toInt(),
majorVersion = beCharacter.characterCreationFirmwareVersion.majorVersion.toInt(),
applicationNavigationHandlers = AppNavigationHandlers(
settingsScreenController,
scanScreenController,
itemsScreenController,
adventureScreenController,
storageScreenController,
homeScreenController,
spriteViewerController
)
storageRepository
.userCharacterDao()
.insertBECharacterData(extraCharacterData)
val transformationHistoryWatch = beCharacter.transformationHistory
transformationHistoryWatch.map { item ->
if (item.toCharIndex.toInt() != 255) {
val date = GregorianCalendar(item.year.toInt(), item.month.toInt(), item.day.toInt())
.time
.time
storageRepository
.characterDao()
.insertTransformation(characterId, item.toCharIndex.toInt(), dimData.id, date)
storageRepository
.dexDao()
.insertCharacter(item.toCharIndex.toInt(), dimData.id, date)
}
}
} else if (nfcCharacter.value is VBNfcCharacter) {
return "Not implemented yet"
}
return "Done reading character!"
)
}
}
@@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.components
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@@ -18,29 +17,27 @@ 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 androidx.compose.ui.unit.sp
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 androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import com.github.nacabaro.vbhelper.utils.getObscuredBitmap
import java.nio.ByteBuffer
@Composable
fun CharacterEntry(
icon: BitmapData,
obscure: Boolean = false,
modifier: Modifier = Modifier,
obscure: Boolean = false,
disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 3,
multiplier: Int = 4,
onClick: () -> Unit = { }
) {
val bitmap = remember (icon.bitmap) {
@@ -52,7 +49,10 @@ fun CharacterEntry(
Card(
shape = shape,
onClick = onClick,
onClick = when (disabled) {
true -> { {} }
false -> onClick
},
modifier = modifier
.aspectRatio(1f)
.padding(8.dp)
@@ -61,12 +61,16 @@ fun CharacterEntry(
contentAlignment = Alignment.BottomCenter,
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.padding(4.dp)
) {
Image(
bitmap = imageBitmap,
contentDescription = "Icon",
filterQuality = FilterQuality.None,
colorFilter = when (obscure) {
true -> ColorFilter.tint(color = MaterialTheme.colorScheme.secondary)
false -> null
},
modifier = Modifier
.size(dpSize)
)
@@ -79,8 +83,6 @@ fun ItemDisplay(
icon: Int,
textValue: String,
modifier: Modifier = Modifier,
iconSize: Dp = 48.dp,
textSize: TextUnit = 24.sp,
definition: String = "",
) {
val context = LocalContext.current
@@ -101,15 +103,14 @@ fun ItemDisplay(
painter = painterResource(icon),
contentDescription = "Vitals",
modifier = Modifier
.fillMaxSize(0.5f)
.padding(8.dp)
.size(iconSize)
)
Text(
text = textValue,
textAlign = TextAlign.Center,
fontSize = textSize,
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Bold,
)
}
}
@@ -23,7 +23,8 @@ fun TopBanner(
modifier: Modifier = Modifier,
onGearClick: (() -> Unit)? = null,
onBackClick: (() -> Unit)? = null,
onScanClick: (() -> Unit)? = null
onScanClick: (() -> Unit)? = null,
onAdventureClick: (() -> Unit)? = null
) {
Box( // Use Box to overlay elements
modifier = modifier
@@ -49,7 +50,18 @@ fun TopBanner(
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(
@@ -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)
}
@@ -4,7 +4,7 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.characters.Sprite
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@Dao
@@ -22,10 +22,10 @@ interface CharacterDao {
fun getCharacterByMonIndex(monIndex: Int, dimId: Long): Character
@Insert
suspend fun insertSprite(vararg sprite: Sprites)
suspend fun insertSprite(vararg sprite: Sprite)
@Query("SELECT * FROM Sprites")
suspend fun getAllSprites(): List<Sprites>
@Query("SELECT * FROM Sprite")
suspend fun getAllSprites(): List<Sprite>
@Query(
"""
@@ -19,11 +19,12 @@ interface DexDao {
@Query("""
SELECT
c.id AS id,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
s.spriteIdle1 AS spriteIdle,
s.width AS spriteWidth,
s.height AS spriteHeight,
d.discoveredOn AS discoveredOn
FROM character c
FROM Character c
JOIN Sprite s ON c.spriteId = s.id
LEFT JOIN dex d ON c.id = d.id
WHERE c.dimId = :cardId
""")
@@ -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>
}
@@ -4,6 +4,8 @@ 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
@@ -17,19 +19,26 @@ interface UserCharacterDao {
@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,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
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>?
@@ -38,17 +47,50 @@ interface UserCharacterDao {
"""
SELECT
uc.*,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard
c.stage,
c.attribute,
s.spriteIdle1 AS spriteIdle,
s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.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 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
@@ -59,17 +101,25 @@ interface UserCharacterDao {
"""
SELECT
uc.*,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard
c.stage,
c.attribute,
s.spriteIdle1 AS spriteIdle,
s.spriteIdle2 AS spriteIdle2,
s.width AS spriteWidth,
s.height AS spriteHeight,
c.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")
@@ -80,4 +130,15 @@ interface UserCharacterDao {
@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
}
@@ -2,28 +2,35 @@ 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.Sprites
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,
Sprites::class,
Sprite::class,
UserCharacter::class,
BECharacterData::class,
TransformationHistory::class,
Dex::class
Dex::class,
Items::class,
Adventure::class
]
)
abstract class AppDatabase : RoomDatabase() {
@@ -31,4 +38,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun characterDao(): CharacterDao
abstract fun userCharacterDao(): UserCharacterDao
abstract fun dexDao(): DexDao
abstract fun itemDao(): ItemDao
abstract fun adventureDao(): AdventureDao
abstract fun spriteDao(): SpriteDao
}
@@ -22,7 +22,9 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
context = context,
klass = AppDatabase::class.java,
"internalDb"
).build()
)
.createFromAsset("items.db")
.build()
}
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
@@ -1,12 +0,0 @@
package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Sprites(
@PrimaryKey(autoGenerate = true) val id : Int,
val sprite: ByteArray,
val width: Int,
val height: Int
)
@@ -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
)
@@ -3,6 +3,7 @@ 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 = [
@@ -11,23 +12,32 @@ import androidx.room.ForeignKey
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: Int, // This one too
val attribute: NfcCharacter.Attribute, // This one too
val baseHp: Int,
val baseBp: Int,
val baseAp: Int,
val sprite1: ByteArray,
val sprite2: ByteArray,
val nameWidth: Int,
val nameHeight: Int,
val spritesWidth: Int,
val spritesHeight: Int
val nameHeight: Int
)
@@ -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
)
@@ -20,7 +20,7 @@ data class BECharacterData (
val trainingHp: Int,
val trainingAp: Int,
val trainingBp: Int,
val remainingTrainingTimeInMinutes: Int,
var remainingTrainingTimeInMinutes: Int,
val itemEffectMentalStateValue: Int,
val itemEffectMentalStateMinutesRemaining: Int,
val itemEffectActivityLevelValue: Int,
@@ -32,9 +32,9 @@ data class BECharacterData (
val abilityBranch: Int,
val abilityReset: Int,
val rank: Int,
val itemType: Int,
val itemMultiplier: Int,
val itemRemainingTime: Int,
var itemType: Int,
var itemMultiplier: Int,
var itemRemainingTime: Int,
val otp0: String,
val otp1: String,
val minorVersion: Int,
@@ -17,11 +17,12 @@ import com.github.nacabaro.vbhelper.domain.characters.Character
)
]
)
/**
* UserCharacter represents and instance of a character. The charId should map to a Character
*/
data class UserCharacter (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
var charId: Long,
var stage: Int,
var attribute: NfcCharacter.Attribute,
var ageInDays: Int,
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
var mood: Int,
@@ -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
)
@@ -25,9 +25,14 @@ object CharacterDtos {
var heartRateCurrent: Int,
var characterType: DeviceType,
val spriteIdle: ByteArray,
val spriteIdle2: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val isBemCard: Boolean
val nameSprite: ByteArray,
val nameSpriteWidth: Int,
val nameSpriteHeight: Int,
val isBemCard: Boolean,
val isInAdventure: Boolean
)
data class DiMInfo(
@@ -51,4 +56,31 @@ object CharacterDtos {
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
)
}
@@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.navigation
import android.util.Log
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
@@ -12,20 +11,35 @@ import com.github.nacabaro.vbhelper.screens.BattlesScreen
import com.github.nacabaro.vbhelper.screens.DexScreen
import com.github.nacabaro.vbhelper.screens.DiMScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen
import com.github.nacabaro.vbhelper.screens.ItemsScreen
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
import com.github.nacabaro.vbhelper.screens.SettingsScreenController
import com.github.nacabaro.vbhelper.screens.SpriteViewer
import com.github.nacabaro.vbhelper.screens.StorageScreen
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: SettingsScreenController, val scanScreenController: ScanScreenControllerImpl)
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
fun AppNavigation(
applicationNavigationHandlers: AppNavigationHandlers,
onClickImportCard: () -> Unit,
) {
val navController = rememberNavController()
@@ -45,12 +59,15 @@ fun AppNavigation(
}
composable(NavigationItems.Home.route) {
HomeScreen(
navController = navController
navController = navController,
homeScreenController = applicationNavigationHandlers.homeScreenController
)
}
composable(NavigationItems.Storage.route) {
StorageScreen(
navController = navController
navController = navController,
adventureScreenController = applicationNavigationHandlers.adventureScreenController,
storageScreenController = applicationNavigationHandlers.storageScreenController
)
}
composable(NavigationItems.Scan.route) {
@@ -71,13 +88,13 @@ fun AppNavigation(
composable(NavigationItems.Settings.route) {
SettingsScreen(
navController = navController,
settingsScreenController = applicationNavigationHandlers.settingsScreenController,
onClickImportCard = onClickImportCard
settingsScreenController = applicationNavigationHandlers.settingsScreenController
)
}
composable(NavigationItems.Viewer.route) {
SpriteViewer(
navController = navController
navController = navController,
spriteViewerController = applicationNavigationHandlers.spriteViewerController
)
}
composable(NavigationItems.CardView.route) {
@@ -94,6 +111,29 @@ fun AppNavigation(
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
)
}
}
}
}
@@ -19,6 +19,7 @@ fun BottomNavigationBar(navController: NavController) {
NavigationItems.Dex,
NavigationItems.Storage,
)
NavigationBar {
val currentBackStackEntry = navController.currentBackStackEntryAsState()
val currentRoute = currentBackStackEntry.value?.destination?.route
@@ -16,4 +16,9 @@ sealed class NavigationItems (
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,12 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
@Composable
fun ItemsScreen(
navController: NavController
) {
Text(text = "Items")
}
@@ -1,71 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import android.net.Uri
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
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 kotlinx.coroutines.launch
data class SettingsScreenController(val apkFilePickLauncher: ActivityResultLauncher<Array<String>>) {
class Factory(private val componentActivity: ComponentActivity, private val secretsImporter: SecretsImporter, private val secretsRepository: SecretsRepository) {
fun buildSettingScreenHandlers(): SettingsScreenController {
return SettingsScreenController(
apkFilePickLauncher = buildFilePickerActivityLauncher(this::importApk)
)
}
private fun buildFilePickerActivityLauncher(onResult : (Uri?) ->Unit): ActivityResultLauncher<Array<String>> {
return componentActivity.registerForActivityResult(ActivityResultContracts.OpenDocument()) {
onResult.invoke(it)
}
}
private fun importApk(uri: Uri?) {
if(uri == null) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "APK Import Cancelled", Toast.LENGTH_SHORT)
.show()
}
return
}
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
componentActivity.contentResolver.openInputStream(uri).use {
if(it == null) {
componentActivity.runOnUiThread {
Toast.makeText(
componentActivity,
"Selected file is empty!",
Toast.LENGTH_SHORT
).show()
}
return@launch
}
var secrets: Secrets? = null
try {
secrets = secretsImporter.importSecrets(it)
} catch (e: Exception) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Secrets import failed. Please only select the official Vital Arena App 2.1.0 APK.", Toast.LENGTH_SHORT).show()
}
return@launch
}
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
secretsRepository.updateSecrets(secrets)
}.invokeOnCompletion {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Secrets successfully imported. Connections with devices are now possible.", Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
}
@@ -1,196 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import android.util.Log
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun StorageScreen(
navController: NavController
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
Scaffold (
topBar = { TopBanner(text = "My Digimon") }
) { contentPadding ->
if (monList.value.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(
text = "Nothing to see here",
textAlign = TextAlign.Center,
modifier = Modifier
)
}
}
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding())
) {
items(monList.value) { index ->
CharacterEntry(
icon = BitmapData(
bitmap = index.spriteIdle,
width = index.spriteWidth,
height = index.spriteHeight
),
onClick = {
selectedCharacter = index.id
}
)
if (selectedCharacter != null) {
StorageDialog(
characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null },
onClickSetActive = {
coroutineScope.launch {
withContext(Dispatchers.IO) {
storageRepository.setActiveCharacter(selectedCharacter!!)
selectedCharacter = null
}
navController.navigate(NavigationItems.Home.route)
}
},
onSendToBracelet = {
navController.navigate(
NavigationItems.Scan.route.replace(
"{characterId}",
selectedCharacter.toString()
)
)
}
)
}
}
}
}
}
@Composable
fun StorageDialog(
characterId: Long,
onDismissRequest: () -> Unit,
onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val character = remember { mutableStateOf<UserCharacter?>(null) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
character.value = storageRepository.getSingleCharacter(characterId)
}
}
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Card(
shape = RoundedCornerShape(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
if (character.value != null) {
Text(
text = character.value?.toString() ?: "Loading...",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(8.dp)
)
}
Row (
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
Button(
onClick = onSendToBracelet
) {
Text(text = "Send to bracelet")
}
Button(
onClick = onClickSetActive
) {
Text(text = "Set active")
}
Button(
onClick = onDismissRequest
) {
Text(text = "Close")
}
}
}
}
}
}
@@ -0,0 +1,68 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import java.util.Locale
@Composable
fun AdventureEntry(
icon: BitmapData,
timeLeft: Long,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
val bitmap = remember (icon.bitmap) { icon.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * 4 / density).dp
Card(
onClick = onClick,
modifier = modifier
.padding(8.dp)
.fillMaxWidth()
) {
Row(
modifier = Modifier
.padding(8.dp)
.height(96.dp)
) {
Image(
bitmap = imageBitmap,
contentDescription = null,
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
)
Text(
text = when {
timeLeft < 0 -> "Adventure finished"
else -> "Time left: ${formatSeconds(timeLeft)}"
}
)
}
}
}
fun formatSeconds(totalSeconds: Long): String {
val hours = totalSeconds / 3600
val minutes = (totalSeconds % 3600) / 60
val seconds = totalSeconds % 60
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
}
@@ -0,0 +1,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
}
)
}
}
@@ -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)
}
@@ -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
)
}
}
@@ -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")
}
}
}
}
}
}
@@ -15,8 +15,10 @@ 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
@@ -153,6 +155,24 @@ fun BEBEmHomeScreen(
.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
@@ -16,8 +16,10 @@ 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
@@ -33,7 +35,7 @@ fun BEDiMHomeScreen(
.padding(top = contentPadding.calculateTopPadding())
.verticalScroll(state = rememberScrollState())
) {
Row (
Row(
modifier = Modifier
.fillMaxWidth()
) {
@@ -49,7 +51,7 @@ fun BEDiMHomeScreen(
.weight(1f)
.aspectRatio(1f)
)
Column (
Column(
modifier = Modifier
.weight(0.5f)
.aspectRatio(0.5f)
@@ -74,7 +76,7 @@ fun BEDiMHomeScreen(
)
}
}
Row (
Row(
modifier = Modifier
.fillMaxWidth()
) {
@@ -108,7 +110,7 @@ fun BEDiMHomeScreen(
.padding(8.dp)
)
}
Row (
Row(
modifier = Modifier
.fillMaxWidth()
) {
@@ -130,8 +132,13 @@ fun BEDiMHomeScreen(
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
val battleWinPercentage =
activeMon.totalBattlesWon.toFloat() / (activeMon.totalBattlesWon + activeMon.totalBattlesLost).toFloat()
String.format(
Locale.getDefault(),
"%.2f",
battleWinPercentage * 100
) + " %" // Specify locale
}
},
definition = "Total battle win %",
@@ -145,8 +152,13 @@ fun BEDiMHomeScreen(
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
val battleWinPercentage =
activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
String.format(
Locale.getDefault(),
"%.2f",
battleWinPercentage * 100
) + " %" // Specify locale
}
},
definition = "Current phase win %",
@@ -155,8 +167,26 @@ fun BEDiMHomeScreen(
.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 (
Row(
modifier = Modifier
.fillMaxWidth()
) {
@@ -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")
}
}
}
}
}
@@ -1,19 +1,27 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.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
@@ -28,7 +36,8 @@ import kotlinx.coroutines.withContext
@Composable
fun HomeScreen(
navController: NavController
navController: NavController,
homeScreenController: HomeScreenControllerImpl
) {
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
@@ -36,6 +45,8 @@ fun HomeScreen(
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) {
@@ -47,6 +58,13 @@ fun HomeScreen(
}
}
LaunchedEffect(true) {
homeScreenController
.didAdventureMissionsFinish {
adventureMissionsFinished = it
}
}
Scaffold (
topBar = {
TopBanner(
@@ -72,7 +90,6 @@ fun HomeScreen(
}
} else {
if (activeMon.value!!.isBemCard) {
Log.d("HomeScreen", "BEDeviceBEm")
BEBEmHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
@@ -80,7 +97,6 @@ fun HomeScreen(
contentPadding = contentPadding
)
} else if (!activeMon.value!!.isBemCard && activeMon.value!!.characterType == DeviceType.BEDevice) {
Log.d("HomeScreen", "BEDevice")
BEDiMHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
@@ -97,6 +113,40 @@ fun HomeScreen(
}
}
}
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
}
}
}
@@ -0,0 +1,5 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
}
@@ -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,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)
}
}
}
}
@@ -0,0 +1,6 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
interface ItemsScreenController {
fun applyItem(itemId: Long, characterId: Long, onCompletion: () -> Unit)
}
@@ -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 }
)
}
}
}
@@ -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")
}
}
}
}
}
}
@@ -11,20 +11,26 @@ 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 nfcHandler: (NfcCharacter)->String,
private val context: ComponentActivity,
private val componentActivity: ComponentActivity,
private val registerActivityLifecycleListener: (String, ActivityLifecycleListener)->Unit,
private val unregisterActivityLifecycleListener: (String)->Unit,
): ScanScreenController {
@@ -32,9 +38,9 @@ class ScanScreenControllerImpl(
private val nfcAdapter: NfcAdapter
init {
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(context)
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity)
if (maybeNfcAdapter == null) {
Toast.makeText(context, "No NFC on device!", Toast.LENGTH_SHORT).show()
Toast.makeText(componentActivity, "No NFC on device!", Toast.LENGTH_SHORT).show()
}
nfcAdapter = maybeNfcAdapter
checkSecrets()
@@ -43,7 +49,7 @@ class ScanScreenControllerImpl(
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {
handleTag(secrets) { tagCommunicator ->
val character = tagCommunicator.receiveCharacter()
val resultMessage = nfcHandler(character)
val resultMessage = addCharacterScannedIntoDatabase(character)
onComplete.invoke()
resultMessage
}
@@ -51,7 +57,7 @@ class ScanScreenControllerImpl(
override fun cancelRead() {
if(nfcAdapter.isEnabled) {
nfcAdapter.disableReaderMode(context)
nfcAdapter.disableReaderMode(componentActivity)
}
}
@@ -74,7 +80,7 @@ class ScanScreenControllerImpl(
val options = Bundle()
// Work around for some broken Nfc firmware implementations that poll the card too fast
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
nfcAdapter.enableReaderMode(context, buildOnReadTag(secrets, handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
nfcAdapter.enableReaderMode(componentActivity, buildOnReadTag(secrets, handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
options
)
}
@@ -85,26 +91,26 @@ class ScanScreenControllerImpl(
return { tag->
val nfcData = NfcA.get(tag)
if (nfcData == null) {
context.runOnUiThread {
Toast.makeText(context, "Tag detected is not VB", Toast.LENGTH_SHORT).show()
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Tag detected is not VB", Toast.LENGTH_SHORT).show()
}
}
nfcData.connect()
nfcData.use {
val tagCommunicator = TagCommunicator.getInstance(nfcData, secrets.getCryptographicTransformerMap())
val successText = handlerFunc(tagCommunicator)
context.runOnUiThread {
Toast.makeText(context, successText, Toast.LENGTH_SHORT).show()
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, successText, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun checkSecrets() {
context.lifecycleScope.launch(Dispatchers.IO) {
if(secretsFlow.stateIn(context.lifecycleScope).value.isMissingSecrets()) {
context.runOnUiThread {
Toast.makeText(context, "Missing Secrets. Go to settings and import Vital Arena APK", Toast.LENGTH_SHORT).show()
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
if(secretsFlow.stateIn(componentActivity.lifecycleScope).value.isMissingSecrets()) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Missing Secrets. Go to settings and import Vital Arena APK", Toast.LENGTH_SHORT).show()
}
}
}
@@ -141,7 +147,105 @@ class ScanScreenControllerImpl(
// EXTRACTED DIRECTLY FROM EXAMPLE APP
private fun showWirelessSettings() {
Toast.makeText(context, "NFC must be enabled", Toast.LENGTH_SHORT).show()
context.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
Toast.makeText(componentActivity, "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!"
}
}
@@ -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.") { }
}
}
}
@@ -1,9 +1,5 @@
package com.github.nacabaro.vbhelper.screens
package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -22,12 +18,12 @@ 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: SettingsScreenController,
onClickImportCard: () -> Unit
settingsScreenController: SettingsScreenControllerImpl,
) {
Scaffold (
topBar = {
@@ -49,24 +45,29 @@ fun SettingsScreen(
) {
SettingsSection("NFC Communication")
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
settingsScreenController.apkFilePickLauncher.launch(arrayOf("*/*"))
settingsScreenController.onClickImportApk()
}
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file", onClick = onClickImportCard)
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") { }
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()
}
}
}
}
fun buildFilePickLauncher(activity: ComponentActivity, onItemPicked: (Uri?) -> Unit): ActivityResultLauncher<Array<String>> {
return activity.registerForActivityResult(ActivityResultContracts.OpenDocument()) {
onItemPicked.invoke(it)
}
}
@Composable
fun SettingsEntry(
title: String,
@@ -0,0 +1,8 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
interface SettingsScreenController {
fun onClickOpenDirectory()
fun onClickImportDatabase()
fun onClickImportApk()
fun onClickImportCard()
}
@@ -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()
}
}
}
}
}
}
@@ -1,55 +1,38 @@
package com.github.nacabaro.vbhelper.screens
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.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.source.SpriteRepo
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
@Composable
fun SpriteViewer(
navController: NavController
navController: NavController,
spriteViewerController: SpriteViewerController
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val db = application.container.db
val spriteRepo = SpriteRepo(db)
val spriteList = remember { mutableStateListOf<Sprites>() }
val spriteList = remember { mutableStateListOf<Bitmap>() }
Log.d("SpriteViewer", "spriteList: $spriteList")
LaunchedEffect(spriteRepo) {
coroutineScope.launch {
spriteList.clear()
spriteList.addAll(spriteRepo.getAllSprites())
}
LaunchedEffect(spriteViewerController) {
val sprites = spriteViewerController.getAllSprites()
val bitmapData = spriteViewerController.convertToBitmap(sprites)
spriteList.addAll(bitmapData)
}
Scaffold (
@@ -69,13 +52,7 @@ fun SpriteViewer(
.padding(top = contentPadding.calculateTopPadding())
) {
items(spriteList) { sprite ->
val bitmap = remember (sprite.sprite) {
Log.d("SpriteViewer", "sprite: $sprite")
Bitmap.createBitmap(sprite.width, sprite.height, Bitmap.Config.RGB_565).apply {
copyPixelsFromBuffer(ByteBuffer.wrap(sprite.sprite))
}
}
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val imageBitmap = remember(sprite) { sprite.asImageBitmap() }
Image(
bitmap = imageBitmap,
contentDescription = "Sprite",
@@ -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>
}
@@ -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
}
}
@@ -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")
}
}
}
}
}
@@ -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 }
)
}
}
@@ -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
}
)
}
}
}
@@ -0,0 +1,5 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
interface StorageScreenController {
fun setActive(characterId: Long, onCompletion: () -> Unit)
}
@@ -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,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()
}
}
@@ -1,12 +0,0 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.Sprites
class SpriteRepo (
private val db: AppDatabase
) {
suspend fun getAllSprites(): List<Sprites> {
return db.characterDao().getAllSprites()
}
}
@@ -2,8 +2,6 @@ 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.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
class StorageRepository (
@@ -13,8 +11,8 @@ class StorageRepository (
return db.userCharacterDao().getAllCharacters()
}
suspend fun getSingleCharacter(id: Long): UserCharacter {
return db.userCharacterDao().getCharacter(id)
suspend fun getSingleCharacter(id: Long): CharacterDtos.CharacterWithSprites {
return db.userCharacterDao().getCharacterWithSprites(id)
}
suspend fun getCharacterBeData(id: Long): BECharacterData {
@@ -37,8 +35,7 @@ class StorageRepository (
return db.userCharacterDao().deleteCharacterById(id)
}
fun setActiveCharacter(id: Long) {
db.userCharacterDao().clearActiveCharacter()
return db.userCharacterDao().setActiveCharacter(id)
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> {
return db.adventureDao().getAdventureCharacters()
}
}
@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m300.57,506.26c0,-5.08 0.1,-11.73 0.29,-19.95 0.39,-8.21 0.68,-15.45 0.88,-21.71 -0.98,1.17 -3.13,3.32 -6.45,6.45 -3.13,2.93 -6.06,5.57 -8.8,7.92l-24.05,19.36L241.02,471.65 308.49,417.97l36.37,0l0,209.44L300.57,627.41Z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m518.57,522.69q0,33.73 -7.33,57.79 -7.04,24.05 -23.17,36.96 -15.84,12.91 -42.83,12.91 -37.84,0 -55.44,-28.45 -17.6,-28.75 -17.6,-79.2 0,-34.03 7.04,-58.08 7.04,-24.05 23.17,-36.96 16.13,-12.91 42.83,-12.91 37.55,0 55.44,28.45 17.89,28.45 17.89,79.49zM416.2,522.69q0,35.79 6.16,53.97 6.16,17.89 22.88,17.89 16.43,0 22.88,-17.89 6.45,-17.89 6.45,-53.97 0,-35.79 -6.45,-53.97 -6.45,-18.19 -22.88,-18.19 -16.72,0 -22.88,18.19 -6.16,18.19 -6.16,53.97z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m600.71,449.94q0,11.73 -0.88,22.59 -0.59,10.85 -1.17,15.25l2.35,0q7.63,-12.32 19.65,-17.89 12.03,-5.57 26.69,-5.57 26.11,0 41.65,14.08 15.84,13.79 15.84,44.59l0,104.43l-43.71,0l0,-93.57q0,-34.61 -25.81,-34.61 -19.65,0 -27.28,13.79 -7.33,13.49 -7.33,39.01l0,75.39l-43.71,0l0,-222.93l43.71,0z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="m287.83,506.91q0,-7.63 0.29,-19.95 0.59,-12.32 0.88,-21.71 -1.47,1.76 -6.45,6.45 -4.69,4.4 -8.8,7.92L249.7,498.99 228.29,472.3 295.75,418.62l36.37,0l0,209.44l-44.29,0z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="m386.69,628.06l0,-30.8l52.51,-53.09q15.84,-16.43 25.52,-27.28 9.68,-11.15 14.08,-19.95 4.4,-9.09 4.4,-19.36 0,-12.61 -7.04,-18.77 -6.75,-6.16 -18.48,-6.16 -12.03,0 -23.47,5.57Q422.77,463.79 410.15,474.06L386.1,445.61Q395.19,437.69 405.17,430.94 415.43,424.19 428.63,420.09 442.13,415.69 460.9,415.69q20.53,0 35.2,7.63 14.96,7.33 22.88,20.24 8.21,12.61 8.21,28.75 0,17.31 -7.04,31.68 -6.75,14.37 -19.95,28.45 -12.91,14.08 -31.39,31.09l-26.99,25.23l0,2.05l91.23,0l0,37.25z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="m606.98,450.59c0,7.82 -0.29,15.35 -0.88,22.59 -0.39,7.24 -0.78,12.32 -1.17,15.25L607.27,488.43C612.36,480.22 618.91,474.25 626.93,470.54 634.94,466.82 643.84,464.97 653.62,464.97c17.4,0 31.29,4.69 41.65,14.08 10.56,9.19 15.84,24.05 15.84,44.59L711.11,628.06L667.41,628.06l0,-93.57c0,-23.08 -8.6,-34.61 -25.81,-34.61 -13.1,0 -22.2,4.6 -27.28,13.79 -4.89,9 -7.33,22 -7.33,39.01L606.98,628.06L563.27,628.06L563.27,413.88l43.71,0z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="m337.48,503.79q0,-11.09 0.43,-29.01 0.85,-17.92 1.28,-31.57 -2.13,2.56 -9.39,9.39 -6.83,6.4 -12.8,11.52L282.01,492.27 250.87,453.44 349,375.36L401.91,375.36L401.91,680L337.48,680Z"
android:fillColor="#000000"/>
<path
android:pathData="m594.76,485.44q27.73,0 49.49,10.67 21.76,10.67 34.13,31.15 12.8,20.48 12.8,50.77 0,49.49 -30.72,78.08Q629.75,684.27 569.59,684.27 545.7,684.27 524.36,680 503.46,675.73 487.67,667.63L487.67,612.16q15.79,8.11 37.97,14.08 22.19,5.55 41.81,5.55 28.59,0 43.52,-11.52 15.36,-11.95 15.36,-36.69 0,-46.08 -61.01,-46.08 -11.95,0 -24.75,2.56 -12.8,2.13 -21.33,4.27l-25.6,-13.65 11.52,-155.31l165.12,0l0,54.61L561.48,429.97l-5.55,59.73q7.25,-1.28 15.36,-2.56 8.53,-1.71 23.47,-1.71z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="M239.19,624.91L239.19,594.11l52.51,-53.09q15.84,-16.43 25.52,-27.28 9.68,-11.15 14.08,-19.95 4.4,-9.09 4.4,-19.36 0,-12.61 -7.04,-18.77 -6.75,-6.16 -18.48,-6.16 -12.03,0 -23.47,5.57 -11.44,5.57 -24.05,15.84L238.6,442.46Q247.7,434.54 257.67,427.79 267.94,421.05 281.14,416.94 294.63,412.54 313.4,412.54q20.53,0 35.2,7.63 14.96,7.33 22.88,20.24 8.21,12.61 8.21,28.75 0,17.31 -7.04,31.68 -6.75,14.37 -19.95,28.45 -12.91,14.08 -31.39,31.09l-26.99,25.23l0,2.05l91.23,0L385.56,624.91Z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="M487.35,624.91L487.35,581.5L397.88,581.5L397.88,550.7L489.7,415.47L530.47,415.47L530.47,547.18L555.7,547.18L555.7,581.5L530.47,581.5L530.47,624.91ZM487.35,497.02q0,-10.27 0.29,-17.01 0.59,-6.75 0.88,-12.03l-1.17,0Q482.36,477.95 474.74,489.39L436.9,547.18L487.35,547.18Z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="m609.48,447.47c0,7.82 -0.29,15.35 -0.88,22.59 -0.39,7.24 -0.78,12.32 -1.17,15.25L609.77,485.31C614.86,477.09 621.41,471.13 629.43,467.41 637.44,463.7 646.34,461.84 656.12,461.84c17.4,0 31.29,4.69 41.65,14.08 10.56,9.19 15.84,24.05 15.84,44.59l0,104.43L669.91,624.93l0,-93.57c0,-23.08 -8.6,-34.61 -25.81,-34.61 -13.1,0 -22.2,4.6 -27.28,13.79 -4.89,9 -7.33,22 -7.33,39.01l0,75.39L565.77,624.93L565.77,414.5l43.71,0z"
android:strokeWidth="0"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="m453.77,437.38q0,31.57 -19.2,50.35 -18.77,18.77 -46.51,25.6l0,1.28q36.69,4.27 55.47,22.19 19.2,17.92 19.2,48.21 0,26.45 -13.23,47.79Q436.71,653.7 409.4,666.07 382.52,678.02 339.85,678.02q-49.49,0 -87.89,-16.64L251.96,606.76q19.63,9.81 40.96,14.93 21.76,5.12 40.11,5.12 34.56,0 48.21,-11.95 14.08,-11.95 14.08,-33.71 0,-12.8 -6.4,-21.33Q382.52,550.87 366.31,546.6 350.52,541.91 321.93,541.91L298.89,541.91l0,-49.49l23.47,0q28.16,0 42.67,-5.12 14.93,-5.55 20.05,-14.51 5.55,-9.39 5.55,-21.33 0,-16.21 -10.24,-25.17 -9.81,-9.39 -33.28,-9.39 -21.76,0 -37.97,7.68 -15.79,7.25 -26.88,14.51L252.39,394.71q17.92,-12.8 41.81,-21.33 24.32,-8.53 57.6,-8.53 46.93,0 74.24,19.2 27.73,18.77 27.73,53.33z"
android:fillColor="#000000"/>
<path
android:pathData="m715.32,521.43q0,49.07 -10.67,84.05 -10.24,34.99 -33.71,53.76Q647.91,678.02 608.66,678.02q-55.04,0 -80.64,-41.39 -25.6,-41.81 -25.6,-115.2 0,-49.49 10.24,-84.48 10.24,-34.99 33.71,-53.76 23.47,-18.77 62.29,-18.77 54.61,0 80.64,41.39 26.03,41.39 26.03,115.63zM566.42,521.43q0,52.05 8.96,78.51 8.96,26.03 33.28,26.03 23.89,0 33.28,-26.03 9.39,-26.03 9.39,-78.51 0,-52.05 -9.39,-78.51 -9.39,-26.45 -33.28,-26.45 -24.32,0 -33.28,26.45 -8.96,26.45 -8.96,78.51z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m381.45,481.69q20.8,0 37.12,8 16.32,8 25.6,23.36 9.6,15.36 9.6,38.08 0,37.12 -23.04,58.56 -23.04,21.12 -68.16,21.12 -17.92,0 -33.92,-3.2 -15.68,-3.2 -27.52,-9.28v-41.6q11.84,6.08 28.48,10.56 16.64,4.16 31.36,4.16 21.44,0 32.64,-8.64 11.52,-8.96 11.52,-27.52 0,-34.56 -45.76,-34.56 -8.96,0 -18.56,1.92 -9.6,1.6 -16,3.2l-19.2,-10.24 8.64,-116.48h123.84v40.96h-81.6l-4.16,44.8q5.44,-0.96 11.52,-1.92 6.4,-1.28 17.6,-1.28z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m540.81,434.01c0,8.53 -0.32,16.75 -0.96,24.64 -0.43,7.89 -0.85,13.44 -1.28,16.64h2.56c5.55,-8.96 12.69,-15.47 21.44,-19.52 8.75,-4.05 18.45,-6.08 29.12,-6.08 18.99,0 34.13,5.12 45.44,15.36 11.52,10.03 17.28,26.24 17.28,48.64v113.92h-47.68v-102.08c0,-25.17 -9.39,-37.76 -28.16,-37.76 -14.29,0 -24.21,5.01 -29.76,15.04 -5.33,9.81 -8,24 -8,42.56v82.24h-47.68v-228.82h47.68z"
android:strokeWidth="0"
android:fillColor="#000000"/>
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m360,120v-80h240v80zM480,880c-49.33,0 -95.83,-9.5 -139.5,-28.5 -43.67,-19 -81.83,-44.83 -114.5,-77.5 -32.67,-32.67 -58.5,-70.83 -77.5,-114.5 -19,-43.67 -28.5,-90.17 -28.5,-139.5 0,-49.33 9.5,-95.83 28.5,-139.5 19,-43.67 44.83,-81.83 77.5,-114.5 32.67,-32.67 70.83,-58.5 114.5,-77.5 43.67,-19 90.17,-28.5 139.5,-28.5 41.33,0 81,6.67 119,20 38,13.33 73.67,32.67 107,58l56,-56 56,56 -56,56c25.33,33.33 44.67,69 58,107 13.33,38 20,77.67 20,119 0,49.33 -9.5,95.83 -28.5,139.5 -19,43.67 -44.83,81.83 -77.5,114.5 -32.67,32.67 -70.83,58.5 -114.5,77.5 -43.67,19 -90.17,28.5 -139.5,28.5zM480,800c77.33,0 143.33,-27.33 198,-82 54.67,-54.67 82,-120.67 82,-198 0,-77.33 -27.33,-143.33 -82,-198 -54.67,-54.67 -120.67,-82 -198,-82 -77.33,0 -143.33,27.33 -198,82 -54.67,54.67 -82,120.67 -82,198 0,77.33 27.33,143.33 82,198 54.67,54.67 120.67,82 198,82z"
android:fillColor="#000000"/>
<path
android:pathData="m240.86,541.97q0,-26.45 3.84,-52.05 3.84,-25.6 13.23,-48.21 9.81,-23.04 26.88,-40.53 17.49,-17.92 43.95,-27.73 26.88,-10.24 64.85,-10.24 8.96,0 20.91,0.85 11.95,0.43 20.05,2.13L434.57,417.81q-8.11,-2.13 -17.92,-2.99 -9.39,-1.28 -18.77,-1.28 -37.97,0 -58.88,11.95 -20.48,11.95 -29.01,33.71 -8.53,21.33 -9.81,49.49l2.56,0Q311.26,493.76 327.05,483.52 343.26,473.28 368.86,473.28q40.11,0 63.57,25.17 23.47,25.17 23.47,71.25 0,49.49 -28.16,77.65Q400.01,675.52 352.22,675.52q-31.15,0 -56.32,-14.08Q270.73,646.93 255.79,617.49 240.86,587.62 240.86,541.97ZM350.94,623.89q18.77,0 30.72,-12.8 11.95,-13.23 11.95,-40.53 0,-22.19 -10.24,-34.99 -10.24,-12.8 -31.15,-12.8 -14.08,0 -24.75,6.4 -10.67,5.97 -16.64,15.79 -5.97,9.81 -5.97,20.05 0,14.08 5.12,27.73 5.12,13.23 15.36,22.19 10.67,8.96 25.6,8.96z"
android:fillColor="#000000"/>
<path
android:pathData="m710.62,518.93q0,49.07 -10.67,84.05 -10.24,34.99 -33.71,53.76Q643.21,675.52 603.96,675.52q-55.04,0 -80.64,-41.39 -25.6,-41.81 -25.6,-115.2 0,-49.49 10.24,-84.48 10.24,-34.99 33.71,-53.76 23.47,-18.77 62.29,-18.77 54.61,0 80.64,41.39 26.03,41.39 26.03,115.63zM561.71,518.93q0,52.05 8.96,78.51 8.96,26.03 33.28,26.03 23.89,0 33.28,-26.03 9.39,-26.03 9.39,-78.51 0,-52.05 -9.39,-78.51 -9.39,-26.45 -33.28,-26.45 -24.32,0 -33.28,26.45 -8.96,26.45 -8.96,78.51z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m296,736 l-56,-56 240,-240 240,240 -56,56 -184,-183 -184,183ZM296,496 L240,440 480,200 720,440 664,496 480,313 296,496Z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m664,229.06 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
<path
android:pathData="m664,434.94 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m296,730.94 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
<path
android:pathData="m296,525.06 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M40,840v-160l80,-80v-240l-80,-80v-160h80v80h80v-80h80v80h80v-80h80v160l-80,80v40h240v-40l-80,-80v-160h80v80h80v-80h80v80h80v-80h80v160l-80,80v240l80,80v160L560,840v-120q0,-33 -23.5,-56.5T480,640q-33,0 -56.5,23.5T400,720v120L40,840ZM120,760h200v-40q0,-66 47,-113t113,-47q66,0 113,47t47,113v40h200v-47l-80,-80v-306l47,-47L633,280l47,47v153L280,480v-153l47,-47L153,280l47,47v306l-80,80v47ZM480,520Z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M440,838q-121,-15 -200.5,-105.5T160,520q0,-66 26,-126.5T260,288l57,57q-38,34 -57.5,79T240,520q0,88 56,155.5T440,758v80ZM520,838v-80q87,-16 143.5,-83T720,520q0,-100 -70,-170t-170,-70h-3l44,44 -56,56 -140,-140 140,-140 56,56 -44,44h3q134,0 227,93t93,227q0,121 -79.5,211.5T520,838Z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,880q-139,-35 -229.5,-159.5T160,444v-244l320,-120 320,120v244q0,152 -90.5,276.5T480,880ZM480,796q104,-33 172,-132t68,-220v-189l-240,-90 -240,90v189q0,121 68,220t172,132ZM480,480Z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m664,332 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m296,628 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m664,332 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
<path
android:pathData="m664,537.89 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
<path
android:pathData="m664,126.11 l56,56 -240,240 -240,-240 56,-56 184,183z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m296,628 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
<path
android:pathData="m296,422.11 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
<path
android:pathData="m296,833.89 l-56,-56 240,-240 240,240 -56,56 -184,-183z"
android:fillColor="#000000"/>
</vector>
+4 -4
View File
@@ -1,18 +1,18 @@
[versions]
agp = "8.7.3"
datastore = "1.1.1"
datastore = "1.1.2"
kotlin = "2.0.0"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.9.3"
composeBom = "2024.04.01"
activityCompose = "1.10.0"
composeBom = "2025.01.00"
protobufGradlePlugin = "0.9.4"
protobufJavalite = "4.27.0"
roomRuntime = "2.6.1"
vbNfcReader = "0.1.0"
vbNfcReader = "0.2.0-SNAPSHOT"
dimReader = "2.1.0"
[libraries]