Compare commits

..
Author SHA1 Message Date
Nacho 1a6753d2aa Fixed date issues with TransformationHistory 2025-09-02 04:54:08 +02:00
Nacho f8ec899335 Version up and credits 2025-08-09 12:59:01 +02:00
Nacho b95edfa2a1 New icon 2025-08-09 12:55:38 +02:00
Nacho 1a570170ca Merge remote-tracking branch 'origin/main' 2025-08-06 20:18:43 +02:00
Nacho 1566876106 Version up 2025-08-06 20:18:32 +02:00
nacabaroandGitHub 019f07d827 Merge pull request #36 from nacabaro/card/card_management
Few things here and there
2025-08-06 20:13:09 +02:00
Nacho 4474e6689d Few things here and there
- Card management, you can now modify the name of the cards and remove cards too.
- Also, support for multiple cards with the same ID works too. When scanning for the first time, if a repeat card exists, the user will be asked to choose which card the character comes from. Future scans will not ask since it is stored in the watch app reserved area.
2025-08-06 20:10:21 +02:00
38 changed files with 730 additions and 180 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ android {
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "Alpha 0.4.1"
versionName = "Alpha 0.5.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -14,6 +14,7 @@ import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImp
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@@ -47,6 +48,7 @@ class MainActivity : ComponentActivity() {
val storageScreenController = StorageScreenControllerImpl(this)
val homeScreenController = HomeScreenControllerImpl(this)
val spriteViewerController = SpriteViewerControllerImpl(this)
val cardScreenController = CardScreenControllerImpl(this)
super.onCreate(savedInstanceState)
@@ -61,7 +63,8 @@ class MainActivity : ComponentActivity() {
adventureScreenController = adventureScreenController,
homeScreenController = homeScreenController,
storageScreenController = storageScreenController,
spriteViewerController = spriteViewerController
spriteViewerController = spriteViewerController,
cardScreenController = cardScreenController
)
}
}
@@ -93,8 +96,9 @@ class MainActivity : ComponentActivity() {
adventureScreenController: AdventureScreenControllerImpl,
storageScreenController: StorageScreenControllerImpl,
homeScreenController: HomeScreenControllerImpl,
spriteViewerController: SpriteViewerControllerImpl
) {
spriteViewerController: SpriteViewerControllerImpl,
cardScreenController: CardScreenControllerImpl
) {
AppNavigation(
applicationNavigationHandlers = AppNavigationHandlers(
settingsScreenController,
@@ -103,7 +107,8 @@ class MainActivity : ComponentActivity() {
adventureScreenController,
storageScreenController,
homeScreenController,
spriteViewerController
spriteViewerController,
cardScreenController
)
)
}
@@ -24,7 +24,8 @@ fun TopBanner(
onGearClick: (() -> Unit)? = null,
onBackClick: (() -> Unit)? = null,
onScanClick: (() -> Unit)? = null,
onAdventureClick: (() -> Unit)? = null
onAdventureClick: (() -> Unit)? = null,
onModifyClick: (() -> Unit)? = null
) {
Box( // Use Box to overlay elements
modifier = modifier
@@ -37,16 +38,16 @@ fun TopBanner(
textAlign = TextAlign.Center,
fontSize = 24.sp,
modifier = Modifier
.align(Alignment.Center) // Center the text
.align(Alignment.Center)
)
if (onGearClick != null) {
IconButton(
onClick = onGearClick,
modifier = Modifier
.align(Alignment.CenterEnd) // Place gear icon at the end
.align(Alignment.CenterEnd)
) {
Icon(
painter = painterResource(R.drawable.baseline_settings_24), // Use a gear icon
painter = painterResource(R.drawable.baseline_settings_24),
contentDescription = "Settings"
)
}
@@ -54,23 +55,34 @@ fun TopBanner(
IconButton(
onClick = onAdventureClick,
modifier = Modifier
.align(Alignment.CenterEnd) // Place gear icon at the end
.align(Alignment.CenterEnd)
) {
Icon(
painter = painterResource(R.drawable.baseline_fort_24), // Use a gear icon
painter = painterResource(R.drawable.baseline_fort_24),
contentDescription = "Adventure"
)
}
} else if (onModifyClick != null) {
IconButton(
onClick = onModifyClick,
modifier = Modifier
.align(Alignment.CenterEnd)
) {
Icon(
painter = painterResource(R.drawable.baseline_edit_24),
contentDescription = "Adventure"
)
}
}
if (onScanClick != null) {
IconButton(
onClick = onScanClick,
modifier = Modifier
.align(Alignment.CenterStart) // Place gear icon at the end
.align(Alignment.CenterStart)
) {
Icon(
painter = painterResource(R.drawable.baseline_nfc_24), // Use a gear icon
painter = painterResource(R.drawable.baseline_nfc_24),
contentDescription = "Scan"
)
}
@@ -78,10 +90,10 @@ fun TopBanner(
IconButton(
onClick = onBackClick,
modifier = Modifier
.align(Alignment.CenterStart) // Place gear icon at the end
.align(Alignment.CenterStart)
) {
Icon(
painter = painterResource(R.drawable.baseline_arrow_back_24), // Use a gear icon
painter = painterResource(R.drawable.baseline_arrow_back_24),
contentDescription = "Settings"
)
}
@@ -9,8 +9,26 @@ import com.github.nacabaro.vbhelper.domain.card.Card
@Dao
interface CardDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertNewDim(card: Card): Long
suspend fun insertNewCard(card: Card): Long
@Query("SELECT * FROM Card WHERE cardId = :id")
fun getDimById(id: Int): Card?
fun getCardByCardId(id: Int): List<Card>
@Query("SELECT * FROM Card WHERE id = :id")
fun getCardById(id: Long): Card?
@Query("""
SELECT ca.*
FROM Card ca
JOIN Character ch ON ca.id = ch.dimId
JOIN UserCharacter uc ON ch.id = uc.charId
WHERE uc.id = :id
""")
suspend fun getCardByCharacterId(id: Long): Card
@Query("UPDATE Card SET name = :newName WHERE id = :id")
suspend fun renameCard(id: Int, newName: String)
@Query("DELETE FROM Card WHERE id = :id")
suspend fun deleteCard(id: Long)
}
@@ -19,8 +19,8 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.screens.BattlesScreen
import com.github.nacabaro.vbhelper.screens.DexScreen
import com.github.nacabaro.vbhelper.screens.DiMScreen
import com.github.nacabaro.vbhelper.screens.cardScreen.CardsScreen
import com.github.nacabaro.vbhelper.screens.cardScreen.CardViewScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
@@ -34,6 +34,7 @@ import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImp
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreen
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.cardScreen.CardScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.CreditsScreen
import com.github.nacabaro.vbhelper.screens.spriteViewer.SpriteViewerControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
@@ -46,7 +47,8 @@ data class AppNavigationHandlers(
val adventureScreenController: AdventureScreenControllerImpl,
val storageScreenController: StorageScreenControllerImpl,
val homeScreenController: HomeScreenControllerImpl,
val spriteViewerController: SpriteViewerControllerImpl
val spriteViewerController: SpriteViewerControllerImpl,
val cardScreenController: CardScreenControllerImpl
)
@Composable
@@ -121,8 +123,9 @@ fun AppNavigation(
)
}
composable(NavigationItems.Dex.route) {
DexScreen(
navController = navController
CardsScreen(
navController = navController,
cardScreenController = applicationNavigationHandlers.cardScreenController
)
}
composable(NavigationItems.Settings.route) {
@@ -140,7 +143,7 @@ fun AppNavigation(
composable(NavigationItems.CardView.route) {
val cardId = it.arguments?.getString("cardId")
if (cardId != null) {
DiMScreen(
CardViewScreen(
navController = navController,
dimId = cardId.toLong()
)
@@ -1,83 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.DexDiMEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch
@Composable
fun DexScreen(
navController: NavController
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db)
val cardList = remember { mutableStateOf<List<CardDtos.CardProgress>>(emptyList()) }
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newDimList = dexRepository.getAllDims()
cardList.value = newDimList // Replace the entire list atomically
}
}
Scaffold (
topBar = {
TopBanner(
text = "Discovered characters",
onGearClick = {
navController.navigate(NavigationItems.Viewer.route)
}
)
}
) { contentPadding ->
LazyColumn (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(cardList.value) {
DexDiMEntry(
name = it.cardName,
logo = BitmapData(
bitmap = it.cardLogo,
width = it.logoWidth,
height = it.logoHeight
),
onClick = {
navController
.navigate(
NavigationItems
.CardView.route
.replace("{cardId}", "${it.cardId}")
)
},
obtainedCharacters = it.obtainedCharacters,
totalCharacters = it.totalCharacters,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
}
}
}
@@ -0,0 +1,109 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
@Composable
fun CardEntry(
name: String,
logo: BitmapData,
obtainedCharacters: Int,
totalCharacters: Int,
onClick: () -> Unit,
displayModify: Boolean,
onClickModify: () -> Unit,
onClickDelete: () -> Unit,
modifier: Modifier = Modifier
) {
val bitmap = remember (logo.bitmap) { logo.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (logo.width * 4 / density).dp
Card (
shape = MaterialTheme.shapes.medium,
modifier = modifier,
onClick = if (!displayModify) {
onClick
} else {
{ }
}
) {
Row (
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(8.dp)
) {
Image (
bitmap = imageBitmap,
contentDescription = name,
filterQuality = FilterQuality.None,
modifier = Modifier
.padding(8.dp)
.size(dpSize)
)
Column(
modifier = Modifier
.padding(8.dp)
.weight(1f)
) {
Text(
text = name,
modifier = Modifier
)
Text(
text = "$obtainedCharacters of $totalCharacters characters obtained",
fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier
)
}
if (displayModify) {
Row (
modifier = Modifier,
horizontalArrangement = Arrangement.End,
) {
IconButton(
onClick = onClickModify
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "Edit"
)
}
IconButton(
onClick = onClickDelete
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete"
)
}
}
}
}
}
}
@@ -0,0 +1,6 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
interface CardScreenController {
fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit)
fun deleteCard(cardId: Long, onDeleted: () -> Unit)
}
@@ -0,0 +1,34 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import kotlinx.coroutines.launch
class CardScreenControllerImpl(
private val componentActivity: ComponentActivity,
) : CardScreenController {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
override fun renameCard(cardId: Long, newName: String, onRenamed: (String) -> Unit) {
componentActivity.lifecycleScope.launch {
database
.cardDao()
.renameCard(cardId.toInt(), newName)
onRenamed(newName)
}
}
override fun deleteCard(cardId: Long, onDeleted: () -> Unit) {
componentActivity.lifecycleScope.launch {
database
.cardDao()
.deleteCard(cardId)
onDeleted()
}
}
}
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.screens
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
@@ -14,14 +14,13 @@ import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch
@Composable
fun DiMScreen(
fun CardViewScreen(
navController: NavController,
dimId: Long
) {
@@ -0,0 +1,145 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import android.util.Log
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CardDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardDeleteDialog
import com.github.nacabaro.vbhelper.screens.cardScreen.dialogs.CardRenameDialog
import com.github.nacabaro.vbhelper.source.DexRepository
import kotlinx.coroutines.launch
@Composable
fun CardsScreen(
navController: NavController,
cardScreenController: CardScreenControllerImpl
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val dexRepository = DexRepository(application.container.db)
val cardList = remember { mutableStateOf<List<CardDtos.CardProgress>>(emptyList()) }
val selectedCard = remember { mutableStateOf<CardDtos.CardProgress?>(null) }
var clickedDelete by remember { mutableStateOf(false) }
var clickedRename by remember { mutableStateOf(false) }
var modifyCards by remember { mutableStateOf(false) }
LaunchedEffect(dexRepository) {
coroutineScope.launch {
val newDimList = dexRepository.getAllDims()
cardList.value = newDimList
}
}
Scaffold (
topBar = {
TopBanner(
text = "My cards",
onModifyClick = {
modifyCards = !modifyCards
}
)
}
) { contentPadding ->
LazyColumn (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(cardList.value) {
CardEntry(
name = it.cardName,
logo = BitmapData(
bitmap = it.cardLogo,
width = it.logoWidth,
height = it.logoHeight
),
onClick = {
navController
.navigate(
NavigationItems
.CardView.route
.replace("{cardId}", "${it.cardId}")
)
},
obtainedCharacters = it.obtainedCharacters,
totalCharacters = it.totalCharacters,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
displayModify = modifyCards,
onClickModify = {
selectedCard.value = it
clickedRename = true
},
onClickDelete = {
selectedCard.value = it
clickedDelete = true
}
)
}
}
}
if (clickedRename) {
CardRenameDialog(
onDismiss = {
clickedRename = false
selectedCard.value = null
},
onRename = { newName ->
Log.d("CardsScreen", "New name: $newName")
Log.d("CardsScreen", "Card: ${selectedCard.value.toString()}")
cardScreenController
.renameCard(
cardId = selectedCard.value!!.cardId,
newName = newName,
onRenamed = {
clickedRename = false
selectedCard.value = null
}
)
},
currentName = selectedCard.value!!.cardName
)
}
if (clickedDelete) {
CardDeleteDialog(
cardName = selectedCard.value!!.cardName,
onDismiss = {
clickedDelete = false
selectedCard.value = null
},
onConfirm = {
cardScreenController
.deleteCard(
cardId = selectedCard.value!!.cardId,
onDeleted = {
clickedDelete = false
selectedCard.value = null
}
)
}
)
}
}
@@ -0,0 +1,51 @@
package com.github.nacabaro.vbhelper.screens.cardScreen.dialogs
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@Composable
fun CardDeleteDialog(
cardName: String,
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
Dialog(
onDismissRequest = onDismiss
) {
Card ( ) {
Column (
modifier = Modifier
.padding(16.dp)
) {
Text(text = "Are you sure you want to delete $cardName. This action will also delete all the characters raised from this card.")
Spacer(modifier = Modifier.padding(8.dp))
Row {
Button(
onClick = {
onDismiss()
}
) {
Text(text = "Confirm")
}
Button(
onClick = {
onConfirm()
}
) {
Text(text = "Delete")
}
}
}
}
}
}
@@ -0,0 +1,52 @@
package com.github.nacabaro.vbhelper.screens.cardScreen.dialogs
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@Composable
fun CardRenameDialog(
onDismiss: () -> Unit,
onRename: (String) -> Unit,
currentName: String
) {
var cardName by remember { mutableStateOf(currentName) }
Dialog(
onDismissRequest = onDismiss
) {
Card ( ) {
Column (
modifier = Modifier
.padding(16.dp)
) {
TextField(
value = cardName,
onValueChange = { cardName = it }
)
Spacer(modifier = Modifier.padding(8.dp))
Button(
onClick = {
onRename(cardName)
onDismiss()
}
) {
Text(text = "Rename")
}
}
}
}
}
@@ -30,7 +30,9 @@ import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.cardScreen.ChooseCard
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.source.isMissingSecrets
import com.github.nacabaro.vbhelper.source.proto.Secrets
@@ -53,6 +55,8 @@ fun ScanScreen(
val storageRepository = StorageRepository(application.container.db)
var nfcCharacter by remember { mutableStateOf<NfcCharacter?>(null) }
var cardsRead by remember { mutableStateOf<List<Card>?>(null) }
val context = LocalContext.current
LaunchedEffect(storageRepository) {
@@ -71,6 +75,7 @@ fun ScanScreen(
var readingScreen by remember { mutableStateOf(false) }
var writingScreen by remember { mutableStateOf(false) }
var cardSelectScreen by remember { mutableStateOf(false) }
var isDoneReadingCharacter by remember { mutableStateOf(false) }
var isDoneSendingCard by remember { mutableStateOf(false) }
var isDoneWritingCharacter by remember { mutableStateOf(false) }
@@ -85,15 +90,33 @@ fun ScanScreen(
}
override fun onResume() {
scanScreenController.onClickRead(secrets!!) {
isDoneReadingCharacter = true
}
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
}
)
scanScreenController.onClickRead(secrets!!) {
isDoneReadingCharacter = true
}
scanScreenController.onClickRead(
secrets = secrets!!,
onComplete = {
isDoneReadingCharacter = true
},
onMultipleCards = { cards ->
cardsRead = cards
readingScreen = false
cardSelectScreen = true
isDoneReadingCharacter = true
}
)
}
onDispose {
if(readingScreen) {
@@ -149,7 +172,7 @@ fun ScanScreen(
}
}
if (isDoneReadingCharacter) {
if (isDoneReadingCharacter && !cardSelectScreen) {
readingScreen = false
navController.navigate(NavigationItems.Home.route)
} else if (isDoneSendingCard && isDoneWritingCharacter) {
@@ -181,6 +204,14 @@ fun ScanScreen(
scanScreenController.cancelRead()
}
}
} else if (cardSelectScreen) {
ChooseCard(
cards = cardsRead!!,
onCardSelected = { card ->
cardSelectScreen = false
scanScreenController.flushCharacter(card.id)
}
)
} else {
ChooseConnectOption(
onClickRead = when {
@@ -290,11 +321,12 @@ fun ScanScreenPreview() {
) {
}
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {}
override fun flushCharacter(cardId: Long) {}
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit) {}
override fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
override fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit) {}
override fun cancelRead() {}
override fun characterFromNfc(nfcCharacter: NfcCharacter): String { return "" }
override fun characterFromNfc(nfcCharacter: NfcCharacter, onMultipleCards: (List<Card>, NfcCharacter) -> Unit): String { return "" }
override suspend fun characterToNfc(characterId: Long): NfcCharacter? { return null }
},
characterId = null,
@@ -2,12 +2,13 @@ package com.github.nacabaro.vbhelper.screens.scanScreen
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.source.proto.Secrets
import kotlinx.coroutines.flow.Flow
interface ScanScreenController {
val secretsFlow: Flow<Secrets>
fun onClickRead(secrets: Secrets, onComplete: ()->Unit)
fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit)
fun onClickCheckCard(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit)
fun onClickWrite(secrets: Secrets, nfcCharacter: NfcCharacter, onComplete: () -> Unit)
@@ -16,6 +17,11 @@ interface ScanScreenController {
fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener)
fun unregisterActivityLifecycleListener(key: String)
fun characterFromNfc(nfcCharacter: NfcCharacter): String
fun flushCharacter(cardId: Long)
fun characterFromNfc(
nfcCharacter: NfcCharacter,
onMultipleCards: (List<Card>, NfcCharacter) -> Unit
): String
suspend fun characterToNfc(characterId: Long): NfcCharacter?
}
@@ -15,6 +15,7 @@ import com.github.cfogrady.vbnfc.be.BENfcCharacter
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.FromNfcConverter
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.ToNfcConverter
import com.github.nacabaro.vbhelper.source.getCryptographicTransformerMap
@@ -31,7 +32,7 @@ class ScanScreenControllerImpl(
private val registerActivityLifecycleListener: (String, ActivityLifecycleListener)->Unit,
private val unregisterActivityLifecycleListener: (String)->Unit,
): ScanScreenController {
private var lastScannedCharacter: NfcCharacter? = null
private val nfcAdapter: NfcAdapter
init {
@@ -43,10 +44,14 @@ class ScanScreenControllerImpl(
checkSecrets()
}
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit) {
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit) {
handleTag(secrets) { tagCommunicator ->
val character = tagCommunicator.receiveCharacter()
val resultMessage = characterFromNfc(character)
val resultMessage = characterFromNfc(character) { cards, nfcCharacter ->
lastScannedCharacter = nfcCharacter
onMultipleCards(cards)
}
onComplete.invoke()
resultMessage
}
@@ -156,11 +161,14 @@ class ScanScreenControllerImpl(
componentActivity.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
}
override fun characterFromNfc(nfcCharacter: NfcCharacter): String {
override fun characterFromNfc(
nfcCharacter: NfcCharacter,
onMultipleCards: (List<Card>, NfcCharacter) -> Unit
): String {
val nfcConverter = FromNfcConverter(
componentActivity = componentActivity
)
return nfcConverter.addCharacter(nfcCharacter)
return nfcConverter.addCharacter(nfcCharacter, onMultipleCards)
}
override suspend fun characterToNfc(characterId: Long): NfcCharacter {
@@ -172,4 +180,17 @@ class ScanScreenControllerImpl(
Log.d("CharacterType", character.toString())
return character
}
override fun flushCharacter(cardId: Long) {
val nfcConverter = FromNfcConverter(
componentActivity = componentActivity
)
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
if (lastScannedCharacter != null) {
nfcConverter.addCharacterUsingCard(lastScannedCharacter!!, cardId)
lastScannedCharacter = null
}
}
}
}
@@ -0,0 +1,48 @@
package com.github.nacabaro.vbhelper.screens.cardScreen
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.domain.card.Card
import com.github.nacabaro.vbhelper.screens.scanScreen.cardSelect.ScanCardEntry
import com.github.nacabaro.vbhelper.utils.BitmapData
@Composable
fun ChooseCard(
cards: List<Card>,
onCardSelected: (Card) -> Unit
) {
Scaffold (
topBar = {
TopBanner(
text = "Choose card",
)
}
) { contentPadding ->
LazyColumn (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(cards) {
ScanCardEntry(
name = it.name,
logo = BitmapData(
it.logo,
it.logoWidth,
it.logoHeight
),
onClick = {
onCardSelected(it)
},
modifier = Modifier
.padding(8.dp)
)
}
}
}
}
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.components
package com.github.nacabaro.vbhelper.screens.scanScreen.cardSelect
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@@ -21,11 +21,9 @@ import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
@Composable
fun DexDiMEntry(
fun ScanCardEntry(
name: String,
logo: BitmapData,
obtainedCharacters: Int,
totalCharacters: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
@@ -56,17 +54,12 @@ fun DexDiMEntry(
Column(
modifier = Modifier
.padding(8.dp)
.weight(1f)
) {
Text(
text = name,
modifier = Modifier
)
Text(
text = "$obtainedCharacters of $totalCharacters characters obtained",
fontFamily = MaterialTheme.typography.labelSmall.fontFamily,
fontSize = MaterialTheme.typography.labelSmall.fontSize,
modifier = Modifier
)
}
}
}
@@ -21,17 +21,70 @@ class FromNfcConverter (
) {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
fun addCharacter(nfcCharacter: NfcCharacter): String {
fun addCharacterUsingCard(
nfcCharacter: NfcCharacter,
cardId: Long
): String {
val cardData = database
.cardDao()
.getDimById(nfcCharacter.dimId.toInt())
.getCardById(cardId)
if (cardData == null)
if (cardData == null) {
return "Card not found"
}
return insertCharacter(nfcCharacter, cardData)
}
fun addCharacter(
nfcCharacter: NfcCharacter,
onMultipleCards: (List<Card>, NfcCharacter) -> Unit
): String {
val appReservedCardId = nfcCharacter
.appReserved2[0].toLong()
var cardData: Card? = null
if (appReservedCardId != 0L) {
val fetchedCard = database
.cardDao()
.getCardById(appReservedCardId)
if (fetchedCard == null) {
return "Card not found"
} else if (fetchedCard.cardId == nfcCharacter.dimId.toInt()) {
cardData = fetchedCard
}
}
if (cardData == null) {
val allCards = database
.cardDao()
.getCardByCardId(nfcCharacter.dimId.toInt())
if (allCards.isEmpty())
return "Card not found"
if (allCards.size > 1) {
onMultipleCards(allCards, nfcCharacter)
return "Multiple cards found"
}
cardData = allCards[0]
}
return insertCharacter(nfcCharacter, cardData)
}
private fun insertCharacter(
nfcCharacter: NfcCharacter,
cardData: Card
): String {
val cardCharData = database
.characterDao()
.getCharacterByMonIndex(nfcCharacter.charIndex.toInt(), cardData.id)
@@ -92,7 +145,7 @@ class FromNfcConverter (
return "Done reading character!"
}
private fun updateCardProgress(
@@ -1,6 +1,7 @@
package com.github.nacabaro.vbhelper.screens.scanScreen.converters
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.util.Log
import androidx.activity.ComponentActivity
import com.github.cfogrady.vbnfc.be.BENfcCharacter
@@ -84,7 +85,7 @@ class ToNfcConverter(
transformationHistory = paddedTransformationArray,
vitalHistory = generateVitalsHistoryArray(characterId),
appReserved1 = ByteArray(12) {0},
appReserved2 = Array(3) {0u},
appReserved2 = generateUShortAppReserved(userCharacter),
generation = vbData.generation.toUShort(),
totalTrophies = vbData.totalTrophies.toUShort(),
specialMissions = watchSpecialMissions.toTypedArray()
@@ -94,6 +95,23 @@ class ToNfcConverter(
}
private suspend fun generateUShortAppReserved(
userCharacter: UserCharacter
): Array<UShort> {
val cardData = database
.cardDao()
.getCardByCharacterId(userCharacter.id)
val appReserved = Array<UShort>(3) {
0u
}
appReserved[0] = cardData.id.toUShort()
return appReserved
}
private suspend fun generateSpecialMissionsArray(
characterId: Long
@@ -226,16 +244,23 @@ class ToNfcConverter(
.getTransformationHistory(characterId)!!
.map {
val date = Date(it.transformationDate)
val calendar = android.icu.util.GregorianCalendar()
val calendar = android.icu.util.GregorianCalendar(TimeZone.getTimeZone("UTC"))
calendar.time = date
Log.d(
"TransformationHistory",
"Year: ${calendar.get(Calendar.YEAR)}, " +
"Month: ${calendar.get(Calendar.MONTH) + 1}, " +
"Day: ${calendar.get(Calendar.DAY_OF_MONTH)}"
)
NfcCharacter.Transformation(
toCharIndex = it.monIndex.toUByte(),
year = calendar
.get(Calendar.YEAR)
.toUShort(),
month = calendar
.get(Calendar.MONTH)
month = (calendar
.get(Calendar.MONTH) + 1)
.toUByte(),
day = calendar
.get(Calendar.DAY_OF_MONTH)
@@ -35,6 +35,7 @@ fun CreditsScreen(
SettingsEntry(title = "cfogrady", description = "Developed vb-lib-nfc and part of this application.") { }
SettingsEntry(title = "nacabaro", description = "Developed this application.") { }
SettingsEntry(title = "lightheel", description = "Developing the battling part for this application, including server. Still in the works.") { }
SettingsEntry(title = "shvstrz", description = "Designing the app icon in SVG.") { }
}
}
}
@@ -129,7 +129,7 @@ class SettingsScreenControllerImpl(
val dimId = database
.cardDao()
.insertNewDim(cardModel)
.insertNewCard(cardModel)
val cardProgress = CardProgress(
cardId = dimId,
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M200,760h57l391,-391 -57,-57 -391,391v57ZM120,840v-170l528,-527q12,-11 26.5,-17t30.5,-6q16,0 31,6t26,18l55,56q12,11 17.5,26t5.5,30q0,16 -5.5,30.5T817,313L290,840L120,840ZM760,256 L704,200 760,256ZM619,341 L591,312 648,369 619,341Z"
android:fillColor="#000000"/>
</vector>
@@ -1,30 +1,39 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
android:viewportWidth="512"
android:viewportHeight="512">
<group android:scaleX="0.79"
android:scaleY="0.79"
android:translateX="53.76"
android:translateY="53.76">
<group>
<clip-path
android:pathData="M0,0h512v512h-512z"/>
<path
android:pathData="M0,0h512v512h-512z"
android:fillColor="#000000"/>
<path
android:pathData="M164,107L186.19,107L186.19,129C186.19,132.86 189.32,136 193.19,136L219,136L235.95,163.3C236.53,164.24 237.9,165 239,165L267,165C268.1,165 269.47,164.24 270.05,163.3L287,136L312.8,136C316.66,136 319.8,132.86 319.8,129L319.8,107L313.1,107L313.1,128C313.1,129.1 312.2,130 311.1,130L283,130L267.04,156.29C266.46,157.23 265.1,158 264,158L242,158C240.9,158 239.54,157.23 238.96,156.29L223,130L194.94,130C193.83,130 192.94,129.1 192.94,128L192.94,107L217.75,107L229,92L277,92L288,107L342,107L342,405L319.81,405L319.81,383C319.81,379.14 316.68,376 312.81,376L287,376L270.05,348.7C269.47,347.76 268.1,347 267,347L239,347C237.9,347 236.53,347.76 235.95,348.7L219,376L193.2,376C189.34,376 186.2,379.14 186.2,383L186.2,405L192.94,405L192.94,384C192.94,382.9 193.83,382 194.94,382L223,382L238.96,355.71C239.54,354.77 240.9,354 242,354L264,354C265.1,354 266.46,354.77 267.04,355.71L283,382L311.06,382C312.17,382 313.06,382.9 313.06,384L313.06,405L288.25,405L277,420L229,420L218,405L164,405L164,107Z"
android:fillColor="#EBEBEB"/>
<path
android:pathData="M212.61,181.5L299.39,181.5C306.15,181.5 311.63,186.98 311.63,193.74L311.63,318.26C311.63,325.02 306.15,330.5 299.39,330.5L212.61,330.5C205.85,330.5 200.37,325.02 200.37,318.26L200.37,193.74C200.37,186.98 205.85,181.5 212.61,181.5Z"
android:fillColor="#000000"
android:strokeColor="#00000000"/>
<path
android:pathData="M0,249L192.88,249L214,208L232.38,249L253.69,171L274.85,255L296,194L310.27,249L512,249L512,269L296,269L294,261L274.85,331L262.59,283.5L253.69,249L232.38,316L213.38,255L205.75,269L0,269L0,249Z"
android:fillColor="#EBEBEB"/>
<group>
<clip-path
android:pathData="M-1024,-1024h2048v2048h-2048zM0,249L192.88,249L214,208L232.38,249L253.69,171L274.85,255L296,194L310.27,249L512,249L512,269L296,269L294,261L274.85,331L262.59,283.5L253.69,249L232.38,316L213.38,255L205.75,269L0,269L0,249Z"/>
<path
android:pathData="M0,249L192.88,249L214,208L232.38,249L253.69,171L274.85,255L296,194L310.27,249L512,249L512,269L296,269L294,261L274.85,331L262.59,283.5L253.69,249L232.38,316L213.38,255L205.75,269L0,269L0,249Z"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#EBEBEB"
android:strokeColor="#000000"
android:strokeLineCap="square"/>
</group>
</group>
</group>
</vector>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#000000</color>
</resources>