Compare commits

..

16 Commits

Author SHA1 Message Date
99d309adc1
Merge pull request #60 from b13st/fix/hce-send-crash
Fix double character deletion causing crash on HCE transfer to VitalWear
2026-07-01 17:24:49 +02:00
2760af5d2b
Merge pull request #59 from b13st/chore/visual-improvements
improving various visuals
2026-07-01 17:23:34 +02:00
b13st
931f730406 Fix double character deletion causing crash on HCE transfer to VitalWear
Character was deleted from the local DB both right after sendCharacterToWatch
and again in WritingScreen's completion callback. The second deletion failed
because the character no longer existed, causing the reported random crash
when sending back to the watch. Deletion is now handled only once, in
WritingScreen.
2026-07-01 15:04:26 +02:00
b13st
4eaf6cdf1b Fix double character deletion causing crash on HCE transfer to VitalWear
Character was deleted from the local DB both right after sendCharacterToWatch
and again in WritingScreen's completion callback. The second deletion failed
because the character no longer existed, causing the reported random crash
when sending back to the watch. Deletion is now handled only once, in
WritingScreen.
2026-07-01 15:00:56 +02:00
b13st
416b30f7a8 improving various visuals 2026-07-01 13:51:34 +02:00
df713855f5 I forgot to update the version number. 2026-06-29 21:26:11 +02:00
e5ccff16d0 Pulidora industrial
- Removed all the warnings during building
- Upgraded all the libraries
- Upgraded grandle and the SDK version
- Fix japanese strings
- Added vibration permission
2026-06-29 16:15:12 +00:00
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
81bfec5ac5
Merge pull request #55 from b13st/fix/vitalwear-hce-transfer
Fix VitalWear HCE transfer in both directions
2026-06-23 20:30:46 +00:00
8ec98b9d39
Merge pull request #54 from shiki8826/main
Thank you very much
2026-06-23 20:30:04 +00:00
b13st
e549fb4469 Fix VitalWear HCE transfer in both directions
- Detect ISO-DEP tags (VitalWear watch) vs NFC-A (physical bracelets)
- Watch->Phone: use VitalWearHceReaderClient + VitalWearCharacterImporter
- Phone->Watch: use VitalWearHceReaderClient + VitalWearCharacterExporter
- Add AppDatabase to ScanScreenControllerImpl for HCE import/export
- Extract buildCharacterProto() from VitalWearCharacterExporter
- Add JitPack repository to settings.gradle.kts
2026-06-23 13:44:26 +02:00
shiki8826
8d62437aae 日本語翻訳の修正及び追加項目の翻訳 2026-06-19 20:46:23 +09:00
39 changed files with 896 additions and 258 deletions

View File

