Compare commits

..
Author SHA1 Message Date
Nacho 660dbaca19 Forgot to include the settingsscreencontroller interface my bad 2025-01-18 16:01:42 +01:00
Nacho 23e233227a Finished refactoring the old SettingsScreenController.kt 2025-01-18 12:05:10 +01:00
Nacho 542072c238 Forgot to close DB before importing process
This will clear the database lock file before closing the application, ensuring that during the second startup the application does not see anything weird with the data. Not closing the database before import can lead to weird behavior.
2025-01-18 01:05:45 +01:00
Nacho 42dd87f0c4 Data export working
During export the database will be closed, otherwise the WAL will ruin the export. data will be saved as a .vbhelper file, then the app will be closed to avoid making any changes to the DB
For import, we check if the extension is .vbhelper, otherwise we can corrupt the database, IMPORTANT. Next we delete everything related to RoomDB and we swap it with the new files. Finally the app will be closed to ensure RoomDB is running on the new DB.

Finally, I still have to reintegrate the importApk functionality with the NewSettingsScreenController and get rid of the old one.
2025-01-18 00:45:16 +01:00
Nacho 88163684ca Dex stuff
- Added dex domain model, and rearranged class
- Added dex functionality to the dex screen, and updated the corresponding controller
2025-01-17 21:12:41 +01:00
nacabaroandGitHub 586d6c01ef Merge pull request #18 from nacabaro/ui/home_screen
A lot more things
2025-01-16 01:05:29 +01:00
29 changed files with 436 additions and 175 deletions
@@ -18,21 +18,18 @@ 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.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.Dim import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.Sprites import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.Character import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
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.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.SettingsScreenController import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
import com.github.nacabaro.vbhelper.utils.DeviceType
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Date
import java.util.GregorianCalendar import java.util.GregorianCalendar
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -58,21 +55,20 @@ class MainActivity : ComponentActivity() {
registerFileActivityResult() registerFileActivityResult()
val application = applicationContext as VBHelper val application = applicationContext as VBHelper
val settingsScreenController = SettingsScreenController.Factory(this, ApkSecretsImporter(), application.container.dataStoreSecretsRepository)
.buildSettingScreenHandlers()
val scanScreenController = ScanScreenControllerImpl( val scanScreenController = ScanScreenControllerImpl(
application.container.dataStoreSecretsRepository.secretsFlow, application.container.dataStoreSecretsRepository.secretsFlow,
this::handleReceivedNfcCharacter, this::handleReceivedNfcCharacter,
this, this,
this::registerActivityLifecycleListener, this::registerActivityLifecycleListener,
this::unregisterActivityLifecycleListener) this::unregisterActivityLifecycleListener
)
val settingsScreenController = SettingsScreenControllerImpl(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
VBHelperTheme { VBHelperTheme {
MainApplication(settingsScreenController, scanScreenController) MainApplication(scanScreenController, settingsScreenController)
} }
} }
Log.i("MainActivity", "Activity onCreated") Log.i("MainActivity", "Activity onCreated")
@@ -113,7 +109,7 @@ class MainActivity : ComponentActivity() {
Log.i("MainActivity", "Card name: ${card is BemCard}") Log.i("MainActivity", "Card name: ${card is BemCard}")
val dimModel = Dim( val cardModel = Card(
dimId = card.header.dimId, dimId = card.header.dimId,
logo = card.spriteData.sprites[0].pixelData, logo = card.spriteData.sprites[0].pixelData,
name = card.spriteData.text, // TODO Make user write card name name = card.spriteData.text, // TODO Make user write card name
@@ -125,7 +121,7 @@ class MainActivity : ComponentActivity() {
val dimId = storageRepository val dimId = storageRepository
.dimDao() .dimDao()
.insertNewDim(dimModel) .insertNewDim(cardModel)
val characters = card.characterStats.characterEntries val characters = card.characterStats.characterEntries
@@ -191,10 +187,16 @@ class MainActivity : ComponentActivity() {
} }
@Composable @Composable
private fun MainApplication(settingsScreenController: SettingsScreenController, scanScreenController: ScanScreenControllerImpl) { private fun MainApplication(
scanScreenController: ScanScreenControllerImpl,
settingsScreenController: SettingsScreenControllerImpl
) {
AppNavigation( AppNavigation(
applicationNavigationHandlers = AppNavigationHandlers(settingsScreenController, scanScreenController), applicationNavigationHandlers = AppNavigationHandlers(
settingsScreenController,
scanScreenController,
),
onClickImportCard = { onClickImportCard = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
@@ -216,7 +218,6 @@ class MainActivity : ComponentActivity() {
// //
/* /*
TODO: TODO:
- Make it able to detect the different model of watches
- Support for regular VB - Support for regular VB
The good news is that the theory behind inserting to the database should be working The good news is that the theory behind inserting to the database should be working
@@ -255,8 +256,8 @@ class MainActivity : ComponentActivity() {
activityLevel = nfcCharacter.value!!.activityLevel.toInt(), activityLevel = nfcCharacter.value!!.activityLevel.toInt(),
heartRateCurrent = nfcCharacter.value!!.heartRateCurrent.toInt(), heartRateCurrent = nfcCharacter.value!!.heartRateCurrent.toInt(),
characterType = when (nfcCharacter.value) { characterType = when (nfcCharacter.value) {
is BENfcCharacter -> com.github.nacabaro.vbhelper.domain.DeviceType.BEDevice is BENfcCharacter -> DeviceType.BEDevice
else -> com.github.nacabaro.vbhelper.domain.DeviceType.VBDevice else -> DeviceType.VBDevice
}, },
isActive = true isActive = true
) )
@@ -307,12 +308,16 @@ class MainActivity : ComponentActivity() {
val date = GregorianCalendar(item.year.toInt(), item.month.toInt(), item.day.toInt()) val date = GregorianCalendar(item.year.toInt(), item.month.toInt(), item.day.toInt())
.time .time
.time .time
storageRepository storageRepository
.characterDao() .characterDao()
.insertTransformation(characterId, item.toCharIndex.toInt(), dimData.id, date) .insertTransformation(characterId, item.toCharIndex.toInt(), dimData.id, date)
storageRepository
.dexDao()
.insertCharacter(item.toCharIndex.toInt(), dimData.id, date)
} }
} }
} else if (nfcCharacter.value is VBNfcCharacter) { } else if (nfcCharacter.value is VBNfcCharacter) {
return "Not implemented yet" return "Not implemented yet"
} }
@@ -2,6 +2,7 @@ package com.github.nacabaro.vbhelper.components
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@@ -23,6 +24,8 @@ import com.github.nacabaro.vbhelper.utils.getBitmap
fun DexDiMEntry( fun DexDiMEntry(
name: String, name: String,
logo: BitmapData, logo: BitmapData,
obtainedCharacters: Int,
totalCharacters: Int,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@@ -50,11 +53,21 @@ fun DexDiMEntry(
.padding(8.dp) .padding(8.dp)
.size(dpSize) .size(dpSize)
) )
Text( Column(
text = name,
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(8.dp)
) ) {
Text(
text = name,
modifier = Modifier
)
Text(
text = "$obtainedCharacters of $totalCharacters characters obtained",
fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier
)
}
} }
} }
} }
@@ -3,10 +3,9 @@ package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.Character import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.Sprites import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import java.util.GregorianCalendar
@Dao @Dao
interface CharacterDao { interface CharacterDao {
@@ -28,15 +27,17 @@ interface CharacterDao {
@Query("SELECT * FROM Sprites") @Query("SELECT * FROM Sprites")
suspend fun getAllSprites(): List<Sprites> suspend fun getAllSprites(): List<Sprites>
@Query(""" @Query(
"""
SELECT SELECT
d.dimId as cardId, d.dimId as cardId,
c.monIndex as charId c.monIndex as charId
FROM Character c FROM Character c
JOIN UserCharacter uc ON c.id = uc.charId JOIN UserCharacter uc ON c.id = uc.charId
JOIN Dim d ON c.dimId = d.id JOIN Card d ON c.dimId = d.id
WHERE uc.id = :charId WHERE uc.id = :charId
""") """
)
suspend fun getCharacterInfo(charId: Long): CharacterDtos.DiMInfo suspend fun getCharacterInfo(charId: Long): CharacterDtos.DiMInfo
@Query(""" @Query("""
@@ -0,0 +1,44 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@Dao
interface DexDao {
@Query("""
INSERT OR IGNORE INTO Dex(id, discoveredOn)
VALUES (
(SELECT id FROM Character WHERE monIndex = :charIndex AND dimId = :cardId),
:discoveredOn
)
""")
fun insertCharacter(charIndex: Int, cardId: Long, discoveredOn: Long)
@Query("""
SELECT
c.id AS id,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.discoveredOn AS discoveredOn
FROM character c
LEFT JOIN dex d ON c.id = d.id
WHERE c.dimId = :cardId
""")
suspend fun getSingleCardProgress(cardId: Long): List<CharacterDtos.CardProgress>
@Query("""
SELECT
c.id as cardId,
c.name as cardName,
c.logo as cardLogo,
c.logoWidth as logoWidth,
c.logoHeight as logoHeight,
(SELECT COUNT(*) FROM Character cc WHERE cc.dimId = c.id) AS totalCharacters,
(SELECT COUNT(*) FROM Dex d JOIN Character cc ON d.id = cc.id WHERE cc.dimId = c.id AND d.discoveredOn IS NOT NULL) AS obtainedCharacters
FROM Card c
""")
suspend fun getCardsWithProgress(): List<CardDtos.CardProgress>
}
@@ -4,16 +4,16 @@ import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.github.nacabaro.vbhelper.domain.Dim import com.github.nacabaro.vbhelper.domain.characters.Card
@Dao @Dao
interface DiMDao { interface DiMDao {
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertNewDim(dim: Dim): Long suspend fun insertNewDim(card: Card): Long
@Query("SELECT * FROM Dim") @Query("SELECT * FROM Card")
suspend fun getAllDims(): List<Dim> suspend fun getAllDims(): List<Card>
@Query("SELECT * FROM Dim WHERE dimId = :id") @Query("SELECT * FROM Card WHERE dimId = :id")
fun getDimById(id: Int): Dim? fun getDimById(id: Int): Card?
} }
@@ -34,7 +34,8 @@ interface UserCharacterDao {
""") """)
fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>? fun getTransformationHistory(monId: Long): List<CharacterDtos.TransformationHistory>?
@Query(""" @Query(
"""
SELECT SELECT
uc.*, uc.*,
c.sprite1 AS spriteIdle, c.sprite1 AS spriteIdle,
@@ -43,8 +44,9 @@ interface UserCharacterDao {
d.isBEm as isBemCard d.isBEm as isBemCard
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN Character c ON uc.charId = c.id
JOIN Dim d ON c.dimId = d.id JOIN Card d ON c.dimId = d.id
""") """
)
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites> suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
@Query("SELECT * FROM UserCharacter WHERE id = :id") @Query("SELECT * FROM UserCharacter WHERE id = :id")
@@ -53,7 +55,8 @@ interface UserCharacterDao {
@Query("SELECT * FROM BECharacterData WHERE id = :id") @Query("SELECT * FROM BECharacterData WHERE id = :id")
suspend fun getBeData(id: Long): BECharacterData suspend fun getBeData(id: Long): BECharacterData
@Query(""" @Query(
"""
SELECT SELECT
uc.*, uc.*,
c.sprite1 AS spriteIdle, c.sprite1 AS spriteIdle,
@@ -62,10 +65,11 @@ interface UserCharacterDao {
d.isBEm as isBemCard d.isBEm as isBemCard
FROM UserCharacter uc FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id JOIN Character c ON uc.charId = c.id
JOIN Dim d ON c.dimId = d.id JOIN Card d ON c.dimId = d.id
WHERE uc.isActive = 1 WHERE uc.isActive = 1
LIMIT 1 LIMIT 1
""") """
)
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites? suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites?
@Query("DELETE FROM UserCharacter WHERE id = :id") @Query("DELETE FROM UserCharacter WHERE id = :id")
@@ -3,11 +3,13 @@ package com.github.nacabaro.vbhelper.database
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.daos.CharacterDao import com.github.nacabaro.vbhelper.daos.CharacterDao
import com.github.nacabaro.vbhelper.daos.DexDao
import com.github.nacabaro.vbhelper.daos.DiMDao import com.github.nacabaro.vbhelper.daos.DiMDao
import com.github.nacabaro.vbhelper.daos.UserCharacterDao import com.github.nacabaro.vbhelper.daos.UserCharacterDao
import com.github.nacabaro.vbhelper.domain.Character import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.Dim import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.Sprites import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.characters.Dex
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory 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.UserCharacter
@@ -15,16 +17,18 @@ import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
@Database( @Database(
version = 1, version = 1,
entities = [ entities = [
Dim::class, Card::class,
Character::class, Character::class,
Sprites::class, Sprites::class,
UserCharacter::class, UserCharacter::class,
BECharacterData::class, BECharacterData::class,
TransformationHistory::class TransformationHistory::class,
Dex::class
] ]
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun dimDao(): DiMDao abstract fun dimDao(): DiMDao
abstract fun characterDao(): CharacterDao abstract fun characterDao(): CharacterDao
abstract fun userCharacterDao(): UserCharacterDao abstract fun userCharacterDao(): UserCharacterDao
abstract fun dexDao(): DexDao
} }
@@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.characters.Card
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
@@ -13,7 +14,7 @@ import androidx.room.PrimaryKey
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
), ),
ForeignKey( ForeignKey(
entity = Dim::class, entity = Card::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["dimId"], childColumns = ["dimId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
@@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.characters.Character
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
@@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.domain
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.characters.Character
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
@@ -1,11 +1,10 @@
package com.github.nacabaro.vbhelper.domain package com.github.nacabaro.vbhelper.domain.characters
import android.icu.text.ListFormatter.Width
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity @Entity
data class Dim( data class Card(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val id: Long = 0, val id: Long = 0,
val dimId: Int, val dimId: Int,
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.domain package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@@ -7,7 +7,7 @@ import androidx.room.ForeignKey
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
ForeignKey( ForeignKey(
entity = Dim::class, entity = Card::class,
parentColumns = ["id"], parentColumns = ["id"],
childColumns = ["dimId"], childColumns = ["dimId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE
@@ -0,0 +1,20 @@
package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = Character::class,
parentColumns = ["id"],
childColumns = ["id"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Dex(
@PrimaryKey val id: Long = 0,
val discoveredOn: Long
)
@@ -3,7 +3,7 @@ package com.github.nacabaro.vbhelper.domain.device_data
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.Character import com.github.nacabaro.vbhelper.domain.characters.Character
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
@@ -4,8 +4,8 @@ import androidx.room.Entity
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
import com.github.nacabaro.vbhelper.domain.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.domain.Character import com.github.nacabaro.vbhelper.domain.characters.Character
@Entity( @Entity(
foreignKeys = [ foreignKeys = [
@@ -0,0 +1,13 @@
package com.github.nacabaro.vbhelper.dtos
object CardDtos {
data class CardProgress (
val cardId: Long,
val cardName: String,
val cardLogo: ByteArray,
val logoWidth: Int,
val logoHeight: Int,
val totalCharacters: Int,
val obtainedCharacters: Int,
)
}
@@ -1,7 +1,7 @@
package com.github.nacabaro.vbhelper.dtos package com.github.nacabaro.vbhelper.dtos
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.domain.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
object CharacterDtos { object CharacterDtos {
@@ -43,4 +43,12 @@ object CharacterDtos {
val monIndex: Int, val monIndex: Int,
val transformationDate: Long val transformationDate: Long
) )
data class CardProgress(
val id: Long,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val discoveredOn: Long?
)
} }
@@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.navigation package com.github.nacabaro.vbhelper.navigation
import android.util.Log
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -15,12 +14,15 @@ import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen
import com.github.nacabaro.vbhelper.screens.ItemsScreen import com.github.nacabaro.vbhelper.screens.ItemsScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.SettingsScreen import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen
import com.github.nacabaro.vbhelper.screens.SettingsScreenController
import com.github.nacabaro.vbhelper.screens.SpriteViewer import com.github.nacabaro.vbhelper.screens.SpriteViewer
import com.github.nacabaro.vbhelper.screens.StorageScreen import com.github.nacabaro.vbhelper.screens.StorageScreen
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
data class AppNavigationHandlers(val settingsScreenController: SettingsScreenController, val scanScreenController: ScanScreenControllerImpl) data class AppNavigationHandlers(
val settingsScreenController: SettingsScreenControllerImpl,
val scanScreenController: ScanScreenControllerImpl,
)
@Composable @Composable
fun AppNavigation( fun AppNavigation(
@@ -72,7 +74,7 @@ fun AppNavigation(
SettingsScreen( SettingsScreen(
navController = navController, navController = navController,
settingsScreenController = applicationNavigationHandlers.settingsScreenController, settingsScreenController = applicationNavigationHandlers.settingsScreenController,
onClickImportCard = onClickImportCard onClickImportCard = onClickImportCard,
) )
} }
composable(NavigationItems.Viewer.route) { composable(NavigationItems.Viewer.route) {
@@ -81,12 +83,11 @@ fun AppNavigation(
) )
} }
composable(NavigationItems.CardView.route) { composable(NavigationItems.CardView.route) {
val dimId = it.arguments?.getString("dimId") val cardId = it.arguments?.getString("cardId")
Log.d("dimId", dimId.toString()) if (cardId != null) {
if (dimId != null) {
DiMScreen( DiMScreen(
navController = navController, navController = navController,
dimId = dimId.toInt() dimId = cardId.toLong()
) )
} }
} }
@@ -14,6 +14,6 @@ sealed class NavigationItems (
object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage") object Storage : NavigationItems("Storage", R.drawable.baseline_catching_pokemon_24, "Storage")
object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings") object Settings : NavigationItems("Settings", R.drawable.baseline_settings_24, "Settings")
object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer") object Viewer : NavigationItems("Viewer", R.drawable.baseline_image_24, "Viewer")
object CardView : NavigationItems("Card/{dimId}", R.drawable.baseline_image_24, "Card") object CardView : NavigationItems("Card/{cardId}", R.drawable.baseline_image_24, "Card")
object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items") object Items : NavigationItems("Items", R.drawable.baseline_data_24, "Items")
} }
@@ -18,7 +18,8 @@ import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.DexDiMEntry import com.github.nacabaro.vbhelper.components.DexDiMEntry
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.Dim import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -31,19 +32,19 @@ fun DexScreen(
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db) val dexRepository = DexRepository(application.container.db)
val dimList = remember { mutableStateOf<List<Dim>>(emptyList()) } val cardList = remember { mutableStateOf<List<CardDtos.CardProgress>>(emptyList()) }
LaunchedEffect(dexRepository) { LaunchedEffect(dexRepository) {
coroutineScope.launch { coroutineScope.launch {
val newDimList = dexRepository.getAllDims() val newDimList = dexRepository.getAllDims()
dimList.value = newDimList // Replace the entire list atomically cardList.value = newDimList // Replace the entire list atomically
} }
} }
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "Discovered Digimon", text = "Discovered characters",
onGearClick = { onGearClick = {
navController.navigate(NavigationItems.Viewer.route) navController.navigate(NavigationItems.Viewer.route)
} }
@@ -54,11 +55,11 @@ fun DexScreen(
modifier = Modifier modifier = Modifier
.padding(top = contentPadding.calculateTopPadding()) .padding(top = contentPadding.calculateTopPadding())
) { ) {
items(dimList.value) { items(cardList.value) {
DexDiMEntry( DexDiMEntry(
name = it.name, name = it.cardName,
logo = BitmapData( logo = BitmapData(
bitmap = it.logo, bitmap = it.cardLogo,
width = it.logoWidth, width = it.logoWidth,
height = it.logoHeight height = it.logoHeight
), ),
@@ -67,9 +68,11 @@ fun DexScreen(
.navigate( .navigate(
NavigationItems NavigationItems
.CardView.route .CardView.route
.replace("{dimId}", "${it.id}") .replace("{cardId}", "${it.cardId}")
) )
}, },
obtainedCharacters = it.obtainedCharacters,
totalCharacters = it.totalCharacters,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(8.dp) .padding(8.dp)
@@ -1,6 +1,5 @@
package com.github.nacabaro.vbhelper.screens package com.github.nacabaro.vbhelper.screens
import android.util.Log
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
@@ -15,21 +14,22 @@ import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.CharacterEntry import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.domain.Character import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.DexRepository import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun DiMScreen( fun DiMScreen(
navController: NavController, navController: NavController,
dimId: Int dimId: Long
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db) val dexRepository = DexRepository(application.container.db)
val characterList = remember { mutableStateOf<List<Character>>(emptyList()) } val characterList = remember { mutableStateOf<List<CharacterDtos.CardProgress>>(emptyList()) }
LaunchedEffect(dexRepository) { LaunchedEffect(dexRepository) {
coroutineScope.launch { coroutineScope.launch {
@@ -41,7 +41,7 @@ fun DiMScreen(
Scaffold ( Scaffold (
topBar = { topBar = {
TopBanner( TopBanner(
text = "Discovered Digimon", text = "Discovered characters",
onBackClick = { onBackClick = {
navController.popBackStack() navController.popBackStack()
} }
@@ -55,11 +55,11 @@ fun DiMScreen(
items(characterList.value) { character -> items(characterList.value) { character ->
CharacterEntry( CharacterEntry(
onClick = { }, onClick = { },
obscure = true, obscure = character.discoveredOn == null,
icon = BitmapData( icon = BitmapData(
bitmap = character.sprite1, bitmap = character.spriteIdle,
width = character.spritesWidth, width = character.spriteWidth,
height = character.spritesHeight height = character.spriteHeight,
), ),
) )
} }
@@ -1,71 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import android.net.Uri
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.source.SecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsRepository
import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
data class SettingsScreenController(val apkFilePickLauncher: ActivityResultLauncher<Array<String>>) {
class Factory(private val componentActivity: ComponentActivity, private val secretsImporter: SecretsImporter, private val secretsRepository: SecretsRepository) {
fun buildSettingScreenHandlers(): SettingsScreenController {
return SettingsScreenController(
apkFilePickLauncher = buildFilePickerActivityLauncher(this::importApk)
)
}
private fun buildFilePickerActivityLauncher(onResult : (Uri?) ->Unit): ActivityResultLauncher<Array<String>> {
return componentActivity.registerForActivityResult(ActivityResultContracts.OpenDocument()) {
onResult.invoke(it)
}
}
private fun importApk(uri: Uri?) {
if(uri == null) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "APK Import Cancelled", Toast.LENGTH_SHORT)
.show()
}
return
}
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
componentActivity.contentResolver.openInputStream(uri).use {
if(it == null) {
componentActivity.runOnUiThread {
Toast.makeText(
componentActivity,
"Selected file is empty!",
Toast.LENGTH_SHORT
).show()
}
return@launch
}
var secrets: Secrets? = null
try {
secrets = secretsImporter.importSecrets(it)
} catch (e: Exception) {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Secrets import failed. Please only select the official Vital Arena App 2.1.0 APK.", Toast.LENGTH_SHORT).show()
}
return@launch
}
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
secretsRepository.updateSecrets(secrets)
}.invokeOnCompletion {
componentActivity.runOnUiThread {
Toast.makeText(componentActivity, "Secrets successfully imported. Connections with devices are now possible.", Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
}
@@ -17,7 +17,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.DeviceType import com.github.nacabaro.vbhelper.utils.DeviceType
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData import com.github.nacabaro.vbhelper.domain.device_data.VBCharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@@ -1,9 +1,5 @@
package com.github.nacabaro.vbhelper.screens package com.github.nacabaro.vbhelper.screens.settingsScreen
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -26,7 +22,7 @@ import com.github.nacabaro.vbhelper.components.TopBanner
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
navController: NavController, navController: NavController,
settingsScreenController: SettingsScreenController, settingsScreenController: SettingsScreenControllerImpl,
onClickImportCard: () -> Unit onClickImportCard: () -> Unit
) { ) {
Scaffold ( Scaffold (
@@ -49,7 +45,14 @@ fun SettingsScreen(
) { ) {
SettingsSection("NFC Communication") SettingsSection("NFC Communication")
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") { SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
settingsScreenController.apkFilePickLauncher.launch(arrayOf("*/*")) settingsScreenController.onClickImportApk()
}
SettingsSection("Data management")
SettingsEntry(title = "Export data", description = "Export application database") {
settingsScreenController.onClickOpenDirectory()
}
SettingsEntry(title = "Import data", description = "Import application database") {
settingsScreenController.onClickImportDatabase()
} }
SettingsSection("DiM/BEm management") SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file", onClick = onClickImportCard) SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file", onClick = onClickImportCard)
@@ -61,12 +64,6 @@ fun SettingsScreen(
} }
} }
fun buildFilePickLauncher(activity: ComponentActivity, onItemPicked: (Uri?) -> Unit): ActivityResultLauncher<Array<String>> {
return activity.registerForActivityResult(ActivityResultContracts.OpenDocument()) {
onItemPicked.invoke(it)
}
}
@Composable @Composable
fun SettingsEntry( fun SettingsEntry(
title: String, title: String,
@@ -0,0 +1,7 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
interface SettingsScreenController {
fun onClickOpenDirectory()
fun onClickImportDatabase()
fun onClickImportApk()
}
@@ -0,0 +1,211 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.source.ApkSecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsImporter
import com.github.nacabaro.vbhelper.source.SecretsRepository
import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.Dispatchers
import java.io.File
import java.io.InputStream
import java.io.OutputStream
class SettingsScreenControllerImpl(
private val context: ComponentActivity,
): SettingsScreenController {
private val roomDbName = "internalDb"
private val filePickerLauncher: ActivityResultLauncher<String>
private val filePickerOpenerLauncher: ActivityResultLauncher<Array<String>>
private val filePickerApk: ActivityResultLauncher<Array<String>>
private val secretsImporter: SecretsImporter = ApkSecretsImporter()
private val application = context.applicationContext as VBHelper
private val secretsRepository: SecretsRepository = application.container.dataStoreSecretsRepository
init {
filePickerLauncher = context.registerForActivityResult(
ActivityResultContracts.CreateDocument("application/octet-stream")
) { uri ->
if (uri != null) {
exportDatabase(uri)
} else {
context.runOnUiThread {
Toast.makeText(context, "No destination selected", Toast.LENGTH_SHORT)
.show()
}
}
}
filePickerOpenerLauncher = context.registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri ->
if (uri != null) {
importDatabase(uri)
} else {
context.runOnUiThread {
Toast.makeText(context, "No source selected", Toast.LENGTH_SHORT).show()
}
}
}
filePickerApk = context.registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri ->
if (uri != null) {
importApk(uri)
} else {
context.runOnUiThread {
Toast.makeText(context, "APK import cancelled", Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onClickOpenDirectory() {
filePickerLauncher.launch("My application data.vbhelper")
}
override fun onClickImportDatabase() {
filePickerOpenerLauncher.launch(arrayOf("application/octet-stream"))
}
override fun onClickImportApk() {
filePickerApk.launch(arrayOf("*/*"))
}
private fun exportDatabase(destinationUri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) {
try {
val dbFile = File(context.getDatabasePath(roomDbName).absolutePath)
if (!dbFile.exists()) {
throw IllegalStateException("Database file does not exist!")
}
application.container.db.close()
context.contentResolver.openOutputStream(destinationUri)?.use { outputStream ->
dbFile.inputStream().use { inputStream ->
copyFile(inputStream, outputStream)
}
} ?: throw IllegalArgumentException("Unable to open destination Uri for writing")
context.runOnUiThread {
Toast.makeText(context, "Database exported successfully!", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Closing application to avoid changes.", Toast.LENGTH_LONG).show()
context.finishAffinity()
}
} catch (e: Exception) {
Log.e("ScanScreenController", "Error exporting database $e")
context.runOnUiThread {
Toast.makeText(context, "Error exporting database: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
private fun importDatabase(sourceUri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) {
try {
if (!getFileNameFromUri(sourceUri)!!.endsWith(".vbhelper")) {
context.runOnUiThread {
Toast.makeText(context, "Invalid file format", Toast.LENGTH_SHORT).show()
}
return@launch
}
application.container.db.close()
val dbPath = context.getDatabasePath(roomDbName)
val shmFile = File(dbPath.parent, "$roomDbName-shm")
val walFile = File(dbPath.parent, "$roomDbName-wal")
// Delete existing database files
if (dbPath.exists()) dbPath.delete()
if (shmFile.exists()) shmFile.delete()
if (walFile.exists()) walFile.delete()
val dbFile = File(dbPath.absolutePath)
context.contentResolver.openInputStream(sourceUri)?.use { inputStream ->
dbFile.outputStream().use { outputStream ->
copyFile(inputStream, outputStream)
}
} ?: throw IllegalArgumentException("Unable to open source Uri for reading")
context.runOnUiThread {
Toast.makeText(context, "Database imported successfully!", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Reopen the app to finish import process!", Toast.LENGTH_LONG).show()
context.finishAffinity()
}
} catch (e: Exception) {
Log.e("ScanScreenController", "Error importing database $e")
context.runOnUiThread {
Toast.makeText(context, "Error importing database: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
private fun getFileNameFromUri(uri: Uri): String? {
var fileName: String? = null
val cursor = context.contentResolver.query(uri, null, null, null, null)
cursor?.use {
if (it.moveToFirst()) {
val nameIndex = it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
fileName = it.getString(nameIndex)
}
}
return fileName
}
private fun copyFile(inputStream: InputStream, outputStream: OutputStream) {
val buffer = ByteArray(1024)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
outputStream.flush()
}
private fun importApk(uri: Uri) {
context.lifecycleScope.launch(Dispatchers.IO) {
context.contentResolver.openInputStream(uri).use {
if(it == null) {
context.runOnUiThread {
Toast.makeText(
context,
"Selected file is empty!",
Toast.LENGTH_SHORT
).show()
}
return@launch
}
val secrets: Secrets?
try {
secrets = secretsImporter.importSecrets(it)
} catch (e: Exception) {
context.runOnUiThread {
Toast.makeText(context, "Secrets import failed. Please only select the official Vital Arena App 2.1.0 APK.", Toast.LENGTH_SHORT).show()
}
return@launch
}
context.lifecycleScope.launch(Dispatchers.IO) {
secretsRepository.updateSecrets(secrets)
}.invokeOnCompletion {
context.runOnUiThread {
Toast.makeText(context, "Secrets successfully imported. Connections with devices are now possible.", Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
@@ -1,18 +1,18 @@
package com.github.nacabaro.vbhelper.source package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.domain.Character import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.Dim import com.github.nacabaro.vbhelper.dtos.CardDtos
import kotlinx.coroutines.flow.Flow import com.github.nacabaro.vbhelper.dtos.CharacterDtos
class DexRepository ( class DexRepository (
private val db: AppDatabase private val db: AppDatabase
) { ) {
suspend fun getAllDims(): List<Dim> { suspend fun getAllDims(): List<CardDtos.CardProgress> {
return db.dimDao().getAllDims() return db.dexDao().getCardsWithProgress()
} }
suspend fun getCharactersByDimId(dimId: Int): List<Character> { suspend fun getCharactersByDimId(cardId: Long): List<CharacterDtos.CardProgress> {
return db.characterDao().getCharacterByDimId(dimId) return db.dexDao().getSingleCardProgress(cardId)
} }
} }
@@ -7,7 +7,6 @@ import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.be.FirmwareVersion import com.github.cfogrady.vbnfc.be.FirmwareVersion
import com.github.cfogrady.vbnfc.data.NfcCharacter import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.di.VBHelper import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.DeviceType
import com.github.nacabaro.vbhelper.source.StorageRepository import com.github.nacabaro.vbhelper.source.StorageRepository
import java.util.Date import java.util.Date
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.domain package com.github.nacabaro.vbhelper.utils
enum class DeviceType { enum class DeviceType {
VBDevice, VBDevice,