Compare commits

..
Author SHA1 Message Date
nacabaro 37076a9bf0 VERSION UP 2026-01-25 20:00:55 +01:00
nacabaroandGitHub 8c99046edb Merge pull request #47 from nacabaro/specialMissions/fix
Fix Special Missions a bit more
2026-01-25 19:58:39 +01:00
nacabaro c990a495b7 Fix Special Missions a bit more
Now they should behave a bit better, and it will not allow the watch to have multiple missions of the same type, plus you can now delete special missions.
2026-01-25 19:58:03 +01:00
nacabaroandGitHub 0e61723db1 Merge pull request #46 from nacabaro/ui/cardIcon
Card icon in home screen
2026-01-25 17:53:30 +01:00
nacabaro 8815907563 Card icon in home screen
Together with character sprite
2026-01-25 17:35:54 +01:00
nacabaroandGitHub 6cce33c5f6 Merge pull request #44 from jeffersoncarlospedroso/feature/add-multilanguage-support
Add multilanguage support (i18n) initial implementation
2025-12-14 12:31:27 +01:00
jeffersoncarlospedroso 16fbfae0c2 feat(i18n): continue translation work 2025-12-11 22:38:43 +00:00
jeffersoncarlospedroso 0d174f1550 Add multilanguage support (i18n) initial implementation 2025-12-10 23:06:37 +00:00
nacabaro 489e27b038 More things
- Active characters display different
- Adventure missions award credits (same as special missions)
- Clicking on about will open the github page
- Corrected from arean to arena
- Removed rename from the settings (it was not used)
2025-11-16 21:59:08 +00:00
nacabaro 6be167bbed Might be incomplete uhhh 2025-11-16 21:13:28 +01:00
nacabaroandGitHub 61dfc2ce0d Merge pull request #43 from nacabaro/screen/upload_screen
Other few things
2025-11-16 01:35:31 +01:00
nacabaro dce186737d Other few things
- I changed more things to flows from the database
- Cleaned up the logic coming from the scan screen
- Added a delete button to a character. CAREFUL, IT HAS NO CONFIRMATION YET!
- Fixed a few things, now scanning is more stable and will fix the second whoops thing.
- Quick patch, should improve stability when writing to the watch
2025-11-16 01:34:31 +01:00
nacabaroandGitHub cf272e8030 Merge pull request #42 from nacabaro/dex/possible_fusions
Few things
2025-11-15 20:12:36 +01:00
nacabaroandGitHub 1d9879fb50 Merge pull request #41 from nacabaro/store/updated_store
Updated the store
2025-11-15 10:23:07 +01:00
59 changed files with 1980 additions and 516 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ android {
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "Alpha 0.6.1"
versionName = "Alpha 0.6.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -35,20 +36,27 @@ import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.utils.getObscuredBitmap
import androidx.compose.ui.res.stringResource
@Composable
fun CharacterEntry(
icon: BitmapData,
modifier: Modifier = Modifier,
cardIcon: BitmapData? = null,
obscure: Boolean = false,
disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 4,
cardColors: CardColors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
),
onClick: () -> Unit = { }
) {
val bitmap = remember (icon.bitmap) {
if(obscure) icon.getObscuredBitmap() else icon.getBitmap()
}
val iconSizeMultiplier = 3
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * multiplier / density).dp
@@ -61,7 +69,8 @@ fun CharacterEntry(
},
modifier = modifier
.aspectRatio(1f)
.padding(8.dp)
.padding(8.dp),
colors = cardColors
) {
Box(
contentAlignment = Alignment.BottomCenter,
@@ -79,7 +88,24 @@ fun CharacterEntry(
},
modifier = Modifier
.size(dpSize)
.align(Alignment.BottomCenter)
)
if (cardIcon != null) {
val bitmap = remember (icon.bitmap) { cardIcon.getBitmap() }
val iconBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val dpSize = (icon.width * iconSizeMultiplier /density).dp
Image(
bitmap = iconBitmap,
contentDescription = "Card icon",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
.align(Alignment.BottomEnd)
.padding(8.dp)
)
}
}
}
}
@@ -126,14 +152,27 @@ fun ItemDisplay(
fun SpecialMissionsEntry(
specialMission: SpecialMissions,
modifier: Modifier = Modifier,
onClickCard: () -> Unit = { },
onClickMission: (Long) -> Unit = { },
onClickCollect: (Long) -> Unit = { }
) {
val textValue = when (specialMission.missionType) {
SpecialMission.Type.NONE -> "No mission selected"
SpecialMission.Type.STEPS -> "Walk ${specialMission.goal} steps"
SpecialMission.Type.BATTLES -> "Battle ${specialMission.goal} times"
SpecialMission.Type.WINS -> "Win ${specialMission.goal} battles"
SpecialMission.Type.VITALS -> "Earn ${specialMission.goal} vitals"
SpecialMission.Type.NONE -> stringResource(R.string.special_mission_none)
SpecialMission.Type.STEPS -> stringResource(
R.string.special_mission_steps,
specialMission.goal
)
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles,
specialMission.goal
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins,
specialMission.goal
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals,
specialMission.goal
)
}
val progress = if (specialMission.status == SpecialMission.Status.COMPLETED) {
@@ -144,10 +183,22 @@ fun SpecialMissionsEntry(
val completion = when (specialMission.missionType) {
SpecialMission.Type.NONE -> ""
SpecialMission.Type.STEPS -> "Walked $progress steps"
SpecialMission.Type.BATTLES -> "Battled $progress times"
SpecialMission.Type.WINS -> "Won $progress battles"
SpecialMission.Type.VITALS -> "Earned $progress vitals"
SpecialMission.Type.STEPS -> stringResource(
R.string.special_mission_steps_progress,
progress
)
SpecialMission.Type.BATTLES -> stringResource(
R.string.special_mission_battles_progress,
progress
)
SpecialMission.Type.WINS -> stringResource(
R.string.special_mission_wins_progress,
progress
)
SpecialMission.Type.VITALS -> stringResource(
R.string.special_mission_vitals_progress,
progress
)
}
val icon = when (specialMission.missionType) {
@@ -169,10 +220,12 @@ fun SpecialMissionsEntry(
Card(
modifier = modifier,
shape = androidx.compose.material.MaterialTheme.shapes.small,
onClick = if (specialMission.status == SpecialMission.Status.COMPLETED || specialMission.status == SpecialMission.Status.FAILED) {
onClickCard
onClick = if (specialMission.status == SpecialMission.Status.COMPLETED) {
{ onClickCollect(specialMission.id) }
} else if (specialMission.status == SpecialMission.Status.UNAVAILABLE) {
{ }
} else {
{ }
{ onClickMission(specialMission.id) }
},
colors = CardDefaults.cardColors(
containerColor = color
@@ -188,7 +241,7 @@ fun SpecialMissionsEntry(
) {
Icon(
painter = painterResource(icon),
contentDescription = "Vitals",
contentDescription = stringResource(R.string.special_mission_icon_content_description),
modifier = Modifier
.fillMaxHeight()
.padding(16.dp)
@@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
@@ -37,7 +38,7 @@ interface AdventureDao {
JOIN Adventure a ON uc.id = a.characterId
"""
)
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites>
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>>
@Query("""
DELETE FROM Adventure
@@ -5,6 +5,8 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface CardDao {
@@ -26,11 +28,22 @@ interface CardDao {
WHERE uc.id = :id
"""
)
suspend fun getCardByCharacterId(id: Long): Card
fun getCardByCharacterId(id: Long): Flow<Card>
@Query("UPDATE Card SET name = :newName WHERE id = :id")
suspend fun renameCard(id: Int, newName: String)
@Query("DELETE FROM Card WHERE id = :id")
suspend fun deleteCard(id: Long)
@Query("""
SELECT
c.logo as cardIcon,
c.logoWidth as cardIconWidth,
c.logoHeight as cardIconHeight
FROM Card c
JOIN CardCharacter cc ON cc.cardId = c.id
WHERE cc.id = :charaId
""")
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon>
}
@@ -2,6 +2,8 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import kotlinx.coroutines.flow.Flow
@Dao
interface SpecialMissionDao {
@@ -12,4 +14,11 @@ interface SpecialMissionDao {
WHERE id = :id
""")
suspend fun clearSpecialMission(id: Long)
@Query("""
SELECT *
FROM SpecialMissions
WHERE id = :id
""")
fun getSpecialMission(id: Long): Flow<SpecialMissions>
}
@@ -13,6 +13,7 @@ import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow
@Dao
interface UserCharacterDao {
@@ -52,7 +53,7 @@ interface UserCharacterDao {
WHERE monId = :monId
"""
)
suspend fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
fun getTransformationHistory(monId: Long): Flow<List<CharacterDtos.TransformationHistory>>
@Query(
"""
@@ -68,7 +69,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
@@ -76,7 +78,7 @@ interface UserCharacterDao {
LEFT JOIN Adventure a ON a.characterId = uc.id
"""
)
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>>
@Query(
"""
@@ -92,7 +94,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = d.id
@@ -107,13 +110,13 @@ interface UserCharacterDao {
suspend fun getCharacter(id: Long): UserCharacter
@Query("SELECT * FROM BECharacterData WHERE id = :id")
suspend fun getBeData(id: Long): BECharacterData
fun getBeData(id: Long): Flow<BECharacterData>
@Query("SELECT * FROM VBCharacterData WHERE id = :id")
suspend fun getVbData(id: Long): VBCharacterData
fun getVbData(id: Long): Flow<VBCharacterData>
@Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
suspend fun getSpecialMissions(id: Long): List<SpecialMissions>
fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>>
@Query(
"""
@@ -129,7 +132,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId as isInAdventure
a.characterId as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON c.cardId = d.id
@@ -139,7 +143,7 @@ interface UserCharacterDao {
LIMIT 1
"""
)
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites?
fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?>
@Query("DELETE FROM UserCharacter WHERE id = :id")
fun deleteCharacterById(id: Long)
@@ -193,7 +197,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
@@ -218,7 +223,8 @@ interface UserCharacterDao {
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
a.characterId = uc.id as isInAdventure,
uc.isActive as active
FROM UserCharacter uc
JOIN CardCharacter c ON uc.charId = c.id
JOIN Card d ON d.id = c.cardId
@@ -24,4 +24,10 @@ object CardDtos {
val characterHp: Int,
val steps: Int,
)
data class CardIcon (
val cardIcon: ByteArray,
val cardIconWidth: Int,
val cardIconHeight: Int
)
}
@@ -31,7 +31,8 @@ object CharacterDtos {
val nameSpriteWidth: Int,
val nameSpriteHeight: Int,
val isBemCard: Boolean,
val isInAdventure: Boolean
val isInAdventure: Boolean,
val active: Boolean
)
data class CardCharacterInfo(
@@ -1,13 +1,12 @@
package com.github.nacabaro.vbhelper.navigation
import android.util.Log
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -99,20 +98,14 @@ fun AppNavigation(
composable(NavigationItems.Scan.route) {
val characterIdString = it.arguments?.getString("characterId")
var characterId by remember { mutableStateOf(characterIdString?.toLongOrNull()) }
Log.d("ScanScreen", "characterId: $characterId")
val launchedFromHomeScreen = (characterIdString?.toLongOrNull() == null)
if (characterId == null) {
val context = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(context.container.db)
LaunchedEffect(characterId) {
if (characterId == null) {
val characterData = storageRepository.getActiveCharacter()
if (characterData != null) {
characterId = characterData.id
}
}
val characterData by storageRepository.getActiveCharacter().collectAsState(null)
if (characterData != null) {
characterId = characterData!!.id
}
}
@@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.compose.ui.res.stringResource
@Composable
fun BottomNavigationBar(navController: NavController) {
@@ -26,8 +26,8 @@ fun BottomNavigationBar(navController: NavController) {
items.forEach { item ->
NavigationBarItem (
icon = { Icon(painter = painterResource(item.icon), contentDescription = item.label) },
label = { Text(item.label) },
icon = { Icon(painter = painterResource(item.icon), contentDescription = stringResource(item.label)) },
label = { Text(text = stringResource(item.label)) },
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
@@ -1,25 +1,101 @@
package com.github.nacabaro.vbhelper.navigation
import com.github.nacabaro.vbhelper.R
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
sealed class NavigationItems (
var route: String,
var icon: Int,
var label: String
sealed class NavigationItems(
val route: String,
@DrawableRes val icon: Int,
@StringRes val label: Int
) {
object Scan : NavigationItems("Scan/{characterId}", R.drawable.baseline_nfc_24, "Scan")
object Battles : NavigationItems("Battle", R.drawable.baseline_swords_24, "Battle")
object Home : NavigationItems("Home", R.drawable.baseline_cottage_24, "Home")
object Dex : NavigationItems("Dex", R.drawable.baseline_menu_book_24, "Dex")
object CardAdventure : NavigationItems("CardAdventure/{cardId}", R.drawable.baseline_fort_24, "Card adventure")
object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings")
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer")
object CardView : NavigationItems("Card/{cardId}", R.drawable.baseline_image_24, "Card")
object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items")
object MyItems : NavigationItems("MyItems", R.drawable.baseline_data_24, "My items")
object ItemsStore : NavigationItems("ItemsStore", R.drawable.baseline_data_24, "Items store")
object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item")
object Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure")
object Credits : NavigationItems("Credits", R.drawable.baseline_data_24, "Credits")
object Scan : NavigationItems(
"Scan/{characterId}",
R.drawable.baseline_nfc_24,
R.string.nav_scan
)
object Battles : NavigationItems(
"Battle",
R.drawable.baseline_swords_24,
R.string.nav_battle
)
object Home : NavigationItems(
"Home",
R.drawable.baseline_cottage_24,
R.string.nav_home
)
object Dex : NavigationItems(
"Dex",
R.drawable.baseline_menu_book_24,
R.string.nav_dex
)
object CardAdventure : NavigationItems(
"CardAdventure/{cardId}",
R.drawable.baseline_fort_24,
R.string.nav_card_adventure
)
object Storage : NavigationItems(
"Storage",
R.drawable.baseline_catching_pokemon_24,
R.string.nav_storage
)
object Settings : NavigationItems(
"Settings",
R.drawable.baseline_settings_24,
R.string.nav_settings
)
object Viewer : NavigationItems(
"Viewer",
R.drawable.baseline_image_24,
R.string.nav_viewer
)
object CardView : NavigationItems(
"Card/{cardId}",
R.drawable.baseline_image_24,
R.string.nav_card
)
object Items : NavigationItems(
"Items",
R.drawable.baseline_data_24,
R.string.nav_items
)
object MyItems : NavigationItems(
"MyItems",
R.drawable.baseline_data_24,
R.string.nav_my_items
)
object ItemsStore : NavigationItems(
"ItemsStore",
R.drawable.baseline_data_24,
R.string.nav_items_store
)
object ApplyItem : NavigationItems(
"ApplyItem/{itemId}",
R.drawable.baseline_data_24,
R.string.nav_apply_item
)
object Adventure : NavigationItems(
"Adventure",
R.drawable.baseline_fort_24,
R.string.nav_adventure
)
object Credits : NavigationItems(
"Credits",
R.drawable.baseline_data_24,
R.string.nav_credits
)
}
@@ -10,13 +10,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.github.nacabaro.vbhelper.components.TopBanner
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun BattlesScreen() {
Scaffold (
topBar = {
TopBanner(
text = "Online battles"
text = stringResource(R.string.battles_online_title)
)
}
) { contentPadding ->
@@ -27,7 +30,7 @@ fun BattlesScreen() {
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text("Coming soon")
Text(stringResource(R.string.battles_coming_soon))
}
}
}
@@ -9,16 +9,16 @@ 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.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.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.res.stringResource
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.components.TopBanner
@@ -29,24 +29,25 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.Instant
import com.github.nacabaro.vbhelper.R
@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())
}
val characterList by storageRepository.getAdventureCharacters().collectAsState(emptyList())
var obtainedItem by remember {
mutableStateOf<ItemDtos.PurchasedItem?>(null)
}
var obtainedCurrency by remember {
mutableStateOf(0)
}
val currentTime by produceState(initialValue = Instant.now().epochSecond) {
while (true) {
@@ -59,24 +60,17 @@ fun AdventureScreen(
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
}
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository
.getAdventureCharacters()
}
}
Scaffold(
topBar = {
TopBanner(
text = "Adventure",
text = stringResource(R.string.adventure_title),
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
if (characterList.value.isEmpty()) {
if (characterList.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -84,14 +78,14 @@ fun AdventureScreen(
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(text = "Nothing to see here")
Text(text = stringResource(R.string.adventure_empty_state))
}
} else {
LazyColumn(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(characterList.value) {
items(characterList) {
AdventureEntry(
icon = BitmapData(
bitmap = it.spriteIdle,
@@ -102,8 +96,9 @@ fun AdventureScreen(
onClick = {
if (it.finishesAdventure < currentTime) {
storageScreenController
.getItemFromAdventure(it.id) { adventureResult ->
.getItemFromAdventure(it.id) { adventureResult, generatedCurrency ->
obtainedItem = adventureResult
obtainedCurrency = generatedCurrency
}
} else {
cancelAdventureDialog = it
@@ -118,6 +113,7 @@ fun AdventureScreen(
if (obtainedItem != null) {
ObtainedItemDialog(
obtainedItem = obtainedItem!!,
obtainedCurrency = obtainedCurrency,
onClickDismiss = {
obtainedItem = null
}
@@ -4,6 +4,6 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface AdventureScreenController {
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit)
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem, Int) -> Unit)
fun cancelAdventure(characterId: Long, onResult: () -> Unit)
}
@@ -38,19 +38,29 @@ class AdventureScreenControllerImpl(
override fun getItemFromAdventure(
characterId: Long,
onResult: (ItemDtos.PurchasedItem) -> Unit
onResult: (ItemDtos.PurchasedItem, Int) -> Unit
) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.adventureDao()
.deleteAdventure(characterId)
val generatedCurrency = generateRandomCurrency()
val generatedItem = generateItem(characterId)
onResult(generatedItem)
onResult(generatedItem, generatedCurrency)
}
}
private suspend fun generateRandomCurrency(): Int {
val currentValue = application.container.currencyRepository.currencyValue.first()
val random = (2..6).random() * 1000
application.container.currencyRepository.setCurrencyValue(currentValue + random)
return random
}
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
@@ -11,6 +11,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun CardAdventureScreen(
@@ -28,7 +31,7 @@ fun CardAdventureScreen(
Scaffold (
topBar = {
TopBanner(
text = "Adventure missions",
text = stringResource(R.string.card_adventure_missions_title),
onBackClick = {
navController.popBackStack()
}
@@ -24,6 +24,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun CardEntry(
@@ -75,7 +78,11 @@ fun CardEntry(
modifier = Modifier
)
Text(
text = "$obtainedCharacters of $totalCharacters characters obtained",
text = stringResource(
R.string.card_entry_characters_obtained,
obtainedCharacters,
totalCharacters
),
fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier
@@ -91,7 +98,7 @@ fun CardEntry(
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "Edit"
contentDescription = stringResource(R.string.card_entry_edit)
)
}
IconButton(
@@ -99,7 +106,7 @@ fun CardEntry(
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete"
contentDescription = stringResource(R.string.card_entry_delete)
)
}
}
@@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.CharacterEntry
@@ -19,6 +20,8 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.DexCharaDetailsDialog
import com.github.nacabaro.vbhelper.source.DexRepository
import com.github.nacabaro.vbhelper.R
@Composable
fun CardViewScreen(
@@ -35,7 +38,7 @@ fun CardViewScreen(
Scaffold (
topBar = {
TopBanner(
text = "Discovered characters",
text = stringResource(R.string.card_view_discovered_characters),
onBackClick = {
navController.popBackStack()
},
@@ -24,6 +24,8 @@ import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog
import com.github.nacabaro.vbhelper.source.DexRepository
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun CardsScreen(
@@ -43,7 +45,7 @@ fun CardsScreen(
Scaffold (
topBar = {
TopBanner(
text = "My cards",
text = stringResource(R.string.cards_my_cards_title),
onModifyClick = {
modifyCards = !modifyCards
}
@@ -33,7 +33,8 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.DexRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun DexCharaDetailsDialog(
@@ -119,7 +120,7 @@ fun DexCharaDetailsDialog(
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = "Icon",
contentDescription = stringResource(R.string.dex_chara_icon_description),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
@@ -138,7 +139,7 @@ fun DexCharaDetailsDialog(
Column {
Image(
bitmap = nameImageBitmapData.imageBitmap,
contentDescription = "Icon",
contentDescription = stringResource(R.string.dex_chara_name_icon_description),
modifier = Modifier
.width(nameImageBitmapData.dpWidth)
.height(nameImageBitmapData.dpHeight),
@@ -147,17 +148,28 @@ fun DexCharaDetailsDialog(
Spacer(modifier = Modifier.padding(4.dp))
if (currentChara.baseHp != 65535) {
Text(
text = "HP: ${currentChara.baseHp}, BP: ${currentChara.baseBp}, AP: ${currentChara.baseAp}"
text = stringResource(
R.string.dex_chara_stats,
currentChara.baseHp,
currentChara.baseBp,
currentChara.baseAp
)
)
Text(
text = stringResource(
R.string.dex_chara_stage_attribute,
romanNumeralsStage,
currentChara.attribute.toString().substring(0, 2)
)
)
Text(text = "Stg: ${romanNumeralsStage}, Atr: ${currentChara.attribute.toString().substring(0, 2)}")
}
}
} else {
Column {
Text(text = "????????????????")
Text(stringResource(R.string.dex_chara_unknown_name))
Spacer(modifier = Modifier.padding(4.dp))
Text(text = "Stg: -, Atr: -")
Text(text = "HP: -, BP: -, AP: -")
Text(stringResource(R.string.dex_chara_stage_attribute_unknown))
Text(stringResource(R.string.dex_chara_stats_unknown))
}
}
}
@@ -198,7 +210,7 @@ fun DexCharaDetailsDialog(
) {
Image(
bitmap = selectedCharaImageBitmap.imageBitmap,
contentDescription = "Icon",
contentDescription = stringResource(R.string.dex_chara_icon_description),
modifier = Modifier
.size(selectedCharaImageBitmap.dpWidth)
.padding(8.dp),
@@ -214,8 +226,22 @@ fun DexCharaDetailsDialog(
.padding(16.dp)
)
Column {
Text("Tr: ${it.requiredTrophies}; Bt: ${it.requiredBattles}; Vr: ${it.requiredVitals}; Wr: ${it.requiredWinRate}%; Ct: ${it.changeTimerHours}h")
Text("AdvLvl ${it.requiredAdventureLevelCompleted + 1}")
Text(
text = stringResource(
R.string.dex_chara_requirements,
it.requiredTrophies,
it.requiredBattles,
it.requiredVitals,
it.requiredWinRate,
it.changeTimerHours
)
)
Text(
text = stringResource(
R.string.dex_chara_adventure_level,
it.requiredAdventureLevelCompleted + 1
)
)
}
}
}
@@ -229,7 +255,7 @@ fun DexCharaDetailsDialog(
showFusions = true
}
) {
Text("Fusions")
Text(stringResource(R.string.dex_chara_fusions_button))
}
}
@@ -241,7 +267,7 @@ fun DexCharaDetailsDialog(
Button(
onClick = onClickClose
) {
Text("Close")
Text(stringResource(R.string.dex_chara_close_button))
}
}
}
@@ -8,8 +8,10 @@ import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable
fun BetaWarning(
@@ -19,26 +21,26 @@ fun BetaWarning(
onDismissRequest = onDismissRequest
) {
Card {
Column (
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = "This application is currently in alpha and it is not complete. Do not use to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!"
text = stringResource(R.string.beta_warning_message_main)
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = "Application should work now with the original VB and the VH."
text = stringResource(R.string.beta_warning_message_compatibility)
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = "Thank you for your understanding and patience. Sincerely, the dev team."
text = stringResource(R.string.beta_warning_message_thanks)
)
Spacer(modifier = Modifier.padding(8.dp))
Button(
onClick = onDismissRequest
) {
Text(text = "Dismiss")
Text(text = stringResource(R.string.beta_warning_button_dismiss))
}
}
}
@@ -11,6 +11,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -19,6 +20,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@@ -27,9 +29,7 @@ import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEBEmHomeScreen
@@ -37,8 +37,12 @@ import com.github.nacabaro.vbhelper.screens.homeScreens.screens.BEDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.screens.VBDiMHomeScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.source.CardRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.flowOf
import kotlin.collections.emptyList
@Composable
fun HomeScreen(
@@ -47,28 +51,59 @@ fun HomeScreen(
) {
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val activeMon = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
val beData = remember { mutableStateOf<BECharacterData?>(null) }
val vbData = remember { mutableStateOf<VBCharacterData?>(null) }
val vbSpecialMissions = remember { mutableStateOf<List<SpecialMissions>>(emptyList()) }
val cardRepository = CardRepository(application.container.db)
val activeMon by storageRepository
.getActiveCharacter()
.collectAsState(initial = null)
val cardIconData by (
activeMon
?.let { chara ->
cardRepository.getCardIconByCharaId(chara.charId)
}
?: flowOf<CardDtos.CardIcon?>(null)
).collectAsState(initial = null)
val transformationHistory by (
activeMon
?.let { chara ->
storageRepository.getTransformationHistory(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbSpecialMissions by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getSpecialMissions(chara.id)
}
?: flowOf(emptyList())
).collectAsState(initial = emptyList())
val vbData by (
activeMon
?.takeIf { it.characterType == DeviceType.VBDevice }
?.let { chara ->
storageRepository.getCharacterVbData(chara.id)
}
?: flowOf<VBCharacterData?>(null)
).collectAsState(initial = null)
val beData by (
activeMon
?.takeIf { it.characterType == DeviceType.BEDevice }
?.let { chara ->
storageRepository.getCharacterBeData(chara.id)
}
?: flowOf<BECharacterData?>(null)
).collectAsState(initial = null)
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
var betaWarning by rememberSaveable { mutableStateOf(true) }
var collectedItem by remember { mutableStateOf<ItemDtos.PurchasedItem?>(null) }
LaunchedEffect(storageRepository, activeMon, collectedItem) {
withContext(Dispatchers.IO) {
activeMon.value = storageRepository.getActiveCharacter()
if (activeMon.value != null && activeMon.value!!.characterType == DeviceType.BEDevice) {
beData.value = storageRepository.getCharacterBeData(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
} else if (activeMon.value != null && activeMon.value!!.characterType == DeviceType.VBDevice) {
vbData.value = storageRepository.getCharacterVbData(activeMon.value!!.id)
vbSpecialMissions.value = storageRepository.getSpecialMissions(activeMon.value!!.id)
transformationHistory.value = storageRepository.getTransformationHistory(activeMon.value!!.id)
}
}
}
var collectedCurrency by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(true) {
homeScreenController
@@ -80,7 +115,7 @@ fun HomeScreen(
Scaffold (
topBar = {
TopBanner(
text = "VB Helper",
text = stringResource(R.string.home_title),
onScanClick = {
navController.navigate(NavigationItems.Scan.route)
},
@@ -90,7 +125,7 @@ fun HomeScreen(
)
}
) { contentPadding ->
if (activeMon.value == null || (beData.value == null && vbData.value == null) || transformationHistory.value == null) {
if (activeMon == null || (beData == null && vbData == null) || cardIconData == null || transformationHistory.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
@@ -98,34 +133,44 @@ fun HomeScreen(
.fillMaxSize()
.padding(top = contentPadding.calculateTopPadding())
) {
Text(text = "Nothing to see here")
Text(text = stringResource(R.string.adventure_empty_state))
}
} else {
if (activeMon.value!!.isBemCard) {
val cardIcon = BitmapData(
bitmap = cardIconData!!.cardIcon,
width = cardIconData!!.cardIconWidth,
height = cardIconData!!.cardIconHeight
)
if (activeMon!!.isBemCard && beData != null) {
BEBEmHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
)
} else if (!activeMon.value!!.isBemCard && activeMon.value!!.characterType == DeviceType.BEDevice) {
BEDiMHomeScreen(
activeMon = activeMon.value!!,
beData = beData.value!!,
transformationHistory = transformationHistory.value!!,
contentPadding = contentPadding
)
} else {
VBDiMHomeScreen(
activeMon = activeMon.value!!,
vbData = vbData.value!!,
transformationHistory = transformationHistory.value!!,
activeMon = activeMon!!,
beData = beData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
specialMissions = vbSpecialMissions.value,
cardIcon = cardIcon
)
} else if (!activeMon!!.isBemCard && activeMon!!.characterType == DeviceType.BEDevice && beData != null) {
BEDiMHomeScreen(
activeMon = activeMon!!,
beData = beData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
cardIcon = cardIcon
)
} else if (vbData != null) {
VBDiMHomeScreen(
activeMon = activeMon!!,
vbData = vbData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
specialMissions = vbSpecialMissions,
homeScreenController = homeScreenController,
onClickCollect = { item ->
onClickCollect = { item, currency ->
collectedItem = item
}
collectedCurrency = currency
},
cardIcon = cardIcon
)
}
}
@@ -134,8 +179,10 @@ fun HomeScreen(
if (collectedItem != null) {
ObtainedItemDialog(
obtainedItem = collectedItem!!,
obtainedCurrency = collectedCurrency!!,
onClickDismiss = {
collectedItem = null
collectedCurrency = null
}
)
}
@@ -150,7 +197,7 @@ fun HomeScreen(
.padding(16.dp)
) {
Text(
text = "One of your characters has finished their adventure mission!",
text = stringResource(R.string.home_adventure_mission_finished),
textAlign = TextAlign.Center
)
Button(
@@ -161,7 +208,7 @@ fun HomeScreen(
.padding(8.dp)
.fillMaxWidth()
) {
Text(text = "Dismiss")
Text(text = stringResource(R.string.beta_warning_button_dismiss))
}
}
}
@@ -1,9 +1,8 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import com.github.cfogrady.vbnfc.vb.SpecialMission
import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?) -> Unit)
fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit)
}
@@ -23,6 +23,7 @@ class HomeScreenControllerImpl(
val adventureCharacters = database
.adventureDao()
.getAdventureCharacters()
.first()
val finishedAdventureCharacters = adventureCharacters.filter { character ->
character.finishesAdventure <= currentTime
@@ -32,13 +33,18 @@ class HomeScreenControllerImpl(
}
}
override fun clearSpecialMission(missionId: Long, missionCompletion: SpecialMission.Status, onCleared: (ItemDtos.PurchasedItem?) -> Unit) {
override fun clearSpecialMission(missionId: Long, onCleared: (ItemDtos.PurchasedItem?, Int?) -> Unit) {
componentActivity.lifecycleScope.launch {
val missionStatus = database
.specialMissionDao()
.getSpecialMission(missionId)
.first()
database
.specialMissionDao()
.clearSpecialMission(missionId)
if (missionCompletion == SpecialMission.Status.COMPLETED) {
if (missionStatus.status == SpecialMission.Status.COMPLETED) {
val randomItem = database
.itemDao()
.getAllItems()
@@ -64,11 +70,14 @@ class HomeScreenControllerImpl(
itemType = randomItem.itemType
)
onCleared(purchasedItem)
} else {
onCleared(null)
}
val randomAmount = (2..6).random() * 1000
val currentCurrency = application.container.currencyRepository.currencyValue.first()
application.container.currencyRepository.setCurrencyValue(currentCurrency + randomAmount)
onCleared(purchasedItem, randomAmount)
} else {
onCleared(null, null)
}
}
}
}
@@ -0,0 +1,56 @@
package com.github.nacabaro.vbhelper.screens.homeScreens.dialogs
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
@Composable
fun DeleteSpecialMissionDialog(
onClickDismiss: () -> Unit,
onClickDelete: () -> Unit
) {
Dialog(
onDismissRequest = onClickDismiss
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_special_mission_delete_main),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.padding(8.dp))
Row {
Button(
onClick = onClickDismiss,
modifier = Modifier
.padding(8.dp)
) {
Text(text = stringResource(R.string.home_special_mission_delete_dismiss))
}
Button(
onClick = onClickDelete,
modifier = Modifier
.padding(8.dp)
) {
Text(text = stringResource(R.string.home_special_mission_delete_remove))
}
}
}
}
}
}
@@ -26,6 +26,7 @@ import java.util.Locale
fun BEBEmHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
beData: BECharacterData,
cardIcon: BitmapData,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues
) {
@@ -46,6 +47,7 @@ fun BEBEmHomeScreen(
),
multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small,
cardIcon = cardIcon,
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
@@ -26,6 +26,7 @@ import kotlin.text.format
@Composable
fun BEDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
beData: BECharacterData,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues
@@ -45,6 +46,7 @@ fun BEDiMHomeScreen(
width = activeMon.spriteWidth,
height = activeMon.spriteHeight
),
cardIcon = cardIcon,
multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier
@@ -10,6 +10,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -25,17 +29,23 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.utils.BitmapData
import java.util.Locale
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.screens.homeScreens.dialogs.DeleteSpecialMissionDialog
@Composable
fun VBDiMHomeScreen(
activeMon: CharacterDtos.CharacterWithSprites,
cardIcon: BitmapData,
vbData: VBCharacterData,
specialMissions: List<SpecialMissions>,
homeScreenController: HomeScreenControllerImpl,
transformationHistory: List<CharacterDtos.TransformationHistory>,
contentPadding: PaddingValues,
onClickCollect: (ItemDtos.PurchasedItem?) -> Unit
onClickCollect: (ItemDtos.PurchasedItem?, Int?) -> Unit
) {
var selectedSpecialMissionId by remember { mutableStateOf<Long>(-1) }
Column(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
@@ -51,6 +61,7 @@ fun VBDiMHomeScreen(
width = activeMon.spriteWidth,
height = activeMon.spriteHeight
),
cardIcon = cardIcon,
multiplier = 8,
shape = androidx.compose.material.MaterialTheme.shapes.small,
modifier = Modifier
@@ -65,7 +76,7 @@ fun VBDiMHomeScreen(
ItemDisplay(
icon = R.drawable.baseline_vitals_24,
textValue = activeMon.vitalPoints.toString(),
definition = "Vitals",
definition = stringResource(R.string.home_vbdim_vitals),
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
@@ -74,7 +85,7 @@ fun VBDiMHomeScreen(
ItemDisplay(
icon = R.drawable.baseline_trophy_24,
textValue = activeMon.trophies.toString(),
definition = "Trophies",
definition = stringResource(R.string.home_vbdim_trophies),
modifier = Modifier
.weight(0.5f)
.aspectRatio(1f)
@@ -89,7 +100,7 @@ fun VBDiMHomeScreen(
ItemDisplay(
icon = R.drawable.baseline_mood_24,
textValue = activeMon.mood.toString(),
definition = "Mood",
definition = stringResource(R.string.home_vbdim_mood),
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
@@ -102,7 +113,7 @@ fun VBDiMHomeScreen(
0 -> "${activeMon.transformationCountdown} m"
else -> "$transformationCountdownInHours h"
},
definition = "Next timer",
definition = stringResource(R.string.home_vbdim_next_timer),
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
@@ -122,7 +133,7 @@ fun VBDiMHomeScreen(
) + " %" // Specify locale
}
},
definition = "Total battle win %",
definition = stringResource(R.string.home_vbdim_total_battle_win),
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
@@ -142,7 +153,7 @@ fun VBDiMHomeScreen(
) + " %" // Specify locale
}
},
definition = "Current phase win %",
definition = stringResource(R.string.home_vbdim_current_phase_win),
modifier = Modifier
.weight(1f)
.aspectRatio(1f)
@@ -165,7 +176,7 @@ fun VBDiMHomeScreen(
.padding(16.dp)
) {
Text(
text = "Special missions",
text = stringResource(R.string.home_vbdim_special_missions),
fontSize = 24.sp
)
}
@@ -179,11 +190,28 @@ fun VBDiMHomeScreen(
modifier = Modifier
.weight(1f)
.padding(8.dp),
) {
homeScreenController
.clearSpecialMission(mission.id, mission.status, onClickCollect)
}
onClickMission = { missionId ->
selectedSpecialMissionId = missionId
},
onClickCollect = {
homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
}
)
}
}
}
if (selectedSpecialMissionId.toInt() != -1) {
DeleteSpecialMissionDialog(
onClickDismiss = {
selectedSpecialMissionId = -1
},
onClickDelete = {
homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
selectedSpecialMissionId = -1
}
)
}
}
@@ -15,6 +15,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
@@ -24,8 +25,9 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable
fun ChooseCharacterScreen(
@@ -35,7 +37,7 @@ fun ChooseCharacterScreen(
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val storageRepository = StorageRepository(application.container.db, )
val characterList = remember {
mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList())
}
@@ -57,7 +59,7 @@ fun ChooseCharacterScreen(
characterList.value = storageRepository.getVBCharacters()
}
else -> {
characterList.value = storageRepository.getAllCharacters()
characterList.value = storageRepository.getAllCharacters().first()
}
}
}
@@ -68,7 +70,7 @@ fun ChooseCharacterScreen(
itemsScreenController.applyItem(itemId, selectedCharacter!!) {
Toast.makeText(
application.applicationContext,
"Item applied!",
application.getString(R.string.choose_character_item_applied),
Toast.LENGTH_SHORT
).show()
navController.popBackStack()
@@ -79,7 +81,7 @@ fun ChooseCharacterScreen(
Scaffold(
topBar = {
TopBanner(
text = "Choose character",
text = stringResource(R.string.choose_character_title),
onBackClick = {
navController.popBackStack()
}
@@ -17,6 +17,7 @@ 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.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -27,6 +28,8 @@ import com.github.nacabaro.vbhelper.domain.items.ItemType
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@Composable
fun ItemDialog(
item: ItemDtos.ItemsWithQuantities,
@@ -95,7 +98,10 @@ fun ItemDialog(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "Costs ${item.price} credits",
text = stringResource(
R.string.item_dialog_costs_credits,
item.price
),
modifier = Modifier
.fillMaxWidth()
)
@@ -104,7 +110,10 @@ fun ItemDialog(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have ${item.quantity} of this item",
text = stringResource(
R.string.item_dialog_you_have_quantity,
item.quantity
),
modifier = Modifier
.fillMaxWidth()
)
@@ -118,7 +127,7 @@ fun ItemDialog(
Button(
onClick = onClickUse
) {
Text("Use item")
Text(stringResource(R.string.item_dialog_use))
}
}
@@ -126,7 +135,7 @@ fun ItemDialog(
Button(
onClick = onClickPurchase
) {
Text("Purchase")
Text(stringResource(R.string.item_dialog_purchase))
}
}
@@ -134,7 +143,7 @@ fun ItemDialog(
Button(
onClick = onClickCancel
) {
Text("Cancel")
Text(stringResource(R.string.item_dialog_cancel))
}
}
}
@@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.R
@Composable
fun ItemElement(
@@ -17,6 +17,9 @@ import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun ItemsScreen(
@@ -30,14 +33,14 @@ fun ItemsScreen(
Scaffold(
topBar = {
Column {
TopBanner("Items")
TopBanner(text = stringResource(R.string.items_title))
TabRow(
selectedTabIndex = selectedTabItem,
modifier = Modifier
) {
items.forEachIndexed { index, item ->
Tab(
text = { Text(item.label) },
text = { Text(text = stringResource(item.label)) },
selected = selectedTabItem == index,
onClick = { selectedTabItem = index }
)
@@ -11,6 +11,8 @@ import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -52,9 +54,16 @@ class ItemsScreenControllerImpl (
var vbCharacterData: VBCharacterData? = null
if (characterData.characterType == DeviceType.BEDevice) {
beCharacterData = database.userCharacterDao().getBeData(characterId)
beCharacterData = database
.userCharacterDao()
.getBeData(characterId)
.firstOrNull()
} else if (characterData.characterType == DeviceType.VBDevice) {
vbCharacterData = database.userCharacterDao().getVbData(characterId)
vbCharacterData = database
.userCharacterDao()
.getVbData(characterId)
.firstOrNull()
}
if (
@@ -154,32 +163,34 @@ class ItemsScreenControllerImpl (
else -> 0
}
val specialMissionSlot = when (itemIcon) {
ItemTypes.Step8k.id -> 0
ItemTypes.Step4k.id -> 0
ItemTypes.Vitals1000.id -> 1
ItemTypes.Vitals250.id -> 1
ItemTypes.Battle20.id -> 2
ItemTypes.Battle5.id -> 2
ItemTypes.Win10.id -> 3
ItemTypes.Win4.id -> 3
else -> 0
}
val availableSpecialMissions = database
.userCharacterDao()
.getSpecialMissions(characterId)
.first()
var firstUnavailableMissionSlot: Long = 0
var watchId = 0
for ((index, mission) in availableSpecialMissions.withIndex()) {
if (
mission.status == SpecialMission.Status.UNAVAILABLE
) {
firstUnavailableMissionSlot = mission.id
watchId = index + 1
}
}
val newSpecialMission = SpecialMissions(
id = firstUnavailableMissionSlot,
characterId = characterId,
missionType = specialMissionType,
var newSpecialMission = availableSpecialMissions[specialMissionSlot]
newSpecialMission = SpecialMissions(
id = newSpecialMission.id,
characterId = newSpecialMission.characterId,
goal = specialMissionGoal,
timeLimitInMinutes = itemLength,
watchId = watchId,
status = SpecialMission.Status.AVAILABLE,
watchId = newSpecialMission.watchId,
progress = 0,
timeElapsedInMinutes = 0
status = SpecialMission.Status.AVAILABLE,
timeElapsedInMinutes = 0,
timeLimitInMinutes = itemLength,
missionType = specialMissionType
)
database
@@ -21,6 +21,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.database.AppDatabase
@@ -30,6 +31,7 @@ import com.github.nacabaro.vbhelper.source.CurrencyRepository
import com.github.nacabaro.vbhelper.source.ItemsRepository
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
@Composable
fun ItemsStore(
@@ -52,7 +54,7 @@ fun ItemsStore(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("No items")
Text(stringResource(R.string.items_no_items))
}
} else {
Column() {
@@ -62,7 +64,10 @@ fun ItemsStore(
.fillMaxWidth()
) {
Text(
text = "${currentCurrency.value} credits",
text = stringResource(
R.string.items_store_credits,
currentCurrency.value
),
modifier = Modifier
.padding(8.dp)
)
@@ -94,14 +99,15 @@ fun ItemsStore(
scope.launch {
Toast.makeText(
application.applicationContext,
purchaseItem(
application.container.db,
myItems[selectedElementIndex!!],
currencyRepository
application.getString(
purchaseItem(
application.container.db,
myItems[selectedElementIndex!!],
currencyRepository
)
),
Toast.LENGTH_SHORT
).show(
)
).show()
}
},
onClickCancel = { selectedElementIndex = null }
@@ -113,9 +119,9 @@ suspend fun purchaseItem(
db: AppDatabase,
item: ItemDtos.ItemsWithQuantities,
currencyRepository: CurrencyRepository
): String {
if (currencyRepository.currencyValue.first() < item.price) {
return "Not enough credits"
): Int {
return if (currencyRepository.currencyValue.first() < item.price) {
R.string.items_not_enough_credits
} else {
db
.itemDao()
@@ -129,6 +135,6 @@ suspend fun purchaseItem(
currencyRepository.currencyValue.first() - item.price
)
return "Purchase successful!"
R.string.items_purchase_success
}
}
@@ -17,11 +17,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.ItemsRepository
import com.github.nacabaro.vbhelper.R
@Composable
fun MyItems(
@@ -39,7 +42,7 @@ fun MyItems(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text("No items")
Text(stringResource(R.string.items_no_items))
}
} else {
LazyVerticalGrid(
@@ -19,10 +19,13 @@ 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
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun ObtainedItemDialog(
obtainedItem: ItemDtos.PurchasedItem,
obtainedCurrency: Int,
onClickDismiss: () -> Unit
) {
Dialog(
@@ -81,17 +84,32 @@ fun ObtainedItemDialog(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have obtained ${obtainedItem.itemAmount} of this item",
text = stringResource(
R.string.obtained_item_you_have,
obtainedItem.itemAmount
),
modifier = Modifier
.fillMaxWidth()
.padding(5.dp)
.padding(top = 4.dp)
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = stringResource(
R.string.obtained_item_you_also_got_credits,
obtainedCurrency
),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
)
Button(
onClick = onClickDismiss,
modifier = Modifier
.fillMaxWidth()
) {
Text(text = "Dismiss")
Text(text = stringResource(R.string.obtained_item_dismiss))
}
}
}
@@ -0,0 +1,80 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.scan_title),
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = stringResource(R.string.scan_vb_to_app),
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = stringResource(R.string.scan_app_to_vb),
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}
@@ -1,44 +1,31 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.ReadingScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.screens.WritingScreen
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
import com.github.nacabaro.vbhelper.R
const val SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER = "SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER"
@@ -55,8 +42,6 @@ fun ScanScreen(
val storageRepository = StorageRepository(application.container.db)
var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
val context = LocalContext.current
LaunchedEffect(storageRepository) {
@@ -73,143 +58,33 @@ fun ScanScreen(
}
}
var readingScreen by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
var readingScreen by remember { mutableStateOf(false) }
DisposableEffect(readingScreen) {
if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
}
)
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
DisposableEffect(writingScreen, isDoneSendingCard) {
if (writingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
}
)
}
if (secrets != null && nfcCharacter != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter!!) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter!!) {
isDoneWritingCharacter = true
}
}
}
onDispose {
if(writingScreen) {
scanScreenController.unregisterActivityLifecycleListener(SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
} else if (isDoneSendingCard && isDoneWritingCharacter) {
writingScreen = false
navController.navigate(NavigationItems.Home.route)
LaunchedEffect(storageRepository) {
withContext(Dispatchers.IO) {
storageRepository
.deleteCharacter(characterId!!)
}
}
}
if (readingScreen) {
ReadingCharacterScreen("Reading character") {
readingScreen = false
scanScreenController.cancelRead()
}
} else if (writingScreen) {
if (!isDoneSendingCard) {
ReadingCharacterScreen("Sending card") {
if (writingScreen && nfcCharacter != null && characterId != null) {
WritingScreen(
scanScreenController = scanScreenController,
nfcCharacter = nfcCharacter!!,
characterId = characterId,
onComplete = {
writingScreen = false
scanScreenController.cancelRead()
}
} else if (!isDoneWritingCharacter) {
ReadingCharacterScreen("Writing character") {
isDoneSendingCard = false
navController.navigate(NavigationItems.Home.route)
},
onCancel = {
writingScreen = false
scanScreenController.cancelRead()
navController.navigate(NavigationItems.Home.route)
}
}
} else if (cardSelectScreen) {
ChooseCard(
cards = cardsRead!!,
onCardSelected = { card ->
cardSelectScreen = false
scanScreenController.flushCharacter(card.id)
)
} else if (readingScreen) {
ReadingScreen(
scanScreenController = scanScreenController,
onCancel = {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
},
onComplete = {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
}
)
} else {
@@ -219,9 +94,9 @@ fun ScanScreen(
else -> {
{
if(secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.scan_secrets_not_initialized), Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.scan_secrets_not_imported), Toast.LENGTH_SHORT).show()
} else {
readingScreen = true // kicks off nfc adapter in DisposableEffect
}
@@ -233,9 +108,9 @@ fun ScanScreen(
else -> {
{
if(secrets == null) {
Toast.makeText(context, "Secrets is not yet initialized. Try again.", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.scan_secrets_not_initialized), Toast.LENGTH_SHORT).show()
} else if(secrets?.isMissingSecrets() == true) {
Toast.makeText(context, "Secrets not yet imported. Go to Settings and Import APK", Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.scan_secrets_not_imported), Toast.LENGTH_SHORT).show()
} else {
writingScreen = true // kicks off nfc adapter in DisposableEffect
}
@@ -247,66 +122,8 @@ fun ScanScreen(
}
}
@Composable
fun ChooseConnectOption(
onClickRead: (() -> Unit)? = null,
onClickWrite: (() -> Unit)? = null,
navController: NavController
) {
Scaffold(
topBar = {
TopBanner(
text = "Scan a Vital Bracelet",
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(contentPadding)
) {
ScanButton(
text = "Vital Bracelet to App",
disabled = onClickRead == null,
onClick = onClickRead?: { },
)
Spacer(modifier = Modifier.height(16.dp))
ScanButton(
text = "App to Vital Bracelet",
disabled = onClickWrite == null,
onClick = onClickWrite?: { },
)
}
}
}
@Composable
fun ScanButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
disabled: Boolean = false,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = !disabled,
) {
Text(
text = text,
fontSize = 16.sp,
modifier = Modifier
.padding(4.dp)
)
}
}
@Preview(showBackground = true)
@Composable
fun ScanScreenPreview() {
@@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import com.github.nacabaro.vbhelper.R
class ScanScreenControllerImpl(
override val secretsFlow: Flow<Secrets>,
@@ -38,7 +39,7 @@ class ScanScreenControllerImpl(
init {
val maybeNfcAdapter = NfcAdapter.getDefaultAdapter(componentActivity)
if (maybeNfcAdapter == null) {
Toast.makeText(componentActivity, "No NFC on device!", Toast.LENGTH_SHORT).show()
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_no_nfc_on_device), Toast.LENGTH_SHORT).show()
}
nfcAdapter = maybeNfcAdapter
checkSecrets()
@@ -94,7 +95,7 @@ class ScanScreenControllerImpl(
val nfcData = NfcA.get(tag)
if (nfcData == null) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Tag detected is not VB", Toast.LENGTH_SHORT).show()
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_tag_not_vb), Toast.LENGTH_SHORT).show()
}
}
nfcData.connect()
@@ -112,7 +113,7 @@ class ScanScreenControllerImpl(
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()
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_missing_secrets), Toast.LENGTH_SHORT).show()
}
}
}
@@ -135,10 +136,10 @@ class ScanScreenControllerImpl(
tagCommunicator.sendCharacter(castNfcCharacter)
}
onComplete.invoke()
"Sent character successfully!"
componentActivity.getString(R.string.scan_sent_character_success)
} catch (e: Throwable) {
Log.e("TAG", e.stackTraceToString())
"Whoops"
componentActivity.getString(R.string.scan_error_generic)
}
}
}
@@ -151,13 +152,13 @@ class ScanScreenControllerImpl(
handleTag(secrets) { tagCommunicator ->
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
onComplete.invoke()
"Sent DIM successfully!"
componentActivity.getString(R.string.scan_sent_dim_success)
}
}
// EXTRACTED DIRECTLY FROM EXAMPLE APP
private fun showWirelessSettings() {
Toast.makeText(componentActivity, "NFC must be enabled", Toast.LENGTH_SHORT).show()
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_nfc_must_be_enabled), Toast.LENGTH_SHORT).show()
componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
}
@@ -1,13 +1,11 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.util.Log
import androidx.activity.ComponentActivity
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.card.Card
import com.github.nacabaro.vbhelper.domain.card.CardProgress
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
@@ -250,10 +248,11 @@ class FromNfcConverter (
) {
val vitalsHistoryWatch = nfcCharacter.vitalHistory
val vitalsHistory = vitalsHistoryWatch.map { historyElement ->
Log.d("VitalsHistory", "${historyElement.year.toInt()} ${historyElement.month.toInt()} ${historyElement.day.toInt()}")
val year = if (historyElement.year.toInt() !in 2021 .. 2035) 0 else historyElement.year.toInt()
VitalsHistory(
charId = characterId,
year = historyElement.year.toInt(),
year = year,
month = historyElement.month.toInt(),
day = historyElement.day.toInt(),
vitalPoints = historyElement.vitalsGained.toInt()
@@ -14,6 +14,7 @@ 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.utils.DeviceType
import kotlinx.coroutines.flow.first
import java.util.Date
class ToNfcConverter(
@@ -54,6 +55,7 @@ class ToNfcConverter(
val vbData = database
.userCharacterDao()
.getVbData(characterId)
.first()
val paddedTransformationArray = generateTransformationHistory(characterId, 9)
@@ -96,6 +98,7 @@ class ToNfcConverter(
val cardData = database
.cardDao()
.getCardByCharacterId(userCharacter.id)
.first()
val appReserved = Array<UShort>(3) {
0u
@@ -114,6 +117,7 @@ class ToNfcConverter(
val specialMissions = database
.userCharacterDao()
.getSpecialMissions(characterId)
.first()
val watchSpecialMissions = specialMissions.map {
SpecialMission(
@@ -173,6 +177,7 @@ class ToNfcConverter(
val beData = database
.userCharacterDao()
.getBeData(characterId)
.first()
val paddedTransformationArray = generateTransformationHistory(characterId)
@@ -235,7 +240,8 @@ class ToNfcConverter(
): Array<NfcCharacter.Transformation> {
val transformationHistory = database
.userCharacterDao()
.getTransformationHistory(characterId)!!
.getTransformationHistory(characterId)
.first()
.map {
val date = Date(it.transformationDate)
val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC"))
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.screens.scanScreen
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -10,17 +10,22 @@ 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.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadingCharacterScreen(
fun ActionScreen(
topBannerText: String,
onClickCancel: () -> Unit,
) {
Scaffold (
topBar = {
TopBanner(topBannerText)
TopBanner(
text = topBannerText,
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column (
@@ -30,12 +35,12 @@ fun ReadingCharacterScreen(
.padding(innerPadding)
.fillMaxSize()
) {
Text("Place your Vital Bracelet near the reader...")
Text(stringResource(R.string.action_place_near_reader))
Button(
onClick = onClickCancel,
modifier = Modifier.padding(16.dp)
) {
Text("Cancel")
Text(stringResource(R.string.action_cancel))
}
}
}
@@ -0,0 +1,61 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadCharacterScreen(
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.read_character_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Text(
text = stringResource(R.string.read_character_prepare_device),
textAlign = TextAlign.Center
)
Text(
text = stringResource(R.string.read_character_go_to_connect),
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.padding(8.dp)
)
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.read_character_confirm))
}
}
}
}
@@ -0,0 +1,112 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.R
@Composable
fun ReadingScreen(
scanScreenController: ScanScreenController,
onCancel: () -> Unit,
onComplete: () -> Unit
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
var readingScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) }
DisposableEffect(readingScreen) {
if(readingScreen) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object: ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
}
)
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
onComplete()
}
if (!readingScreen) {
ReadCharacterScreen(
onClickConfirm = {
readingScreen = true
},
onClickCancel = {
onCancel()
}
)
}
if (readingScreen) {
ActionScreen(topBannerText = stringResource(R.string.reading_character_title),) {
readingScreen = false
scanScreenController.cancelRead()
onCancel()
}
} else if (cardSelectScreen) {
ChooseCard(
cards = cardsRead!!,
onCardSelected = { card ->
cardSelectScreen = false
scanScreenController.flushCharacter(card.id)
}
)
}
}
@@ -0,0 +1,142 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable
fun WriteCardScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.write_card_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(
R.string.write_card_icon_description
),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text(stringResource(R.string.write_card_device_ready))
Text(
stringResource(
R.string.write_card_required_card,
cardDetails.name
)
)
}
}
}
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.write_card_confirm))
}
}
}
}
@@ -0,0 +1,138 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.ScanRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getImageBitmap
import com.github.nacabaro.vbhelper.R
@Composable
fun WriteCharacterScreen(
characterId: Long,
onClickCancel: () -> Unit,
onClickConfirm: () -> Unit
) {
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val scanRepository = ScanRepository(database)
val cardDetails by scanRepository.getCardDetails(characterId).collectAsState(Card(
id = 0,
cardId = 0,
name = "",
logo = byteArrayOf(),
logoHeight = 0,
logoWidth = 0,
stageCount = 0,
isBEm = false
))
Scaffold(
topBar = {
TopBanner(
text = stringResource(R.string.write_character_title),
onBackClick = onClickCancel
)
}
) { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Card (
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row (
modifier = Modifier.padding(16.dp),
){
if (cardDetails.logoHeight > 0 && cardDetails.logoWidth > 0) {
val charaBitmapData = BitmapData(
bitmap = cardDetails.logo,
width = cardDetails.logoWidth,
height = cardDetails.logoHeight
)
val charaImageBitmapData = charaBitmapData.getImageBitmap(
context = LocalContext.current,
multiplier = 4,
obscure = false
)
Card (
colors = CardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledContentColor = MaterialTheme.colorScheme.contentColorFor(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
)
)
) {
Image(
bitmap = charaImageBitmapData.imageBitmap,
contentDescription = stringResource(R.string.write_character_icon_description),
modifier = Modifier
.size(charaImageBitmapData.dpWidth)
.padding(8.dp),
filterQuality = FilterQuality.None
)
}
}
Spacer(
modifier = Modifier.width(8.dp)
)
Column {
Text(stringResource(R.string.write_character_success))
Text(stringResource(R.string.write_character_wait_ready))
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = onClickConfirm,
) {
Text(stringResource(R.string.write_character_confirm))
}
}
}
}
@@ -0,0 +1,142 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.screens.scanScreen.SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenController
import com.github.nacabaro.vbhelper.source.StorageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun WritingScreen(
scanScreenController: ScanScreenController,
characterId: Long,
nfcCharacter: NfcCharacter,
onComplete: () -> Unit,
onCancel: () -> Unit,
) {
val secrets by scanScreenController.secretsFlow.collectAsState(null)
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
var writing by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var writingConfirmScreen by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
DisposableEffect(writing) {
if (writing) {
scanScreenController.registerActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER,
object : ActivityLifecycleListener {
override fun onPause() {
scanScreenController.cancelRead()
}
override fun onResume() {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
)
if (secrets != null) {
if (!isDoneSendingCard) {
scanScreenController.onClickCheckCard(secrets!!, nfcCharacter) {
isDoneSendingCard = true
}
} else if (!isDoneWritingCharacter) {
scanScreenController.onClickWrite(secrets!!, nfcCharacter) {
isDoneWritingCharacter = true
}
}
}
}
onDispose {
if (writing) {
scanScreenController.unregisterActivityLifecycleListener(
SCAN_SCREEN_ACTIVITY_LIFECYCLE_LISTENER
)
scanScreenController.cancelRead()
}
}
}
if (!writingScreen) {
writing = false
WriteCardScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingScreen = true
}
)
} else if (!isDoneSendingCard) {
writing = true
ActionScreen( topBannerText = stringResource(R.string.sending_card_title)) {
scanScreenController.cancelRead()
onCancel()
}
} else if (!writingConfirmScreen) {
writing = false
WriteCharacterScreen (
characterId = characterId,
onClickCancel = {
scanScreenController.cancelRead()
onCancel()
},
onClickConfirm = {
writingConfirmScreen = true
}
)
} else if (!isDoneWritingCharacter) {
writing = true
ActionScreen(topBannerText = stringResource(R.string.writing_character_action_title)) {
isDoneSendingCard = false
scanScreenController.cancelRead()
onCancel()
}
}
var completedWriting by remember { mutableStateOf(false) }
LaunchedEffect(isDoneSendingCard, isDoneWritingCharacter) {
withContext(Dispatchers.IO) {
if (isDoneSendingCard && isDoneWritingCharacter) {
storageRepository
.deleteCharacter(characterId)
completedWriting = true
}
}
}
if (completedWriting) {
onComplete()
}
}
@@ -6,8 +6,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.R
@Composable
fun CreditsScreen(
@@ -16,7 +18,7 @@ fun CreditsScreen(
Scaffold (
topBar = {
TopBanner(
text = "Credits",
text = stringResource(R.string.credits_title),
onBackClick = {
navController.popBackStack()
}
@@ -29,13 +31,13 @@ fun CreditsScreen(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
SettingsSection("Reverse engineering")
SettingsEntry(title = "cyanic", description = "Reversed the firmware and helped us during development.") { }
SettingsSection("Application development")
SettingsEntry(title = "cfogrady", description = "Developed vb-lib-nfc and part of this application.") { }
SettingsEntry(title = "nacabaro", description = "Developed this application.") { }
SettingsEntry(title = "lightheel", description = "Developing the battling part for this application, including server. Still in the works.") { }
SettingsEntry(title = "shvstrz", description = "Designing the app icon in SVG.") { }
SettingsSection(stringResource(R.string.credits_section_reverse_engineering))
SettingsEntry(title = "cyanic", description = stringResource(R.string.credits_cyanic_description)) { }
SettingsSection(stringResource(R.string.credits_section_app_development))
SettingsEntry(title = "cfogrady", description = stringResource(R.string.credits_cfogrady_description)) { }
SettingsEntry(title = "nacabaro", description = stringResource(R.string.credits_nacabaro_description)) { }
SettingsEntry(title = "lightheel", description = stringResource(R.string.credits_lightheel_description)) { }
SettingsEntry(title = "shvstrz", description = stringResource(R.string.credits_shvstrz_description)) { }
}
}
}
@@ -1,5 +1,7 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -13,22 +15,28 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.R
@Composable
fun SettingsScreen(
navController: NavController,
settingsScreenController: SettingsScreenControllerImpl,
) {
Scaffold (
val context = LocalContext.current
Scaffold(
topBar = {
TopBanner(
text = "Settings",
text = stringResource(R.string.settings_title),
onBackClick = {
navController.popBackStack()
}
@@ -37,31 +45,57 @@ fun SettingsScreen(
modifier = Modifier
.fillMaxSize()
) { contentPadding ->
Column (
Column(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
SettingsSection("NFC Communication")
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
SettingsSection(title = stringResource(R.string.settings_section_nfc))
SettingsEntry(
title = stringResource(R.string.settings_import_apk_title),
description = stringResource(R.string.settings_import_apk_desc)
) {
settingsScreenController.onClickImportApk()
}
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") {
SettingsSection(title = stringResource(R.string.settings_section_dim_bem))
SettingsEntry(
title = stringResource(R.string.settings_import_card_title),
description = stringResource(R.string.settings_import_card_desc)
) {
settingsScreenController.onClickImportCard()
}
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection("About and credits")
SettingsEntry(title = "Credits", description = "Credits") {
SettingsSection(title = stringResource(R.string.settings_section_about))
SettingsEntry(
title = stringResource(R.string.settings_credits_title),
description = stringResource(R.string.settings_credits_desc)
) {
navController.navigate(NavigationItems.Credits.route)
}
SettingsEntry(title = "About", description = "About") { }
SettingsSection("Data management")
SettingsEntry(title = "Export data", description = "Export application database") {
SettingsEntry(
title = stringResource(R.string.settings_about_title),
description = stringResource(R.string.settings_about_desc)
) {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://github.com/nacabaro/vbhelper/")
)
context.startActivity(browserIntent)
}
SettingsSection(title = stringResource(R.string.settings_section_data))
SettingsEntry(
title = stringResource(R.string.settings_export_data_title),
description = stringResource(R.string.settings_export_data_desc)
) {
settingsScreenController.onClickOpenDirectory()
}
SettingsEntry(title = "Import data", description = "Import application database") {
SettingsEntry(
title = stringResource(R.string.settings_import_data_title),
description = stringResource(R.string.settings_import_data_desc)
) {
settingsScreenController.onClickImportDatabase()
}
}
@@ -93,9 +127,9 @@ fun SettingsEntry(
fun SettingsSection(
title: String
) {
Box (
Box(
modifier = Modifier
.padding(start = 16.dp)
.padding(start = 16.dp, top = 16.dp, bottom = 4.dp)
) {
Text(
text = title,
@@ -33,11 +33,15 @@ 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
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
fun StorageDialog(
characterId: Long,
onDismissRequest: () -> Unit,
onClickDelete: () -> Unit,
onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit,
onClickSendToAdventure: (time: Long) -> Unit
@@ -93,7 +97,7 @@ fun StorageDialog(
val dpSize = (characterSprite.value!!.width * 4 / density).dp
Image(
bitmap = imageBitmap,
contentDescription = "Character image",
contentDescription = stringResource(R.string.storage_character_image_description),
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
@@ -103,7 +107,7 @@ fun StorageDialog(
val nameDpSize = (characterName.value!!.width * 4 / density).dp
Image(
bitmap = nameImageBitmap,
contentDescription = "Character image",
contentDescription = stringResource(R.string.storage_character_image_description),
filterQuality = FilterQuality.None,
modifier = Modifier
.size(nameDpSize)
@@ -120,7 +124,7 @@ fun StorageDialog(
modifier = Modifier
.weight(1f)
) {
Text(text = "Send to watch")
Text(text = stringResource(R.string.storage_send_to_watch))
}
Spacer(
modifier = Modifier
@@ -129,7 +133,7 @@ fun StorageDialog(
Button(
onClick = onClickSetActive,
) {
Text(text = "Set active")
Text(text = stringResource(R.string.storage_set_active))
}
}
Button(
@@ -139,14 +143,21 @@ fun StorageDialog(
modifier = Modifier
.fillMaxWidth()
) {
Text(text = "Send on adventure")
Text(text = stringResource(R.string.storage_send_on_adventure))
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onClickDelete
) {
Text(text = stringResource(R.string.storage_delete_character))
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onDismissRequest
) {
Text(text = "Close")
Text(text = stringResource(R.string.storage_close))
}
}
}
@@ -1,5 +1,6 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
@@ -11,14 +12,15 @@ 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.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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
@@ -28,12 +30,12 @@ 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
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@Composable
@@ -42,30 +44,23 @@ fun StorageScreen(
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) }
val characterList by storageRepository.getAllCharacters().collectAsState(initial = emptyList())
LaunchedEffect(storageRepository, selectedCharacter) {
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
Scaffold (
topBar = {
TopBanner(
text = "My characters",
text = stringResource(R.string.storage_my_characters_title),
onAdventureClick = {
navController.navigate(NavigationItems.Adventure.route)
}
)
}
) { contentPadding ->
if (monList.value.isEmpty()) {
if (characterList.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
@@ -74,7 +69,7 @@ fun StorageScreen(
.fillMaxSize()
) {
Text(
text = "Nothing to see here",
text = stringResource(R.string.storage_nothing_to_see_here),
textAlign = TextAlign.Center,
modifier = Modifier
)
@@ -86,7 +81,7 @@ fun StorageScreen(
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding())
) {
items(monList.value) { index ->
items(characterList) { index ->
CharacterEntry(
icon = BitmapData(
bitmap = index.spriteIdle,
@@ -99,7 +94,7 @@ fun StorageScreen(
} else {
Toast.makeText(
application,
"This character is in an adventure",
application.getString(R.string.storage_in_adventure_toast),
Toast.LENGTH_SHORT
).show()
navController.navigate(
@@ -107,6 +102,16 @@ fun StorageScreen(
)
}
},
cardColors = if (index.active) {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
)
} else {
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHighest
)
}
)
}
}
@@ -138,6 +143,16 @@ fun StorageScreen(
timeInMinutes = time
)
selectedCharacter = null
},
onClickDelete = {
storageScreenController
.deleteCharacter(
characterId = selectedCharacter!!,
onCompletion = {
Log.d("StorageScreen", "Character deleted")
}
)
selectedCharacter = null
}
)
}
@@ -2,4 +2,5 @@ package com.github.nacabaro.vbhelper.screens.storageScreen
interface StorageScreenController {
fun setActive(characterId: Long, onCompletion: () -> Unit)
fun deleteCharacter(characterId: Long, onCompletion: () -> Unit)
}
@@ -28,4 +28,21 @@ class StorageScreenControllerImpl(
}
}
}
override fun deleteCharacter(characterId: Long, onCompletion: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.userCharacterDao()
.deleteCharacterById(characterId)
componentActivity.runOnUiThread {
Toast.makeText(
componentActivity,
"Character deleted!",
Toast.LENGTH_SHORT
).show()
onCompletion()
}
}
}
}
@@ -0,0 +1,15 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow
class CardRepository (
private val db: AppDatabase
) {
fun getCardIconByCharaId(charaId: Long): Flow<CardDtos.CardIcon> {
return db
.cardDao()
.getCardIconByCharaId(charaId = charaId)
}
}
@@ -0,0 +1,13 @@
package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.card.Card
import kotlinx.coroutines.flow.Flow
class ScanRepository(
val database: AppDatabase
) {
fun getCardDetails(characterId: Long): Flow<Card> {
return database.cardDao().getCardByCharacterId(characterId)
}
}
@@ -6,11 +6,12 @@ import com.github.nacabaro.vbhelper.domain.device_data.SpecialMissions
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.flow.Flow
class StorageRepository (
private val db: AppDatabase
) {
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> {
fun getAllCharacters(): Flow<List<CharacterDtos.CharacterWithSprites>> {
return db.userCharacterDao().getAllCharacters()
}
@@ -18,19 +19,19 @@ class StorageRepository (
return db.userCharacterDao().getCharacterWithSprites(id)
}
suspend fun getCharacterBeData(id: Long): BECharacterData {
fun getCharacterBeData(id: Long): Flow<BECharacterData> {
return db.userCharacterDao().getBeData(id)
}
suspend fun getTransformationHistory(characterId: Long): List<CharacterDtos.TransformationHistory>? {
fun getTransformationHistory(characterId: Long): Flow<List<CharacterDtos.TransformationHistory>> {
return db.userCharacterDao().getTransformationHistory(characterId)
}
suspend fun getCharacterVbData(id: Long): VBCharacterData {
fun getCharacterVbData(id: Long): Flow<VBCharacterData> {
return db.userCharacterDao().getVbData(id)
}
suspend fun getSpecialMissions(id: Long): List<SpecialMissions> {
fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>> {
return db.userCharacterDao().getSpecialMissions(id)
}
@@ -38,7 +39,7 @@ class StorageRepository (
return db.itemDao().getItem(id)
}
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? {
fun getActiveCharacter(): Flow<CharacterDtos.CharacterWithSprites?> {
return db.userCharacterDao().getActiveCharacter()
}
@@ -46,7 +47,7 @@ class StorageRepository (
return db.userCharacterDao().deleteCharacterById(id)
}
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> {
fun getAdventureCharacters(): Flow<List<CharacterDtos.AdventureCharacterWithSprites>> {
return db.adventureDao().getAdventureCharacters()
}
+220
View File
@@ -0,0 +1,220 @@
<resources>
<string name="app_name">VBHelper</string>
<string name="beta_warning_message_main">
Este aplicativo ainda está em versão alfa e não está completo. Não o use para armazenar personagens importantes para você, pois futuras atualizações podem apagar todos os seus personagens. Desculpe pelo transtorno!
</string>
<string name="beta_warning_message_compatibility">
O aplicativo agora deve funcionar com o VB original e com o VH.
</string>
<string name="beta_warning_message_thanks">
Obrigado pela compreensão e pela paciência. Atenciosamente, a equipe de desenvolvimento.
</string>
<string name="beta_warning_button_dismiss">Fechar</string>
<string name="nav_scan">Escanear</string>
<string name="nav_battle">Batalhas</string>
<string name="nav_home">Início</string>
<string name="nav_dex">Dex</string>
<string name="nav_card_adventure">Aventura do card</string>
<string name="nav_storage">Armazenamento</string>
<string name="nav_settings">Configurações</string>
<string name="nav_viewer">Visualizador</string>
<string name="nav_card">Card</string>
<string name="nav_items">Itens</string>
<string name="nav_my_items">Meus itens</string>
<string name="nav_items_store">Loja de itens</string>
<string name="nav_apply_item">Aplicar item</string>
<string name="nav_adventure">Aventura</string>
<string name="nav_credits">Créditos</string>
<string name="adventure_title">Aventura</string>
<string name="adventure_empty_state">Nada para ver aqui</string>
<string name="home_title">VB Helper</string>
<string name="home_adventure_mission_finished">
Um dos seus personagens terminou a missão de aventura!
</string>
<string name="scan_secrets_not_initialized">
Os segredos ainda não foram inicializados. Tente novamente.
</string>
<string name="scan_secrets_not_imported">
Os segredos ainda não foram importados. Vá em Configurações e importe o APK.
</string>
<string name="scan_title">Escanear um Vital Bracelet</string>
<string name="scan_vb_to_app">Vital Bracelet para o app</string>
<string name="scan_app_to_vb">App para o Vital Bracelet</string>
<string name="scan_no_nfc_on_device">O dispositivo não possui NFC!</string>
<string name="scan_tag_not_vb">A tag detectada não é do Vital Bracelet.</string>
<string name="scan_missing_secrets">
Segredos ausentes. Vá em Configurações e importe o APK do Vital Arena.
</string>
<string name="scan_sent_character_success">Personagem enviado com sucesso!</string>
<string name="scan_error_generic">Ops!</string>
<string name="scan_sent_dim_success">DIM enviado com sucesso!</string>
<string name="scan_nfc_must_be_enabled">O NFC precisa estar ativado.</string>
<string name="settings_title">Configurações</string>
<string name="settings_section_nfc">Comunicação NFC</string>
<string name="settings_section_dim_bem">Gerenciamento de DiM/BEm</string>
<string name="settings_section_about">Sobre e créditos</string>
<string name="settings_section_data">Gerenciamento de dados</string>
<string name="settings_import_apk_title">Importar APK</string>
<string name="settings_import_apk_desc">
Importar segredos do APK do Vital Arena 2.1.0
</string>
<string name="settings_import_card_title">Importar card</string>
<string name="settings_import_card_desc">
Importar arquivo de card DiM/BEm
</string>
<string name="settings_credits_title">Créditos</string>
<string name="settings_credits_desc">Créditos</string>
<string name="settings_about_title">Sobre</string>
<string name="settings_about_desc">Sobre</string>
<string name="settings_export_data_title">Exportar dados</string>
<string name="settings_export_data_desc">
Exportar banco de dados do aplicativo
</string>
<string name="settings_import_data_title">Importar dados</string>
<string name="settings_import_data_desc">
Importar banco de dados do aplicativo
</string>
<string name="credits_title">Créditos</string>
<string name="credits_section_reverse_engineering">Engenharia reversa</string>
<string name="credits_section_app_development">Desenvolvimento do aplicativo</string>
<string name="credits_cyanic_description">
Reverteu o firmware e nos ajudou durante o desenvolvimento.
</string>
<string name="credits_cfogrady_description">
Desenvolveu vb-lib-nfc e parte deste aplicativo.
</string>
<string name="credits_nacabaro_description">
Desenvolveu este aplicativo.
</string>
<string name="credits_lightheel_description">
Está desenvolvendo a parte de batalhas deste aplicativo, incluindo o servidor. Ainda em desenvolvimento.
</string>
<string name="credits_shvstrz_description">
Responsável pelo design do ícone do aplicativo em SVG.
</string>
<string name="action_place_near_reader">Aproxime o seu Vital Bracelet do leitor...</string>
<string name="action_cancel">Cancelar</string>
<string name="read_character_title">Ler personagem</string>
<string name="read_character_prepare_device">Prepare seu dispositivo!</string>
<string name="read_character_go_to_connect">Vá em conectar e, quando estiver pronto, pressione confirmar!</string>
<string name="read_character_confirm">Confirmar</string>
<string name="reading_character_title">Lendo personagem</string>
<string name="write_card_title">Escrevendo detalhes do cartão</string>
<string name="write_card_icon_description">Ícone do cartão</string>
<string name="write_card_device_ready">Prepare o seu dispositivo!</string>
<string name="write_card_required_card">Você vai precisar do cartão %1$s!</string>
<string name="write_card_confirm">Confirmar</string>
<string name="write_character_title">Escrevendo personagem</string>
<string name="write_character_icon_description">Ícone do personagem</string>
<string name="write_character_success">
Cartão instalado com sucesso!!
</string>
<string name="write_character_wait_ready">
Aguarde até que seu dispositivo esteja pronto e então toque em Confirmar
</string>
<string name="write_character_confirm">Confirmar</string>
<string name="sending_card_title">Enviando cartão</string>
<string name="writing_character_action_title">Escrevendo personagem</string>
<string name="items_title">Itens</string>
<string name="items_no_items">Nenhum item</string>
<string name="items_store_credits">%1$d créditos</string>
<string name="items_not_enough_credits">Créditos insuficientes</string>
<string name="items_purchase_success">Compra realizada com sucesso!</string>
<string name="obtained_item_you_have">Você obteve %1$d deste item</string>
<string name="obtained_item_you_also_got_credits">Você também recebeu %1$d créditos</string>
<string name="obtained_item_dismiss">Fechar</string>
<string name="choose_character_title">Escolher personagem</string>
<string name="choose_character_item_applied">Item aplicado!</string>
<string name="item_dialog_costs_credits">Custa %1$d créditos</string>
<string name="item_dialog_you_have_quantity">Você tem %1$d deste item</string>
<string name="item_dialog_use">Usar item</string>
<string name="item_dialog_purchase">Comprar</string>
<string name="item_dialog_cancel">Cancelar</string>
<string name="battles_online_title">Batalhas online</string>
<string name="battles_coming_soon">Em breve</string>
<string name="cards_my_cards_title">Meus cards</string>
<string name="card_view_discovered_characters">Personagens descobertos</string>
<string name="dex_chara_icon_description">Ícone do personagem</string>
<string name="dex_chara_name_icon_description">Nome do personagem</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">
Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh
</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="dex_chara_fusions_button">Fusões</string>
<string name="dex_chara_close_button">Fechar</string>
<string name="card_adventure_missions_title">Missões de aventura</string>
<string name="card_entry_characters_obtained">%1$d de %2$d personagens obtidos</string>
<string name="card_entry_edit">Editar card</string>
<string name="card_entry_delete">Excluir card</string>
<string name="storage_my_characters_title">Meus personagens</string>
<string name="storage_nothing_to_see_here">Nada para ver aqui</string>
<string name="storage_in_adventure_toast">Este personagem está em uma aventura</string>
<string name="storage_character_image_description">Imagem do personagem</string>
<string name="storage_send_to_watch">Enviar para o relógio</string>
<string name="storage_set_active">Definir como ativo</string>
<string name="storage_send_on_adventure">Enviar para aventura</string>
<string name="storage_delete_character">Excluir personagem</string>
<string name="storage_close">Fechar</string>
<string name="home_vbdim_vitals">Vitais</string>
<string name="home_vbdim_trophies">Troféus</string>
<string name="home_vbdim_mood">Humor</string>
<string name="home_vbdim_next_timer">Próximo timer</string>
<string name="home_vbdim_total_battle_win">% vitórias totais</string>
<string name="home_vbdim_current_phase_win">% vitórias fase atual</string>
<string name="home_vbdim_special_missions">Missões especiais</string>
<string name="special_mission_none">Nenhuma missão selecionada</string>
<string name="special_mission_steps">Ande %1$d passos</string>
<string name="special_mission_battles">Lute %1$d vezes</string>
<string name="special_mission_wins">Vença %1$d batalhas</string>
<string name="special_mission_vitals">Ganhe %1$d vitals</string>
<string name="special_mission_steps_progress">Andou %1$d passos</string>
<string name="special_mission_battles_progress">Lutou %1$d vezes</string>
<string name="special_mission_wins_progress">Venceu %1$d batalhas</string>
<string name="special_mission_vitals_progress">Ganhou %1$d vitals</string>
<string name="special_mission_icon_content_description">Ícone de missão especial</string>
</resources>
+226
View File
@@ -1,3 +1,229 @@
<resources>
<string name="app_name">VBHelper</string>
<string name="beta_warning_message_main">
This application is currently in alpha and it is not complete. Do not use it to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!
</string>
<string name="beta_warning_message_compatibility">
The application should now work with the original VB and the VH.
</string>
<string name="beta_warning_message_thanks">
Thank you for your understanding and patience. Sincerely, the dev team.
</string>
<string name="beta_warning_button_dismiss">
Dismiss
</string>
<string name="nav_scan">Scan</string>
<string name="nav_battle">Battle</string>
<string name="nav_home">Home</string>
<string name="nav_dex">Dex</string>
<string name="nav_card_adventure">Card adventure</string>
<string name="nav_storage">Storage</string>
<string name="nav_settings">Settings</string>
<string name="nav_viewer">Viewer</string>
<string name="nav_card">Card</string>
<string name="nav_items">Items</string>
<string name="nav_my_items">My items</string>
<string name="nav_items_store">Items store</string>
<string name="nav_apply_item">Apply item</string>
<string name="nav_adventure">Adventure</string>
<string name="nav_credits">Credits</string>
<string name="adventure_title">Adventure</string>
<string name="adventure_empty_state">Nothing to see here</string>
<string name="home_title">VB Helper</string>
<string name="home_adventure_mission_finished">
One of your characters has finished their adventure mission!
</string>
<string name="scan_secrets_not_initialized">
Secrets is not yet initialized. Try again.
</string>
<string name="scan_secrets_not_imported">
Secrets not yet imported. Go to Settings and Import APK.
</string>
<string name="scan_title">Scan a Vital Bracelet</string>
<string name="scan_vb_to_app">Vital Bracelet to App</string>
<string name="scan_app_to_vb">App to Vital Bracelet</string>
<string name="scan_no_nfc_on_device">No NFC on device!</string>
<string name="scan_tag_not_vb">Tag detected is not VB</string>
<string name="scan_missing_secrets">
Missing Secrets. Go to settings and import Vital Arena APK.
</string>
<string name="scan_sent_character_success">Sent character successfully!</string>
<string name="scan_error_generic">Whoops</string>
<string name="scan_sent_dim_success">Sent DIM successfully!</string>
<string name="scan_nfc_must_be_enabled">NFC must be enabled</string>
<string name="settings_title">Settings</string>
<string name="settings_section_nfc">NFC Communication</string>
<string name="settings_section_dim_bem">DiM/BEm management</string>
<string name="settings_section_about">About and credits</string>
<string name="settings_section_data">Data management</string>
<string name="settings_import_apk_title">Import APK</string>
<string name="settings_import_apk_desc">
Import Secrets From Vital Arena 2.1.0 APK
</string>
<string name="settings_import_card_title">Import card</string>
<string name="settings_import_card_desc">
Import DiM/BEm card file
</string>
<string name="settings_credits_title">Credits</string>
<string name="settings_credits_desc">Credits</string>
<string name="settings_about_title">About</string>
<string name="settings_about_desc">About</string>
<string name="settings_export_data_title">Export data</string>
<string name="settings_export_data_desc">
Export application database
</string>
<string name="settings_import_data_title">Import data</string>
<string name="settings_import_data_desc">
Import application database
</string>
<string name="credits_title">Credits</string>
<string name="credits_section_reverse_engineering">Reverse engineering</string>
<string name="credits_section_app_development">Application development</string>
<string name="credits_cyanic_description">
Reversed the firmware and helped us during development.
</string>
<string name="credits_cfogrady_description">
Developed vb-lib-nfc and part of this application.
</string>
<string name="credits_nacabaro_description">
Developed this application.
</string>
<string name="credits_lightheel_description">
Developing the battling part for this application, including server. Still in the works.
</string>
<string name="credits_shvstrz_description">
Designing the app icon in SVG.
</string>
<string name="action_place_near_reader">Place your Vital Bracelet near the reader...</string>
<string name="action_cancel">Cancel</string>
<string name="read_character_title">Read character</string>
<string name="read_character_prepare_device">Prepare your device!</string>
<string name="read_character_go_to_connect">Go to connect and when ready press confirm!</string>
<string name="read_character_confirm">Confirm</string>
<string name="reading_character_title">Reading character</string>
<string name="write_card_title">Writing card details</string>
<string name="write_card_icon_description">Card icon</string>
<string name="write_card_device_ready">Get your device ready!</string>
<string name="write_card_required_card">You will need %1$s card!</string>
<string name="write_card_confirm">Confirm</string>
<string name="write_character_title">Writing character</string>
<string name="write_character_icon_description">Character icon</string>
<string name="write_character_success">
Card installed successfully!!
</string>
<string name="write_character_wait_ready">
Wait until your device is ready, then tap Confirm
</string>
<string name="write_character_confirm">Confirm</string>
<string name="sending_card_title">Sending card</string>
<string name="writing_character_action_title">Writing character</string>
<string name="items_title">Items</string>
<string name="items_no_items">No items</string>
<string name="items_store_credits">%1$d credits</string>
<string name="items_not_enough_credits">Not enough credits</string>
<string name="items_purchase_success">Purchase successful!</string>
<string name="obtained_item_you_have">You have obtained %1$d of this item</string>
<string name="obtained_item_you_also_got_credits">You also got %1$d credits</string>
<string name="obtained_item_dismiss">Dismiss</string>
<string name="choose_character_title">Choose character</string>
<string name="choose_character_item_applied">Item applied!</string>
<string name="item_dialog_costs_credits">Costs %1$d credits</string>
<string name="item_dialog_you_have_quantity">You have %1$d of this item</string>
<string name="item_dialog_use">Use item</string>
<string name="item_dialog_purchase">Purchase</string>
<string name="item_dialog_cancel">Cancel</string>
<string name="battles_online_title">Online battles</string>
<string name="battles_coming_soon">Coming soon</string>
<string name="cards_my_cards_title">My cards</string>
<string name="card_view_discovered_characters">Discovered characters</string>
<string name="dex_chara_icon_description">Character icon</string>
<string name="dex_chara_name_icon_description">Character name</string>
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute">Stg: %1$s, Atr: %2$s</string>
<string name="dex_chara_unknown_name">\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</string>
<string name="dex_chara_stats_unknown">HP: -, BP: -, AP: -</string>
<string name="dex_chara_requirements">
Tr: %1$d; Bt: %2$d; Vr: %3$d; Wr: %4$d%%; Ct: %5$dh
</string>
<string name="dex_chara_adventure_level">AdvLvl %1$d</string>
<string name="dex_chara_fusions_button">Fusions</string>
<string name="dex_chara_close_button">Close</string>
<string name="card_adventure_missions_title">Adventure missions</string>
<string name="card_entry_characters_obtained">%1$d of %2$d characters obtained</string>
<string name="card_entry_edit">Edit card</string>
<string name="card_entry_delete">Delete card</string>
<string name="storage_my_characters_title">My characters</string>
<string name="storage_nothing_to_see_here">Nothing to see here</string>
<string name="storage_in_adventure_toast">This character is in an adventure</string>
<string name="storage_character_image_description">Character image</string>
<string name="storage_send_to_watch">Send to watch</string>
<string name="storage_set_active">Set active</string>
<string name="storage_send_on_adventure">Send on adventure</string>
<string name="storage_delete_character">Delete character</string>
<string name="storage_close">Close</string>
<string name="home_vbdim_vitals">Vitals</string>
<string name="home_vbdim_trophies">Trophies</string>
<string name="home_vbdim_mood">Mood</string>
<string name="home_vbdim_next_timer">Next timer</string>
<string name="home_vbdim_total_battle_win">Total battle win %</string>
<string name="home_vbdim_current_phase_win">Current phase win %</string>
<string name="home_vbdim_special_missions">Special missions</string>
<string name="special_mission_none">No mission selected</string>
<string name="special_mission_steps">Walk %1$d steps</string>
<string name="special_mission_battles">Battle %1$d times</string>
<string name="special_mission_wins">Win %1$d battles</string>
<string name="special_mission_vitals">Earn %1$d vitals</string>
<string name="special_mission_steps_progress">Walked %1$d steps</string>
<string name="special_mission_battles_progress">Battled %1$d times</string>
<string name="special_mission_wins_progress">Won %1$d battles</string>
<string name="special_mission_vitals_progress">Earned %1$d vitals</string>
<string name="special_mission_icon_content_description">Special mission icon</string>
<string name="home_special_mission_delete_main">Are you sure you want to delete this special mission with this progress?</string>
<string name="home_special_mission_delete_dismiss">Dismiss</string>
<string name="home_special_mission_delete_remove">Remove</string>
</resources>
+1 -1
View File
@@ -1,5 +1,5 @@
[versions]
agp = "8.13.1"
agp = "8.13.2"
datastore = "1.1.2"
kotlin = "2.0.0"
coreKtx = "1.15.0"