Compare commits

...

5 Commits

Author SHA1 Message Date
4b866aad8a
Merge pull request #57 from b13st/fix/special-missions-nan-crash
Fix special missions crash and NaN% stat display
2026-06-24 23:05:51 +02:00
33ad2728b8
Merge pull request #56 from b13st/fix/hce-character-cloning
Fix/hce character cloning
2026-06-24 23:04:53 +02:00
b13st
05f04fdc49 Fix LaunchedEffect key so characterToNfc runs when active character loads
When launching the scan screen from the home button, characterId is
loaded asynchronously from the active character. The previous key
(storageRepository) never re-triggered the effect, so characterToNfc
(which sets pendingExportCharacterId) was skipped if characterId was
null on first composition, making the HCE delete unreachable.
2026-06-24 15:40:19 +02:00
b13st
07315fe11b Delete character from app after successful HCE transfer to VitalWear watch
When sending a character to the VitalWear watch via HCE, the transfer
is atomic (single NFC interaction). The character was being left in the
app after transfer, allowing it to be cloned indefinitely.

Delete the character from the database immediately after sendCharacterToWatch
succeeds, matching the behavior of physical bracelet transfers which delete
the character once both write phases complete.
2026-06-24 15:29:58 +02:00
b13st
2e72b543f6 Fix special missions crash and NaN% stat display
- Fix crash when tapping a completed special mission: onClickCollect
  was ignoring the missionId passed by SpecialMissionsEntry and using
  selectedSpecialMissionId (-1 by default), causing Room to query id=-1
  on a non-nullable Flow which throws when no row exists.
- Fix NaN% in current phase win rate: the guard was checking
  totalBattlesLost==0 but dividing by currentPhaseBattles, producing
  0/0=NaN when total>0 but current phase has no battles yet.
- Guard against empty items list in clearSpecialMission to avoid
  NoSuchElementException from random() on an empty collection.
2026-06-24 15:24:54 +02:00
4 changed files with 14 additions and 17 deletions

View File

@ -45,11 +45,12 @@ class HomeScreenControllerImpl(
.clearSpecialMission(missionId)
if (missionStatus.status == SpecialMission.Status.COMPLETED) {
val randomItem = database
.itemDao()
.getAllItems()
.first()
.random()
val allItems = database.itemDao().getAllItems().first()
if (allItems.isEmpty()) {
onCleared(null, null)
return@launch
}
val randomItem = allItems.random()
val randomItemAmount = (Random.nextFloat() * 5).roundToInt()

View File

@ -142,7 +142,7 @@ fun VBDiMHomeScreen(
ItemDisplay(
icon = R.drawable.baseline_swords_24,
textValue = when {
activeMon.totalBattlesLost == 0 -> "0.00 %"
activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost == 0 -> "0.00 %"
else -> {
val battleWinPercentage =
activeMon.currentPhaseBattlesWon.toFloat() / (activeMon.currentPhaseBattlesWon + activeMon.currentPhaseBattlesLost).toFloat()
@ -150,7 +150,7 @@ fun VBDiMHomeScreen(
Locale.getDefault(),
"%.2f",
battleWinPercentage * 100
) + " %" // Specify locale
) + " %"
}
},
definition = stringResource(R.string.home_vbdim_current_phase_win),
@ -193,9 +193,9 @@ fun VBDiMHomeScreen(
onClickMission = { missionId ->
selectedSpecialMissionId = missionId
},
onClickCollect = {
onClickCollect = { missionId ->
homeScreenController
.clearSpecialMission(selectedSpecialMissionId, onClickCollect)
.clearSpecialMission(missionId, onClickCollect)
}
)
}

View File

@ -44,14 +44,8 @@ fun ScanScreen(
val context = LocalContext.current
LaunchedEffect(storageRepository) {
LaunchedEffect(characterId) {
withContext(Dispatchers.IO) {
/*
First check if there is a character sent through the navigation system
If there is not, that means we got here through the home screen nfc button
If we got here through the home screen, it does not hurt to check if there is
an active character.
*/
if (characterId != null && nfcCharacter == null) {
nfcCharacter = scanScreenController.characterToNfc(characterId)
}

View File

@ -231,7 +231,9 @@ class ScanScreenControllerImpl(
val proto = VitalWearCharacterExporter(componentActivity, database).buildCharacterProto(characterId)
val client = VitalWearHceReaderClient(isoDep)
client.sendCharacterToWatch(proto)
Log.i("NFC_WRITE", "Character sent to VitalWear via HCE")
database.userCharacterDao().deleteCharacterById(characterId)
pendingExportCharacterId = null
Log.i("NFC_WRITE", "Character sent to VitalWear and deleted from app (id=$characterId)")
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_sent_character_success), Toast.LENGTH_SHORT).show()
}