mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-10-01 07:31:37 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb6b6d2825 | ||
|
|
e17f6c23e4 | ||
|
|
409474b5d1 | ||
|
|
547318864b | ||
|
|
5e92895656 | ||
|
|
25500e9cb4 | ||
|
|
1a38cefb92 | ||
|
|
a974bd366e | ||
|
|
bb5f66d167 | ||
|
|
09394871e5 |
Binary file not shown.
@@ -1,44 +1,24 @@
|
||||
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.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.settingsScreen.SettingsScreenControllerImpl
|
||||
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
|
||||
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) {
|
||||
@@ -53,30 +33,36 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
registerFileActivityResult()
|
||||
|
||||
val application = applicationContext as VBHelper
|
||||
val scanScreenController = ScanScreenControllerImpl(
|
||||
application.container.dataStoreSecretsRepository.secretsFlow,
|
||||
this::handleReceivedNfcCharacter,
|
||||
this,
|
||||
this::registerActivityLifecycleListener,
|
||||
this::unregisterActivityLifecycleListener
|
||||
)
|
||||
val settingsScreenController = SettingsScreenControllerImpl(this)
|
||||
val itemsScreenController = ItemsScreenControllerImpl(this)
|
||||
val adventureScreenController = AdventureScreenControllerImpl(this)
|
||||
val storageScreenController = StorageScreenControllerImpl(this)
|
||||
val homeScreenController = HomeScreenControllerImpl(this)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
VBHelperTheme {
|
||||
MainApplication(
|
||||
scanScreenController = scanScreenController,
|
||||
settingsScreenController = settingsScreenController,
|
||||
itemsScreenController = itemsScreenController
|
||||
itemsScreenController = itemsScreenController,
|
||||
adventureScreenController = adventureScreenController,
|
||||
homeScreenController = homeScreenController,
|
||||
storageScreenController = storageScreenController
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("MainActivity", "Activity onCreated")
|
||||
}
|
||||
|
||||
@@ -96,240 +82,25 @@ 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(
|
||||
scanScreenController: ScanScreenControllerImpl,
|
||||
settingsScreenController: SettingsScreenControllerImpl,
|
||||
itemsScreenController: ItemsScreenControllerImpl
|
||||
) {
|
||||
itemsScreenController: ItemsScreenControllerImpl,
|
||||
adventureScreenController: AdventureScreenControllerImpl,
|
||||
storageScreenController: StorageScreenControllerImpl,
|
||||
homeScreenController: HomeScreenControllerImpl,
|
||||
|
||||
) {
|
||||
AppNavigation(
|
||||
applicationNavigationHandlers = AppNavigationHandlers(
|
||||
settingsScreenController,
|
||||
scanScreenController,
|
||||
itemsScreenController
|
||||
),
|
||||
onClickImportCard = {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
}
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
itemsScreenController,
|
||||
adventureScreenController,
|
||||
storageScreenController,
|
||||
homeScreenController
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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(),
|
||||
)
|
||||
|
||||
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!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ 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
|
||||
@@ -34,8 +35,9 @@ fun CharacterEntry(
|
||||
icon: BitmapData,
|
||||
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) {
|
||||
@@ -47,7 +49,10 @@ fun CharacterEntry(
|
||||
|
||||
Card(
|
||||
shape = shape,
|
||||
onClick = onClick,
|
||||
onClick = when (disabled) {
|
||||
true -> { {} }
|
||||
false -> onClick
|
||||
},
|
||||
modifier = modifier
|
||||
.aspectRatio(1f)
|
||||
.padding(8.dp)
|
||||
@@ -56,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)
|
||||
)
|
||||
|
||||
@@ -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,6 +50,17 @@ 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) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
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.sprite1 AS spriteIdle,
|
||||
c.spritesWidth AS spriteWidth,
|
||||
c.spritesHeight AS spriteHeight,
|
||||
d.isBEm as isBemCard,
|
||||
a.finishesAdventure AS finishesAdventure,
|
||||
a.originalDuration AS originalTimeInMinutes
|
||||
FROM UserCharacter uc
|
||||
JOIN Character c ON uc.charId = c.id
|
||||
JOIN Card d ON c.dimId = d.id
|
||||
JOIN 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)
|
||||
}
|
||||
@@ -6,40 +6,48 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
|
||||
@Dao
|
||||
interface ItemDao {
|
||||
@Query("""
|
||||
SELECT Items.*, UserItems.quantity
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM Items
|
||||
LEFT JOIN UserItems ON Items.id = UserItems.itemId
|
||||
ORDER BY Items.itemIcon ASC
|
||||
""")
|
||||
"""
|
||||
)
|
||||
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities>
|
||||
|
||||
@Query("""
|
||||
SELECT Items.*, UserItems.quantity
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM Items
|
||||
JOIN UserItems ON Items.id = UserItems.itemId
|
||||
""")
|
||||
WHERE quantity > 0
|
||||
"""
|
||||
)
|
||||
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
|
||||
|
||||
@Query("""
|
||||
SELECT Items.*, UserItems.quantity
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM Items
|
||||
JOIN UserItems ON Items.id = UserItems.itemId
|
||||
WHERE UserItems.itemId = :itemId
|
||||
""")
|
||||
fun getUserItem(itemId: Long): ItemDtos.ItemsWithQuantities
|
||||
WHERE Items.id = :itemId
|
||||
"""
|
||||
)
|
||||
fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities
|
||||
|
||||
@Query("""
|
||||
UPDATE UserItems
|
||||
@Query(
|
||||
"""
|
||||
UPDATE Items
|
||||
SET quantity = quantity - 1
|
||||
WHERE itemId = :itemId
|
||||
""")
|
||||
WHERE id = :itemId
|
||||
"""
|
||||
)
|
||||
fun useItem(itemId: Long)
|
||||
|
||||
@Query("""
|
||||
UPDATE UserItems
|
||||
SET quantity = quantity - :itemAmount
|
||||
WHERE itemId = :itemId
|
||||
""")
|
||||
@Query(
|
||||
"""
|
||||
UPDATE Items
|
||||
SET quantity = quantity + :itemAmount
|
||||
WHERE id = :itemId
|
||||
"""
|
||||
)
|
||||
suspend fun purchaseItem(itemId: Long, itemAmount: Int)
|
||||
}
|
||||
@@ -48,14 +48,39 @@ interface UserCharacterDao {
|
||||
c.sprite1 AS spriteIdle,
|
||||
c.spritesWidth AS spriteWidth,
|
||||
c.spritesHeight AS spriteHeight,
|
||||
d.isBEm as isBemCard
|
||||
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
|
||||
LEFT JOIN Adventure a ON a.characterId = uc.id
|
||||
"""
|
||||
)
|
||||
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT
|
||||
uc.*,
|
||||
c.sprite1 AS spriteIdle,
|
||||
c.spritesWidth AS spriteWidth,
|
||||
c.spritesHeight 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
|
||||
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
|
||||
|
||||
@@ -69,14 +94,18 @@ interface UserCharacterDao {
|
||||
c.sprite1 AS spriteIdle,
|
||||
c.spritesWidth AS spriteWidth,
|
||||
c.spritesHeight AS spriteHeight,
|
||||
d.isBEm as isBemCard
|
||||
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
|
||||
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")
|
||||
|
||||
@@ -2,6 +2,7 @@ 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
|
||||
@@ -10,12 +11,12 @@ 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.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
|
||||
import com.github.nacabaro.vbhelper.domain.items.UserItems
|
||||
|
||||
@Database(
|
||||
version = 1,
|
||||
@@ -28,7 +29,7 @@ import com.github.nacabaro.vbhelper.domain.items.UserItems
|
||||
TransformationHistory::class,
|
||||
Dex::class,
|
||||
Items::class,
|
||||
UserItems::class
|
||||
Adventure::class
|
||||
]
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@@ -37,4 +38,5 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun userCharacterDao(): UserCharacterDao
|
||||
abstract fun dexDao(): DexDao
|
||||
abstract fun itemDao(): ItemDao
|
||||
abstract fun adventureDao(): AdventureDao
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -10,5 +10,6 @@ data class Items(
|
||||
val description: String,
|
||||
val itemIcon: Int,
|
||||
val itemLength: Int,
|
||||
val price: Int
|
||||
val price: Int,
|
||||
val quantity: Int
|
||||
)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.github.nacabaro.vbhelper.domain.items
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Items::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["itemId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
]
|
||||
)
|
||||
data class UserItems(
|
||||
@PrimaryKey val itemId: Long,
|
||||
val quantity: Int,
|
||||
)
|
||||
@@ -27,7 +27,11 @@ object CharacterDtos {
|
||||
val spriteIdle: 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 +55,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
|
||||
)
|
||||
}
|
||||
@@ -11,4 +11,13 @@ object ItemDtos {
|
||||
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
|
||||
)
|
||||
}
|
||||
@@ -16,21 +16,27 @@ import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
|
||||
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
|
||||
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen
|
||||
import com.github.nacabaro.vbhelper.screens.SpriteViewer
|
||||
import com.github.nacabaro.vbhelper.screens.StorageScreen
|
||||
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.storageScreen.StorageScreenControllerImpl
|
||||
|
||||
data class AppNavigationHandlers(
|
||||
val settingsScreenController: SettingsScreenControllerImpl,
|
||||
val scanScreenController: ScanScreenControllerImpl,
|
||||
val itemsScreenController: ItemsScreenControllerImpl
|
||||
val itemsScreenController: ItemsScreenControllerImpl,
|
||||
val adventureScreenController: AdventureScreenControllerImpl,
|
||||
val storageScreenController: StorageScreenControllerImpl,
|
||||
val homeScreenController: HomeScreenControllerImpl
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun AppNavigation(
|
||||
applicationNavigationHandlers: AppNavigationHandlers,
|
||||
onClickImportCard: () -> Unit,
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
|
||||
@@ -50,12 +56,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) {
|
||||
@@ -76,8 +85,7 @@ fun AppNavigation(
|
||||
composable(NavigationItems.Settings.route) {
|
||||
SettingsScreen(
|
||||
navController = navController,
|
||||
settingsScreenController = applicationNavigationHandlers.settingsScreenController,
|
||||
onClickImportCard = onClickImportCard,
|
||||
settingsScreenController = applicationNavigationHandlers.settingsScreenController
|
||||
)
|
||||
}
|
||||
composable(NavigationItems.Viewer.route) {
|
||||
@@ -110,6 +118,13 @@ fun AppNavigation(
|
||||
)
|
||||
}
|
||||
}
|
||||
composable(NavigationItems.Adventure.route) {
|
||||
AdventureScreen(
|
||||
navController = navController,
|
||||
storageScreenController = applicationNavigationHandlers
|
||||
.adventureScreenController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,5 @@ sealed class NavigationItems (
|
||||
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")
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package com.github.nacabaro.vbhelper.screens
|
||||
|
||||
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.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 characters") }
|
||||
) { 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun AdventureEntry(
|
||||
icon: BitmapData,
|
||||
timeLeft: Long,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val bitmap = remember (icon.bitmap) { icon.getBitmap() }
|
||||
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||
val dpSize = (icon.width * 4 / density).dp
|
||||
|
||||
Card(
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.height(96.dp)
|
||||
) {
|
||||
Image(
|
||||
bitmap = imageBitmap,
|
||||
contentDescription = null,
|
||||
filterQuality = FilterQuality.None,
|
||||
modifier = Modifier
|
||||
.size(dpSize)
|
||||
)
|
||||
Text(
|
||||
text = when {
|
||||
timeLeft < 0 -> "Adventure finished"
|
||||
else -> "Time left: ${formatSeconds(timeLeft)}"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun formatSeconds(totalSeconds: Long): String {
|
||||
val hours = totalSeconds / 3600
|
||||
val minutes = (totalSeconds % 3600) / 60
|
||||
val seconds = totalSeconds % 60
|
||||
|
||||
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.navigation.NavController
|
||||
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
|
||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Instant
|
||||
|
||||
@Composable
|
||||
fun AdventureScreen(
|
||||
navController: NavController,
|
||||
storageScreenController: AdventureScreenControllerImpl
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val application = LocalContext.current.applicationContext as VBHelper
|
||||
val database = application.container.db
|
||||
val storageRepository = StorageRepository(database)
|
||||
val characterList = remember {
|
||||
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
|
||||
}
|
||||
var obtainedItem by remember {
|
||||
mutableStateOf<ItemDtos.PurchasedItem?>(null)
|
||||
}
|
||||
|
||||
val currentTime by produceState(initialValue = Instant.now().epochSecond) {
|
||||
while (true) {
|
||||
value = Instant.now().epochSecond
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
|
||||
var cancelAdventureDialog by remember {
|
||||
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(storageRepository) {
|
||||
coroutineScope.launch {
|
||||
characterList.value = storageRepository
|
||||
.getAdventureCharacters()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBanner(
|
||||
text = "Adventure",
|
||||
onBackClick = {
|
||||
navController.popBackStack()
|
||||
}
|
||||
)
|
||||
}
|
||||
) { contentPadding ->
|
||||
if (characterList.value.isEmpty()) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.padding(top = contentPadding.calculateTopPadding())
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Text(text = "Nothing to see here")
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(top = contentPadding.calculateTopPadding())
|
||||
) {
|
||||
items(characterList.value) {
|
||||
AdventureEntry(
|
||||
icon = BitmapData(
|
||||
bitmap = it.spriteIdle,
|
||||
width = it.spriteWidth,
|
||||
height = it.spriteHeight
|
||||
),
|
||||
timeLeft = it.finishesAdventure - currentTime,
|
||||
onClick = {
|
||||
if (it.finishesAdventure < currentTime) {
|
||||
storageScreenController
|
||||
.getItemFromAdventure(it.id) { adventureResult ->
|
||||
obtainedItem = adventureResult
|
||||
}
|
||||
} else {
|
||||
cancelAdventureDialog = it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obtainedItem != null) {
|
||||
ObtainedItemDialog(
|
||||
obtainedItem = obtainedItem!!,
|
||||
onClickDismiss = {
|
||||
obtainedItem = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (cancelAdventureDialog != null) {
|
||||
CancelAdventureDialog(
|
||||
characterSprite = BitmapData(
|
||||
bitmap = cancelAdventureDialog!!.spriteIdle,
|
||||
width = cancelAdventureDialog!!.spriteWidth,
|
||||
height = cancelAdventureDialog!!.spriteHeight
|
||||
),
|
||||
onDismissRequest = {
|
||||
cancelAdventureDialog = null
|
||||
},
|
||||
onClickConfirm = {
|
||||
storageScreenController.cancelAdventure(cancelAdventureDialog!!.id) {
|
||||
navController.navigate(NavigationItems.Storage.route)
|
||||
}
|
||||
cancelAdventureDialog = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
|
||||
interface AdventureScreenController {
|
||||
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
|
||||
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit)
|
||||
fun cancelAdventure(characterId: Long, onResult: () -> Unit)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
|
||||
class AdventureScreenControllerImpl(
|
||||
private val componentActivity: ComponentActivity,
|
||||
) : AdventureScreenController {
|
||||
private val application = componentActivity.applicationContext as VBHelper
|
||||
private val database = application.container.db
|
||||
|
||||
override fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) {
|
||||
val finishesAdventureAt = timeInMinutes * 60
|
||||
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
val characterData = database
|
||||
.userCharacterDao()
|
||||
.getCharacter(characterId)
|
||||
|
||||
if (characterData.isActive) {
|
||||
database
|
||||
.userCharacterDao()
|
||||
.clearActiveCharacter()
|
||||
}
|
||||
|
||||
database
|
||||
.adventureDao()
|
||||
.insertNewAdventure(characterId, timeInMinutes, finishesAdventureAt)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemFromAdventure(
|
||||
characterId: Long,
|
||||
onResult: (ItemDtos.PurchasedItem) -> Unit
|
||||
) {
|
||||
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
database
|
||||
.adventureDao()
|
||||
.deleteAdventure(characterId)
|
||||
|
||||
val generatedItem = generateItem(characterId)
|
||||
|
||||
onResult(generatedItem)
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
|
||||
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
database
|
||||
.adventureDao()
|
||||
.deleteAdventure(characterId)
|
||||
|
||||
componentActivity
|
||||
.runOnUiThread {
|
||||
Toast.makeText(
|
||||
componentActivity,
|
||||
"Adventure canceled",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
onResult()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun generateItem(characterId: Long): ItemDtos.PurchasedItem {
|
||||
val character = database
|
||||
.userCharacterDao()
|
||||
.getCharacter(characterId)
|
||||
|
||||
val randomItem = database
|
||||
.itemDao()
|
||||
.getAllItems()
|
||||
.random()
|
||||
|
||||
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()
|
||||
|
||||
database
|
||||
.itemDao()
|
||||
.purchaseItem(
|
||||
itemId = randomItem.id,
|
||||
itemAmount = random
|
||||
)
|
||||
|
||||
return ItemDtos.PurchasedItem(
|
||||
itemId = randomItem.id,
|
||||
itemAmount = random,
|
||||
itemName = randomItem.name,
|
||||
itemIcon = randomItem.itemIcon,
|
||||
itemLength = randomItem.itemLength,
|
||||
itemDescription = randomItem.description
|
||||
)
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.github.nacabaro.vbhelper.screens.adventureScreen
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||
|
||||
@Composable
|
||||
fun CancelAdventureDialog(
|
||||
characterSprite: BitmapData,
|
||||
onDismissRequest: () -> Unit,
|
||||
onClickConfirm: () -> Unit
|
||||
) {
|
||||
val bitmap = remember (characterSprite) { characterSprite.getBitmap() }
|
||||
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||
val dpSize = (characterSprite.width * 4 / density).dp
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Card {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row {
|
||||
Image(
|
||||
bitmap = imageBitmap,
|
||||
contentDescription = null,
|
||||
filterQuality = FilterQuality.None,
|
||||
modifier = Modifier
|
||||
.size(dpSize)
|
||||
)
|
||||
Text(
|
||||
text = "Are you sure you want to cancel this character's adventure?"
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = onClickConfirm
|
||||
) {
|
||||
Text(text = "Confirm")
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(4.dp))
|
||||
Button(
|
||||
onClick = onDismissRequest
|
||||
) {
|
||||
Text(text = "Dismiss")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ 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.components.getIconResource
|
||||
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
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ 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.components.getIconResource
|
||||
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
|
||||
|
||||
@@ -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,7 @@ 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) }
|
||||
|
||||
LaunchedEffect(storageRepository, activeMon) {
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -47,6 +57,13 @@ fun HomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(true) {
|
||||
homeScreenController
|
||||
.didAdventureMissionsFinish {
|
||||
adventureMissionsFinished = it
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold (
|
||||
topBar = {
|
||||
TopBanner(
|
||||
@@ -72,7 +89,6 @@ fun HomeScreen(
|
||||
}
|
||||
} else {
|
||||
if (activeMon.value!!.isBemCard) {
|
||||
Log.d("HomeScreen", "BEDeviceBEm")
|
||||
BEBEmHomeScreen(
|
||||
activeMon = activeMon.value!!,
|
||||
beData = beData.value!!,
|
||||
@@ -80,7 +96,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 +112,34 @@ 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||
|
||||
interface HomeScreenController {
|
||||
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.github.nacabaro.vbhelper.screens.homeScreens
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Instant
|
||||
|
||||
class HomeScreenControllerImpl(
|
||||
private val componentActivity: ComponentActivity,
|
||||
): HomeScreenController {
|
||||
private val application = componentActivity.applicationContext as VBHelper
|
||||
private val database = application.container.db
|
||||
|
||||
override fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit) {
|
||||
componentActivity.lifecycleScope.launch {
|
||||
val currentTime = Instant.now().epochSecond
|
||||
val adventureCharacters = database
|
||||
.adventureDao()
|
||||
.getAdventureCharacters()
|
||||
|
||||
val finishedAdventureCharacters = adventureCharacters.filter { character ->
|
||||
character.finishesAdventure <= currentTime
|
||||
}
|
||||
|
||||
onCompletion(finishedAdventureCharacters.isNotEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -1,4 +1,4 @@
|
||||
package com.github.nacabaro.vbhelper.components
|
||||
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -25,7 +25,6 @@ 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.screens.itemsScreen.ItemsScreenControllerImpl
|
||||
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
|
||||
|
||||
@Composable
|
||||
@@ -41,13 +40,13 @@ fun ItemElement(
|
||||
.aspectRatio(1f)
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
// Background image (full size)
|
||||
Icon(
|
||||
painter = painterResource(id = itemIcon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(96.dp)
|
||||
.align(Alignment.Center)
|
||||
.padding(16.dp)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = lengthIcon),
|
||||
@@ -55,9 +54,8 @@ fun ItemElement(
|
||||
tint = MaterialTheme.colorScheme.surfaceTint,
|
||||
modifier = Modifier
|
||||
.size(48.dp) // Set the size of the overlay image
|
||||
.padding(4.dp
|
||||
)
|
||||
.align(Alignment.TopStart) // Align to the top end (top-right corner)
|
||||
.padding(8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -124,7 +122,10 @@ fun ItemDialog(
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
|
||||
text = description
|
||||
text = description,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp)
|
||||
)
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
@@ -133,7 +134,7 @@ fun ItemDialog(
|
||||
text = "You have $amount of this item",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.padding(8.dp)
|
||||
)
|
||||
Row (
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
+1
-1
@@ -107,7 +107,7 @@ class ItemsScreenControllerImpl (
|
||||
private fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities {
|
||||
return database
|
||||
.itemDao()
|
||||
.getUserItem(itemId)
|
||||
.getItem(itemId)
|
||||
}
|
||||
|
||||
private fun consumeItem(itemId: Long) {
|
||||
|
||||
@@ -15,10 +15,6 @@ 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.components.ItemDialog
|
||||
import com.github.nacabaro.vbhelper.components.ItemElement
|
||||
import com.github.nacabaro.vbhelper.components.getIconResource
|
||||
import com.github.nacabaro.vbhelper.components.getLengthResource
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
import com.github.nacabaro.vbhelper.source.ItemsRepository
|
||||
|
||||
@@ -19,10 +19,6 @@ 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.components.ItemDialog
|
||||
import com.github.nacabaro.vbhelper.components.ItemElement
|
||||
import com.github.nacabaro.vbhelper.components.getIconResource
|
||||
import com.github.nacabaro.vbhelper.components.getLengthResource
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.github.nacabaro.vbhelper.screens.itemsScreen
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.github.nacabaro.vbhelper.dtos.ItemDtos
|
||||
|
||||
@Composable
|
||||
fun ObtainedItemDialog(
|
||||
obtainedItem: ItemDtos.PurchasedItem,
|
||||
onClickDismiss: () -> Unit
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onClickDismiss
|
||||
) {
|
||||
Card {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row {
|
||||
Box(modifier = Modifier) {
|
||||
Icon(
|
||||
painter = painterResource(id = getIconResource(obtainedItem.itemIcon)),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(96.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = getLengthResource(obtainedItem.itemLength)),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.align(Alignment.BottomEnd)
|
||||
)
|
||||
}
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||
text = obtainedItem.itemName,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
|
||||
text = obtainedItem.itemDescription,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp)
|
||||
|
||||
)
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
|
||||
text = "You have obtained ${obtainedItem.itemAmount} of this item",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(5.dp)
|
||||
)
|
||||
Button(
|
||||
onClick = onClickDismiss,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "Dismiss")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
-17
@@ -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,107 @@ 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,
|
||||
stage = nfcCharacter.stage.toInt(),
|
||||
attribute = nfcCharacter.attribute,
|
||||
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!"
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -23,7 +23,6 @@ import com.github.nacabaro.vbhelper.components.TopBanner
|
||||
fun SettingsScreen(
|
||||
navController: NavController,
|
||||
settingsScreenController: SettingsScreenControllerImpl,
|
||||
onClickImportCard: () -> Unit
|
||||
) {
|
||||
Scaffold (
|
||||
topBar = {
|
||||
@@ -47,6 +46,14 @@ fun SettingsScreen(
|
||||
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
|
||||
settingsScreenController.onClickImportApk()
|
||||
}
|
||||
SettingsSection("DiM/BEm management")
|
||||
SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") {
|
||||
settingsScreenController.onClickImportCard()
|
||||
}
|
||||
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
|
||||
SettingsSection("About and credits")
|
||||
SettingsEntry(title = "Credits", description = "Credits") { }
|
||||
SettingsEntry(title = "About", description = "About") { }
|
||||
SettingsSection("Data management")
|
||||
SettingsEntry(title = "Export data", description = "Export application database") {
|
||||
settingsScreenController.onClickOpenDirectory()
|
||||
@@ -54,12 +61,6 @@ fun SettingsScreen(
|
||||
SettingsEntry(title = "Import data", description = "Import application database") {
|
||||
settingsScreenController.onClickImportDatabase()
|
||||
}
|
||||
SettingsSection("DiM/BEm management")
|
||||
SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file", onClick = onClickImportCard)
|
||||
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
|
||||
SettingsSection("About and credits")
|
||||
SettingsEntry(title = "Credits", description = "Credits") { }
|
||||
SettingsEntry(title = "About", description = "About") { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -4,4 +4,5 @@ interface SettingsScreenController {
|
||||
fun onClickOpenDirectory()
|
||||
fun onClickImportDatabase()
|
||||
fun onClickImportApk()
|
||||
fun onClickImportCard()
|
||||
}
|
||||
+110
@@ -9,7 +9,13 @@ 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.DimReader
|
||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import com.github.nacabaro.vbhelper.domain.Sprites
|
||||
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
|
||||
@@ -27,9 +33,11 @@ class SettingsScreenControllerImpl(
|
||||
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(
|
||||
@@ -68,6 +76,18 @@ class SettingsScreenControllerImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -82,6 +102,96 @@ class SettingsScreenControllerImpl(
|
||||
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) {
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
spriteCounter += if (card is BemCard) {
|
||||
14
|
||||
} else {
|
||||
when (index) {
|
||||
0 -> 6
|
||||
1 -> 7
|
||||
else -> 14
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
database
|
||||
.characterDao()
|
||||
.insertCharacter(*domainCharacters.toTypedArray())
|
||||
|
||||
val sprites = card.spriteData.sprites.map { sprite ->
|
||||
Sprites(
|
||||
id = 0,
|
||||
sprite = sprite.pixelData,
|
||||
width = sprite.width,
|
||||
height = sprite.height
|
||||
)
|
||||
}
|
||||
database
|
||||
.characterDao()
|
||||
.insertSprite(*sprites.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 {
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.github.nacabaro.vbhelper.R
|
||||
|
||||
fun getAdventureTime(time: Int): String {
|
||||
return when (time) {
|
||||
360 -> "6 hours"
|
||||
720 -> "12 hours"
|
||||
1440 -> "24 hours"
|
||||
else -> "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StorageAdventureTimeDialog(
|
||||
onClickSendToAdventure: (time: Long) -> Unit,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
val times = arrayOf(360, 720, 1440)
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var itemPosition by remember { mutableIntStateOf(-1) }
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Card {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row (
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.width(256.dp)
|
||||
.clickable(true) {
|
||||
expanded = true
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = when (itemPosition) {
|
||||
-1 -> "Choose time"
|
||||
else -> getAdventureTime(times[itemPosition])
|
||||
}
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.baseline_single_arrow_down),
|
||||
contentDescription = "Show more"
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
modifier = Modifier
|
||||
.width(256.dp)
|
||||
) {
|
||||
times.forEach { time ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(getAdventureTime(time)) },
|
||||
onClick = {
|
||||
itemPosition = times.indexOf(time)
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
onClick = {
|
||||
if (itemPosition != -1) {
|
||||
onClickSendToAdventure(times[itemPosition].toLong())
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(text = "Send on adventure")
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
onClick = onDismissRequest
|
||||
) {
|
||||
Text(text = "Dismiss")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||
import com.github.nacabaro.vbhelper.utils.getBitmap
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun StorageDialog(
|
||||
characterId: Long,
|
||||
onDismissRequest: () -> Unit,
|
||||
onSendToBracelet: () -> Unit,
|
||||
onClickSetActive: () -> Unit,
|
||||
onClickSendToAdventure: (time: Long) -> Unit
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val application = LocalContext.current.applicationContext as VBHelper
|
||||
val storageRepository = StorageRepository(application.container.db)
|
||||
val character = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
|
||||
val characterSprite = remember { mutableStateOf<BitmapData?>(null) }
|
||||
val characterName = remember { mutableStateOf<BitmapData?>(null) }
|
||||
var onSendToAdventureClicked by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(storageRepository) {
|
||||
coroutineScope.launch {
|
||||
character.value = storageRepository.getSingleCharacter(characterId)
|
||||
characterSprite.value = BitmapData(
|
||||
bitmap = character.value!!.spriteIdle,
|
||||
width = character.value!!.spriteWidth,
|
||||
height = character.value!!.spriteHeight
|
||||
)
|
||||
characterName.value = BitmapData(
|
||||
bitmap = character.value!!.nameSprite,
|
||||
width = character.value!!.nameSpriteWidth,
|
||||
height = character.value!!.nameSpriteHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = true
|
||||
)
|
||||
) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
) {
|
||||
if (character.value != null &&
|
||||
characterSprite.value != null &&
|
||||
characterName.value != null
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val bitmap = remember (characterSprite.value!!) { characterSprite.value!!.getBitmap() }
|
||||
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
|
||||
val density: Float = LocalContext.current.resources.displayMetrics.density
|
||||
val dpSize = (characterSprite.value!!.width * 4 / density).dp
|
||||
Image(
|
||||
bitmap = imageBitmap,
|
||||
contentDescription = "Character image",
|
||||
filterQuality = FilterQuality.None,
|
||||
modifier = Modifier
|
||||
.size(dpSize)
|
||||
)
|
||||
val nameBitmap = remember (characterName.value!!) { characterName.value!!.getBitmap() }
|
||||
val nameImageBitmap = remember(nameBitmap) { nameBitmap.asImageBitmap() }
|
||||
val nameDpSize = (characterName.value!!.width * 4 / density).dp
|
||||
Image(
|
||||
bitmap = nameImageBitmap,
|
||||
contentDescription = "Character image",
|
||||
filterQuality = FilterQuality.None,
|
||||
modifier = Modifier
|
||||
.size(nameDpSize)
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Button(
|
||||
onClick = onSendToBracelet,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(text = "Send to watch")
|
||||
}
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
)
|
||||
Button(
|
||||
onClick = onClickSetActive,
|
||||
) {
|
||||
Text(text = "Set active")
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
onSendToAdventureClicked = true
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "Send on adventure")
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
onClick = onDismissRequest
|
||||
) {
|
||||
Text(text = "Close")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onSendToAdventureClicked) {
|
||||
StorageAdventureTimeDialog(
|
||||
onClickSendToAdventure = { time ->
|
||||
onClickSendToAdventure(time)
|
||||
},
|
||||
onDismissRequest = { onSendToAdventureClicked = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.navigation.NavController
|
||||
import com.github.nacabaro.vbhelper.components.CharacterEntry
|
||||
import com.github.nacabaro.vbhelper.components.TopBanner
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
|
||||
import com.github.nacabaro.vbhelper.navigation.NavigationItems
|
||||
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
|
||||
import com.github.nacabaro.vbhelper.source.StorageRepository
|
||||
import com.github.nacabaro.vbhelper.utils.BitmapData
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@Composable
|
||||
fun StorageScreen(
|
||||
navController: NavController,
|
||||
storageScreenController: StorageScreenControllerImpl,
|
||||
adventureScreenController: AdventureScreenControllerImpl
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val application = LocalContext.current.applicationContext as VBHelper
|
||||
val storageRepository = StorageRepository(application.container.db)
|
||||
val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
|
||||
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
|
||||
|
||||
LaunchedEffect(storageRepository, selectedCharacter) {
|
||||
coroutineScope.launch {
|
||||
val characterList = storageRepository.getAllCharacters()
|
||||
monList.value = characterList
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold (
|
||||
topBar = {
|
||||
TopBanner(
|
||||
text = "My characters",
|
||||
onAdventureClick = {
|
||||
navController.navigate(NavigationItems.Adventure.route)
|
||||
}
|
||||
)
|
||||
}
|
||||
) { contentPadding ->
|
||||
if (monList.value.isEmpty()) {
|
||||
Column (
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.padding(top = contentPadding.calculateTopPadding())
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Text(
|
||||
text = "Nothing to see here",
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
modifier = Modifier
|
||||
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
|
||||
.padding(top = contentPadding.calculateTopPadding())
|
||||
) {
|
||||
items(monList.value) { index ->
|
||||
CharacterEntry(
|
||||
icon = BitmapData(
|
||||
bitmap = index.spriteIdle,
|
||||
width = index.spriteWidth,
|
||||
height = index.spriteHeight
|
||||
),
|
||||
onClick = {
|
||||
if (!index.isInAdventure) {
|
||||
selectedCharacter = index.id
|
||||
} else {
|
||||
Toast.makeText(
|
||||
application,
|
||||
"This character is in an adventure",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
navController.navigate(
|
||||
NavigationItems.Adventure.route
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedCharacter != null) {
|
||||
StorageDialog(
|
||||
characterId = selectedCharacter!!,
|
||||
onDismissRequest = { selectedCharacter = null },
|
||||
onClickSetActive = {
|
||||
storageScreenController
|
||||
.setActive(selectedCharacter!!) {
|
||||
selectedCharacter = null
|
||||
navController.navigate(NavigationItems.Home.route)
|
||||
}
|
||||
},
|
||||
onSendToBracelet = {
|
||||
navController.navigate(
|
||||
NavigationItems.Scan.route.replace(
|
||||
"{characterId}",
|
||||
selectedCharacter.toString()
|
||||
)
|
||||
)
|
||||
},
|
||||
onClickSendToAdventure = { time ->
|
||||
adventureScreenController
|
||||
.sendCharacterToAdventure(
|
||||
characterId = selectedCharacter!!,
|
||||
timeInMinutes = time
|
||||
)
|
||||
selectedCharacter = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||
|
||||
interface StorageScreenController {
|
||||
fun setActive(characterId: Long, onCompletion: () -> Unit)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.github.nacabaro.vbhelper.screens.storageScreen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.github.nacabaro.vbhelper.di.VBHelper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class StorageScreenControllerImpl(
|
||||
private val componentActivity: ComponentActivity
|
||||
): StorageScreenController {
|
||||
private val application = componentActivity.applicationContext as VBHelper
|
||||
private val database = application.container.db
|
||||
|
||||
override fun setActive(characterId: Long, onCompletion: () -> Unit) {
|
||||
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
database.userCharacterDao().clearActiveCharacter()
|
||||
database.userCharacterDao().setActiveCharacter(characterId)
|
||||
|
||||
componentActivity.runOnUiThread {
|
||||
Toast.makeText(
|
||||
componentActivity,
|
||||
"Active character updated!",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
onCompletion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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>
|
||||
Reference in New Issue
Block a user