Update: changes and new files from local development

This commit is contained in:
Taveon Nelson 2026-04-18 07:24:06 -05:00
parent 3f29d725ea
commit 5273bae7b6
17 changed files with 2346 additions and 47 deletions

1782
app/lint-baseline.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,15 @@
android:theme="@style/Theme.VBHelper"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<activity
android:name=".MainActivity"
android:exported="true"
@ -45,6 +54,11 @@
<data android:scheme="http" android:host="localhost" android:port="8080" android:pathPrefix="/authenticate" />
<data android:scheme="http" android:host="127.0.0.1" android:port="8080" android:pathPrefix="/authenticate" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/x-vitalwear-character" />
</intent-filter>
</activity>
</application>

View File

@ -1,11 +1,16 @@
package com.github.nacabaro.vbhelper
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.lifecycle.lifecycleScope
import com.github.cfogrady.vitalwear.protos.Character
import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
@ -17,7 +22,10 @@ import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenContr
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
import com.github.nacabaro.vbhelper.source.VitalWearCharacterImporter
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
@ -70,6 +78,7 @@ class MainActivity : ComponentActivity() {
}
Log.i("MainActivity", "Activity onCreated")
handleImportIntent(intent)
}
override fun onPause() {
@ -88,6 +97,58 @@ class MainActivity : ComponentActivity() {
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleImportIntent(intent)
}
private fun handleImportIntent(intent: Intent?) {
val importUri = extractVitalWearImportUri(intent) ?: return
val application = applicationContext as VBHelper
lifecycleScope.launch(Dispatchers.IO) {
val result = runCatching {
contentResolver.openInputStream(importUri)?.use { inputStream ->
val character = Character.parseFrom(inputStream)
VitalWearCharacterImporter(application.container.db).importCharacter(character)
} ?: VitalWearCharacterImporter.ImportResult(
success = false,
message = "VitalWear import file could not be opened."
)
}.getOrElse {
VitalWearCharacterImporter.ImportResult(
success = false,
message = "VitalWear import failed: ${it.message ?: "Unknown error"}"
)
}
runOnUiThread {
Toast.makeText(this@MainActivity, result.message, Toast.LENGTH_LONG).show()
}
}
}
private fun extractVitalWearImportUri(intent: Intent?): Uri? {
if (intent == null) {
return null
}
val isVitalWearImport = intent.type == VITALWEAR_CHARACTER_MIME
if (!isVitalWearImport) {
return null
}
return when (intent.action) {
Intent.ACTION_SEND -> {
@Suppress("DEPRECATION")
intent.getParcelableExtra(Intent.EXTRA_STREAM)
}
Intent.ACTION_VIEW -> intent.data
else -> null
}
}
@Composable
private fun MainApplication(
scanScreenController: ScanScreenControllerImpl,
@ -112,4 +173,8 @@ class MainActivity : ComponentActivity() {
)
)
}
companion object {
private const val VITALWEAR_CHARACTER_MIME = "application/x-vitalwear-character"
}
}

View File

