mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-07-30 00:31:54 +00:00
Make VBH-VW transfer DB owner via provider and shared transfer DAO
This commit is contained in:
parent
d40428ccfd
commit
895b704c8d
@ -2,6 +2,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<permission
|
||||||
|
android:name="com.github.nacabaro.vbhelper.permission.ACCESS_TRANSFER_SEEN"
|
||||||
|
android:protectionLevel="signature" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
@ -31,6 +35,12 @@
|
|||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider
|
||||||
|
android:name=".transfer.SharedTransferSeenProvider"
|
||||||
|
android:authorities="com.github.nacabaro.vbhelper.transferseen"
|
||||||
|
android:exported="true"
|
||||||
|
android:readPermission="com.github.nacabaro.vbhelper.permission.ACCESS_TRANSFER_SEEN"
|
||||||
|
android:writePermission="com.github.nacabaro.vbhelper.permission.ACCESS_TRANSFER_SEEN" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.github.cfogrady.vitalwear.common.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Room
|
||||||
|
|
||||||
|
object SharedDatabaseFactory {
|
||||||
|
private const val DATABASE_NAME = "shared_transfer.db"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var instance: SharedTransferDatabase? = null
|
||||||
|
|
||||||
|
fun getDatabase(context: Context): SharedTransferDatabase {
|
||||||
|
return instance ?: synchronized(this) {
|
||||||
|
instance ?: Room.databaseBuilder(
|
||||||
|
context.applicationContext,
|
||||||
|
SharedTransferDatabase::class.java,
|
||||||
|
DATABASE_NAME
|
||||||
|
).build().also { instance = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.github.cfogrady.vitalwear.common.data
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
entities = [
|
||||||
|
SharedTransferSeenEntity::class,
|
||||||
|
],
|
||||||
|
version = 1,
|
||||||
|
exportSchema = false,
|
||||||
|
)
|
||||||
|
abstract class SharedTransferDatabase : RoomDatabase() {
|
||||||
|
abstract fun transferSeenDao(): SharedTransferSeenDao
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.github.cfogrady.vitalwear.common.data
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface SharedTransferSeenDao {
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
fun insert(entry: SharedTransferSeenEntity): Long
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
UPDATE ${SharedTransferSeenEntity.TABLE}
|
||||||
|
SET cardName = :cardName,
|
||||||
|
seenAtEpochMillis = :seenAtEpochMillis
|
||||||
|
WHERE cardLookupKey = :cardLookupKey
|
||||||
|
AND slotId = :slotId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun update(cardName: String, cardLookupKey: String, slotId: Int, seenAtEpochMillis: Long)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
fun markSeen(cardName: String, slotId: Int, seenAtEpochMillis: Long) {
|
||||||
|
if (slotId < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val cardLookupKey = cardName.lowercase().filter { it.isLetterOrDigit() }
|
||||||
|
if (cardLookupKey.isBlank()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val inserted = insert(
|
||||||
|
SharedTransferSeenEntity(
|
||||||
|
cardName = cardName,
|
||||||
|
cardLookupKey = cardLookupKey,
|
||||||
|
slotId = slotId,
|
||||||
|
seenAtEpochMillis = seenAtEpochMillis,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (inserted == -1L) {
|
||||||
|
update(cardName, cardLookupKey, slotId, seenAtEpochMillis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.github.cfogrady.vitalwear.common.data
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Index
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = SharedTransferSeenEntity.TABLE,
|
||||||
|
indices = [
|
||||||
|
Index(value = ["cardLookupKey", "slotId"], unique = true)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
data class SharedTransferSeenEntity(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||||
|
val cardName: String,
|
||||||
|
val cardLookupKey: String,
|
||||||
|
val slotId: Int,
|
||||||
|
val seenAtEpochMillis: Long,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val TABLE = "TransferSeen"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.daos
|
||||||
|
|
||||||
|
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenDao
|
||||||
|
|
||||||
|
@Deprecated("Use SharedTransferSeenDao from the shared transfer database.")
|
||||||
|
typealias TransferSeenDao = SharedTransferSeenDao
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ import com.github.nacabaro.vbhelper.domain.device_data.VitalWearCharacterSetting
|
|||||||
import com.github.nacabaro.vbhelper.domain.items.Items
|
import com.github.nacabaro.vbhelper.domain.items.Items
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
version = 2,
|
version = 5,
|
||||||
entities = [
|
entities = [
|
||||||
Card::class,
|
Card::class,
|
||||||
CardProgress::class,
|
CardProgress::class,
|
||||||
@ -104,5 +104,40 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val MIGRATION_2_3 = object : Migration(2, 3) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
val backfillTimestamp = System.currentTimeMillis()
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
INSERT INTO `TransformationHistory`(`monId`, `stageId`, `transformationDate`)
|
||||||
|
SELECT uc.`id`, uc.`charId`, $backfillTimestamp
|
||||||
|
FROM `UserCharacter` uc
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `TransformationHistory` th
|
||||||
|
WHERE th.`monId` = uc.`id`
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val MIGRATION_3_5 = object : Migration(3, 5) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
// TransferSeen moved to shared_transfer.db
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS `TransferSeen`")
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS `index_TransferSeen_cardLookupKey_slotId`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
// TransferSeen moved to shared_transfer.db
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS `TransferSeen`")
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS `index_TransferSeen_cardLookupKey_slotId`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,13 @@
|
|||||||
package com.github.nacabaro.vbhelper.di
|
package com.github.nacabaro.vbhelper.di
|
||||||
|
|
||||||
|
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenDao
|
||||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
||||||
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||||
|
|
||||||
interface AppContainer {
|
interface AppContainer {
|
||||||
val db: AppDatabase
|
val db: AppDatabase
|
||||||
|
val transferSeenDao: SharedTransferSeenDao
|
||||||
val dataStoreSecretsRepository: DataStoreSecretsRepository
|
val dataStoreSecretsRepository: DataStoreSecretsRepository
|
||||||
val currencyRepository: CurrencyRepository
|
val currencyRepository: CurrencyRepository
|
||||||
}
|
}
|
||||||
@ -4,8 +4,10 @@ import androidx.datastore.dataStore
|
|||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
import com.github.cfogrady.vitalwear.common.data.SharedDatabaseFactory
|
||||||
|
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenDao
|
||||||
import com.github.nacabaro.vbhelper.di.AppContainer
|
import com.github.nacabaro.vbhelper.di.AppContainer
|
||||||
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
||||||
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||||
import com.github.nacabaro.vbhelper.source.SecretsSerializer
|
import com.github.nacabaro.vbhelper.source.SecretsSerializer
|
||||||
@ -32,9 +34,16 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
|
|||||||
)
|
)
|
||||||
.createFromAsset("items.db")
|
.createFromAsset("items.db")
|
||||||
.addMigrations(AppDatabase.MIGRATION_1_2)
|
.addMigrations(AppDatabase.MIGRATION_1_2)
|
||||||
|
.addMigrations(AppDatabase.MIGRATION_2_3)
|
||||||
|
.addMigrations(AppDatabase.MIGRATION_3_5)
|
||||||
|
.addMigrations(AppDatabase.MIGRATION_4_5)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val transferSeenDao: SharedTransferSeenDao by lazy {
|
||||||
|
SharedDatabaseFactory.getDatabase(context).transferSeenDao()
|
||||||
|
}
|
||||||
|
|
||||||
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
|
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
|
||||||
|
|
||||||
override val currencyRepository = CurrencyRepository(context.currencyStore)
|
override val currencyRepository = CurrencyRepository(context.currencyStore)
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.domain.transfer
|
||||||
|
|
||||||
|
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenEntity
|
||||||
|
|
||||||
|
@Deprecated("Use SharedTransferSeenEntity from the shared transfer database.")
|
||||||
|
typealias TransferSeenEntity = SharedTransferSeenEntity
|
||||||
|
|
||||||
@ -66,6 +66,9 @@ class ScanScreenControllerImpl(
|
|||||||
0xF0.toByte(), 0x56, 0x49, 0x54, 0x41, 0x4C, 0x57, 0x45, 0x41, 0x52
|
0xF0.toByte(), 0x56, 0x49, 0x54, 0x41, 0x4C, 0x57, 0x45, 0x41, 0x52
|
||||||
)
|
)
|
||||||
private const val SW_OK = 0x9000
|
private const val SW_OK = 0x9000
|
||||||
|
// Lifecycle key for the always-on NFC suppressor that prevents Android from launching
|
||||||
|
// com.android.apps.tag/.TagViewer whenever the watch's HCE comes into range.
|
||||||
|
private const val LIFECYCLE_KEY_NFC_SUPPRESSOR = "nfc_suppressor"
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -75,6 +78,57 @@ class ScanScreenControllerImpl(
|
|||||||
}
|
}
|
||||||
nfcAdapter = maybeNfcAdapter
|
nfcAdapter = maybeNfcAdapter
|
||||||
checkSecrets()
|
checkSecrets()
|
||||||
|
registerNfcSuppressor()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a lifecycle listener that keeps reader mode active (with a no-op callback)
|
||||||
|
* whenever this Activity is in the foreground. This prevents Android's default NFC dispatch
|
||||||
|
* system from launching com.android.apps.tag/.TagViewer when the watch's HCE service is
|
||||||
|
* detected while the user hasn't explicitly pressed a transfer button yet.
|
||||||
|
*
|
||||||
|
* When the user presses Read/Write/CheckCard, [handleTag] replaces this suppressor with
|
||||||
|
* the real transfer callback via [NfcAdapter.enableReaderMode]. After the transfer
|
||||||
|
* completes, [enableNfcSuppressor] is called again to restore the passive suppressor.
|
||||||
|
*/
|
||||||
|
private fun registerNfcSuppressor() {
|
||||||
|
registerActivityLifecycleListener(
|
||||||
|
LIFECYCLE_KEY_NFC_SUPPRESSOR,
|
||||||
|
object : ActivityLifecycleListener {
|
||||||
|
override fun onResume() {
|
||||||
|
enableNfcSuppressor()
|
||||||
|
}
|
||||||
|
override fun onPause() {
|
||||||
|
disableReaderModeSafely()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables reader mode with a silent no-op callback. Calling [NfcAdapter.enableReaderMode]
|
||||||
|
* suppresses Android's default tag-dispatch system (and therefore the "new tag scanned"
|
||||||
|
* TagViewer screen) for as long as this Activity is in the foreground. The actual transfer
|
||||||
|
* logic is wired up separately via [handleTag] when the user presses a button.
|
||||||
|
*/
|
||||||
|
private fun enableNfcSuppressor() {
|
||||||
|
if (!nfcAdapter.isEnabled) return
|
||||||
|
val options = Bundle()
|
||||||
|
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
|
||||||
|
val flags = NfcAdapter.FLAG_READER_NFC_A or
|
||||||
|
NfcAdapter.FLAG_READER_NFC_B or
|
||||||
|
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS or
|
||||||
|
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
|
||||||
|
runCatching {
|
||||||
|
nfcAdapter.enableReaderMode(
|
||||||
|
componentActivity,
|
||||||
|
{ /* suppress default dispatch — user must tap a transfer button */ },
|
||||||
|
flags,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}.onFailure {
|
||||||
|
Log.w("NFC", "Failed to enable NFC suppressor reader mode", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Read (phone receives character FROM watch or bracelet) --------------------
|
// ---- Read (phone receives character FROM watch or bracelet) --------------------
|
||||||
@ -103,7 +157,7 @@ class ScanScreenControllerImpl(
|
|||||||
},
|
},
|
||||||
isoDepHandler = { isoDep ->
|
isoDepHandler = { isoDep ->
|
||||||
val application = componentActivity.applicationContext as VBHelper
|
val application = componentActivity.applicationContext as VBHelper
|
||||||
val importer = VitalWearCharacterImporter(application.container.db)
|
val importer = VitalWearCharacterImporter(application.container.db, application.container.transferSeenDao)
|
||||||
try {
|
try {
|
||||||
var importResult: VitalWearCharacterImporter.ImportResult? = null
|
var importResult: VitalWearCharacterImporter.ImportResult? = null
|
||||||
val moved = VitalWearHceReaderClient(isoDep).moveCharacterFromWatch { character ->
|
val moved = VitalWearHceReaderClient(isoDep).moveCharacterFromWatch { character ->
|
||||||
@ -176,7 +230,7 @@ class ScanScreenControllerImpl(
|
|||||||
runCatching {
|
runCatching {
|
||||||
var importResult: VitalWearCharacterImporter.ImportResult? = null
|
var importResult: VitalWearCharacterImporter.ImportResult? = null
|
||||||
val moved = hceClient.moveCharacterFromWatch { character ->
|
val moved = hceClient.moveCharacterFromWatch { character ->
|
||||||
val result = VitalWearCharacterImporter(application.container.db).importCharacter(character)
|
val result = VitalWearCharacterImporter(application.container.db, application.container.transferSeenDao).importCharacter(character)
|
||||||
importResult = result
|
importResult = result
|
||||||
result.success
|
result.success
|
||||||
}
|
}
|
||||||
@ -236,7 +290,9 @@ class ScanScreenControllerImpl(
|
|||||||
lastTagId = null
|
lastTagId = null
|
||||||
lastTagHandledAtMs = 0L
|
lastTagHandledAtMs = 0L
|
||||||
}
|
}
|
||||||
disableReaderModeSafely()
|
// Re-arm the suppressor so the TagViewer doesn't appear while the user is still
|
||||||
|
// on the scan screen but hasn't pressed a transfer button yet.
|
||||||
|
enableNfcSuppressor()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener) {
|
override fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener) {
|
||||||
@ -370,7 +426,9 @@ class ScanScreenControllerImpl(
|
|||||||
Log.w("NFC", "Failed to ignore handled tag", it)
|
Log.w("NFC", "Failed to ignore handled tag", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
disableReaderModeSafely()
|
// Restore the passive suppressor so devices can remain close without Android
|
||||||
|
// launching the TagViewer between transfers or while waiting for the next button press.
|
||||||
|
enableNfcSuppressor()
|
||||||
isHandlingTag.set(false)
|
isHandlingTag.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ class FromNfcConverter (
|
|||||||
) {
|
) {
|
||||||
private val application = componentActivity.applicationContext as VBHelper
|
private val application = componentActivity.applicationContext as VBHelper
|
||||||
private val database = application.container.db
|
private val database = application.container.db
|
||||||
|
private val transferSeenDao = application.container.transferSeenDao
|
||||||
|
|
||||||
|
|
||||||
fun addCharacterUsingCard(
|
fun addCharacterUsingCard(
|
||||||
@ -118,6 +119,9 @@ class FromNfcConverter (
|
|||||||
.userCharacterDao()
|
.userCharacterDao()
|
||||||
.insertCharacterData(characterData)
|
.insertCharacterData(characterData)
|
||||||
|
|
||||||
|
val seenTimestamp = System.currentTimeMillis()
|
||||||
|
markSeen(cardData, nfcCharacter.charIndex.toInt(), seenTimestamp)
|
||||||
|
|
||||||
if (nfcCharacter is BENfcCharacter) {
|
if (nfcCharacter is BENfcCharacter) {
|
||||||
addBeCharacterToDatabase(
|
addBeCharacterToDatabase(
|
||||||
characterId = characterId,
|
characterId = characterId,
|
||||||
@ -292,12 +296,14 @@ class FromNfcConverter (
|
|||||||
|
|
||||||
database
|
database
|
||||||
.dexDao()
|
.dexDao()
|
||||||
.insertCharacter(
|
.insertCharacter(item.toCharIndex.toInt(), dimData.id, date)
|
||||||
item.toCharIndex.toInt(),
|
transferSeenDao.markSeen(dimData.name, item.toCharIndex.toInt(), date)
|
||||||
dimData.id,
|
|
||||||
date
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun markSeen(cardData: Card, slotId: Int, timestamp: Long) {
|
||||||
|
database.dexDao().insertCharacter(slotId, cardData.id, timestamp)
|
||||||
|
transferSeenDao.markSeen(cardData.name, slotId, timestamp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package com.github.nacabaro.vbhelper.source
|
package com.github.nacabaro.vbhelper.source
|
||||||
|
|
||||||
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||||
|
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenDao
|
||||||
import com.github.cfogrady.vitalwear.protos.Character
|
import com.github.cfogrady.vitalwear.protos.Character
|
||||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||||
import com.github.nacabaro.vbhelper.domain.card.Card
|
import com.github.nacabaro.vbhelper.domain.card.Card
|
||||||
@ -13,7 +14,8 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class VitalWearCharacterImporter(
|
class VitalWearCharacterImporter(
|
||||||
private val database: AppDatabase
|
private val database: AppDatabase,
|
||||||
|
private val transferSeenDao: SharedTransferSeenDao,
|
||||||
) {
|
) {
|
||||||
data class ImportResult(
|
data class ImportResult(
|
||||||
val success: Boolean,
|
val success: Boolean,
|
||||||
@ -113,8 +115,9 @@ class VitalWearCharacterImporter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
database.dexDao().insertCharacter(slotId, importedCard.id, now)
|
markSeen(importedCard.name, slotId, importedCard.id, now)
|
||||||
|
|
||||||
|
var insertedTransformationCount = 0
|
||||||
for (transformation in character.transformationHistoryList) {
|
for (transformation in character.transformationHistoryList) {
|
||||||
val transformationCard = resolveRelatedCard(
|
val transformationCard = resolveRelatedCard(
|
||||||
incomingCardName = transformation.cardName,
|
incomingCardName = transformation.cardName,
|
||||||
@ -129,10 +132,21 @@ class VitalWearCharacterImporter(
|
|||||||
transformationCard.id,
|
transformationCard.id,
|
||||||
now
|
now
|
||||||
)
|
)
|
||||||
database.dexDao().insertCharacter(transformation.slotId, transformationCard.id, now)
|
insertedTransformationCount++
|
||||||
|
markSeen(transformationCard.name, transformation.slotId, transformationCard.id, now)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (insertedTransformationCount == 0) {
|
||||||
|
// Keep HomeScreen renderable for freshly imported characters with empty history.
|
||||||
|
database.userCharacterDao().insertTransformation(
|
||||||
|
userCharacterId,
|
||||||
|
slotId,
|
||||||
|
importedCard.id,
|
||||||
|
now
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
for ((cardName, maxAdventureCompleted) in character.maxAdventureCompletedByCardMap) {
|
for ((cardName, maxAdventureCompleted) in character.maxAdventureCompletedByCardMap) {
|
||||||
val adventureCard = resolveRelatedCard(
|
val adventureCard = resolveRelatedCard(
|
||||||
incomingCardName = cardName,
|
incomingCardName = cardName,
|
||||||
@ -210,6 +224,11 @@ class VitalWearCharacterImporter(
|
|||||||
private fun resolveDefaultAbilityRarity(): NfcCharacter.AbilityRarity {
|
private fun resolveDefaultAbilityRarity(): NfcCharacter.AbilityRarity {
|
||||||
return enumValues<NfcCharacter.AbilityRarity>().first()
|
return enumValues<NfcCharacter.AbilityRarity>().first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun markSeen(cardName: String, slotId: Int, cardId: Long, timestamp: Long) {
|
||||||
|
database.dexDao().insertCharacter(slotId, cardId, timestamp)
|
||||||
|
transferSeenDao.markSeen(cardName, slotId, timestamp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun selectImportedCard(
|
internal fun selectImportedCard(
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
package com.github.nacabaro.vbhelper.transfer
|
||||||
|
|
||||||
|
import android.content.ContentProvider
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.github.cfogrady.vitalwear.common.data.SharedDatabaseFactory
|
||||||
|
|
||||||
|
class SharedTransferSeenProvider : ContentProvider() {
|
||||||
|
companion object {
|
||||||
|
const val AUTHORITY = "com.github.nacabaro.vbhelper.transferseen"
|
||||||
|
val URI: Uri = Uri.parse("content://$AUTHORITY")
|
||||||
|
|
||||||
|
const val METHOD_MARK_SEEN = "markSeen"
|
||||||
|
const val EXTRA_CARD_NAME = "cardName"
|
||||||
|
const val EXTRA_SLOT_ID = "slotId"
|
||||||
|
const val EXTRA_SEEN_AT_EPOCH_MILLIS = "seenAtEpochMillis"
|
||||||
|
const val RESULT_OK = "ok"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(): Boolean = true
|
||||||
|
|
||||||
|
override fun call(method: String, arg: String?, extras: Bundle?): Bundle {
|
||||||
|
if (method != METHOD_MARK_SEEN) {
|
||||||
|
return Bundle().apply { putBoolean(RESULT_OK, false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = context ?: return Bundle().apply { putBoolean(RESULT_OK, false) }
|
||||||
|
val cardName = extras?.getString(EXTRA_CARD_NAME).orEmpty()
|
||||||
|
val slotId = extras?.getInt(EXTRA_SLOT_ID, -1) ?: -1
|
||||||
|
val seenAt = extras?.getLong(EXTRA_SEEN_AT_EPOCH_MILLIS, 0L) ?: 0L
|
||||||
|
|
||||||
|
if (cardName.isBlank() || slotId < 0 || seenAt <= 0L) {
|
||||||
|
return Bundle().apply { putBoolean(RESULT_OK, false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return runCatching {
|
||||||
|
SharedDatabaseFactory.getDatabase(context).transferSeenDao().markSeen(cardName, slotId, seenAt)
|
||||||
|
Bundle().apply { putBoolean(RESULT_OK, true) }
|
||||||
|
}.getOrElse {
|
||||||
|
Bundle().apply { putBoolean(RESULT_OK, false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun query(
|
||||||
|
uri: Uri,
|
||||||
|
projection: Array<out String>?,
|
||||||
|
selection: String?,
|
||||||
|
selectionArgs: Array<out String>?,
|
||||||
|
sortOrder: String?
|
||||||
|
): Cursor? = null
|
||||||
|
|
||||||
|
override fun getType(uri: Uri): String? = null
|
||||||
|
|
||||||
|
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||||
|
|
||||||
|
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
|
||||||
|
|
||||||
|
override fun update(
|
||||||
|
uri: Uri,
|
||||||
|
values: ContentValues?,
|
||||||
|
selection: String?,
|
||||||
|
selectionArgs: Array<out String>?
|
||||||
|
): Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user