@ -2,7 +2,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
alias(libs.plugins.vksp) alias(libs.plugins.vksp)
alias(libs.plugins.vprotobuf) alias(libs.plugins.vprotobuf)
@ -10,14 +9,14 @@ plugins {
android { android {
namespace = "com.github.nacabaro.vbhelper" namespace = "com.github.nacabaro.vbhelper"
compileSdk = 36 compileSdk = 37
defaultConfig { defaultConfig {
applicationId = "com.github.nacabaro.vbhelper" applicationId = "com.github.nacabaro.vbhelper"
minSdk = 28 minSdk = 28
targetSdk = 36 targetSdk = 37
versionCode = 1 versionCode = 1
versionName = "Alpha 0.6.4" versionName = "Alpha 0.6.5"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@ -56,9 +55,6 @@ protobuf {
artifact = "com.google.protobuf:protoc:4.27.0" artifact = "com.google.protobuf:protoc:4.27.0"
} }
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks { generateProtoTasks {
all().forEach { task -> all().forEach { task ->
task.builtins { task.builtins {

View File

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
@ -34,7 +35,6 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.VBHelper"> android:theme="@style/Theme.VBHelper">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -51,8 +51,10 @@
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="localhost" android:port="8080" android:pathPrefix="/authenticate" /> <data android:scheme="http" />
<data android:scheme="http" android:host="127.0.0.1" android:port="8080" android:pathPrefix="/authenticate" /> <data android:host="localhost" android:port="8080" />
<data android:host="127.0.0.1" android:port="8080" />
<data android:pathPrefix="/authenticate" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />

View File

@ -49,7 +49,8 @@ class MainActivity : ComponentActivity() {
application.container.dataStoreSecretsRepository.secretsFlow, application.container.dataStoreSecretsRepository.secretsFlow,
this, this,
this::registerActivityLifecycleListener, this::registerActivityLifecycleListener,
this::unregisterActivityLifecycleListener this::unregisterActivityLifecycleListener,
application.container.db,
) )
val settingsScreenController = SettingsScreenControllerImpl(this) val settingsScreenController = SettingsScreenControllerImpl(this)
val itemsScreenController = ItemsScreenControllerImpl(this) val itemsScreenController = ItemsScreenControllerImpl(this)

View File

@ -2,11 +2,13 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@RewriteQueriesToDropUnusedColumns
interface AdventureDao { interface AdventureDao {
@Query(""" @Query("""
INSERT INTO Adventure (characterId, originalDuration, finishesAdventure) INSERT INTO Adventure (characterId, originalDuration, finishesAdventure)

View File

@ -2,11 +2,13 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@RewriteQueriesToDropUnusedColumns
interface CardFusionsDao { interface CardFusionsDao {
@Query(""" @Query("""
INSERT INTO INSERT INTO

View File

@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Upsert import androidx.room.Upsert
import androidx.room.RewriteQueriesToDropUnusedColumns
import com.github.nacabaro.vbhelper.domain.card.CardCharacter import com.github.nacabaro.vbhelper.domain.card.CardCharacter
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
@ -16,6 +17,7 @@ import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@RewriteQueriesToDropUnusedColumns
interface UserCharacterDao { interface UserCharacterDao {
@Insert @Insert
fun insertCharacterData(characterData: UserCharacter): Long fun insertCharacterData(characterData: UserCharacter): Long

View File

@ -40,7 +40,8 @@ import com.github.nacabaro.vbhelper.domain.device_data.CharacterTransferPolicy
import com.github.nacabaro.vbhelper.domain.items.Items import com.github.nacabaro.vbhelper.domain.items.Items
@Database( @Database(
version = 2, version = 3,
exportSchema = false,
entities = [ entities = [
Card::class, Card::class,
CardProgress::class, CardProgress::class,

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.card package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@ -12,7 +13,8 @@ import androidx.room.PrimaryKey
childColumns = ["cardId"], childColumns = ["cardId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
] ],
indices = [Index(value = ["cardId"])]
) )
data class Background ( data class Background (
@PrimaryKey(autoGenerate = true) val id: Long, @PrimaryKey(autoGenerate = true) val id: Long,
@ -20,4 +22,28 @@ data class Background (
val background: ByteArray, val background: ByteArray,
val backgroundWidth: Int, val backgroundWidth: Int,
val backgroundHeight: Int val backgroundHeight: Int
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Background
if (id != other.id) return false
if (cardId != other.cardId) return false
if (!background.contentEquals(other.background)) return false
if (backgroundWidth != other.backgroundWidth) return false
if (backgroundHeight != other.backgroundHeight) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + cardId.hashCode()
result = 31 * result + background.contentHashCode()
result = 31 * result + backgroundWidth
result = 31 * result + backgroundHeight
return result
}
}

View File

@ -14,4 +14,34 @@ data class Card(
val name: String, val name: String,
val stageCount: Int, val stageCount: Int,
val isBEm: Boolean val isBEm: Boolean
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Card
if (id != other.id) return false
if (cardId != other.cardId) return false
if (!logo.contentEquals(other.logo)) return false
if (logoWidth != other.logoWidth) return false
if (logoHeight != other.logoHeight) return false
if (name != other.name) return false
if (stageCount != other.stageCount) return false
if (isBEm != other.isBEm) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + cardId
result = 31 * result + logo.contentHashCode()
result = 31 * result + logoWidth
result = 31 * result + logoHeight
result = 31 * result + name.hashCode()
result = 31 * result + stageCount
result = 31 * result + isBEm.hashCode()
return result
}
}

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.card package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@ -18,6 +19,10 @@ import androidx.room.PrimaryKey
childColumns = ["cardId"], childColumns = ["cardId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
],
indices = [
Index(value = ["characterId"]),
Index(value = ["cardId"])
] ]
) )
data class CardAdventure( data class CardAdventure(
@ -29,4 +34,4 @@ data class CardAdventure(
val bossAp: Int, val bossAp: Int,
val bossDp: Int, val bossDp: Int,
val bossBp: Int? val bossBp: Int?
) )

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.card package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
@ -20,6 +21,10 @@ import com.github.nacabaro.vbhelper.domain.characters.Sprite
childColumns = ["spriteId"], childColumns = ["spriteId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
],
indices = [
Index(value = ["cardId"]),
Index(value = ["spriteId"])
] ]
) )
@ -41,4 +46,42 @@ data class CardCharacter (
val nameSprite: ByteArray, val nameSprite: ByteArray,
val nameWidth: Int, val nameWidth: Int,
val nameHeight: Int val nameHeight: Int
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CardCharacter
if (id != other.id) return false
if (cardId != other.cardId) return false
if (spriteId != other.spriteId) return false
if (charaIndex != other.charaIndex) return false
if (stage != other.stage) return false
if (attribute != other.attribute) return false
if (baseHp != other.baseHp) return false
if (baseBp != other.baseBp) return false
if (baseAp != other.baseAp) return false
if (!nameSprite.contentEquals(other.nameSprite)) return false
if (nameWidth != other.nameWidth) return false
if (nameHeight != other.nameHeight) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + cardId.hashCode()
result = 31 * result + spriteId.hashCode()
result = 31 * result + charaIndex
result = 31 * result + stage
result = 31 * result + attribute.hashCode()
result = 31 * result + baseHp
result = 31 * result + baseBp
result = 31 * result + baseAp
result = 31 * result + nameSprite.contentHashCode()
result = 31 * result + nameWidth
result = 31 * result + nameHeight
return result
}
}

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.card package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
@ -19,6 +20,10 @@ import com.github.cfogrady.vbnfc.data.NfcCharacter
childColumns = ["toCharaId"], childColumns = ["toCharaId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
],
indices = [
Index(value = ["fromCharaId"]),
Index(value = ["toCharaId"])
] ]
) )
data class CardFusions( data class CardFusions(
@ -26,4 +31,4 @@ data class CardFusions(
val fromCharaId: Long, val fromCharaId: Long,
val attribute: NfcCharacter.Attribute, val attribute: NfcCharacter.Attribute,
val toCharaId: Long val toCharaId: Long
) )

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.card package com.github.nacabaro.vbhelper.domain.card
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@ -18,6 +19,10 @@ import androidx.room.PrimaryKey
childColumns = ["toCharaId"], childColumns = ["toCharaId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
],
indices = [
Index(value = ["charaId"]),
Index(value = ["toCharaId"])
] ]
) )
data class PossibleTransformations ( data class PossibleTransformations (
@ -30,4 +35,4 @@ data class PossibleTransformations (
val changeTimerHours: Int, val changeTimerHours: Int,
val requiredAdventureLevelCompleted: Int, val requiredAdventureLevelCompleted: Int,
val toCharaId: Long? val toCharaId: Long?
) )

View File

@ -20,4 +20,48 @@ data class Sprite(
val spriteDodge: ByteArray, val spriteDodge: ByteArray,
val width: Int, val width: Int,
val height: Int val height: Int
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Sprite
if (id != other.id) return false
if (!spriteIdle1.contentEquals(other.spriteIdle1)) return false
if (!spriteIdle2.contentEquals(other.spriteIdle2)) return false
if (!spriteWalk1.contentEquals(other.spriteWalk1)) return false
if (!spriteWalk2.contentEquals(other.spriteWalk2)) return false
if (!spriteRun1.contentEquals(other.spriteRun1)) return false
if (!spriteRun2.contentEquals(other.spriteRun2)) return false
if (!spriteTrain1.contentEquals(other.spriteTrain1)) return false
if (!spriteTrain2.contentEquals(other.spriteTrain2)) return false
if (!spriteHappy.contentEquals(other.spriteHappy)) return false
if (!spriteSleep.contentEquals(other.spriteSleep)) return false
if (!spriteAttack.contentEquals(other.spriteAttack)) return false
if (!spriteDodge.contentEquals(other.spriteDodge)) return false
if (width != other.width) return false
if (height != other.height) return false
return true
}
override fun hashCode(): Int {
var result = id
result = 31 * result + spriteIdle1.contentHashCode()
result = 31 * result + spriteIdle2.contentHashCode()
result = 31 * result + spriteWalk1.contentHashCode()
result = 31 * result + spriteWalk2.contentHashCode()
result = 31 * result + spriteRun1.contentHashCode()
result = 31 * result + spriteRun2.contentHashCode()
result = 31 * result + spriteTrain1.contentHashCode()
result = 31 * result + spriteTrain2.contentHashCode()
result = 31 * result + spriteHappy.contentHashCode()
result = 31 * result + spriteSleep.contentHashCode()
result = 31 * result + spriteAttack.contentHashCode()
result = 31 * result + spriteDodge.contentHashCode()
result = 31 * result + width
result = 31 * result + height
return result
}
}

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.device_data package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.vb.SpecialMission import com.github.cfogrady.vbnfc.vb.SpecialMission
@ -13,7 +14,8 @@ import com.github.cfogrady.vbnfc.vb.SpecialMission
childColumns = ["characterId"], childColumns = ["characterId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
] ],
indices = [Index(value = ["characterId"])]
) )
data class SpecialMissions ( data class SpecialMissions (
@PrimaryKey(autoGenerate = true) var id: Long = 0, @PrimaryKey(autoGenerate = true) var id: Long = 0,
@ -25,4 +27,4 @@ data class SpecialMissions (
val timeElapsedInMinutes: Int, val timeElapsedInMinutes: Int,
val timeLimitInMinutes: Int, val timeLimitInMinutes: Int,
val missionType: SpecialMission.Type val missionType: SpecialMission.Type
) )

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.device_data package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.card.CardCharacter import com.github.nacabaro.vbhelper.domain.card.CardCharacter
@ -19,6 +20,10 @@ import com.github.nacabaro.vbhelper.domain.card.CardCharacter
childColumns = ["stageId"], childColumns = ["stageId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
],
indices = [
Index(value = ["monId"]),
Index(value = ["stageId"])
] ]
) )
data class TransformationHistory ( data class TransformationHistory (

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.device_data package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
@ -15,7 +16,8 @@ import com.github.nacabaro.vbhelper.domain.card.CardCharacter
childColumns = ["charId"], childColumns = ["charId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
] ],
indices = [Index(value = ["charId"])]
) )
/** /**
* UserCharacter represents and instance of a character. The charId should map to a Character * UserCharacter represents and instance of a character. The charId should map to a Character
@ -37,4 +39,4 @@ data class UserCharacter (
var heartRateCurrent: Int, var heartRateCurrent: Int,
var characterType: DeviceType, var characterType: DeviceType,
var isActive: Boolean var isActive: Boolean
) )

View File

@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.domain.device_data package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@ -12,7 +13,8 @@ import androidx.room.PrimaryKey
childColumns = ["charId"], childColumns = ["charId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
) )
] ],
indices = [Index(value = ["charId"])]
) )
data class VitalsHistory ( data class VitalsHistory (
@PrimaryKey(autoGenerate = true) val id: Long = 0, @PrimaryKey(autoGenerate = true) val id: Long = 0,
@ -21,4 +23,4 @@ data class VitalsHistory (
val month: Int, val month: Int,
val day: Int, val day: Int,
val vitalPoints: Int val vitalPoints: Int
) )

View File

@ -9,7 +9,35 @@ object CardDtos {
val logoHeight: Int, val logoHeight: Int,
val totalCharacters: Int, val totalCharacters: Int,
val obtainedCharacters: Int, val obtainedCharacters: Int,
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CardProgress
if (cardId != other.cardId) return false
if (cardName != other.cardName) return false
if (!cardLogo.contentEquals(other.cardLogo)) return false
if (logoWidth != other.logoWidth) return false
if (logoHeight != other.logoHeight) return false
if (totalCharacters != other.totalCharacters) return false
if (obtainedCharacters != other.obtainedCharacters) return false
return true
}
override fun hashCode(): Int {
var result = cardId.hashCode()
result = 31 * result + cardName.hashCode()
result = 31 * result + cardLogo.contentHashCode()
result = 31 * result + logoWidth
result = 31 * result + logoHeight
result = 31 * result + totalCharacters
result = 31 * result + obtainedCharacters
return result
}
}
data class CardAdventureWithSprites ( data class CardAdventureWithSprites (
val characterName: ByteArray, val characterName: ByteArray,
@ -23,11 +51,67 @@ object CardDtos {
val characterDp: Int, val characterDp: Int,
val characterHp: Int, val characterHp: Int,
val steps: Int, val steps: Int,
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CardAdventureWithSprites
if (!characterName.contentEquals(other.characterName)) return false
if (characterNameWidth != other.characterNameWidth) return false
if (characterNameHeight != other.characterNameHeight) return false
if (!characterIdleSprite.contentEquals(other.characterIdleSprite)) return false
if (characterIdleSpriteWidth != other.characterIdleSpriteWidth) return false
if (characterIdleSpriteHeight != other.characterIdleSpriteHeight) return false
if (characterAp != other.characterAp) return false
if (characterBp != other.characterBp) return false
if (characterDp != other.characterDp) return false
if (characterHp != other.characterHp) return false
if (steps != other.steps) return false
return true
}
override fun hashCode(): Int {
var result = characterName.contentHashCode()
result = 31 * result + characterNameWidth
result = 31 * result + characterNameHeight
result = 31 * result + characterIdleSprite.contentHashCode()
result = 31 * result + characterIdleSpriteWidth
result = 31 * result + characterIdleSpriteHeight
result = 31 * result + characterAp
result = 31 * result + (characterBp ?: 0)
result = 31 * result + characterDp
result = 31 * result + characterHp
result = 31 * result + steps
return result
}
}
data class CardIcon ( data class CardIcon (
val cardIcon: ByteArray, val cardIcon: ByteArray,
val cardIconWidth: Int, val cardIconWidth: Int,
val cardIconHeight: Int val cardIconHeight: Int
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CardIcon
if (!cardIcon.contentEquals(other.cardIcon)) return false
if (cardIconWidth != other.cardIconWidth) return false
if (cardIconHeight != other.cardIconHeight) return false
return true
}
override fun hashCode(): Int {
var result = cardIcon.contentHashCode()
result = 31 * result + cardIconWidth
result = 31 * result + cardIconHeight
return result
}
}
} }

View File

@ -33,7 +33,75 @@ object CharacterDtos {
val isBemCard: Boolean, val isBemCard: Boolean,
val isInAdventure: Boolean, val isInAdventure: Boolean,
val active: Boolean val active: Boolean
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CharacterWithSprites
if (id != other.id) return false
if (charId != other.charId) return false
if (stage != other.stage) return false
if (attribute != other.attribute) return false
if (ageInDays != other.ageInDays) return false
if (mood != other.mood) return false
if (vitalPoints != other.vitalPoints) return false
if (transformationCountdown != other.transformationCountdown) return false
if (injuryStatus != other.injuryStatus) return false
if (trophies != other.trophies) return false
if (currentPhaseBattlesWon != other.currentPhaseBattlesWon) return false
if (currentPhaseBattlesLost != other.currentPhaseBattlesLost) return false
if (totalBattlesWon != other.totalBattlesWon) return false
if (totalBattlesLost != other.totalBattlesLost) return false
if (activityLevel != other.activityLevel) return false
if (heartRateCurrent != other.heartRateCurrent) return false
if (characterType != other.characterType) return false
if (!spriteIdle.contentEquals(other.spriteIdle)) return false
if (!spriteIdle2.contentEquals(other.spriteIdle2)) return false
if (spriteWidth != other.spriteWidth) return false
if (spriteHeight != other.spriteHeight) return false
if (!nameSprite.contentEquals(other.nameSprite)) return false
if (nameSpriteWidth != other.nameSpriteWidth) return false
if (nameSpriteHeight != other.nameSpriteHeight) return false
if (isBemCard != other.isBemCard) return false
if (isInAdventure != other.isInAdventure) return false
if (active != other.active) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + charId.hashCode()
result = 31 * result + stage
result = 31 * result + attribute.hashCode()
result = 31 * result + ageInDays
result = 31 * result + mood
result = 31 * result + vitalPoints
result = 31 * result + transformationCountdown
result = 31 * result + injuryStatus.hashCode()
result = 31 * result + trophies
result = 31 * result + currentPhaseBattlesWon
result = 31 * result + currentPhaseBattlesLost
result = 31 * result + totalBattlesWon
result = 31 * result + totalBattlesLost
result = 31 * result + activityLevel
result = 31 * result + heartRateCurrent
result = 31 * result + characterType.hashCode()
result = 31 * result + spriteIdle.contentHashCode()
result = 31 * result + spriteIdle2.contentHashCode()
result = 31 * result + spriteWidth
result = 31 * result + spriteHeight
result = 31 * result + nameSprite.contentHashCode()
result = 31 * result + nameSpriteWidth
result = 31 * result + nameSpriteHeight
result = 31 * result + isBemCard.hashCode()
result = 31 * result + isInAdventure.hashCode()
result = 31 * result + active.hashCode()
return result
}
}
data class CardCharacterInfo( data class CardCharacterInfo(
val cardId: Long, val cardId: Long,
@ -50,7 +118,33 @@ object CharacterDtos {
val spriteHeight: Int, val spriteHeight: Int,
val monIndex: Int, val monIndex: Int,
val transformationDate: Long val transformationDate: Long
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TransformationHistory
if (id != other.id) return false
if (!spriteIdle.contentEquals(other.spriteIdle)) return false
if (spriteWidth != other.spriteWidth) return false
if (spriteHeight != other.spriteHeight) return false
if (monIndex != other.monIndex) return false
if (transformationDate != other.transformationDate) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + spriteIdle.contentHashCode()
result = 31 * result + spriteWidth
result = 31 * result + spriteHeight
result = 31 * result + monIndex
result = 31 * result + transformationDate.hashCode()
return result
}
}
data class TransformationHistoryExport( data class TransformationHistoryExport(
val stageId: Long, val stageId: Long,
@ -73,14 +167,76 @@ object CharacterDtos {
val baseAp: Int, val baseAp: Int,
val stage: Int, val stage: Int,
val attribute: NfcCharacter.Attribute, val attribute: NfcCharacter.Attribute,
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CardCharaProgress
if (id != other.id) return false
if (!spriteIdle.contentEquals(other.spriteIdle)) return false
if (spriteWidth != other.spriteWidth) return false
if (spriteHeight != other.spriteHeight) return false
if (!nameSprite.contentEquals(other.nameSprite)) return false
if (nameSpriteWidth != other.nameSpriteWidth) return false
if (nameSpriteHeight != other.nameSpriteHeight) return false
if (discoveredOn != other.discoveredOn) return false
if (baseHp != other.baseHp) return false
if (baseBp != other.baseBp) return false
if (baseAp != other.baseAp) return false
if (stage != other.stage) return false
if (attribute != other.attribute) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + spriteIdle.contentHashCode()
result = 31 * result + spriteWidth
result = 31 * result + spriteHeight
result = 31 * result + nameSprite.contentHashCode()
result = 31 * result + nameSpriteWidth
result = 31 * result + nameSpriteHeight
result = 31 * result + (discoveredOn?.hashCode() ?: 0)
result = 31 * result + baseHp
result = 31 * result + baseBp
result = 31 * result + baseAp
result = 31 * result + stage
result = 31 * result + attribute.hashCode()
return result
}
}
data class CardProgress( data class CardProgress(
val id: Long, val id: Long,
val spriteIdle: ByteArray, val spriteIdle: ByteArray,
val spriteWidth: Int, val spriteWidth: Int,
val spriteHeight: Int, val spriteHeight: Int,
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CardProgress
if (id != other.id) return false
if (!spriteIdle.contentEquals(other.spriteIdle)) return false
if (spriteWidth != other.spriteWidth) return false
if (spriteHeight != other.spriteHeight) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + spriteIdle.contentHashCode()
result = 31 * result + spriteWidth
result = 31 * result + spriteHeight
return result
}
}
data class AdventureCharacterWithSprites( data class AdventureCharacterWithSprites(
var id: Long = 0, var id: Long = 0,
@ -106,7 +262,67 @@ object CharacterDtos {
val isBemCard: Boolean, val isBemCard: Boolean,
val finishesAdventure: Long, val finishesAdventure: Long,
val originalTimeInMinutes: Long val originalTimeInMinutes: Long
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AdventureCharacterWithSprites
if (id != other.id) return false
if (charId != other.charId) return false
if (stage != other.stage) return false
if (attribute != other.attribute) return false
if (ageInDays != other.ageInDays) return false
if (mood != other.mood) return false
if (vitalPoints != other.vitalPoints) return false
if (transformationCountdown != other.transformationCountdown) return false
if (injuryStatus != other.injuryStatus) return false
if (trophies != other.trophies) return false
if (currentPhaseBattlesWon != other.currentPhaseBattlesWon) return false
if (currentPhaseBattlesLost != other.currentPhaseBattlesLost) return false
if (totalBattlesWon != other.totalBattlesWon) return false
if (totalBattlesLost != other.totalBattlesLost) return false
if (activityLevel != other.activityLevel) return false
if (heartRateCurrent != other.heartRateCurrent) return false
if (characterType != other.characterType) return false
if (!spriteIdle.contentEquals(other.spriteIdle)) return false
if (spriteWidth != other.spriteWidth) return false
if (spriteHeight != other.spriteHeight) return false
if (isBemCard != other.isBemCard) return false
if (finishesAdventure != other.finishesAdventure) return false
if (originalTimeInMinutes != other.originalTimeInMinutes) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + charId.hashCode()
result = 31 * result + stage
result = 31 * result + attribute.hashCode()
result = 31 * result + ageInDays
result = 31 * result + mood
result = 31 * result + vitalPoints
result = 31 * result + transformationCountdown
result = 31 * result + injuryStatus.hashCode()
result = 31 * result + trophies
result = 31 * result + currentPhaseBattlesWon
result = 31 * result + currentPhaseBattlesLost
result = 31 * result + totalBattlesWon
result = 31 * result + totalBattlesLost
result = 31 * result + activityLevel
result = 31 * result + heartRateCurrent
result = 31 * result + characterType.hashCode()
result = 31 * result + spriteIdle.contentHashCode()
result = 31 * result + spriteWidth
result = 31 * result + spriteHeight
result = 31 * result + isBemCard.hashCode()
result = 31 * result + finishesAdventure.hashCode()
result = 31 * result + originalTimeInMinutes.hashCode()
return result
}
}
data class EvolutionRequirementsWithSpritesAndObtained( data class EvolutionRequirementsWithSpritesAndObtained(
val charaId: Long, val charaId: Long,
@ -121,7 +337,45 @@ object CharacterDtos {
val requiredWinRate: Int, val requiredWinRate: Int,
val changeTimerHours: Int, val changeTimerHours: Int,
val requiredAdventureLevelCompleted: Int val requiredAdventureLevelCompleted: Int
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as EvolutionRequirementsWithSpritesAndObtained
if (charaId != other.charaId) return false
if (fromCharaId != other.fromCharaId) return false
if (!spriteIdle.contentEquals(other.spriteIdle)) return false
if (spriteWidth != other.spriteWidth) return false
if (spriteHeight != other.spriteHeight) return false
if (discoveredOn != other.discoveredOn) return false
if (requiredTrophies != other.requiredTrophies) return false
if (requiredVitals != other.requiredVitals) return false
if (requiredBattles != other.requiredBattles) return false
if (requiredWinRate != other.requiredWinRate) return false
if (changeTimerHours != other.changeTimerHours) return false
if (requiredAdventureLevelCompleted != other.requiredAdventureLevelCompleted) return false
return true
}
override fun hashCode(): Int {
var result = charaId.hashCode()
result = 31 * result + fromCharaId.hashCode()
result = 31 * result + spriteIdle.contentHashCode()
result = 31 * result + spriteWidth
result = 31 * result + spriteHeight
result = 31 * result + (discoveredOn?.hashCode() ?: 0)
result = 31 * result + requiredTrophies
result = 31 * result + requiredVitals
result = 31 * result + requiredBattles
result = 31 * result + requiredWinRate
result = 31 * result + changeTimerHours
result = 31 * result + requiredAdventureLevelCompleted
return result
}
}
data class FusionsWithSpritesAndObtained( data class FusionsWithSpritesAndObtained(
val charaId: Long, val charaId: Long,
@ -131,5 +385,33 @@ object CharacterDtos {
val spriteHeight: Int, val spriteHeight: Int,
val discoveredOn: Long?, val discoveredOn: Long?,
val fusionAttribute: NfcCharacter.Attribute val fusionAttribute: NfcCharacter.Attribute
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FusionsWithSpritesAndObtained
if (charaId != other.charaId) return false
if (fromCharaId != other.fromCharaId) return false
if (!spriteIdle.contentEquals(other.spriteIdle)) return false
if (spriteWidth != other.spriteWidth) return false
if (spriteHeight != other.spriteHeight) return false
if (discoveredOn != other.discoveredOn) return false
if (fusionAttribute != other.fusionAttribute) return false
return true
}
override fun hashCode(): Int {
var result = charaId.hashCode()
result = 31 * result + fromCharaId.hashCode()
result = 31 * result + spriteIdle.contentHashCode()
result = 31 * result + spriteWidth
result = 31 * result + spriteHeight
result = 31 * result + (discoveredOn?.hashCode() ?: 0)
result = 31 * result + fusionAttribute.hashCode()
return result
}
}
} }

View File

@ -1,10 +1,15 @@
package com.github.nacabaro.vbhelper.screens package com.github.nacabaro.vbhelper.screens
/**
* This is me, nacabaro.
* File related to battles.
* TODO: Refactor this, 2400+ lines feels like a lot.
*/
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
//import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -28,10 +33,6 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
//import androidx.compose.material3.ExposedDropdownMenuBox
//import androidx.compose.material3.ExposedDropdownMenuDefaults
//import androidx.compose.material3.OutlinedTextField
//import androidx.compose.material3.DropdownMenuItem
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -51,8 +52,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.media.MediaPlayer import android.media.MediaPlayer
import android.os.Environment import android.os.Environment
//import androidx.compose.animation.core.animateFloatAsState
//import androidx.compose.animation.core.tween
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -72,13 +71,8 @@ import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage
import com.github.nacabaro.vbhelper.battle.HitEffectOverlay import com.github.nacabaro.vbhelper.battle.HitEffectOverlay
import com.github.nacabaro.vbhelper.battle.BattleAuthContainer import com.github.nacabaro.vbhelper.battle.BattleAuthContainer
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.collect
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
//import kotlin.math.sin
//import kotlin.math.PI
//import androidx.compose.animation.core.animateDpAsState
//import androidx.compose.animation.core.animateIntAsState
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.Shadow
@ -86,24 +80,20 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
//import android.os.Environment
import java.io.File import java.io.File
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
@Composable /*@Composable
fun isLandscapeMode(): Boolean { fun isLandscapeMode(): Boolean {
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
return configuration.screenWidthDp > configuration.screenHeightDp return configuration.screenWidthDp > configuration.screenHeightDp
} }*/
@Composable @Composable
fun getLandscapeModifier(): Modifier { fun getLandscapeModifier(): Modifier {
@ -1735,7 +1725,7 @@ fun BattlesScreen() {
val currentCharacter = activeUserCharacter val currentCharacter = activeUserCharacter
if (currentCharacter != null && canBattle && playerBattleType != null) { if (currentCharacter != null && canBattle && playerBattleType != null) {
try { try {
RetrofitHelper().getOpponents(context, playerBattleType!!) { opponents -> RetrofitHelper().getOpponents(context, playerBattleType) { opponents ->
try { try {
// Create a new list to trigger UI recomposition // Create a new list to trigger UI recomposition
opponentsList = ArrayList(opponents.opponentsList) opponentsList = ArrayList(opponents.opponentsList)
@ -2134,8 +2124,8 @@ fun BattlesScreen() {
// Create APIBattleCharacter from database character // Create APIBattleCharacter from database character
val playerCharacter = APIBattleCharacter( val playerCharacter = APIBattleCharacter(
name = "Player Digimon", // We could get this from the database if needed name = "Player Character", // We could get this from the database if needed
namekey = "player_digimon", // Name key for the character namekey = "player_digimon", // Name key for the character (functional lookup key, not shown)
charaId = formattedCardId, // Use the formatted card ID for sprite loading charaId = formattedCardId, // Use the formatted card ID for sprite loading
stage = characterData.stage, stage = characterData.stage,
attribute = characterData.attribute.ordinal, // Convert enum to int attribute = characterData.attribute.ordinal, // Convert enum to int
@ -2242,10 +2232,10 @@ fun BattlesScreen() {
) { ) {
// Show active character info // Show active character info
activeUserCharacter?.let { character -> activeUserCharacter?.let { character ->
Text("Active Digimon:") Text("Active character:")
Text("Stage: ${character.stage}") Text("Stage: ${character.stage}")
activeCardId?.let { cardId -> activeCardId?.let { cardId ->
Text("Digimon ID: $cardId", fontSize = 14.sp, color = Color.Blue, fontWeight = FontWeight.Bold) Text("Character ID: $cardId", fontSize = 14.sp, color = Color.Blue, fontWeight = FontWeight.Bold)
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@ -2346,7 +2336,7 @@ fun BattlesScreen() {
textAlign = TextAlign.Center) textAlign = TextAlign.Center)
} }
} else { } else {
Text("Your Digimon must be at least Stage 2 to battle", Text("Your character must be at least Stage 2 to battle",
fontSize = 16.sp, fontSize = 16.sp,
color = Color.Red, color = Color.Red,
textAlign = TextAlign.Center) textAlign = TextAlign.Center)
@ -2415,7 +2405,7 @@ fun BattlesScreen() {
// Also check winner field if it's not empty // Also check winner field if it's not empty
val playerWonFromWinner = activeCardId?.let { cardId -> val playerWonFromWinner = activeCardId?.let { cardId ->
val winner = apiResult.winner ?: "" val winner = apiResult.winner
if (winner.isNotEmpty()) { if (winner.isNotEmpty()) {
if (winner.contains("|")) { if (winner.contains("|")) {
// Pipe-separated format: first part is the winner // Pipe-separated format: first part is the winner
@ -2442,7 +2432,7 @@ fun BattlesScreen() {
println("BATTLESCREEN: Battle result (first call) - winner: '${apiResult.winner}', playerHP: ${apiResult.playerHP}, opponentHP: ${apiResult.opponentHP}, playerWonFromHP: $playerWonFromHP, playerWonFromWinner: $playerWonFromWinner, final playerWon: $playerWon") println("BATTLESCREEN: Battle result (first call) - winner: '${apiResult.winner}', playerHP: ${apiResult.playerHP}, opponentHP: ${apiResult.opponentHP}, playerWonFromHP: $playerWonFromHP, playerWonFromWinner: $playerWonFromWinner, final playerWon: $playerWon")
// Store winner name for display (will be updated in cleanup call if available) // Store winner name for display (will be updated in cleanup call if available)
winnerName = apiResult.winner ?: if (playerWon) "You" else "Opponent" winnerName = apiResult.winner
isWinnerLoaded = true isWinnerLoaded = true
// Then send the cleanup call - this will have the actual winner name // Then send the cleanup call - this will have the actual winner name
@ -2465,7 +2455,7 @@ fun BattlesScreen() {
// Primary method: Check HP values (opponentHP <= 0 means opponent lost = player won) // Primary method: Check HP values (opponentHP <= 0 means opponent lost = player won)
// Secondary method: Check winner name (if winner doesn't match opponent, player won) // Secondary method: Check winner name (if winner doesn't match opponent, player won)
val opponentName = selectedOpponent?.name ?: "" val opponentName = selectedOpponent?.name ?: ""
val winner = cleanupResult.winner ?: "" val winner = cleanupResult.winner
// Primary: HP-based determination (most reliable) // Primary: HP-based determination (most reliable)
// If opponentHP <= 0, opponent is dead = player won // If opponentHP <= 0, opponent is dead = player won

View File

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

View File

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

View File

@ -4,9 +4,9 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -34,7 +34,7 @@ fun ItemsScreen(
topBar = { topBar = {
Column { Column {
TopBanner(text = stringResource(R.string.items_title)) TopBanner(text = stringResource(R.string.items_title))
TabRow( PrimaryTabRow(
selectedTabIndex = selectedTabItem, selectedTabIndex = selectedTabItem,
modifier = Modifier modifier = Modifier
) { ) {

View File

@ -43,7 +43,7 @@ fun ChooseConnectOption(
.padding(contentPadding) .padding(contentPadding)
) { ) {
Text( Text(
text = "Bandai Toys = Vital Bracelet, Vital Hero, BE Bracelet", text = "VB, VH, VBBE",
fontSize = 14.sp, fontSize = 14.sp,
modifier = Modifier.padding(bottom = 24.dp) modifier = Modifier.padding(bottom = 24.dp)
) )

View File

@ -44,14 +44,8 @@ fun ScanScreen(
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(storageRepository) { LaunchedEffect(characterId) {
withContext(Dispatchers.IO) { 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) { if (characterId != null && nfcCharacter == null) {
nfcCharacter = scanScreenController.characterToNfc(characterId) nfcCharacter = scanScreenController.characterToNfc(characterId)
} }

View File

@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.screens.scanScreen
import android.content.Intent import android.content.Intent
import android.nfc.NfcAdapter import android.nfc.NfcAdapter
import android.nfc.Tag import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.nfc.tech.NfcA import android.nfc.tech.NfcA
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
@ -15,12 +16,16 @@ import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.card.Card import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.FromNfcConverter import com.github.nacabaro.vbhelper.screens.scanScreen.converters.FromNfcConverter
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.ToNfcConverter import com.github.nacabaro.vbhelper.screens.scanScreen.converters.ToNfcConverter
import com.github.nacabaro.vbhelper.source.VitalWearCharacterExporter
import com.github.nacabaro.vbhelper.source.VitalWearCharacterImporter
import com.github.nacabaro.vbhelper.source.getCryptographicTransformerMap import com.github.nacabaro.vbhelper.source.getCryptographicTransformerMap
import com.github.nacabaro.vbhelper.source.isMissingSecrets import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets import com.github.nacabaro.vbhelper.source.proto.Secrets
import com.github.nacabaro.vbhelper.transfer.hce.VitalWearHceReaderClient
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
@ -32,8 +37,10 @@ class ScanScreenControllerImpl(
private val componentActivity: ComponentActivity, private val componentActivity: ComponentActivity,
private val registerActivityLifecycleListener: (String, ActivityLifecycleListener)->Unit, private val registerActivityLifecycleListener: (String, ActivityLifecycleListener)->Unit,
private val unregisterActivityLifecycleListener: (String)->Unit, private val unregisterActivityLifecycleListener: (String)->Unit,
private val database: AppDatabase,
): ScanScreenController { ): ScanScreenController {
private var lastScannedCharacter: NfcCharacter? = null private var lastScannedCharacter: NfcCharacter? = null
private var pendingExportCharacterId: Long? = null
private val nfcAdapter: NfcAdapter private val nfcAdapter: NfcAdapter
init { init {
@ -46,24 +53,46 @@ class ScanScreenControllerImpl(
} }
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit) { override fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit) {
handleTag(secrets) { tagCommunicator -> handleTag(
try { secrets,
val character = tagCommunicator.receiveCharacter() handlerFunc = { tagCommunicator ->
val resultMessage = characterFromNfc(character) { cards, nfcCharacter -> try {
lastScannedCharacter = nfcCharacter val character = tagCommunicator.receiveCharacter()
onMultipleCards(cards) val resultMessage = characterFromNfc(character) { cards, nfcCharacter ->
lastScannedCharacter = nfcCharacter
onMultipleCards(cards)
}
onComplete.invoke()
resultMessage
} catch (e: Exception) {
Log.e("NFC_READ", "Error reading character from NFC", e)
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_error_generic) + ": " + (e.message ?: e.javaClass.simpleName), Toast.LENGTH_LONG).show()
}
onComplete.invoke()
componentActivity.getString(R.string.scan_error_generic)
} }
onComplete.invoke() },
resultMessage hceHandler = { isoDep ->
} catch (e: Exception) { try {
Log.e("NFC_READ", "Error reading character from NFC", e) val client = VitalWearHceReaderClient(isoDep)
componentActivity.runOnUiThread { val character = client.receiveCharacterFromWatch()
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_error_generic) + ": " + (e.message ?: e.javaClass.simpleName), Toast.LENGTH_LONG).show() val importer = VitalWearCharacterImporter(database)
val result = importer.importCharacter(character)
Log.i("NFC_READ", "VitalWear HCE import: ${result.message}")
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, result.message, Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Log.e("NFC_READ", "Error reading character from VitalWear HCE", e)
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_error_generic) + ": " + (e.message ?: e.javaClass.simpleName), Toast.LENGTH_LONG).show()
}
} finally {
onComplete()
} }
onComplete.invoke()
componentActivity.getString(R.string.scan_error_generic)
} }
} )
} }
override fun cancelRead() { override fun cancelRead() {
@ -83,35 +112,53 @@ class ScanScreenControllerImpl(
unregisterActivityLifecycleListener.invoke(key) unregisterActivityLifecycleListener.invoke(key)
} }
// EXTRACTED DIRECTLY FROM EXAMPLE APP private fun handleTag(
private fun handleTag(secrets: Secrets, handlerFunc: (TagCommunicator)->String) { secrets: Secrets,
handlerFunc: (TagCommunicator) -> String,
hceHandler: ((IsoDep) -> Unit)? = null,
) {
if (!nfcAdapter.isEnabled) { if (!nfcAdapter.isEnabled) {
showWirelessSettings() showWirelessSettings()
} else { } else {
val options = Bundle() val options = Bundle()
// Work around for some broken Nfc firmware implementations that poll the card too fast // Work around for some broken Nfc firmware implementations that poll the card too fast
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250) options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
nfcAdapter.enableReaderMode(componentActivity, buildOnReadTag(secrets, handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, nfcAdapter.enableReaderMode(
componentActivity,
buildOnReadTag(secrets, handlerFunc, hceHandler),
NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
options options
) )
} }
} }
// EXTRACTED DIRECTLY FROM EXAMPLE APP private fun buildOnReadTag(
private fun buildOnReadTag(secrets: Secrets, handlerFunc: (TagCommunicator)->String): (Tag)->Unit { secrets: Secrets,
return { tag-> handlerFunc: (TagCommunicator) -> String,
val nfcData = NfcA.get(tag) hceHandler: ((IsoDep) -> Unit)? = null,
if (nfcData == null) { ): (Tag) -> Unit {
componentActivity.runOnUiThread { return { tag ->
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_tag_not_vb), Toast.LENGTH_SHORT).show() val isoDep = IsoDep.get(tag)
} if (isoDep != null && hceHandler != null) {
} // VitalWear watch via HCE / ISO-DEP
nfcData.connect() isoDep.connect()
nfcData.use { isoDep.use { hceHandler(isoDep) }
val tagCommunicator = TagCommunicator.getInstance(nfcData, secrets.getCryptographicTransformerMap()) } else {
val successText = handlerFunc(tagCommunicator) // Physical Vital Bracelet via raw NFC-A
componentActivity.runOnUiThread { val nfcData = NfcA.get(tag)
Toast.makeText(componentActivity, successText, Toast.LENGTH_SHORT).show() if (nfcData == null) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_tag_not_vb), Toast.LENGTH_SHORT).show()
}
} else {
nfcData.connect()
nfcData.use {
val tagCommunicator = TagCommunicator.getInstance(nfcData, secrets.getCryptographicTransformerMap())
val successText = handlerFunc(tagCommunicator)
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, successText, Toast.LENGTH_SHORT).show()
}
}
} }
} }
} }
@ -132,24 +179,29 @@ class ScanScreenControllerImpl(
nfcCharacter: NfcCharacter, nfcCharacter: NfcCharacter,
onComplete: () -> Unit onComplete: () -> Unit
) { ) {
handleTag(secrets) { tagCommunicator -> handleTag(
try { secrets,
if (nfcCharacter is VBNfcCharacter) { handlerFunc = { tagCommunicator ->
Log.d("SendCharacter", "VBNfcCharacter") try {
val castNfcCharacter: VBNfcCharacter = nfcCharacter if (nfcCharacter is VBNfcCharacter) {
tagCommunicator.sendCharacter(castNfcCharacter) Log.d("SendCharacter", "VBNfcCharacter")
} else if (nfcCharacter is BENfcCharacter) { tagCommunicator.sendCharacter(nfcCharacter)
Log.d("SendCharacter", "BENfcCharacter") } else if (nfcCharacter is BENfcCharacter) {
val castNfcCharacter: BENfcCharacter = nfcCharacter Log.d("SendCharacter", "BENfcCharacter")
tagCommunicator.sendCharacter(castNfcCharacter) tagCommunicator.sendCharacter(nfcCharacter)
}
onComplete.invoke()
componentActivity.getString(R.string.scan_sent_character_success)
} catch (e: Throwable) {
Log.e("TAG", e.stackTraceToString())
componentActivity.getString(R.string.scan_error_generic)
} }
onComplete.invoke() },
componentActivity.getString(R.string.scan_sent_character_success) hceHandler = { _ ->
} catch (e: Throwable) { // Character was already sent in onClickCheckCard's HCE handler.
Log.e("TAG", e.stackTraceToString()) onComplete()
componentActivity.getString(R.string.scan_error_generic)
} }
} )
} }
override fun onClickCheckCard( override fun onClickCheckCard(
@ -157,11 +209,42 @@ class ScanScreenControllerImpl(
nfcCharacter: NfcCharacter, nfcCharacter: NfcCharacter,
onComplete: () -> Unit onComplete: () -> Unit
) { ) {
handleTag(secrets) { tagCommunicator -> handleTag(
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId) secrets,
onComplete.invoke() handlerFunc = { tagCommunicator ->
componentActivity.getString(R.string.scan_sent_dim_success) tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
} onComplete.invoke()
componentActivity.getString(R.string.scan_sent_dim_success)
},
hceHandler = { isoDep ->
// VitalWear watch: send the character proto directly via HCE.
val characterId = pendingExportCharacterId
if (characterId == null) {
Log.e("NFC_WRITE", "No pending character to send to VitalWear")
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_error_generic), Toast.LENGTH_SHORT).show()
}
onComplete()
return@handleTag
}
try {
val proto = VitalWearCharacterExporter(componentActivity, database).buildCharacterProto(characterId)
val client = VitalWearHceReaderClient(isoDep)
client.sendCharacterToWatch(proto)
Log.i("NFC_WRITE", "Character sent to VitalWear (id=$characterId), deletion deferred to WritingScreen")
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_sent_character_success), Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Log.e("NFC_WRITE", "Error sending character to VitalWear HCE", e)
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_error_generic) + ": " + (e.message ?: e.javaClass.simpleName), Toast.LENGTH_LONG).show()
}
} finally {
onComplete()
}
}
)
} }
// EXTRACTED DIRECTLY FROM EXAMPLE APP // EXTRACTED DIRECTLY FROM EXAMPLE APP
@ -181,10 +264,8 @@ class ScanScreenControllerImpl(
} }
override suspend fun characterToNfc(characterId: Long): NfcCharacter { override suspend fun characterToNfc(characterId: Long): NfcCharacter {
val nfcGenerator = ToNfcConverter( pendingExportCharacterId = characterId
componentActivity = componentActivity val nfcGenerator = ToNfcConverter(componentActivity = componentActivity)
)
val character = nfcGenerator.characterToNfc(characterId) val character = nfcGenerator.characterToNfc(characterId)
Log.d("CharacterType", character.toString()) Log.d("CharacterType", character.toString())
return character return character

View File

@ -104,7 +104,7 @@ fun TransferAnimationScreen(
) { ) {
Image( Image(
bitmap = preview.imageBitmap, bitmap = preview.imageBitmap,
contentDescription = "Transferred Digimon sprite", contentDescription = "Transferred character sprite",
modifier = Modifier modifier = Modifier
.size(preview.dpWidth, preview.dpHeight) .size(preview.dpWidth, preview.dpHeight)
.padding(8.dp), .padding(8.dp),

View File

@ -136,7 +136,7 @@ class SettingsScreenControllerImpl(
secrets = secretsImporter.importSecrets(it) secrets = secretsImporter.importSecrets(it)
} catch (e: Exception) { } catch (e: Exception) {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText(context, "Secrets import failed. Please only select the official Vital Arena App 2.1.0 APK.", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Secrets import failed. Please only select the official app APK.", Toast.LENGTH_SHORT).show()
} }
return@launch return@launch
} }

View File

@ -15,52 +15,56 @@ class VitalWearCharacterExporter(
private val context: Context, private val context: Context,
private val database: AppDatabase private val database: AppDatabase
) { ) {
fun buildCharacterProto(characterId: Long): Character = 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
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()
}
fun buildShareIntent(characterId: Long): Intent { fun buildShareIntent(characterId: Long): Intent {
return runBlocking { return runBlocking {
val characterWithSprites = database.userCharacterDao().getCharacterWithSprites(characterId) val proto = buildCharacterProto(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 exportDir = File(context.cacheDir, "exports").apply { mkdirs() }
val exportFile = File(exportDir, "vbhelper_character_$characterId.vitalwear") val exportFile = File(exportDir, "vbhelper_character_$characterId.vitalwear")

View File

@ -13,7 +13,27 @@ data class BitmapData (
val bitmap: ByteArray, val bitmap: ByteArray,
val width: Int, val width: Int,
val height: Int val height: Int
) ) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as BitmapData
if (!bitmap.contentEquals(other.bitmap)) return false
if (width != other.width) return false
if (height != other.height) return false
return true
}
override fun hashCode(): Int {
var result = bitmap.contentHashCode()
result = 31 * result + width
result = 31 * result + height
return result
}
}
data class ImageBitmapData( data class ImageBitmapData(
val imageBitmap: ImageBitmap, val imageBitmap: ImageBitmap,

View File

@ -17,21 +17,21 @@
<string name="nav_adventure">アドベンチャー</string> <string name="nav_adventure">アドベンチャー</string>
<string name="nav_credits">クレジット</string> <string name="nav_credits">クレジット</string>
<string name="adventure_title">アドベンチャー</string> <string name="adventure_title">アドベンチャー</string>
<string name="adventure_empty_state">何もありません。</string> <string name="adventure_empty_state">No Data</string>
<string name="home_title">VB Helper</string> <string name="home_title">VB Helper</string>
<string name="scan_secrets_not_imported">" APKの読み込みがされていません。設定から読み込をして下さい。 "</string> <string name="scan_secrets_not_imported">"APKの読み込みがされていません。設定から読み込をして下さい。 "</string>
<string name="scan_title">デバイスをスキャン</string> <string name="scan_title">デバイスをスキャン</string>
<string name="scan_sent_dim_success">DIMの転送に成功しました。</string> <string name="scan_sent_dim_success">DIMの転送に成功しました。</string>
<string name="scan_nfc_must_be_enabled">NFC機能を有効にして下さい。</string> <string name="scan_nfc_must_be_enabled">NFC機能を有効にして下さい。</string>
<string name="settings_title">設定</string> <string name="settings_title">設定</string>
<string name="scan_missing_secrets">必要なデータが不足しています。設定を開き、Vital ArenaのAPKを読み込んでください。</string> <string name="scan_missing_secrets">公式アプリのAPKを読み込みして下さい。</string>
<string name="scan_sent_character_success">転送が成功しました。</string> <string name="scan_sent_character_success">転送が成功しました。</string>
<string name="scan_error_generic">エラー</string> <string name="scan_error_generic">エラー</string>
<string name="settings_about_desc">このアプリについて</string> <string name="settings_about_desc">このアプリについて</string>
<string name="settings_export_data_title">データベースの書き出し</string> <string name="settings_export_data_title">データベースのエクスポート</string>
<string name="settings_export_data_desc">" データベースを書き出します。"</string> <string name="settings_export_data_desc">データベースの書き出しをします。</string>
<string name="settings_import_data_title">データベースの読み込み</string> <string name="settings_import_data_title">データベースのインポート</string>
<string name="settings_import_data_desc">" データベースを読み込みます。"</string> <string name="settings_import_data_desc">データベースの読み込みをします。</string>
<string name="credits_title">クレジット</string> <string name="credits_title">クレジット</string>
<string name="battles_coming_soon">Coming soon</string> <string name="battles_coming_soon">Coming soon</string>
<string name="cards_my_cards_title">カードリスト</string> <string name="cards_my_cards_title">カードリスト</string>
@ -42,7 +42,7 @@
<string name="scan_vb_to_app">デバイスからアプリへ</string> <string name="scan_vb_to_app">デバイスからアプリへ</string>
<string name="scan_app_to_vb">アプリからデバイスへ</string> <string name="scan_app_to_vb">アプリからデバイスへ</string>
<string name="read_character_title">キャラクターを読み込みます。</string> <string name="read_character_title">キャラクターを読み込みます。</string>
<string name="read_character_prepare_device">バイタルブレスを準備してください。</string> <string name="read_character_prepare_device">バイスを準備してください。</string>
<string name="read_character_go_to_connect">準備が出来たら転送を押してください。</string> <string name="read_character_go_to_connect">準備が出来たら転送を押してください。</string>
<string name="read_character_confirm">転送</string> <string name="read_character_confirm">転送</string>
<string name="card_adventure_missions_title">アドベンチャーミッション</string> <string name="card_adventure_missions_title">アドベンチャーミッション</string>
@ -54,31 +54,32 @@
<string name="storage_delete_character">削除</string> <string name="storage_delete_character">削除</string>
<string name="storage_close">閉じる</string> <string name="storage_close">閉じる</string>
<string name="storage_character_image_description">キャラクター画像</string> <string name="storage_character_image_description">キャラクター画像</string>
<string name="storage_send_to_watch">転送</string> <string name="storage_send_to_watch">デバイスへ転送</string>
<string name="storage_send_to_vitalwear">VitalWearへ転送</string>
<string name="storage_set_active">選択</string> <string name="storage_set_active">選択</string>
<string name="storage_send_on_adventure">アドベンチャーへ出発</string> <string name="storage_send_on_adventure">アドベンチャーへ出発</string>
<string name="scan_no_nfc_on_device">NFC機能が見つかりません。</string> <string name="scan_no_nfc_on_device">NFC機能が見つかりません。</string>
<string name="scan_tag_not_vb">検出されたタグはバイタルブレスでは使用出来ません。</string> <string name="scan_tag_not_vb">検出されたタグはバイタルブレスでは使用出来ません。</string>
<string name="settings_section_nfc">NFC</string> <string name="settings_section_nfc">NFC通信</string>
<string name="settings_section_dim_bem">DiM / BEm</string> <string name="settings_section_dim_bem">DiM / BEm管理</string>
<string name="settings_section_data">データ管理</string> <string name="settings_section_data">データ管理</string>
<string name="settings_import_apk_title">アプリの読み込み</string> <string name="settings_import_apk_title">アプリのインポート</string>
<string name="settings_import_apk_desc">" Vital Arena 2.1.0のAPKファイルを読み込みします。"</string> <string name="settings_import_apk_desc">公式アプリのAPKファイルを読み込みします。</string>
<string name="settings_import_card_title">カードの読み込み</string> <string name="settings_import_card_title">カードのインポート</string>
<string name="settings_section_about">概要 / クレジット</string> <string name="settings_section_about">概要 / クレジット</string>
<string name="dex_chara_fusions_button">ジョグレス</string> <string name="dex_chara_fusions_button">ジョグレス</string>
<string name="dex_chara_close_button">閉じる</string> <string name="dex_chara_close_button">閉じる</string>
<string name="settings_import_card_desc">" Dim / BEm カードファイルを読み込みます。"</string> <string name="settings_import_card_desc">Dim / BEm のデータファイルを読み込みします。</string>
<string name="settings_credits_title">クレジット</string> <string name="settings_credits_title">クレジット</string>
<string name="settings_credits_desc">開発者について</string> <string name="settings_credits_desc">開発者について</string>
<string name="settings_about_title">概要</string> <string name="settings_about_title">概要</string>
<string name="beta_warning_message_main">" このアプリは現在アルファ版です。今後のアップデートにより保存されているデータの全てが削除される可能性がある為、大切なデータの保存はしないようにお願い致します。ご不便をおかけして申し訳ございません。 "</string> <string name="beta_warning_message_main">このアプリは現在開発中です。\n今後のアップデートにより、保存されているデータ全ての削除が必要になる場合があります。\nその為、大切なデータの保存には使用しないようお願い致します。\nご不便をおかけして申し訳ございません。</string>
<string name="beta_warning_message_compatibility">" このアプリはオリジナルのVBおよびVHで動作するようになりました。 "</string> <string name="beta_warning_message_compatibility">"このアプリはオリジナルのVBおよびVHで動作するようになりました。 "</string>
<string name="beta_warning_message_thanks">" ご理解とご協力に感謝いたします。\n 開発チーム一同"</string> <string name="beta_warning_message_thanks">ご理解とご協力に感謝致します。\n開発チーム一同</string>
<string name="home_vbdim_vitals">バイタル</string> <string name="home_vbdim_vitals">バイタル</string>
<string name="home_adventure_mission_finished">" 1人のキャラクターがアドベンチャーミッションを完了しました。 "</string> <string name="home_adventure_mission_finished">"1人のキャラクターがアドベンチャーミッションを完了しました。 "</string>
<string name="scan_secrets_not_initialized">" 初期化が出来ませんでした。もう一度試して下さい。 "</string> <string name="scan_secrets_not_initialized">"初期化が出来ませんでした。もう一度試して下さい。 "</string>
<string name="home_special_mission_delete_main">現在の進捗状況が失われますが、本当にこのスペシャルミッションを削除しますか?</string> <string name="home_special_mission_delete_main">現在の進捗状況が失われます。本当にこのミッションを削除しますか?</string>
<string name="home_special_mission_delete_dismiss">閉じる</string> <string name="home_special_mission_delete_dismiss">閉じる</string>
<string name="home_special_mission_delete_remove">削除</string> <string name="home_special_mission_delete_remove">削除</string>
<string name="item_dialog_use">アイテム使用</string> <string name="item_dialog_use">アイテム使用</string>
@ -90,25 +91,26 @@
<string name="obtained_item_you_also_got_credits">%1$d クレジットを獲得しました。</string> <string name="obtained_item_you_also_got_credits">%1$d クレジットを獲得しました。</string>
<string name="obtained_item_dismiss">閉じる</string> <string name="obtained_item_dismiss">閉じる</string>
<string name="nav_dex">図鑑</string> <string name="nav_dex">図鑑</string>
<string name="action_place_near_reader">バイタルブレス本体を近くに置いて下さい。</string> <string name="credits_RedEyez_description">献身的なデバッグ作業を行って頂きました。</string>
<string name="action_place_near_reader">デバイスを近づけて下さい。</string>
<string name="action_cancel">キャンセル</string> <string name="action_cancel">キャンセル</string>
<string name="card_view_discovered_characters">解放済みキャラクター</string> <string name="card_view_discovered_characters">解放済みキャラクター</string>
<string name="dex_chara_icon_description">キャラクターアイコン</string> <string name="dex_chara_icon_description">キャラクターアイコン</string>
<string name="dex_chara_name_icon_description">名前</string> <string name="dex_chara_name_icon_description">名前</string>
<string name="credits_section_reverse_engineering">リバースエンジニアリング</string> <string name="credits_section_reverse_engineering">プログラム解析</string>
<string name="credits_section_app_development">アプリケーション開発</string> <string name="credits_section_app_development">アプリケーション開発</string>
<string name="credits_cyanic_description">" ファームウェアの解析を行い、開発をサポートしてくれました。"</string> <string name="credits_cyanic_description">ファームウェアの解析を行い、開発をサポートしてくれました。</string>
<string name="credits_cfogrady_description">" vb-lib-nfc開発およびアプリの開発に協力して頂きました。"</string> <string name="credits_cfogrady_description">vb-lib-nfc開発およびアプリの開発に協力して頂きました。</string>
<string name="credits_nacabaro_description">" アプリの開発をしました。"</string> <string name="credits_nacabaro_description">本アプリの開発をしました。</string>
<string name="credits_lightheel_description">" アプリのサーバー含む対戦機能を開発して頂きました。現在も開発を継続して頂いています。"</string> <string name="credits_lightheel_description">アプリのサーバー含む対戦機能を開発して頂きました。現在も開発を継続して頂いています。</string>
<string name="credits_shvstrz_description">" アプリのアイコンデザインをして頂きました。"</string> <string name="credits_shvstrz_description">アプリのアイコンデザインをして頂きました。</string>
<string name="write_card_title">カード情報書き込み中</string> <string name="write_card_title">カード情報書き込み中</string>
<string name="write_card_icon_description">カードアイコン</string> <string name="write_card_icon_description">カードアイコン</string>
<string name="write_card_device_ready">デバイスの準備をして下さい。</string> <string name="write_card_device_ready">デバイスの準備をして下さい。</string>
<string name="write_card_required_card">%1$s カードが必要です。</string> <string name="write_card_required_card">%1$s カードが必要です。</string>
<string name="write_card_confirm">転送</string> <string name="write_card_confirm">転送</string>
<string name="write_character_success">" カードの読み込みが完了しました。"</string> <string name="write_character_success">カードの読み込みが完了しました。</string>
<string name="write_character_wait_ready">" デバイスの準備をしてから確認を押して下さい。"</string> <string name="write_character_wait_ready">デバイスの準備をしてから確認を押して下さい。</string>
<string name="write_character_confirm">確認</string> <string name="write_character_confirm">確認</string>
<string name="home_vbdim_trophies">トロフィー</string> <string name="home_vbdim_trophies">トロフィー</string>
<string name="items_purchase_success">購入しました。</string> <string name="items_purchase_success">購入しました。</string>
@ -119,10 +121,10 @@
<string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string> <string name="dex_chara_stats">HP: %1$d, BP: %2$d, AP: %3$d</string>
<string name="dex_chara_stage_attribute_unknown">Stg: -, Atr: -</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_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_requirements">\n 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_adventure_level">AdvLvl %1$d</string>
<string name="storage_my_characters_title">キャラクターリスト</string> <string name="storage_my_characters_title">キャラクターリスト</string>
<string name="storage_nothing_to_see_here">表示するデータはありません。</string> <string name="storage_nothing_to_see_here">No Data</string>
<string name="storage_in_adventure_toast">このキャラクターは冒険中です。</string> <string name="storage_in_adventure_toast">このキャラクターは冒険中です。</string>
<string name="home_vbdim_next_timer">次のタイマー</string> <string name="home_vbdim_next_timer">次のタイマー</string>
<string name="home_vbdim_total_battle_win">総勝率 %</string> <string name="home_vbdim_total_battle_win">総勝率 %</string>
@ -143,4 +145,17 @@
<string name="special_mission_steps_progress">歩数 %1$d 歩</string> <string name="special_mission_steps_progress">歩数 %1$d 歩</string>
<string name="special_mission_battles_progress">バトル %1$d 回</string> <string name="special_mission_battles_progress">バトル %1$d 回</string>
<string name="special_mission_wins_progress">勝利 %1$d 回</string> <string name="special_mission_wins_progress">勝利 %1$d 回</string>
</resources> <string name="settings_companion_import_card_image_title">カードのインポート</string>
<string name="companion_validation_connect_vb">デバイスに接続</string>
<string name="companion_card_import_success">カードのインポートに成功しました。</string>
<string name="companion_firmware_no_watch">デバイスが見つかりません。</string>
<string name="companion_validation_insert_card">デバイスへカードを差し込んで下さい。</string>
<string name="companion_validation_success">データチェックが完了しました。</string>
<string name="companion_loading_select_firmware">ファームウェアファイルを選択</string>
<string name="companion_firmware_sent_success">ファームウェアの送信が完了しました。</string>
<string name="companion_logs_receive_failed">ログの受信に失敗しました。</string>
<string name="companion_logs_failed_request">ログの要求に失敗しました。</string>
<string name="companion_logs_warning">注意:ログの転送には時間がかかる場合があります。</string>
<string name="settings_companion_send_watch_logs_title">ログの送信</string>
<string name="companion_transfer_disabled_message">現在、転送は無効になっています。</string>
</resources>

View File

@ -43,13 +43,13 @@
Os segredos ainda não foram importados. Vá em Configurações e importe o APK. Os segredos ainda não foram importados. Vá em Configurações e importe o APK.
</string> </string>
<string name="scan_title">Escanear um Vital Bracelet</string> <string name="scan_title">Escanear um VB</string>
<string name="scan_vb_to_app">Vital Bracelet para o app</string> <string name="scan_vb_to_app">VB para o app</string>
<string name="scan_app_to_vb">App para o Vital Bracelet</string> <string name="scan_app_to_vb">App para o VB</string>
<string name="scan_no_nfc_on_device">O dispositivo não possui NFC!</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_tag_not_vb">A tag detectada não é do VB.</string>
<string name="scan_missing_secrets">" Segredos ausentes. Vá em Configurações e importe o APK do Vital Arena. "</string> <string name="scan_missing_secrets">" Segredos ausentes. Vá em Configurações e importe o APK do app oficial. "</string>
<string name="scan_sent_character_success">Personagem enviado com sucesso!</string> <string name="scan_sent_character_success">Personagem enviado com sucesso!</string>
<string name="scan_error_generic">Ops!</string> <string name="scan_error_generic">Ops!</string>
<string name="scan_sent_dim_success">DIM enviado com sucesso!</string> <string name="scan_sent_dim_success">DIM enviado com sucesso!</string>
@ -64,7 +64,7 @@
<string name="settings_import_apk_title">Importar APK</string> <string name="settings_import_apk_title">Importar APK</string>
<string name="settings_import_apk_desc"> <string name="settings_import_apk_desc">
Importar segredos do APK do Vital Arena 2.1.0 Importar segredos do APK do app oficial
</string> </string>
<string name="settings_import_card_title">Importar card</string> <string name="settings_import_card_title">Importar card</string>
@ -106,7 +106,7 @@
Responsável pelo design do ícone do aplicativo em SVG. Responsável pelo design do ícone do aplicativo em SVG.
</string> </string>
<string name="action_place_near_reader">Aproxime o seu Vital Bracelet do leitor...</string> <string name="action_place_near_reader">Aproxime o seu VB do leitor...</string>
<string name="action_cancel">Cancelar</string> <string name="action_cancel">Cancelar</string>
<string name="read_character_title">Ler personagem</string> <string name="read_character_title">Ler personagem</string>

View File

@ -43,13 +43,13 @@
Secrets not yet imported. Go to Settings and Import APK. Secrets not yet imported. Go to Settings and Import APK.
</string> </string>
<string name="scan_title">Scan a Vital Bracelet</string> <string name="scan_title">Scan a VB</string>
<string name="scan_vb_to_app">Watch to VBH</string> <string name="scan_vb_to_app">Watch to VBH</string>
<string name="scan_app_to_vb">VBH to Watch</string> <string name="scan_app_to_vb">VBH to Watch</string>
<string name="scan_no_nfc_on_device">No NFC on device!</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_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_missing_secrets">" Missing Secrets. Go to settings and import the official app APK. "</string>
<string name="scan_sent_character_success">Sent character successfully!</string> <string name="scan_sent_character_success">Sent character successfully!</string>
<string name="scan_error_generic">Whoops</string> <string name="scan_error_generic">Whoops</string>
<string name="scan_sent_dim_success">Sent DIM successfully!</string> <string name="scan_sent_dim_success">Sent DIM successfully!</string>
@ -65,7 +65,7 @@
<string name="settings_import_apk_title">Import APK</string> <string name="settings_import_apk_title">Import APK</string>
<string name="settings_import_apk_desc"> <string name="settings_import_apk_desc">
Import Secrets From Vital Arena APK Import Secrets From the official app APK
</string> </string>
<string name="settings_import_card_title">Import card</string> <string name="settings_import_card_title">Import card</string>
@ -109,7 +109,7 @@
<string name="credits_RedEyez_description"> <string name="credits_RedEyez_description">
Devout Debugger. Devout Debugger.
</string> </string>
<string name="action_place_near_reader">Place your Vital Bracelet near the reader...</string> <string name="action_place_near_reader">Place your VB near the reader...</string>
<string name="action_cancel">Cancel</string> <string name="action_cancel">Cancel</string>
<string name="read_character_title">Read character</string> <string name="read_character_title">Read character</string>
@ -220,10 +220,10 @@
<string name="home_special_mission_delete_remove">Remove</string> <string name="home_special_mission_delete_remove">Remove</string>
<string name="settings_companion_import_card_image_title">Import Card Image</string> <string name="settings_companion_import_card_image_title">Import Card Image</string>
<string name="companion_validation_connect_vb">Connect Vital Bracelet</string> <string name="companion_validation_connect_vb">Connect VB</string>
<string name="companion_card_import_success">Card imported successfully!</string> <string name="companion_card_import_success">Card imported successfully!</string>
<string name="companion_firmware_no_watch">No watch found!</string> <string name="companion_firmware_no_watch">No watch found!</string>
<string name="companion_validation_insert_card">Insert card into Vital Bracelet</string> <string name="companion_validation_insert_card">Insert card into VB</string>
<string name="companion_validation_success">Validation successful!</string> <string name="companion_validation_success">Validation successful!</string>
<string name="companion_loading_select_firmware">Select firmware file</string> <string name="companion_loading_select_firmware">Select firmware file</string>
<string name="companion_firmware_sent_success">Firmware sent successfully!</string> <string name="companion_firmware_sent_success">Firmware sent successfully!</string>

View File

@ -21,13 +21,6 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.defaults.buildfeatures.resvalues=true
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
android.enableAppCompileTimeRClass=false
android.usesSdkInManifest.disallowed=false
android.uniquePackageNames=false android.uniquePackageNames=false
android.dependency.useConstraints=true android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false android.r8.strictFullModeForKeepRules=false
android.r8.optimizedResourceShrinking=false
android.builtInKotlin=false
android.newDsl=false

View File

@ -1,19 +1,19 @@
[versions] [versions]
agp = "9.2.1" agp = "9.2.1"
datastore = "1.2.0" datastore = "1.2.1"
datastorePreferences = "1.2.0" datastorePreferences = "1.2.1"
kotlin = "2.3.0" kotlin = "2.4.0"
coreKtx = "1.17.0" coreKtx = "1.19.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
lifecycleRuntimeKtx = "2.10.0" lifecycleRuntimeKtx = "2.11.0"
activityCompose = "1.12.2" activityCompose = "1.13.0"
composeBom = "2026.01.00" composeBom = "2026.06.00"
material = "1.13.0" material = "1.14.0"
navigationCompose = "2.9.6" navigationCompose = "2.9.8"
protobufGradlePlugin = "0.9.6" protobufGradlePlugin = "0.10.0"
protobufJavalite = "4.33.4" protobufJavalite = "4.35.1"
roomRuntime = "2.8.4" roomRuntime = "2.8.4"
vbNfcReader = "0.2.0-SNAPSHOT" vbNfcReader = "0.2.0-SNAPSHOT"
dimReader = "2.1.0" dimReader = "2.1.0"
@ -21,10 +21,10 @@ playServicesWearable = "20.0.1"
kotlinxCoroutinesPlayServices = "1.11.0" kotlinxCoroutinesPlayServices = "1.11.0"
timber = "5.0.1" timber = "5.0.1"
tinylog = "2.7.0" tinylog = "2.7.0"
retrofit = "2.9.0" retrofit = "3.0.0"
gson = "2.10.1" gson = "2.14.0"
okhttp = "4.11.0" okhttp = "5.4.0"
ksp = "2.3.2" ksp = "2.3.9"
[libraries] [libraries]
androidx-compose-material = { module = "androidx.compose.material:material" } androidx-compose-material = { module = "androidx.compose.material:material" }
@ -50,7 +50,6 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
material = { module = "com.google.android.material:material", version.ref = "material" } material = { module = "com.google.android.material:material", version.ref = "material" }
protobuf-gradle-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin", version.ref = "protobufGradlePlugin" }
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" } protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" }
vb-nfc-reader = { module = "com.github.cfogrady:vb-nfc-reader", version.ref = "vbNfcReader" } vb-nfc-reader = { module = "com.github.cfogrady:vb-nfc-reader", version.ref = "vbNfcReader" }
dim-reader = { module = "com.github.cfogrady:vb-dim-reader", version.ref = "dimReader" } dim-reader = { module = "com.github.cfogrady:vb-dim-reader", version.ref = "dimReader" }

View File

@ -1,6 +1,6 @@
#Tue Dec 24 10:55:57 UTC 2024 #Tue Dec 24 10:55:57 UTC 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -17,6 +17,7 @@ dependencyResolutionManagement {
mavenLocal() mavenLocal()
google() google()
mavenCentral() mavenCentral()
maven { url = uri("https://jitpack.io") }
} }
} }