@ -19,6 +19,9 @@ interface CardDao {
@Query("SELECT * FROM Card WHERE id = :id")
fun getCardById(id: Long): Card?
@Query("SELECT * FROM Card WHERE LOWER(name) = LOWER(:name) LIMIT 1")
fun getCardByName(name: String): Card?
@Query(
"""
SELECT ca.*
@ -30,6 +33,18 @@ interface CardDao {
)
fun getCardByCharacterId(id: Long): Flow<Card>
@Query(
"""
SELECT ca.*
FROM Card ca
JOIN CardCharacter ch ON ca.id = ch.cardId
JOIN UserCharacter uc ON ch.id = uc.charId
WHERE uc.id = :id
LIMIT 1
"""
)
fun getCardByCharacterIdSync(id: Long): Card?
@Query("UPDATE Card SET name = :newName WHERE id = :id")
suspend fun renameCard(id: Int, newName: String)

View File

@ -24,6 +24,9 @@ interface CardProgressDao {
)
fun getCardProgress(cardId: Long): Flow<Int>
@Query("SELECT currentStage FROM CardProgress WHERE cardId = :cardId LIMIT 1")
fun getCardProgressSync(cardId: Long): Int?
@Insert
fun insertCardProgress(cardProgress: CardProgress)
}

View File

@ -55,6 +55,22 @@ interface UserCharacterDao {
)
fun getTransformationHistory(monId: Long): Flow<List<CharacterDtos.TransformationHistory>>
@Query(
"""
SELECT
t.stageId as stageId,
c.charaIndex AS monIndex,
ca.name as cardName,
t.transformationDate AS transformationDate
FROM TransformationHistory t
JOIN CardCharacter c ON c.id = t.stageId
JOIN Card ca ON ca.id = c.cardId
WHERE t.monId = :monId
ORDER BY t.transformationDate ASC, t.id ASC
"""
)
suspend fun getTransformationHistoryForExport(monId: Long): List<CharacterDtos.TransformationHistoryExport>
@Query(
"""
SELECT

View File

@ -52,6 +52,13 @@ object CharacterDtos {
val transformationDate: Long
)
data class TransformationHistoryExport(
val stageId: Long,
val monIndex: Int,
val cardName: String,
val transformationDate: Long
)
data class CardCharaProgress(
val id: Long,
val spriteIdle: ByteArray,

View File

@ -2,6 +2,7 @@ package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -40,6 +41,8 @@ import com.github.nacabaro.vbhelper.source.StorageRepository
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.source.VitalWearCharacterExporter
import android.widget.Toast
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.flow.flowOf
import kotlin.collections.emptyList
@ -136,6 +139,7 @@ fun HomeScreen(
Text(text = stringResource(R.string.adventure_empty_state))
}
} else {
Column(modifier = Modifier.padding(top = contentPadding.calculateTopPadding())) {
val cardIcon = BitmapData(
bitmap = cardIconData!!.cardIcon,
width = cardIconData!!.cardIconWidth,
@ -147,7 +151,7 @@ fun HomeScreen(
activeMon = activeMon!!,
beData = beData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
contentPadding = PaddingValues(0.dp),
cardIcon = cardIcon
)
} else if (!activeMon!!.isBemCard && activeMon!!.characterType == DeviceType.BEDevice && beData != null) {
@ -155,7 +159,7 @@ fun HomeScreen(
activeMon = activeMon!!,
beData = beData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
contentPadding = PaddingValues(0.dp),
cardIcon = cardIcon
)
} else if (vbData != null) {
@ -163,7 +167,7 @@ fun HomeScreen(
activeMon = activeMon!!,
vbData = vbData!!,
transformationHistory = transformationHistory,
contentPadding = contentPadding,
contentPadding = PaddingValues(0.dp),
specialMissions = vbSpecialMissions,
homeScreenController = homeScreenController,
onClickCollect = { item, currency ->
@ -173,6 +177,29 @@ fun HomeScreen(
cardIcon = cardIcon
)
}
Button(
onClick = {
try {
val intent = VitalWearCharacterExporter(application, application.container.db)
.buildShareIntent(activeMon!!.id)
.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
application.startActivity(intent)
} catch (e: Exception) {
Toast.makeText(
application,
"Could not send character to VitalWear: ${e.message}",
Toast.LENGTH_LONG
).show()
}
},
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = "Send to VitalWear")
}
}
}
}
@ -221,5 +248,3 @@ fun HomeScreen(
}
}
}

View File

