mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-07-30 00:31:54 +00:00
Compare commits
No commits in common. "7a8815694d900d60e8c6f05463d2a502a893a687" and "c67dc4bd02c576d3bf094837dae53c7a441297f1" have entirely different histories.
7a8815694d
...
c67dc4bd02
@ -4,8 +4,8 @@ plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.vksp)
|
||||
alias(libs.plugins.vprotobuf)
|
||||
id("com.google.devtools.ksp") version "2.3.0"
|
||||
id("com.google.protobuf")
|
||||
}
|
||||
|
||||
android {
|
||||
@ -17,7 +17,7 @@ android {
|
||||
minSdk = 28
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "Alpha 0.6.4"
|
||||
versionName = "Alpha 0.6.3"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@ -90,11 +90,6 @@ dependencies {
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.compose.material.icons.extended)
|
||||
implementation(libs.play.services.wearable)
|
||||
implementation(libs.kotlinx.coroutines.play.services)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.tinylog.api)
|
||||
implementation(libs.tinylog.impl)
|
||||
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
@ -109,10 +104,18 @@ dependencies {
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
|
||||
implementation(libs.retrofit)
|
||||
implementation(libs.retrofit.converter.gson)
|
||||
implementation(libs.gson)
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
implementation("androidx.navigation:navigation-compose:2.7.0")
|
||||
implementation("com.google.android.material:material:1.2.0")
|
||||
implementation(libs.protobuf.javalite)
|
||||
implementation("androidx.compose.material:material")
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
|
||||
// HTTP request logging
|
||||
implementation(libs.okhttp.logging.interceptor)
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
|
||||
}
|
||||
@ -0,0 +1,439 @@
|
||||
package com.github.nacabaro.vbhelper.screens.scanScreen
|
||||
|
||||
import android.nfc.IsoDep
|
||||
import android.nfc.NfcA
|
||||
import android.nfc.Tag
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.github.cfogrady.vbnfc.TagCommunicator
|
||||
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
|
||||
import org.junit.Test
|
||||
import org.junit.Before
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.verify
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
/**
|
||||
* Instrumented tests for NFC-A (Bandai toy) transfer functionality.
|
||||
*
|
||||
* These tests verify:
|
||||
* - Transport detection (NFC-A vs ISO-DEP)
|
||||
* - Read operations (Watch to VBH)
|
||||
* - Write operations (VBH to Watch)
|
||||
* - Slot state detection
|
||||
* - Error handling
|
||||
*
|
||||
* Run with:
|
||||
* ./gradlew connectedAndroidTest --tests ScanNfcaIntegrationTest
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ScanNfcaIntegrationTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var mockTag: Tag
|
||||
|
||||
@Mock
|
||||
private lateinit var mockNfcA: NfcA
|
||||
|
||||
@Mock
|
||||
private lateinit var mockIsoDep: IsoDep
|
||||
|
||||
@Mock
|
||||
private lateinit var mockTagCommunicator: TagCommunicator
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TEST GROUP 1: Transport Detection
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* TEST 1.1: Verify NFC-A is correctly detected
|
||||
*
|
||||
* When: Tag with NFC-A support is detected
|
||||
* Then: Transport should be NFC_A
|
||||
*/
|
||||
@Test
|
||||
fun testNfcATransportDetection() {
|
||||
// Arrange
|
||||
whenever(NfcA.get(mockTag)).thenReturn(mockNfcA)
|
||||
whenever(IsoDep.get(mockTag)).thenReturn(null)
|
||||
|
||||
// Act
|
||||
val nfcA = NfcA.get(mockTag)
|
||||
val isoDep = IsoDep.get(mockTag)
|
||||
|
||||
// Assert
|
||||
assertNotNull(nfcA, "NfcA should be detected")
|
||||
assertNull(isoDep, "IsoDep should not be present")
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 1.2: Verify ISO-DEP vs NFC-A detection priority
|
||||
*
|
||||
* When: Both NFC-A and ISO-DEP present (shouldn't happen, but test anyway)
|
||||
* Then: ISO-DEP should take priority if it's VitalWear
|
||||
*/
|
||||
@Test
|
||||
fun testIsoDepPriorityOverNfcA() {
|
||||
// Arrange
|
||||
whenever(NfcA.get(mockTag)).thenReturn(mockNfcA)
|
||||
whenever(IsoDep.get(mockTag)).thenReturn(mockIsoDep)
|
||||
|
||||
// Act
|
||||
val isoDep = IsoDep.get(mockTag)
|
||||
val nfcA = NfcA.get(mockTag)
|
||||
|
||||
// Assert
|
||||
// In actual code, ISO-DEP is checked first
|
||||
assertNotNull(isoDep, "ISO-DEP should be detected first")
|
||||
assertNotNull(nfcA, "NFC-A should also be present")
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TEST GROUP 2: Slot State Detection
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* TEST 2.1: Verify device not full detection
|
||||
*
|
||||
* When: Device has active=true, backup=false
|
||||
* Then: Should allow write operation
|
||||
*/
|
||||
@Test
|
||||
fun testSlotStateAllowsWriteWhenNotFull() {
|
||||
// Arrange: Create mock state - device not full
|
||||
val slotState = NfcASlotState(
|
||||
count = 2,
|
||||
activePresent = true,
|
||||
backupPresent = false
|
||||
)
|
||||
|
||||
// Act
|
||||
val isFull = slotState.isFull()
|
||||
|
||||
// Assert
|
||||
assertEquals(false, isFull, "Device should not be full")
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 2.2: Verify device full detection
|
||||
*
|
||||
* When: Device has active=true, backup=true
|
||||
* Then: Should block write operation
|
||||
*/
|
||||
@Test
|
||||
fun testSlotStateBlocksWriteWhenFull() {
|
||||
// Arrange: Create mock state - device full
|
||||
val slotState = NfcASlotState(
|
||||
count = 2,
|
||||
activePresent = true,
|
||||
backupPresent = true
|
||||
)
|
||||
|
||||
// Act
|
||||
val isFull = slotState.isFull()
|
||||
|
||||
// Assert
|
||||
assertEquals(true, isFull, "Device should be full")
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 2.3: Verify count-based full detection
|
||||
*
|
||||
* When: Device reports count >= 2
|
||||
* Then: Should be considered full
|
||||
*/
|
||||
@Test
|
||||
fun testSlotStateFullByCount() {
|
||||
// Arrange
|
||||
val slotStateFull = NfcASlotState(count = 2, activePresent = null, backupPresent = null)
|
||||
val slotStateNotFull = NfcASlotState(count = 1, activePresent = null, backupPresent = null)
|
||||
|
||||
// Act
|
||||
val isFullCount2 = slotStateFull.isFull()
|
||||
val isFullCount1 = slotStateNotFull.isFull()
|
||||
|
||||
// Assert
|
||||
assertEquals(true, isFullCount2, "Count >= 2 should be full")
|
||||
assertEquals(false, isFullCount1, "Count < 2 should not be full")
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 2.4: Verify fallback when introspection fails
|
||||
*
|
||||
* When: Cannot introspect slot state (all null)
|
||||
* Then: Should default to non-blocking path
|
||||
*/
|
||||
@Test
|
||||
fun testSlotStateFallbackWhenUnknown() {
|
||||
// Arrange: No slot info available
|
||||
val slotState = NfcASlotState(count = null, activePresent = null, backupPresent = null)
|
||||
|
||||
// Act
|
||||
val isFull = slotState.isFull()
|
||||
|
||||
// Assert
|
||||
assertEquals(false, isFull, "Unknown state should not block (default to false)")
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TEST GROUP 3: Character Conversion
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* TEST 3.1: Verify NFC character to DB character conversion
|
||||
*
|
||||
* When: VBNfcCharacter is read from toy
|
||||
* Then: Should convert to database format correctly
|
||||
*/
|
||||
@Test
|
||||
fun testNfcCharacterConversionVB() {
|
||||
// Arrange
|
||||
val vbNfcCharacter = VBNfcCharacter()
|
||||
// In real test, would populate with actual data
|
||||
|
||||
// Act
|
||||
// This would call FromNfcConverter.addCharacter()
|
||||
// For now, just verify the object exists
|
||||
assertNotNull(vbNfcCharacter, "VBNfcCharacter should be created")
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TEST GROUP 4: Write Operation Validation
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* TEST 4.1: Verify write is blocked when device full
|
||||
*
|
||||
* Scenario:
|
||||
* - User selects character to write
|
||||
* - Device has 2 active+backup (full)
|
||||
* - Click "VBH to Watch"
|
||||
*
|
||||
* Expected:
|
||||
* - WriteResult.BLOCKED_DEVICE_FULL
|
||||
* - No transfer occurs
|
||||
*/
|
||||
@Test
|
||||
fun testWriteBlockedWhenDeviceFull() {
|
||||
// Arrange
|
||||
val slotState = NfcASlotState(
|
||||
count = 2,
|
||||
activePresent = true,
|
||||
backupPresent = true
|
||||
)
|
||||
|
||||
// Act
|
||||
val isFull = slotState.isFull()
|
||||
|
||||
// Assert
|
||||
assertEquals(true, isFull, "Should block when full")
|
||||
// In real test, would verify WriteResult.BLOCKED_DEVICE_FULL returned
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 4.2: Verify write allowed when slot available
|
||||
*
|
||||
* Scenario:
|
||||
* - User selects character to write
|
||||
* - Device has active only (backup empty)
|
||||
* - Click "VBH to Watch"
|
||||
*
|
||||
* Expected:
|
||||
* - WriteResult.MOVE_CONFIRMED
|
||||
* - Active→Backup migration performed
|
||||
* - Character transferred
|
||||
*/
|
||||
@Test
|
||||
fun testWriteAllowedWhenSlotAvailable() {
|
||||
// Arrange
|
||||
val slotState = NfcASlotState(
|
||||
count = 2,
|
||||
activePresent = true,
|
||||
backupPresent = false
|
||||
)
|
||||
|
||||
// Act
|
||||
val isFull = slotState.isFull()
|
||||
|
||||
// Assert
|
||||
assertEquals(false, isFull, "Should allow when slot available")
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 4.3: Verify write rejected when no active character
|
||||
*
|
||||
* Scenario:
|
||||
* - Device only has backup (no active)
|
||||
* - Click "VBH to Watch"
|
||||
*
|
||||
* Expected:
|
||||
* - WriteResult.COPIED
|
||||
* - Transfer skipped
|
||||
*/
|
||||
@Test
|
||||
fun testWriteSkippedWhenNoActiveCharacter() {
|
||||
// Arrange: Only backup, no active
|
||||
val slotState = NfcASlotState(
|
||||
count = 1,
|
||||
activePresent = false,
|
||||
backupPresent = true
|
||||
)
|
||||
|
||||
// Act
|
||||
val isValidForWrite = slotState.activePresent == true && slotState.backupPresent == false
|
||||
|
||||
// Assert
|
||||
assertEquals(false, isValidForWrite, "Should reject write when no active")
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TEST GROUP 5: Debouncing
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* TEST 5.1: Verify rapid re-taps are debounced
|
||||
*
|
||||
* Scenario:
|
||||
* - User taps toy for read
|
||||
* - User immediately taps again (< 1.5 sec)
|
||||
*
|
||||
* Expected:
|
||||
* - Only first tap processes
|
||||
* - Second tap ignored
|
||||
* - Database contains only 1 character
|
||||
*/
|
||||
@Test
|
||||
fun testRapidTapDebouncing() {
|
||||
// Arrange
|
||||
val TAG_DEBOUNCE_MS = 1500L
|
||||
val now = System.currentTimeMillis()
|
||||
val firstTapTime = now
|
||||
val secondTapTime = now + 500 // 500ms later (within debounce)
|
||||
|
||||
// Act
|
||||
val timeDifference = secondTapTime - firstTapTime
|
||||
val isWithinDebounce = timeDifference < TAG_DEBOUNCE_MS
|
||||
|
||||
// Assert
|
||||
assertEquals(true, isWithinDebounce, "Second tap should be debounced")
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 5.2: Verify tap after debounce window is processed
|
||||
*
|
||||
* Scenario:
|
||||
* - User taps toy for read
|
||||
* - User taps again after 2 seconds (> 1.5 sec)
|
||||
*
|
||||
* Expected:
|
||||
* - First tap processes
|
||||
* - Second tap also processes
|
||||
* - Database contains 2 characters
|
||||
*/
|
||||
@Test
|
||||
fun testTapAllowedAfterDebounceWindow() {
|
||||
// Arrange
|
||||
val TAG_DEBOUNCE_MS = 1500L
|
||||
val now = System.currentTimeMillis()
|
||||
val firstTapTime = now
|
||||
val secondTapTime = now + 2000 // 2 seconds later (outside debounce)
|
||||
|
||||
// Act
|
||||
val timeDifference = secondTapTime - firstTapTime
|
||||
val isWithinDebounce = timeDifference < TAG_DEBOUNCE_MS
|
||||
|
||||
// Assert
|
||||
assertEquals(false, isWithinDebounce, "Second tap should be processed")
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TEST GROUP 6: Error Handling
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* TEST 6.1: Verify graceful handling of corrupt character data
|
||||
*
|
||||
* Scenario:
|
||||
* - Toy has corrupted character data
|
||||
* - User clicks "Watch to VBH"
|
||||
* - Tap toy
|
||||
*
|
||||
* Expected:
|
||||
* - Exception caught
|
||||
* - Toast shown: "Whoops"
|
||||
* - App doesn't crash
|
||||
* - User can retry
|
||||
*/
|
||||
@Test
|
||||
fun testCorruptCharacterHandling() {
|
||||
// This is more of an integration test
|
||||
// Would require mocking TagCommunicator.receiveCharacter() to throw
|
||||
|
||||
// Pseudocode:
|
||||
// whenever(mockTagCommunicator.receiveCharacter())
|
||||
// .thenThrow(IOException("Corrupt data"))
|
||||
|
||||
// In real test:
|
||||
// Call onClickRead()
|
||||
// Verify: Toast shown, no crash, app recovers
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 6.2: Verify NFC connection timeout handling
|
||||
*
|
||||
* Scenario:
|
||||
* - User starts read/write
|
||||
* - Remove toy during transfer
|
||||
*
|
||||
* Expected:
|
||||
* - Connection timeout
|
||||
* - Toast: "Whoops"
|
||||
* - Reader mode disabled
|
||||
* - App recovers
|
||||
*/
|
||||
@Test
|
||||
fun testNfcConnectionTimeoutHandling() {
|
||||
// Pseudocode:
|
||||
// whenever(mockNfcA.connect())
|
||||
// .thenThrow(IOException("NFC connection lost"))
|
||||
|
||||
// In real test:
|
||||
// Verify timeout is caught, UI updates, no crash
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Helper Classes (from ScanScreenControllerImpl)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Mirrors the NfcASlotState data class from ScanScreenControllerImpl.kt
|
||||
*/
|
||||
private data class NfcASlotState(
|
||||
val count: Int?,
|
||||
val activePresent: Boolean?,
|
||||
val backupPresent: Boolean?,
|
||||
) {
|
||||
fun isFull(): Boolean {
|
||||
if (count != null) {
|
||||
return count >= 2
|
||||
}
|
||||
if (activePresent != null && backupPresent != null) {
|
||||
return activePresent && backupPresent
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,9 +13,6 @@ interface CardDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertNewCard(card: Card): Long
|
||||
|
||||
@Query("SELECT * FROM Card")
|
||||
fun getAllCards(): List<Card>
|
||||
|
||||
@Query("SELECT * FROM Card WHERE cardId = :id")
|
||||
fun getCardByCardId(id: Int): List<Card>
|
||||
|
||||
|
||||
@ -131,9 +131,6 @@ interface UserCharacterDao {
|
||||
@Query("SELECT * FROM VBCharacterData WHERE id = :id")
|
||||
fun getVbData(id: Long): Flow<VBCharacterData>
|
||||
|
||||
@Query("SELECT * FROM VBCharacterData WHERE id = :id")
|
||||
suspend fun getVbDataOrNull(id: Long): VBCharacterData?
|
||||
|
||||
@Query("SELECT * FROM SpecialMissions WHERE characterId = :id")
|
||||
fun getSpecialMissions(id: Long): Flow<List<SpecialMissions>>
|
||||
|
||||
|
||||
@ -2,8 +2,6 @@ package com.github.nacabaro.vbhelper.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.github.nacabaro.vbhelper.daos.AdventureDao
|
||||
import com.github.nacabaro.vbhelper.daos.CardAdventureDao
|
||||
import com.github.nacabaro.vbhelper.daos.CharacterDao
|
||||
@ -15,10 +13,6 @@ import com.github.nacabaro.vbhelper.daos.ItemDao
|
||||
import com.github.nacabaro.vbhelper.daos.SpecialMissionDao
|
||||
import com.github.nacabaro.vbhelper.daos.SpriteDao
|
||||
import com.github.nacabaro.vbhelper.daos.UserCharacterDao
|
||||
import com.github.nacabaro.vbhelper.daos.VitalWearSettingsDao
|
||||
import com.github.nacabaro.vbhelper.daos.CharacterTransferPolicyDao
|
||||
import com.github.nacabaro.vbhelper.companion.validation.ValidatedCardDao
|
||||
import com.github.nacabaro.vbhelper.companion.validation.ValidatedCardEntity
|
||||
import com.github.nacabaro.vbhelper.domain.card.Background
|
||||
import com.github.nacabaro.vbhelper.domain.card.CardCharacter
|
||||
import com.github.nacabaro.vbhelper.domain.card.Card
|
||||
@ -35,12 +29,10 @@ import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
|
||||
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
|
||||
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
|
||||
import com.github.nacabaro.vbhelper.domain.device_data.VitalsHistory
|
||||
import com.github.nacabaro.vbhelper.domain.device_data.VitalWearCharacterSettings
|
||||
import com.github.nacabaro.vbhelper.domain.device_data.CharacterTransferPolicy
|
||||
import com.github.nacabaro.vbhelper.domain.items.Items
|
||||
|
||||
@Database(
|
||||
version = 2,
|
||||
version = 1,
|
||||
entities = [
|
||||
Card::class,
|
||||
CardProgress::class,
|
||||
@ -58,10 +50,7 @@ import com.github.nacabaro.vbhelper.domain.items.Items
|
||||
Items::class,
|
||||
Adventure::class,
|
||||
Background::class,
|
||||
PossibleTransformations::class,
|
||||
ValidatedCardEntity::class,
|
||||
VitalWearCharacterSettings::class,
|
||||
CharacterTransferPolicy::class
|
||||
PossibleTransformations::class
|
||||
]
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@ -76,35 +65,4 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun specialMissionDao(): SpecialMissionDao
|
||||
abstract fun cardAdventureDao(): CardAdventureDao
|
||||
abstract fun cardFusionsDao(): CardFusionsDao
|
||||
abstract fun validatedCardDao(): ValidatedCardDao
|
||||
abstract fun vitalWearSettingsDao(): VitalWearSettingsDao
|
||||
abstract fun characterTransferPolicyDao(): CharacterTransferPolicyDao
|
||||
|
||||
companion object {
|
||||
val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
// Create new tables
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `Items` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `description` TEXT NOT NULL, `itemIcon` INTEGER NOT NULL, `itemLength` INTEGER NOT NULL, `price` INTEGER NOT NULL, `quantity` INTEGER NOT NULL, `itemType` TEXT NOT NULL, PRIMARY KEY(`id`))")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `SpecialMissions` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `characterId` INTEGER NOT NULL, `goal` INTEGER NOT NULL, `watchId` INTEGER NOT NULL, `progress` INTEGER NOT NULL, `status` TEXT NOT NULL, `timeElapsedInMinutes` INTEGER NOT NULL, `timeLimitInMinutes` INTEGER NOT NULL, `missionType` TEXT NOT NULL, FOREIGN KEY(`characterId`) REFERENCES `UserCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `VBCharacterData` (`id` INTEGER NOT NULL, `generation` INTEGER NOT NULL, `totalTrophies` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `UserCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `TransformationHistory` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monId` INTEGER NOT NULL, `stageId` INTEGER NOT NULL, `transformationDate` INTEGER NOT NULL, FOREIGN KEY(`monId`) REFERENCES `UserCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stageId`) REFERENCES `CardCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `VitalsHistory` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `charId` INTEGER NOT NULL, `year` INTEGER NOT NULL, `month` INTEGER NOT NULL, `day` INTEGER NOT NULL, `vitalPoints` INTEGER NOT NULL, FOREIGN KEY(`charId`) REFERENCES `UserCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `Dex` (`id` INTEGER NOT NULL, `discoveredOn` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `CardCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `Adventure` (`characterId` INTEGER NOT NULL, `originalDuration` INTEGER NOT NULL, `finishesAdventure` INTEGER NOT NULL, PRIMARY KEY(`characterId`), FOREIGN KEY(`characterId`) REFERENCES `UserCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `Background` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cardId` INTEGER NOT NULL, `background` BLOB NOT NULL, `backgroundWidth` INTEGER NOT NULL, `backgroundHeight` INTEGER NOT NULL, FOREIGN KEY(`cardId`) REFERENCES `Card`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `PossibleTransformations` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `charaId` INTEGER NOT NULL, `requiredVitals` INTEGER NOT NULL, `requiredTrophies` INTEGER NOT NULL, `requiredBattles` INTEGER NOT NULL, `requiredWinRate` INTEGER NOT NULL, `changeTimerHours` INTEGER NOT NULL, `requiredAdventureLevelCompleted` INTEGER NOT NULL, `toCharaId` INTEGER, FOREIGN KEY(`charaId`) REFERENCES `CardCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`toCharaId`) REFERENCES `CardCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `validated` (`cardId` INTEGER NOT NULL, PRIMARY KEY(`cardId`))")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `VitalWearCharacterSettings` (`characterId` INTEGER NOT NULL, `trainingInBackground` INTEGER NOT NULL, `allowedBattles` INTEGER NOT NULL, `accumulatedDailyInjuries` INTEGER NOT NULL, PRIMARY KEY(`characterId`))")
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS `CharacterTransferPolicy` (`characterId` INTEGER NOT NULL, `nativeDeviceType` TEXT NOT NULL, `preferredHceExportFormat` TEXT NOT NULL, `preferredNfcaExportFormat` TEXT NOT NULL, `lastObservedImportFormat` TEXT, `lastTransferTransport` TEXT, `lastTransferTarget` TEXT, `preserveVbRoundTrip` INTEGER NOT NULL, `preserveBeRoundTrip` INTEGER NOT NULL, PRIMARY KEY(`characterId`), FOREIGN KEY(`characterId`) REFERENCES `UserCharacter`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||
|
||||
// Add columns to existing tables
|
||||
try {
|
||||
db.execSQL("ALTER TABLE `UserCharacter` ADD COLUMN `characterType` TEXT NOT NULL DEFAULT 'BEDevice'")
|
||||
} catch (e: Exception) {}
|
||||
try {
|
||||
db.execSQL("ALTER TABLE `Card` ADD COLUMN `isBEm` INTEGER NOT NULL DEFAULT 0")
|
||||
} catch (e: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,13 +3,9 @@ package com.github.nacabaro.vbhelper.di
|
||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
||||
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||
import com.github.nacabaro.vbhelper.companion.validation.ValidatedCardManager
|
||||
import com.github.nacabaro.vbhelper.companion.logs.CompanionLogService
|
||||
|
||||
interface AppContainer {
|
||||
val db: AppDatabase
|
||||
val dataStoreSecretsRepository: DataStoreSecretsRepository
|
||||
val currencyRepository: CurrencyRepository
|
||||
val validatedCardManager: ValidatedCardManager
|
||||
val companionLogService: CompanionLogService
|
||||
}
|
||||
@ -8,8 +8,6 @@ import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||
import com.github.nacabaro.vbhelper.di.AppContainer
|
||||
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
||||
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||
import com.github.nacabaro.vbhelper.companion.validation.ValidatedCardManager
|
||||
import com.github.nacabaro.vbhelper.companion.logs.CompanionLogService
|
||||
import com.github.nacabaro.vbhelper.source.SecretsSerializer
|
||||
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||
|
||||
@ -32,7 +30,6 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
|
||||
klass = AppDatabase::class.java,
|
||||
"internalDb"
|
||||
)
|
||||
.addMigrations(AppDatabase.MIGRATION_1_2)
|
||||
.createFromAsset("items.db")
|
||||
.build()
|
||||
}
|
||||
@ -40,11 +37,5 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
|
||||
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
|
||||
|
||||
override val currencyRepository = CurrencyRepository(context.currencyStore)
|
||||
|
||||
override val validatedCardManager by lazy {
|
||||
ValidatedCardManager(db.validatedCardDao())
|
||||
}
|
||||
|
||||
override val companionLogService = CompanionLogService()
|
||||
}
|
||||
|
||||
|
||||
@ -2,18 +2,10 @@ package com.github.nacabaro.vbhelper.di
|
||||
|
||||
import DefaultAppContainer
|
||||
import android.app.Application
|
||||
import com.github.nacabaro.vbhelper.companion.validation.ValidatedCardManager
|
||||
import com.github.nacabaro.vbhelper.companion.logs.CompanionLogService
|
||||
|
||||
class VBHelper : Application() {
|
||||
lateinit var container: DefaultAppContainer
|
||||
|
||||
val validatedCardManager: ValidatedCardManager
|
||||
get() = container.validatedCardManager
|
||||
|
||||
val companionLogService: CompanionLogService
|
||||
get() = container.companionLogService
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
container = DefaultAppContainer(applicationContext)
|
||||
|
||||
@ -5,7 +5,6 @@ import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
|
||||
import com.github.nacabaro.vbhelper.utils.DeviceType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Utility for converting character device types.
|
||||
@ -32,7 +31,7 @@ class DeviceTypeConverter(private val database: AppDatabase) {
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
// Log but don't crash - character can still be sent as-is
|
||||
Timber.e(e, "Failed to convert character $characterId to VB type")
|
||||
android.util.Log.e("DeviceTypeConverter", "Failed to convert character $characterId to VB type", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
@ -43,7 +42,7 @@ class DeviceTypeConverter(private val database: AppDatabase) {
|
||||
val vbCharacterData = VBCharacterData(
|
||||
id = characterId,
|
||||
generation = 0,
|
||||
totalTrophies = 0,
|
||||
totalTrophies = 0
|
||||
)
|
||||
database.userCharacterDao().insertVBCharacterData(vbCharacterData)
|
||||
}
|
||||
|
||||
@ -23,13 +23,6 @@ message Character {
|
||||
int32 total_wins = 13;
|
||||
int32 current_phase_wins = 14;
|
||||
int32 mood = 15;
|
||||
|
||||
enum TransferDeviceType {
|
||||
TRANSFER_DEVICE_TYPE_UNSPECIFIED = 0;
|
||||
TRANSFER_DEVICE_TYPE_VB = 1;
|
||||
TRANSFER_DEVICE_TYPE_BE = 2;
|
||||
}
|
||||
TransferDeviceType transfer_device_type = 16;
|
||||
}
|
||||
CharacterStats character_stats = 3;
|
||||
|
||||
|
||||
@ -48,6 +48,9 @@
|
||||
<string name="card_adventure_missions_title">アドベンチャーミッション</string>
|
||||
<string name="card_entry_characters_obtained">%1$d / %2$d のキャラクターを取得しました。</string>
|
||||
<string name="card_entry_edit">編集</string>
|
||||
<string name="card_entry_edit">編集</string>
|
||||
<string name="card_entry_edit">編集</string>
|
||||
<string name="card_entry_edit">編集</string>
|
||||
<string name="card_entry_delete">削除</string>
|
||||
<string name="home_vbdim_current_phase_win">フェーズ勝率 %</string>
|
||||
<string name="home_vbdim_special_missions">スペシャルミッション</string>
|
||||
@ -89,6 +92,7 @@
|
||||
<string name="battles_online_title">オンラインバトル</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="action_place_near_reader">バイタルブレス本体を近くに置いて下さい。</string>
|
||||
<string name="action_cancel">キャンセル</string>
|
||||
@ -96,6 +100,7 @@
|
||||
<string name="dex_chara_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_cyanic_description">" ファームウェアの解析を行い、開発をサポートしてくれました。"</string>
|
||||
<string name="credits_cfogrady_description">" vb-lib-nfc開発およびアプリの開発に協力して頂きました。"</string>
|
||||
|
||||
@ -219,17 +219,4 @@
|
||||
<string name="home_special_mission_delete_dismiss">Dismiss</string>
|
||||
<string name="home_special_mission_delete_remove">Remove</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_card_import_success">Card imported successfully!</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_success">Validation successful!</string>
|
||||
<string name="companion_loading_select_firmware">Select firmware file</string>
|
||||
<string name="companion_firmware_sent_success">Firmware sent successfully!</string>
|
||||
<string name="companion_logs_receive_failed">Failed to receive logs</string>
|
||||
<string name="companion_logs_failed_request">Failed to request logs</string>
|
||||
<string name="companion_logs_warning">Warning: Log transfer might take a while</string>
|
||||
<string name="settings_companion_send_watch_logs_title">Send Watch Logs</string>
|
||||
<string name="companion_transfer_disabled_message">Transfer is currently disabled</string>
|
||||
</resources>
|
||||
|
||||
@ -3,6 +3,10 @@ plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
alias(libs.plugins.vksp) apply false
|
||||
alias(libs.plugins.vprotobuf) apply false
|
||||
}
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath(libs.protobuf.gradle.plugin)
|
||||
}
|
||||
}
|
||||
@ -21,13 +21,3 @@ kotlin.code.style=official
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
android.defaults.buildfeatures.resvalues=true
|
||||
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
|
||||
android.enableAppCompileTimeRClass=false
|
||||
android.usesSdkInManifest.disallowed=false
|
||||
android.uniquePackageNames=false
|
||||
android.dependency.useConstraints=true
|
||||
android.r8.strictFullModeForKeepRules=false
|
||||
android.r8.optimizedResourceShrinking=false
|
||||
android.builtInKotlin=false
|
||||
android.newDsl=false
|
||||
@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "9.2.1"
|
||||
agp = "8.13.2"
|
||||
datastore = "1.2.0"
|
||||
datastorePreferences = "1.2.0"
|
||||
kotlin = "2.3.0"
|
||||
@ -17,14 +17,6 @@ protobufJavalite = "4.33.4"
|
||||
roomRuntime = "2.8.4"
|
||||
vbNfcReader = "0.2.0-SNAPSHOT"
|
||||
dimReader = "2.1.0"
|
||||
playServicesWearable = "20.0.1"
|
||||
kotlinxCoroutinesPlayServices = "1.11.0"
|
||||
timber = "5.0.1"
|
||||
tinylog = "2.7.0"
|
||||
retrofit = "2.9.0"
|
||||
gson = "2.10.1"
|
||||
okhttp = "4.11.0"
|
||||
ksp = "2.3.2"
|
||||
|
||||
[libraries]
|
||||
androidx-compose-material = { module = "androidx.compose.material:material" }
|
||||
@ -54,20 +46,9 @@ protobuf-gradle-plugin = { module = "com.google.protobuf:protobuf-gradle-plugin"
|
||||
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" }
|
||||
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" }
|
||||
play-services-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "playServicesWearable" }
|
||||
kotlinx-coroutines-play-services = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" }
|
||||
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
||||
tinylog-api = { module = "org.tinylog:tinylog-api", version.ref = "tinylog" }
|
||||
tinylog-impl = { module = "org.tinylog:tinylog-impl", version.ref = "tinylog" }
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||
okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
vksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
vprotobuf = { id = "com.google.protobuf", version.ref = "protobufGradlePlugin" }
|
||||
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Tue Dec 24 10:55:57 UTC 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user