@ -42,6 +42,11 @@ fun ChooseConnectOption(
.fillMaxSize()
.padding(contentPadding)
) {
Text(
text = "Bandai Toys = Vital Bracelet, Vital Hero, BE Bracelet",
fontSize = 14.sp,
modifier = Modifier.padding(bottom = 24.dp)
)
ScanButton(
text = stringResource(R.string.scan_vb_to_app),
disabled = onClickRead == null,

View File

@ -43,6 +43,7 @@ fun StorageDialog(
onDismissRequest: () -> Unit,
onClickDelete: () -> Unit,
onSendToBracelet: () -> Unit,
onSendToVitalWear: () -> Unit,
onClickSetActive: () -> Unit,
onClickSendToAdventure: (time: Long) -> Unit
) {
@ -136,6 +137,13 @@ fun StorageDialog(
Text(text = stringResource(R.string.storage_set_active))
}
}
Button(
onClick = onSendToVitalWear,
modifier = Modifier
.fillMaxWidth()
) {
Text(text = stringResource(R.string.storage_send_to_vitalwear))
}
Button(
onClick = {
onSendToAdventureClicked = true

View File

@ -33,6 +33,7 @@ import com.github.nacabaro.vbhelper.di.VBHelper
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.source.VitalWearCharacterExporter
import com.github.nacabaro.vbhelper.utils.BitmapData
import androidx.compose.ui.res.stringResource
import com.github.nacabaro.vbhelper.R
@ -136,6 +137,21 @@ fun StorageScreen(
)
)
},
onSendToVitalWear = {
try {
val intent = VitalWearCharacterExporter(application, application.container.db)
.buildShareIntent(selectedCharacter!!)
.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
application.startActivity(intent)
selectedCharacter = null
} catch (e: Exception) {
Toast.makeText(
application,
"Could not send character to VitalWear: ${e.message}",
Toast.LENGTH_LONG
).show()
}
},
onClickSendToAdventure = { time ->
adventureScreenController
.sendCharacterToAdventure(

View File

@ -0,0 +1,114 @@
package com.github.nacabaro.vbhelper.source
import android.content.ClipData
import android.content.Context
import android.content.Intent
import androidx.core.content.FileProvider
import com.github.cfogrady.vitalwear.protos.Character
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.runBlocking
import java.io.File
class VitalWearCharacterExporter(
private val context: Context,
private val database: AppDatabase
) {
fun buildShareIntent(characterId: Long): Intent {
return runBlocking {
val characterWithSprites = database.userCharacterDao().getCharacterWithSprites(characterId)
val userCharacter = database.userCharacterDao().getCharacter(characterId)
val card = database.cardDao().getCardByCharacterIdSync(characterId)
?: error("Card not found for character $characterId")
val cardProgress = database.cardProgressDao().getCardProgressSync(card.id) ?: 0
val proto = Character.newBuilder()
.setCardId(card.cardId)
.setCardName(card.name)
.setCharacterStats(
Character.CharacterStats.newBuilder()
.setSlotId(database.userCharacterDao().getCharacterInfo(characterId).charaIndex)
.setVitals(characterWithSprites.vitalPoints)
.setTrainingTimeRemainingInSeconds(resolveTrainingSeconds(characterId, userCharacter.characterType))
.setTimeUntilNextTransformation(characterWithSprites.transformationCountdown.toLong() * 60L)
.setTrainedBp(resolveTrainedBp(characterId, userCharacter.characterType))
.setTrainedHp(resolveTrainedHp(characterId, userCharacter.characterType))
.setTrainedAp(resolveTrainedAp(characterId, userCharacter.characterType))
.setTrainedPp(characterWithSprites.trophies)
.setInjured(characterWithSprites.injuryStatus.name.lowercase().contains("inj"))
.setAccumulatedDailyInjuries(0)
.setTotalBattles(characterWithSprites.totalBattlesWon + characterWithSprites.totalBattlesLost)
.setCurrentPhaseBattles(characterWithSprites.currentPhaseBattlesWon + characterWithSprites.currentPhaseBattlesLost)
.setTotalWins(characterWithSprites.totalBattlesWon)
.setCurrentPhaseWins(characterWithSprites.currentPhaseBattlesWon)
.setMood(characterWithSprites.mood)
.build()
)
.setSettings(
Character.Settings.newBuilder()
.setTrainingInBackground(false)
.setAllowedBattles(Character.Settings.AllowedBattles.CARD_ONLY)
.build()
)
.putMaxAdventureCompletedByCard(card.name, (cardProgress - 1).coerceAtLeast(0))
.addAllTransformationHistory(
database.userCharacterDao().getTransformationHistoryForExport(characterId).map {
Character.TransformationEvent.newBuilder()
.setCardName(it.cardName)
.setPhase(0)
.setSlotId(it.monIndex)
.build()
}
)
.build()
val exportDir = File(context.cacheDir, "exports").apply { mkdirs() }
val exportFile = File(exportDir, "vbhelper_character_$characterId.vitalwear")
exportFile.writeBytes(proto.toByteArray())
val exportUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", exportFile)
Intent(Intent.ACTION_SEND).apply {
`package` = "com.github.cfogrady.vitalwear"
type = VITALWEAR_CHARACTER_MIME
putExtra(Intent.EXTRA_STREAM, exportUri)
clipData = ClipData.newRawUri("", exportUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
}
private fun resolveTrainingSeconds(characterId: Long, deviceType: DeviceType): Long {
if (deviceType != DeviceType.BEDevice) {
return 0L
}
return database.userCharacterDao().getBeData(characterId).valueOrNull()?.remainingTrainingTimeInMinutes?.toLong()?.times(60L) ?: 0L
}
private fun resolveTrainedBp(characterId: Long, deviceType: DeviceType): Int {
return if (deviceType == DeviceType.BEDevice) {
database.userCharacterDao().getBeData(characterId).valueOrNull()?.trainingBp ?: 0
} else 0
}
private fun resolveTrainedHp(characterId: Long, deviceType: DeviceType): Int {
return if (deviceType == DeviceType.BEDevice) {
database.userCharacterDao().getBeData(characterId).valueOrNull()?.trainingHp ?: 0
} else 0
}
private fun resolveTrainedAp(characterId: Long, deviceType: DeviceType): Int {
return if (deviceType == DeviceType.BEDevice) {
database.userCharacterDao().getBeData(characterId).valueOrNull()?.trainingAp ?: 0
} else 0
}
private fun <T> kotlinx.coroutines.flow.Flow<T>.valueOrNull(): T? {
return runBlocking {
firstOrNull()
}
}
companion object {
const val VITALWEAR_CHARACTER_MIME = "application/x-vitalwear-character"
}
}

View File

@ -0,0 +1,163 @@
package com.github.nacabaro.vbhelper.source
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vitalwear.protos.Character
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlin.math.max
class VitalWearCharacterImporter(
private val database: AppDatabase
) {
data class ImportResult(
val success: Boolean,
val message: String
)
fun importCharacter(character: Character): ImportResult {
val importedCard = resolveCard(character)
?: return ImportResult(
success = false,
message = "Matching card not found in VBHelper. Import that card first."
)
val slotId = character.characterStats.slotId
val cardCharacter = runCatching {
database.characterDao().getCharacterByMonIndex(slotId, importedCard.id)
}.getOrNull() ?: return ImportResult(
success = false,
message = "Character slot $slotId was not found on card ${importedCard.name}."
)
database.userCharacterDao().clearActiveCharacter()
val totalBattles = character.characterStats.totalBattles
val totalWins = character.characterStats.totalWins.coerceAtMost(totalBattles)
val currentPhaseBattles = character.characterStats.currentPhaseBattles
val currentPhaseWins = character.characterStats.currentPhaseWins.coerceAtMost(currentPhaseBattles)
val userCharacterId = database.userCharacterDao().insertCharacterData(
UserCharacter(
charId = cardCharacter.id,
ageInDays = max(character.transformationHistoryCount - 1, 0),
mood = character.characterStats.mood,
vitalPoints = character.characterStats.vitals,
transformationCountdown = secondsToMinutes(character.characterStats.timeUntilNextTransformation),
injuryStatus = resolveInjuryStatus(character.characterStats.injured),
trophies = character.characterStats.trainedPp,
currentPhaseBattlesWon = currentPhaseWins,
currentPhaseBattlesLost = (currentPhaseBattles - currentPhaseWins).coerceAtLeast(0),
totalBattlesWon = totalWins,
totalBattlesLost = (totalBattles - totalWins).coerceAtLeast(0),
activityLevel = 0,
heartRateCurrent = 0,
characterType = DeviceType.BEDevice,
isActive = true
)
)
database.userCharacterDao().insertBECharacterData(
BECharacterData(
id = userCharacterId,
trainingHp = character.characterStats.trainedHp,
trainingAp = character.characterStats.trainedAp,
trainingBp = character.characterStats.trainedBp,
remainingTrainingTimeInMinutes = secondsToMinutes(character.characterStats.trainingTimeRemainingInSeconds),
itemEffectMentalStateValue = 0,
itemEffectMentalStateMinutesRemaining = 0,
itemEffectActivityLevelValue = 0,
itemEffectActivityLevelMinutesRemaining = 0,
itemEffectVitalPointsChangeValue = 0,
itemEffectVitalPointsChangeMinutesRemaining = 0,
abilityRarity = resolveDefaultAbilityRarity(),
abilityType = 0,
abilityBranch = 0,
abilityReset = 0,
rank = 0,
itemType = 0,
itemMultiplier = 0,
itemRemainingTime = 0,
otp0 = "",
otp1 = "",
minorVersion = 0,
majorVersion = 0
)
)
val now = System.currentTimeMillis()
database.dexDao().insertCharacter(slotId, importedCard.id, now)
for (transformation in character.transformationHistoryList) {
val transformationCard = resolveCard(transformation.cardName, character.cardId)
if (transformationCard != null) {
database.userCharacterDao().insertTransformation(
userCharacterId,
transformation.slotId,
transformationCard.id,
now
)
database.dexDao().insertCharacter(transformation.slotId, transformationCard.id, now)
}
}
for ((cardName, maxAdventureCompleted) in character.maxAdventureCompletedByCardMap) {
val adventureCard = resolveCard(cardName, null) ?: continue
val currentStage = (maxAdventureCompleted + 1).coerceAtLeast(0)
database.cardProgressDao().updateCardProgress(
currentStage = currentStage,
cardId = adventureCard.id,
unlocked = currentStage > adventureCard.stageCount
)
}
return ImportResult(
success = true,
message = "Imported ${importedCard.name} slot $slotId from VitalWear."
)
}
private fun resolveCard(character: Character) = resolveCard(character.cardName, character.cardId)
private fun resolveCard(cardName: String?, cardId: Int?): com.github.nacabaro.vbhelper.domain.card.Card? {
if (!cardName.isNullOrBlank()) {
database.cardDao().getCardByName(cardName)?.let { return it }
}
if (cardId != null) {
val matches = database.cardDao().getCardByCardId(cardId)
if (matches.size == 1) {
return matches.first()
}
}
return null
}
private fun secondsToMinutes(seconds: Long): Int {
if (seconds <= 0L) {
return 0
}
return (seconds / 60L).coerceAtMost(Int.MAX_VALUE.toLong()).toInt()
}
private fun resolveInjuryStatus(injured: Boolean): NfcCharacter.InjuryStatus {
val statuses = enumValues<NfcCharacter.InjuryStatus>()
if (!injured) {
return statuses.firstOrNull {
val normalized = it.name.lowercase()
normalized.contains("none") || normalized.contains("normal") || normalized.contains("healthy")
} ?: statuses.first()
}
return statuses.firstOrNull {
val normalized = it.name.lowercase()
normalized.contains("inj")
} ?: statuses.last()
}
private fun resolveDefaultAbilityRarity(): NfcCharacter.AbilityRarity {
return enumValues<NfcCharacter.AbilityRarity>().first()
}
}

View File

@ -1,6 +1,14 @@
package com.github.nacabaro.vbhelper.utils
enum class DeviceType {
VBDevice,
BEDevice
VBDevice, // Bandai Vital Bracelet
BEDevice, // Bandai BE Bracelet
VitalWear // WearOS VitalWear app
}
fun detectDeviceType(deviceName: String): DeviceType = when {
deviceName.startsWith("VitalWear", ignoreCase = true) || deviceName.startsWith("VW-", ignoreCase = true) -> DeviceType.VitalWear
deviceName.startsWith("BE-", ignoreCase = true) -> DeviceType.BEDevice
deviceName.startsWith("VB-", ignoreCase = true) -> DeviceType.VBDevice
else -> DeviceType.VBDevice // Default/fallback, can be refined
}

View File

@ -0,0 +1,51 @@
syntax = "proto3";
option java_package = "com.github.cfogrady.vitalwear.protos";
option java_multiple_files = true;
message Character {
string card_name = 1;
int32 card_id = 2;
message CharacterStats {
int32 slot_id = 1;
int32 vitals = 2;
int64 training_time_remaining_in_seconds = 3;
int64 time_until_next_transformation = 4;
int32 trained_bp = 5;
int32 trained_hp = 6;
int32 trained_ap = 7;
int32 trained_pp = 8;
bool injured = 9;
int32 accumulated_daily_injuries = 10;
int32 total_battles = 11;
int32 current_phase_battles = 12;
int32 total_wins = 13;
int32 current_phase_wins = 14;
int32 mood = 15;
}
CharacterStats character_stats = 3;
message Settings {
bool training_in_background = 1;
enum AllowedBattles {
CARD_ONLY = 0;
ALL_FRANCHISE = 1;
ALL_FRANCHISE_AND_DIM = 2;
ALL = 3;
}
AllowedBattles allowed_battles = 2;
optional int32 assumed_franchise = 3;
}
Settings settings = 4;
message TransformationEvent {
string card_name = 1;
int32 phase = 2;
int32 slot_id = 3;
}
repeated TransformationEvent transformation_history = 5;
map<string, int32> max_adventure_completed_by_card = 6;
}

View File

@ -48,8 +48,8 @@
</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_vb_to_app">Vital Bracelet to App (Receive Digimon from Bandai Toys: Vital Bracelet, Vital Hero, BE Bracelet)</string>
<string name="scan_app_to_vb">App to Vital Bracelet (Send Digimon from VBHelper to Bandai Toys: Vital Bracelet, Vital Hero, BE 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>
@ -199,6 +199,7 @@
<string name="storage_character_image_description">Character image</string>
<string name="storage_send_to_watch">Send to watch</string>
<string name="storage_send_to_vitalwear">Send to VitalWear</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>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path
name="exports"
path="exports" />
</paths>