Compare commits

..
13 Commits
Author SHA1 Message Date
Nacho 93f568dd8d Create a small test widget, I just wanted to learn hoe Jetpack Compose Glance works? 2025-07-27 19:48:01 +02:00
Nacho d46769b0cb Added credits for the app in preparation for version 0.1 2025-07-26 22:26:41 +02:00
Nacho fce05870c5 This should allow the app to build, otherwise it won't or it will crash 2025-07-26 14:23:53 +02:00
nacabaroandGitHub d749af0a9c Merge pull request #27 from cfogrady/FixStageAndAttributeFromCharacter
Fix stage and attribute coming from character instead of UserCharacter
2025-04-08 15:35:13 +02:00
Christopher O'Grady 076f6bfacf Fix stage and attribute coming from character instead of UserCharacter 2025-04-05 20:31:03 -04:00
nacabaroandGitHub a4f3b701e5 Merge pull request #26 from cfogrady/RemoveDuplicatedData
Remove duplicated stage and attribute from UserCharacter
2025-03-22 00:34:07 +01:00
Christopher O'Grady da134247b1 Remove duplicated stage and attribute from UserCharacter
These fields are on the Character entity.
2025-03-19 15:33:52 -04:00
nacabaroandGitHub e36a700d9f Merge pull request #25 from nacabaro/database/adventure
Adventure mode
2025-01-26 02:01:24 +01:00
Nacho fb6b6d2825 While I'm at it...
- Resized some buttons in the dialogs
- Added a dismiss button in get item dialog after adventure and in the send on adventure choose time dialogs
- Moved the export/import data lower in the settings screen
2025-01-26 01:59:43 +01:00
Nacho e17f6c23e4 Renaming terms and other bits and bobs
- Changed adventure database so that the original intended time is also stored with the final time. This will come useful once the algorithm for determining which object to give is made.
2025-01-26 01:40:32 +01:00
Nacho 409474b5d1 Phew
- Added adventure screen, dialogs, components and controllers needed to rock.
- Modified DTOs so that it is possible to tell if a character is in adventure or not
- Updated layout of items database
- Fixed an issue with importing bems where the character name would be read incorrectly
- Also added a ton of checks
2025-01-26 01:25:25 +01:00
nacabaroandGitHub 547318864b Merge pull request #24 from nacabaro/cleanup
Bigger icons plus material obscurity
2025-01-21 13:35:43 +01:00
nacabaroandGitHub 1a38cefb92 Merge pull request #23 from nacabaro/cleanup
Cleanup MainActivity
2025-01-21 13:12:38 +01:00
53 changed files with 1627 additions and 296 deletions
+4 -1
View File
@@ -15,7 +15,7 @@ android {
minSdk = 28
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionName = "Alpha 0.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -91,4 +91,7 @@ dependencies {
implementation("com.google.android.material:material:1.2.0")
implementation(libs.protobuf.javalite)
implementation("androidx.compose.material:material")
implementation("androidx.glance:glance:1.1.1")
implementation("androidx.glance:glance-appwidget:1.1.1")
implementation("androidx.work:work-runtime-ktx:2.9.0")
}
+9
View File
@@ -27,6 +27,15 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".widget.CharacterDisplayWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
</application>
</manifest>
Binary file not shown.
@@ -9,9 +9,12 @@ import androidx.compose.runtime.Composable
import com.github.nacabaro.vbhelper.navigation.AppNavigation
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.navigation.AppNavigationHandlers
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
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.storageScreen.StorageScreenControllerImpl
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@@ -39,6 +42,9 @@ class MainActivity : ComponentActivity() {
)
val settingsScreenController = SettingsScreenControllerImpl(this)
val itemsScreenController = ItemsScreenControllerImpl(this)
val adventureScreenController = AdventureScreenControllerImpl(this)
val storageScreenController = StorageScreenControllerImpl(this)
val homeScreenController = HomeScreenControllerImpl(this)
super.onCreate(savedInstanceState)
@@ -49,7 +55,10 @@ class MainActivity : ComponentActivity() {
MainApplication(
scanScreenController = scanScreenController,
settingsScreenController = settingsScreenController,
itemsScreenController = itemsScreenController
itemsScreenController = itemsScreenController,
adventureScreenController = adventureScreenController,
homeScreenController = homeScreenController,
storageScreenController = storageScreenController
)
}
}
@@ -77,13 +86,20 @@ class MainActivity : ComponentActivity() {
private fun MainApplication(
scanScreenController: ScanScreenControllerImpl,
settingsScreenController: SettingsScreenControllerImpl,
itemsScreenController: ItemsScreenControllerImpl
itemsScreenController: ItemsScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl,
storageScreenController: StorageScreenControllerImpl,
homeScreenController: HomeScreenControllerImpl,
) {
AppNavigation(
applicationNavigationHandlers = AppNavigationHandlers(
settingsScreenController,
scanScreenController,
itemsScreenController
itemsScreenController,
adventureScreenController,
storageScreenController,
homeScreenController
)
)
}
@@ -35,6 +35,7 @@ fun CharacterEntry(
icon: BitmapData,
modifier: Modifier = Modifier,
obscure: Boolean = false,
disabled: Boolean = false,
shape: Shape = MaterialTheme.shapes.medium,
multiplier: Int = 4,
onClick: () -> Unit = { }
@@ -48,7 +49,10 @@ fun CharacterEntry(
Card(
shape = shape,
onClick = onClick,
onClick = when (disabled) {
true -> { {} }
false -> onClick
},
modifier = modifier
.aspectRatio(1f)
.padding(8.dp)
@@ -23,7 +23,8 @@ fun TopBanner(
modifier: Modifier = Modifier,
onGearClick: (() -> Unit)? = null,
onBackClick: (() -> Unit)? = null,
onScanClick: (() -> Unit)? = null
onScanClick: (() -> Unit)? = null,
onAdventureClick: (() -> Unit)? = null
) {
Box( // Use Box to overlay elements
modifier = modifier
@@ -49,6 +50,17 @@ fun TopBanner(
contentDescription = "Settings"
)
}
} else if (onAdventureClick != null) {
IconButton(
onClick = onAdventureClick,
modifier = Modifier
.align(Alignment.CenterEnd) // Place gear icon at the end
) {
Icon(
painter = painterResource(R.drawable.baseline_fort_24), // Use a gear icon
contentDescription = "Adventure"
)
}
}
if (onScanClick != null) {
@@ -0,0 +1,44 @@
package com.github.nacabaro.vbhelper.daos
import androidx.room.Dao
import androidx.room.Query
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
@Dao
interface AdventureDao {
@Query("""
INSERT INTO Adventure (characterId, originalDuration, finishesAdventure)
VALUES (:characterId, :originalDuration, strftime('%s', 'now') + :timeInSeconds)
""")
fun insertNewAdventure(characterId: Long, originalDuration: Long, timeInSeconds: Long)
@Query("""
SELECT COUNT(*) FROM Adventure
""")
fun getAdventureCount(): Int
@Query("""
SELECT
uc.*,
c.stage,
c.attribute,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard,
a.finishesAdventure AS finishesAdventure,
a.originalDuration AS originalTimeInMinutes
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
JOIN Adventure a ON uc.id = a.characterId
""")
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites>
@Query("""
DELETE FROM Adventure
WHERE characterId = :characterId
""")
suspend fun deleteAdventure(characterId: Long)
}
@@ -6,40 +6,48 @@ import com.github.nacabaro.vbhelper.dtos.ItemDtos
@Dao
interface ItemDao {
@Query("""
SELECT Items.*, UserItems.quantity
@Query(
"""
SELECT *
FROM Items
LEFT JOIN UserItems ON Items.id = UserItems.itemId
ORDER BY Items.itemIcon ASC
""")
"""
)
suspend fun getAllItems(): List<ItemDtos.ItemsWithQuantities>
@Query("""
SELECT Items.*, UserItems.quantity
@Query(
"""
SELECT *
FROM Items
JOIN UserItems ON Items.id = UserItems.itemId
""")
WHERE quantity > 0
"""
)
suspend fun getAllUserItems(): List<ItemDtos.ItemsWithQuantities>
@Query("""
SELECT Items.*, UserItems.quantity
@Query(
"""
SELECT *
FROM Items
JOIN UserItems ON Items.id = UserItems.itemId
WHERE UserItems.itemId = :itemId
""")
fun getUserItem(itemId: Long): ItemDtos.ItemsWithQuantities
WHERE Items.id = :itemId
"""
)
fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities
@Query("""
UPDATE UserItems
@Query(
"""
UPDATE Items
SET quantity = quantity - 1
WHERE itemId = :itemId
""")
WHERE id = :itemId
"""
)
fun useItem(itemId: Long)
@Query("""
UPDATE UserItems
SET quantity = quantity - :itemAmount
WHERE itemId = :itemId
""")
@Query(
"""
UPDATE Items
SET quantity = quantity + :itemAmount
WHERE id = :itemId
"""
)
suspend fun purchaseItem(itemId: Long, itemAmount: Int)
}
@@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Upsert
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.domain.device_data.TransformationHistory
@@ -45,17 +46,46 @@ interface UserCharacterDao {
"""
SELECT
uc.*,
c.stage,
c.attribute,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard
c.name as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
LEFT JOIN Adventure a ON a.characterId = uc.id
"""
)
suspend fun getAllCharacters(): List<CharacterDtos.CharacterWithSprites>
@Query(
"""
SELECT
uc.*,
c.stage,
c.attribute,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
c.name as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId = uc.id as isInAdventure
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.id = :id
""")
suspend fun getCharacterWithSprites(id: Long): CharacterDtos.CharacterWithSprites
@Query("SELECT * FROM UserCharacter WHERE id = :id")
suspend fun getCharacter(id: Long): UserCharacter
@@ -66,17 +96,23 @@ interface UserCharacterDao {
"""
SELECT
uc.*,
c.stage,
c.attribute,
c.sprite1 AS spriteIdle,
c.spritesWidth AS spriteWidth,
c.spritesHeight AS spriteHeight,
d.isBEm as isBemCard
c.name as nameSprite,
c.nameWidth as nameSpriteWidth,
c.nameHeight as nameSpriteHeight,
d.isBEm as isBemCard,
a.characterId as isInAdventure
FROM UserCharacter uc
JOIN Character c ON uc.charId = c.id
JOIN Card d ON c.dimId = d.id
LEFT JOIN Adventure a ON a.characterId = uc.id
WHERE uc.isActive = 1
LIMIT 1
"""
)
""")
suspend fun getActiveCharacter(): CharacterDtos.CharacterWithSprites?
@Query("DELETE FROM UserCharacter WHERE id = :id")
@@ -87,4 +123,15 @@ interface UserCharacterDao {
@Query("UPDATE UserCharacter SET isActive = 1 WHERE id = :id")
fun setActiveCharacter(id: Long)
@Query(
"""
SELECT c.*
FROM Character c
join UserCharacter uc on c.id = uc.charId
where uc.id = :charId
LIMIT 1
"""
)
suspend fun getCharacterInfo(charId: Long): Character
}
@@ -2,6 +2,7 @@ package com.github.nacabaro.vbhelper.database
import androidx.room.Database
import androidx.room.RoomDatabase
import com.github.nacabaro.vbhelper.daos.AdventureDao
import com.github.nacabaro.vbhelper.daos.CharacterDao
import com.github.nacabaro.vbhelper.daos.DexDao
import com.github.nacabaro.vbhelper.daos.DiMDao
@@ -10,12 +11,12 @@ import com.github.nacabaro.vbhelper.daos.UserCharacterDao
import com.github.nacabaro.vbhelper.domain.characters.Character
import com.github.nacabaro.vbhelper.domain.characters.Card
import com.github.nacabaro.vbhelper.domain.Sprites
import com.github.nacabaro.vbhelper.domain.characters.Adventure
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.TransformationHistory
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.domain.items.Items
import com.github.nacabaro.vbhelper.domain.items.UserItems
@Database(
version = 1,
@@ -28,7 +29,7 @@ import com.github.nacabaro.vbhelper.domain.items.UserItems
TransformationHistory::class,
Dex::class,
Items::class,
UserItems::class
Adventure::class
]
)
abstract class AppDatabase : RoomDatabase() {
@@ -37,4 +38,5 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun userCharacterDao(): UserCharacterDao
abstract fun dexDao(): DexDao
abstract fun itemDao(): ItemDao
abstract fun adventureDao(): AdventureDao
}
@@ -0,0 +1,22 @@
package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
@Entity(
foreignKeys = [
ForeignKey(
entity = UserCharacter::class,
parentColumns = ["id"],
childColumns = ["characterId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Adventure(
@PrimaryKey val characterId: Long,
val originalDuration: Long,
val finishesAdventure: Long
)
@@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.domain.characters
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.ForeignKey
import com.github.cfogrady.vbnfc.data.NfcCharacter
@Entity(
foreignKeys = [
@@ -14,13 +15,18 @@ import androidx.room.ForeignKey
)
]
)
/*
* Character represents a character on a DIM card. There should only be one of these per dimId
* and monIndex.
* TODO: Customs will mean this should be unique per cardName and monIndex
*/
data class Character (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val dimId: Long,
val monIndex: Int,
val name: ByteArray,
val stage: Int, // These should be replaced with enums
val attribute: Int, // This one too
val attribute: NfcCharacter.Attribute, // This one too
val baseHp: Int,
val baseBp: Int,
val baseAp: Int,
@@ -17,11 +17,12 @@ import com.github.nacabaro.vbhelper.domain.characters.Character
)
]
)
/**
* UserCharacter represents and instance of a character. The charId should map to a Character
*/
data class UserCharacter (
@PrimaryKey(autoGenerate = true) val id: Long = 0,
var charId: Long,
var stage: Int,
var attribute: NfcCharacter.Attribute,
var ageInDays: Int,
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
var mood: Int,
@@ -10,5 +10,6 @@ data class Items(
val description: String,
val itemIcon: Int,
val itemLength: Int,
val price: Int
val price: Int,
val quantity: Int
)
@@ -1,20 +0,0 @@
package com.github.nacabaro.vbhelper.domain.items
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(
foreignKeys = [
ForeignKey(
entity = Items::class,
parentColumns = ["id"],
childColumns = ["itemId"],
onDelete = ForeignKey.CASCADE
)
]
)
data class UserItems(
@PrimaryKey val itemId: Long,
val quantity: Int,
)
@@ -27,7 +27,11 @@ object CharacterDtos {
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val isBemCard: Boolean
val nameSprite: ByteArray,
val nameSpriteWidth: Int,
val nameSpriteHeight: Int,
val isBemCard: Boolean,
val isInAdventure: Boolean
)
data class DiMInfo(
@@ -51,4 +55,31 @@ object CharacterDtos {
val spriteHeight: Int,
val discoveredOn: Long?
)
data class AdventureCharacterWithSprites(
var id: Long = 0,
var charId: Long,
var stage: Int,
var attribute: NfcCharacter.Attribute,
var ageInDays: Int,
var nextAdventureMissionStage: Int, // next adventure mission stage on the character's dim
var mood: Int,
var vitalPoints: Int,
var transformationCountdown: Int,
var injuryStatus: NfcCharacter.InjuryStatus,
var trophies: Int,
var currentPhaseBattlesWon: Int,
var currentPhaseBattlesLost: Int,
var totalBattlesWon: Int,
var totalBattlesLost: Int,
var activityLevel: Int,
var heartRateCurrent: Int,
var characterType: DeviceType,
val spriteIdle: ByteArray,
val spriteWidth: Int,
val spriteHeight: Int,
val isBemCard: Boolean,
val finishesAdventure: Long,
val originalTimeInMinutes: Long
)
}
@@ -11,4 +11,13 @@ object ItemDtos {
val price: Int,
val quantity: Int,
)
data class PurchasedItem(
val itemId: Long,
val itemName: String,
val itemDescription: String,
val itemIcon: Int,
val itemLength: Int,
val itemAmount: Int
)
}
@@ -16,15 +16,23 @@ import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreen
import com.github.nacabaro.vbhelper.screens.scanScreen.ScanScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.settingsScreen.SettingsScreen
import com.github.nacabaro.vbhelper.screens.SpriteViewer
import com.github.nacabaro.vbhelper.screens.StorageScreen
import com.github.nacabaro.vbhelper.screens.homeScreens.HomeScreenControllerImpl
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ChooseCharacterScreen
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
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.settingsScreen.CreditsScreen
import com.github.nacabaro.vbhelper.screens.storageScreen.StorageScreenControllerImpl
data class AppNavigationHandlers(
val settingsScreenController: SettingsScreenControllerImpl,
val scanScreenController: ScanScreenControllerImpl,
val itemsScreenController: ItemsScreenControllerImpl
val itemsScreenController: ItemsScreenControllerImpl,
val adventureScreenController: AdventureScreenControllerImpl,
val storageScreenController: StorageScreenControllerImpl,
val homeScreenController: HomeScreenControllerImpl
)
@Composable
@@ -49,12 +57,15 @@ fun AppNavigation(
}
composable(NavigationItems.Home.route) {
HomeScreen(
navController = navController
navController = navController,
homeScreenController = applicationNavigationHandlers.homeScreenController
)
}
composable(NavigationItems.Storage.route) {
StorageScreen(
navController = navController
navController = navController,
adventureScreenController = applicationNavigationHandlers.adventureScreenController,
storageScreenController = applicationNavigationHandlers.storageScreenController
)
}
composable(NavigationItems.Scan.route) {
@@ -108,6 +119,18 @@ fun AppNavigation(
)
}
}
composable(NavigationItems.Adventure.route) {
AdventureScreen(
navController = navController,
storageScreenController = applicationNavigationHandlers
.adventureScreenController
)
}
composable(NavigationItems.Credits.route) {
CreditsScreen(
navController = navController
)
}
}
}
}
@@ -19,6 +19,7 @@ fun BottomNavigationBar(navController: NavController) {
NavigationItems.Dex,
NavigationItems.Storage,
)
NavigationBar {
val currentBackStackEntry = navController.currentBackStackEntryAsState()
val currentRoute = currentBackStackEntry.value?.destination?.route
@@ -19,4 +19,6 @@ sealed class NavigationItems (
object MyItems : NavigationItems("MyItems", R.drawable.baseline_data_24, "My items")
object ItemsStore : NavigationItems("ItemsStore", R.drawable.baseline_data_24, "Items store")
object ApplyItem : NavigationItems("ApplyItem/{itemId}", R.drawable.baseline_data_24, "Apply item")
object Adventure : NavigationItems("Adventure", R.drawable.baseline_fort_24, "Adventure")
object Credits : NavigationItems("Credits", R.drawable.baseline_data_24, "Credits")
}
@@ -1,194 +0,0 @@
package com.github.nacabaro.vbhelper.screens
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.device_data.UserCharacter
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun StorageScreen(
navController: NavController
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
Scaffold (
topBar = { TopBanner(text = "My characters") }
) { contentPadding ->
if (monList.value.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(
text = "Nothing to see here",
textAlign = TextAlign.Center,
modifier = Modifier
)
}
}
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding())
) {
items(monList.value) { index ->
CharacterEntry(
icon = BitmapData(
bitmap = index.spriteIdle,
width = index.spriteWidth,
height = index.spriteHeight
),
onClick = {
selectedCharacter = index.id
}
)
}
}
if (selectedCharacter != null) {
StorageDialog(
characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null },
onClickSetActive = {
coroutineScope.launch {
withContext(Dispatchers.IO) {
storageRepository.setActiveCharacter(selectedCharacter!!)
selectedCharacter = null
}
navController.navigate(NavigationItems.Home.route)
}
},
onSendToBracelet = {
navController.navigate(
NavigationItems.Scan.route.replace(
"{characterId}",
selectedCharacter.toString()
)
)
}
)
}
}
}
@Composable
fun StorageDialog(
characterId: Long,
onDismissRequest: () -> Unit,
onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val character = remember { mutableStateOf<UserCharacter?>(null) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
character.value = storageRepository.getSingleCharacter(characterId)
}
}
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Card(
shape = RoundedCornerShape(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
if (character.value != null) {
Text(
text = character.value?.toString() ?: "Loading...",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(8.dp)
)
}
Row (
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
Button(
onClick = onSendToBracelet
) {
Text(text = "Send to bracelet")
}
Button(
onClick = onClickSetActive
) {
Text(text = "Set active")
}
Button(
onClick = onDismissRequest
) {
Text(text = "Close")
}
}
}
}
}
}
@@ -0,0 +1,68 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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
import java.util.Locale
@Composable
fun AdventureEntry(
icon: BitmapData,
timeLeft: Long,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
val bitmap = remember (icon.bitmap) { icon.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (icon.width * 4 / density).dp
Card(
onClick = onClick,
modifier = modifier
.padding(8.dp)
.fillMaxWidth()
) {
Row(
modifier = Modifier
.padding(8.dp)
.height(96.dp)
) {
Image(
bitmap = imageBitmap,
contentDescription = null,
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
)
Text(
text = when {
timeLeft < 0 -> "Adventure finished"
else -> "Time left: ${formatSeconds(timeLeft)}"
}
)
}
}
}
fun formatSeconds(totalSeconds: Long): String {
val hours = totalSeconds / 3600
val minutes = (totalSeconds % 3600) / 60
val seconds = totalSeconds % 60
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds)
}
@@ -0,0 +1,145 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
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.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.screens.itemsScreen.ObtainedItemDialog
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.Instant
@Composable
fun AdventureScreen(
navController: NavController,
storageScreenController: AdventureScreenControllerImpl
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val database = application.container.db
val storageRepository = StorageRepository(database)
val characterList = remember {
mutableStateOf<List<CharacterDtos.AdventureCharacterWithSprites>>(emptyList())
}
var obtainedItem by remember {
mutableStateOf<ItemDtos.PurchasedItem?>(null)
}
val currentTime by produceState(initialValue = Instant.now().epochSecond) {
while (true) {
value = Instant.now().epochSecond
delay(1000)
}
}
var cancelAdventureDialog by remember {
mutableStateOf<CharacterDtos.AdventureCharacterWithSprites?>(null)
}
LaunchedEffect(storageRepository) {
coroutineScope.launch {
characterList.value = storageRepository
.getAdventureCharacters()
}
}
Scaffold(
topBar = {
TopBanner(
text = "Adventure",
onBackClick = {
navController.popBackStack()
}
)
}
) { contentPadding ->
if (characterList.value.isEmpty()) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(text = "Nothing to see here")
}
} else {
LazyColumn(
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
items(characterList.value) {
AdventureEntry(
icon = BitmapData(
bitmap = it.spriteIdle,
width = it.spriteWidth,
height = it.spriteHeight
),
timeLeft = it.finishesAdventure - currentTime,
onClick = {
if (it.finishesAdventure < currentTime) {
storageScreenController
.getItemFromAdventure(it.id) { adventureResult ->
obtainedItem = adventureResult
}
} else {
cancelAdventureDialog = it
}
}
)
}
}
}
}
if (obtainedItem != null) {
ObtainedItemDialog(
obtainedItem = obtainedItem!!,
onClickDismiss = {
obtainedItem = null
}
)
}
if (cancelAdventureDialog != null) {
CancelAdventureDialog(
characterSprite = BitmapData(
bitmap = cancelAdventureDialog!!.spriteIdle,
width = cancelAdventureDialog!!.spriteWidth,
height = cancelAdventureDialog!!.spriteHeight
),
onDismissRequest = {
cancelAdventureDialog = null
},
onClickConfirm = {
storageScreenController.cancelAdventure(cancelAdventureDialog!!.id) {
navController.navigate(NavigationItems.Storage.route)
}
cancelAdventureDialog = null
}
)
}
}
@@ -0,0 +1,9 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import com.github.nacabaro.vbhelper.dtos.ItemDtos
interface AdventureScreenController {
fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long)
fun getItemFromAdventure(characterId: Long, onResult: (ItemDtos.PurchasedItem) -> Unit)
fun cancelAdventure(characterId: Long, onResult: () -> Unit)
}
@@ -0,0 +1,100 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
import kotlin.random.Random
class AdventureScreenControllerImpl(
private val componentActivity: ComponentActivity,
) : AdventureScreenController {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
override fun sendCharacterToAdventure(characterId: Long, timeInMinutes: Long) {
val finishesAdventureAt = timeInMinutes * 60
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
val characterData = database
.userCharacterDao()
.getCharacter(characterId)
if (characterData.isActive) {
database
.userCharacterDao()
.clearActiveCharacter()
}
database
.adventureDao()
.insertNewAdventure(characterId, timeInMinutes, finishesAdventureAt)
}
}
override fun getItemFromAdventure(
characterId: Long,
onResult: (ItemDtos.PurchasedItem) -> Unit
) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.adventureDao()
.deleteAdventure(characterId)
val generatedItem = generateItem(characterId)
onResult(generatedItem)
}
}
override fun cancelAdventure(characterId: Long, onResult: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database
.adventureDao()
.deleteAdventure(characterId)
componentActivity
.runOnUiThread {
Toast.makeText(
componentActivity,
"Adventure canceled",
Toast.LENGTH_SHORT
).show()
onResult()
}
}
}
private suspend fun generateItem(characterId: Long): ItemDtos.PurchasedItem {
val character = database
.userCharacterDao()
.getCharacterInfo(characterId)
val randomItem = database
.itemDao()
.getAllItems()
.random()
val random = ((Random.nextFloat() * character.stage) + 3).roundToInt()
database
.itemDao()
.purchaseItem(
itemId = randomItem.id,
itemAmount = random
)
return ItemDtos.PurchasedItem(
itemId = randomItem.id,
itemAmount = random,
itemName = randomItem.name,
itemIcon = randomItem.itemIcon,
itemLength = randomItem.itemLength,
itemDescription = randomItem.description
)
}
}
@@ -0,0 +1,73 @@
package com.github.nacabaro.vbhelper.screens.adventureScreen
import androidx.compose.foundation.Image
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.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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 androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
@Composable
fun CancelAdventureDialog(
characterSprite: BitmapData,
onDismissRequest: () -> Unit,
onClickConfirm: () -> Unit
) {
val bitmap = remember (characterSprite) { characterSprite.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (characterSprite.width * 4 / density).dp
Dialog(
onDismissRequest = onDismissRequest
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Row {
Image(
bitmap = imageBitmap,
contentDescription = null,
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
)
Text(
text = "Are you sure you want to cancel this character's adventure?"
)
}
Row(
modifier = Modifier
.padding(8.dp)
) {
Button(
onClick = onClickConfirm
) {
Text(text = "Confirm")
}
Spacer(modifier = Modifier.padding(4.dp))
Button(
onClick = onDismissRequest
) {
Text(text = "Dismiss")
}
}
}
}
}
}
@@ -15,7 +15,7 @@ import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.screens.itemsScreen.getIconResource
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
@@ -16,7 +16,7 @@ import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.ItemDisplay
import com.github.nacabaro.vbhelper.components.TransformationHistoryCard
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.screens.itemsScreen.getIconResource
import com.github.nacabaro.vbhelper.domain.device_data.BECharacterData
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
@@ -0,0 +1,46 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
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.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@Composable
fun BetaWarning(
onDismissRequest: () -> Unit
) {
Dialog(
onDismissRequest = onDismissRequest
) {
Card {
Column (
modifier = Modifier
.padding(16.dp)
) {
Text(
text = "This application is currently in alpha and it is not complete. Do not use to store important characters for you, as any future updates might delete all your characters. Sorry for the inconvenience!"
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = "Also, this application does not work yet with the original VB."
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = "Thank you for your understanding and patience. Sincerely, the dev team."
)
Spacer(modifier = Modifier.padding(8.dp))
Button(
onClick = onDismissRequest
) {
Text(text = "Dismiss")
}
}
}
}
}
@@ -3,16 +3,25 @@ package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
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.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
@@ -27,7 +36,8 @@ import kotlinx.coroutines.withContext
@Composable
fun HomeScreen(
navController: NavController
navController: NavController,
homeScreenController: HomeScreenControllerImpl
) {
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
@@ -35,6 +45,8 @@ fun HomeScreen(
val transformationHistory = remember { mutableStateOf<List<CharacterDtos.TransformationHistory>?>(null) }
val beData = remember { mutableStateOf<BECharacterData?>(null) }
val vbData = remember { mutableStateOf<VBCharacterData?>(null) }
var adventureMissionsFinished by rememberSaveable { mutableStateOf(false) }
var betaWarning by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(storageRepository, activeMon) {
withContext(Dispatchers.IO) {
@@ -46,6 +58,13 @@ fun HomeScreen(
}
}
LaunchedEffect(true) {
homeScreenController
.didAdventureMissionsFinish {
adventureMissionsFinished = it
}
}
Scaffold (
topBar = {
TopBanner(
@@ -94,6 +113,40 @@ fun HomeScreen(
}
}
}
if (adventureMissionsFinished) {
Dialog(
onDismissRequest = { adventureMissionsFinished = false },
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Text(
text = "One of your characters has finished their adventure mission!",
textAlign = TextAlign.Center
)
Button(
onClick = {
adventureMissionsFinished = false
},
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text(text = "Dismiss")
}
}
}
}
}
if (betaWarning) {
BetaWarning {
betaWarning = false
}
}
}
@@ -0,0 +1,5 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
interface HomeScreenController {
fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit)
}
@@ -0,0 +1,29 @@
package com.github.nacabaro.vbhelper.screens.homeScreens
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import kotlinx.coroutines.launch
import java.time.Instant
class HomeScreenControllerImpl(
private val componentActivity: ComponentActivity,
): HomeScreenController {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
override fun didAdventureMissionsFinish(onCompletion: (Boolean) -> Unit) {
componentActivity.lifecycleScope.launch {
val currentTime = Instant.now().epochSecond
val adventureCharacters = database
.adventureDao()
.getAdventureCharacters()
val finishedAdventureCharacters = adventureCharacters.filter { character ->
character.finishesAdventure <= currentTime
}
onCompletion(finishedAdventureCharacters.isNotEmpty())
}
}
}
@@ -1,4 +1,4 @@
package com.github.nacabaro.vbhelper.components
package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -25,7 +25,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.R
import com.github.nacabaro.vbhelper.screens.itemsScreen.ItemsScreenControllerImpl
import com.github.nacabaro.vbhelper.ui.theme.VBHelperTheme
@Composable
@@ -123,7 +122,10 @@ fun ItemDialog(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
text = description
text = description,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
)
Text(
textAlign = TextAlign.Center,
@@ -132,7 +134,7 @@ fun ItemDialog(
text = "You have $amount of this item",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.padding(8.dp)
)
Row (
horizontalArrangement = Arrangement.Center,
@@ -107,7 +107,7 @@ class ItemsScreenControllerImpl (
private fun getItem(itemId: Long): ItemDtos.ItemsWithQuantities {
return database
.itemDao()
.getUserItem(itemId)
.getItem(itemId)
}
private fun consumeItem(itemId: Long) {
@@ -15,10 +15,6 @@ 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.components.ItemDialog
import com.github.nacabaro.vbhelper.components.ItemElement
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.components.getLengthResource
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.source.ItemsRepository
@@ -19,10 +19,6 @@ 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.components.ItemDialog
import com.github.nacabaro.vbhelper.components.ItemElement
import com.github.nacabaro.vbhelper.components.getIconResource
import com.github.nacabaro.vbhelper.components.getLengthResource
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.ItemDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
@@ -0,0 +1,100 @@
package com.github.nacabaro.vbhelper.screens.itemsScreen
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.dtos.ItemDtos
@Composable
fun ObtainedItemDialog(
obtainedItem: ItemDtos.PurchasedItem,
onClickDismiss: () -> Unit
) {
Dialog(
onDismissRequest = onClickDismiss
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
Row {
Box(modifier = Modifier) {
Icon(
painter = painterResource(id = getIconResource(obtainedItem.itemIcon)),
contentDescription = null,
modifier = Modifier
.size(96.dp)
.align(Alignment.Center)
)
Icon(
painter = painterResource(id = getLengthResource(obtainedItem.itemLength)),
contentDescription = null,
tint = MaterialTheme.colorScheme.outline,
modifier = Modifier
.size(64.dp)
.align(Alignment.BottomEnd)
)
}
Column (
modifier = Modifier
.padding(16.dp)
) {
Text(
fontSize = MaterialTheme.typography.titleLarge.fontSize,
text = obtainedItem.itemName,
modifier = Modifier
.fillMaxWidth()
)
}
}
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
text = obtainedItem.itemDescription,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
)
Text(
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
text = "You have obtained ${obtainedItem.itemAmount} of this item",
modifier = Modifier
.fillMaxWidth()
.padding(5.dp)
)
Button(
onClick = onClickDismiss,
modifier = Modifier
.fillMaxWidth()
) {
Text(text = "Dismiss")
}
}
}
}
}
}
@@ -167,8 +167,6 @@ class ScanScreenControllerImpl(
val characterData = UserCharacter(
charId = cardCharData.id,
stage = nfcCharacter.stage.toInt(),
attribute = nfcCharacter.attribute,
ageInDays = nfcCharacter.ageInDays.toInt(),
nextAdventureMissionStage = nfcCharacter.nextAdventureMissionStage.toInt(),
mood = nfcCharacter.mood.toInt(),
@@ -0,0 +1,40 @@
package com.github.nacabaro.vbhelper.screens.settingsScreen
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
@Composable
fun CreditsScreen(
navController: NavController
) {
Scaffold (
topBar = {
TopBanner(
text = "Credits",
onBackClick = {
navController.popBackStack()
}
)
},
modifier = Modifier
.fillMaxSize()
) { contentPadding ->
Column (
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
) {
SettingsSection("Reverse engineering")
SettingsEntry(title = "cyanic", description = "Reversed the firmware and helped us during development.") { }
SettingsSection("Application development")
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.") { }
}
}
}
@@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.navigation.NavigationItems
@Composable
fun SettingsScreen(
@@ -46,6 +47,16 @@ fun SettingsScreen(
SettingsEntry(title = "Import APK", description = "Import Secrets From Vital Arean 2.1.0 APK") {
settingsScreenController.onClickImportApk()
}
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import card", description = "Import DiM/BEm card file") {
settingsScreenController.onClickImportCard()
}
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection("About and credits")
SettingsEntry(title = "Credits", description = "Credits") {
navController.navigate(NavigationItems.Credits.route)
}
SettingsEntry(title = "About", description = "About") { }
SettingsSection("Data management")
SettingsEntry(title = "Export data", description = "Export application database") {
settingsScreenController.onClickOpenDirectory()
@@ -53,14 +64,6 @@ fun SettingsScreen(
SettingsEntry(title = "Import data", description = "Import application database") {
settingsScreenController.onClickImportDatabase()
}
SettingsSection("DiM/BEm management")
SettingsEntry(title = "Import DiM card", description = "Import DiM/BEm card file") {
settingsScreenController.onClickImportCard()
}
SettingsEntry(title = "Rename DiM/BEm", description = "Set card name") { }
SettingsSection("About and credits")
SettingsEntry(title = "Credits", description = "Credits") { }
SettingsEntry(title = "About", description = "About") { }
}
}
}
@@ -11,6 +11,7 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import com.github.cfogrady.vb.dim.card.BemCard
import com.github.cfogrady.vb.dim.card.DimReader
import com.github.cfogrady.vbnfc.data.NfcCharacter
import com.github.nacabaro.vbhelper.database.AppDatabase
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.domain.Sprites
@@ -131,7 +132,7 @@ class SettingsScreenControllerImpl(
val characters = card.characterStats.characterEntries
var spriteCounter = when (card is BemCard) {
true -> 55
true -> 54
false -> 10
}
@@ -144,7 +145,7 @@ class SettingsScreenControllerImpl(
monIndex = index,
name = card.spriteData.sprites[spriteCounter].pixelData,
stage = characters[index].stage,
attribute = characters[index].attribute,
attribute = NfcCharacter.Attribute.entries[characters[index].attribute],
baseHp = characters[index].hp,
baseBp = characters[index].dp,
baseAp = characters[index].ap,
@@ -0,0 +1,117 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.github.nacabaro.vbhelper.R
fun getAdventureTime(time: Int): String {
return when (time) {
360 -> "6 hours"
720 -> "12 hours"
1440 -> "24 hours"
else -> "Unknown"
}
}
@Composable
fun StorageAdventureTimeDialog(
onClickSendToAdventure: (time: Long) -> Unit,
onDismissRequest: () -> Unit
) {
val times = arrayOf(360, 720, 1440)
var expanded by remember { mutableStateOf(false) }
var itemPosition by remember { mutableIntStateOf(-1) }
Dialog(
onDismissRequest = onDismissRequest
) {
Card {
Column(
modifier = Modifier
.padding(16.dp)
) {
Box(
modifier = Modifier
.padding(16.dp)
) {
Row (
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.width(256.dp)
.clickable(true) {
expanded = true
}
) {
Text(
text = when (itemPosition) {
-1 -> "Choose time"
else -> getAdventureTime(times[itemPosition])
}
)
Icon(
painter = painterResource(R.drawable.baseline_single_arrow_down),
contentDescription = "Show more"
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(256.dp)
) {
times.forEach { time ->
DropdownMenuItem(
text = { Text(getAdventureTime(time)) },
onClick = {
itemPosition = times.indexOf(time)
expanded = false
}
)
}
}
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = {
if (itemPosition != -1) {
onClickSendToAdventure(times[itemPosition].toLong())
onDismissRequest()
}
}
) {
Text(text = "Send on adventure")
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onDismissRequest
) {
Text(text = "Dismiss")
}
}
}
}
}
@@ -0,0 +1,163 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
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.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
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.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 androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
import kotlinx.coroutines.launch
@Composable
fun StorageDialog(
characterId: Long,
onDismissRequest: () -> Unit,
onSendToBracelet: () -> Unit,
onClickSetActive: () -> Unit,
onClickSendToAdventure: (time: Long) -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val character = remember { mutableStateOf<CharacterDtos.CharacterWithSprites?>(null) }
val characterSprite = remember { mutableStateOf<BitmapData?>(null) }
val characterName = remember { mutableStateOf<BitmapData?>(null) }
var onSendToAdventureClicked by remember { mutableStateOf(false) }
LaunchedEffect(storageRepository) {
coroutineScope.launch {
character.value = storageRepository.getSingleCharacter(characterId)
characterSprite.value = BitmapData(
bitmap = character.value!!.spriteIdle,
width = character.value!!.spriteWidth,
height = character.value!!.spriteHeight
)
characterName.value = BitmapData(
bitmap = character.value!!.nameSprite,
width = character.value!!.nameSpriteWidth,
height = character.value!!.nameSpriteHeight
)
}
}
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Card(
shape = RoundedCornerShape(16.dp)
) {
Column (
modifier = Modifier
.padding(16.dp)
) {
if (character.value != null &&
characterSprite.value != null &&
characterName.value != null
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
val bitmap = remember (characterSprite.value!!) { characterSprite.value!!.getBitmap() }
val imageBitmap = remember(bitmap) { bitmap.asImageBitmap() }
val density: Float = LocalContext.current.resources.displayMetrics.density
val dpSize = (characterSprite.value!!.width * 4 / density).dp
Image(
bitmap = imageBitmap,
contentDescription = "Character image",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(dpSize)
)
val nameBitmap = remember (characterName.value!!) { characterName.value!!.getBitmap() }
val nameImageBitmap = remember(nameBitmap) { nameBitmap.asImageBitmap() }
val nameDpSize = (characterName.value!!.width * 4 / density).dp
Image(
bitmap = nameImageBitmap,
contentDescription = "Character image",
filterQuality = FilterQuality.None,
modifier = Modifier
.size(nameDpSize)
)
}
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
) {
Button(
onClick = onSendToBracelet,
modifier = Modifier
.weight(1f)
) {
Text(text = "Send to watch")
}
Spacer(
modifier = Modifier
.padding(4.dp)
)
Button(
onClick = onClickSetActive,
) {
Text(text = "Set active")
}
}
Button(
onClick = {
onSendToAdventureClicked = true
},
modifier = Modifier
.fillMaxWidth()
) {
Text(text = "Send on adventure")
}
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = onDismissRequest
) {
Text(text = "Close")
}
}
}
}
if (onSendToAdventureClicked) {
StorageAdventureTimeDialog(
onClickSendToAdventure = { time ->
onClickSendToAdventure(time)
},
onDismissRequest = { onSendToAdventureClicked = false }
)
}
}
@@ -0,0 +1,145 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import android.widget.Toast
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.navigation.NavController
import com.github.nacabaro.vbhelper.components.CharacterEntry
import com.github.nacabaro.vbhelper.components.TopBanner
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.dtos.CharacterDtos
import com.github.nacabaro.vbhelper.navigation.NavigationItems
import com.github.nacabaro.vbhelper.screens.adventureScreen.AdventureScreenControllerImpl
import com.github.nacabaro.vbhelper.source.StorageRepository
import com.github.nacabaro.vbhelper.utils.BitmapData
import kotlinx.coroutines.launch
@Composable
fun StorageScreen(
navController: NavController,
storageScreenController: StorageScreenControllerImpl,
adventureScreenController: AdventureScreenControllerImpl
) {
val coroutineScope = rememberCoroutineScope()
val application = LocalContext.current.applicationContext as VBHelper
val storageRepository = StorageRepository(application.container.db)
val monList = remember { mutableStateOf<List<CharacterDtos.CharacterWithSprites>>(emptyList()) }
var selectedCharacter by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(storageRepository, selectedCharacter) {
coroutineScope.launch {
val characterList = storageRepository.getAllCharacters()
monList.value = characterList
}
}
Scaffold (
topBar = {
TopBanner(
text = "My characters",
onAdventureClick = {
navController.navigate(NavigationItems.Adventure.route)
}
)
}
) { contentPadding ->
if (monList.value.isEmpty()) {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize()
) {
Text(
text = "Nothing to see here",
textAlign = TextAlign.Center,
modifier = Modifier
)
}
} else {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical)
.padding(top = contentPadding.calculateTopPadding())
) {
items(monList.value) { index ->
CharacterEntry(
icon = BitmapData(
bitmap = index.spriteIdle,
width = index.spriteWidth,
height = index.spriteHeight
),
onClick = {
if (!index.isInAdventure) {
selectedCharacter = index.id
} else {
Toast.makeText(
application,
"This character is in an adventure",
Toast.LENGTH_SHORT
).show()
navController.navigate(
NavigationItems.Adventure.route
)
}
},
)
}
}
}
if (selectedCharacter != null) {
StorageDialog(
characterId = selectedCharacter!!,
onDismissRequest = { selectedCharacter = null },
onClickSetActive = {
storageScreenController
.setActive(selectedCharacter!!) {
selectedCharacter = null
navController.navigate(NavigationItems.Home.route)
}
},
onSendToBracelet = {
navController.navigate(
NavigationItems.Scan.route.replace(
"{characterId}",
selectedCharacter.toString()
)
)
},
onClickSendToAdventure = { time ->
adventureScreenController
.sendCharacterToAdventure(
characterId = selectedCharacter!!,
timeInMinutes = time
)
selectedCharacter = null
}
)
}
}
}
@@ -0,0 +1,5 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
interface StorageScreenController {
fun setActive(characterId: Long, onCompletion: () -> Unit)
}
@@ -0,0 +1,31 @@
package com.github.nacabaro.vbhelper.screens.storageScreen
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.github.nacabaro.vbhelper.di.VBHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class StorageScreenControllerImpl(
private val componentActivity: ComponentActivity
): StorageScreenController {
private val application = componentActivity.applicationContext as VBHelper
private val database = application.container.db
override fun setActive(characterId: Long, onCompletion: () -> Unit) {
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
database.userCharacterDao().clearActiveCharacter()
database.userCharacterDao().setActiveCharacter(characterId)
componentActivity.runOnUiThread {
Toast.makeText(
componentActivity,
"Active character updated!",
Toast.LENGTH_SHORT
).show()
onCompletion()
}
}
}
}
@@ -2,8 +2,6 @@ package com.github.nacabaro.vbhelper.source
import com.github.nacabaro.vbhelper.database.AppDatabase
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.dtos.CharacterDtos
class StorageRepository (
@@ -13,8 +11,8 @@ class StorageRepository (
return db.userCharacterDao().getAllCharacters()
}
suspend fun getSingleCharacter(id: Long): UserCharacter {
return db.userCharacterDao().getCharacter(id)
suspend fun getSingleCharacter(id: Long): CharacterDtos.CharacterWithSprites {
return db.userCharacterDao().getCharacterWithSprites(id)
}
suspend fun getCharacterBeData(id: Long): BECharacterData {
@@ -37,8 +35,7 @@ class StorageRepository (
return db.userCharacterDao().deleteCharacterById(id)
}
fun setActiveCharacter(id: Long) {
db.userCharacterDao().clearActiveCharacter()
return db.userCharacterDao().setActiveCharacter(id)
suspend fun getAdventureCharacters(): List<CharacterDtos.AdventureCharacterWithSprites> {
return db.adventureDao().getAdventureCharacters()
}
}
@@ -0,0 +1,117 @@
package com.github.nacabaro.vbhelper.widget
import android.content.Context
import android.graphics.Bitmap
import androidx.compose.material3.MaterialTheme
import androidx.glance.GlanceTheme
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.glance.background
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.size
import androidx.glance.state.PreferencesGlanceStateDefinition
import androidx.glance.text.Text
import androidx.glance.unit.ColorProvider
import com.github.nacabaro.vbhelper.di.VBHelper
import com.github.nacabaro.vbhelper.utils.BitmapData
import com.github.nacabaro.vbhelper.utils.getBitmap
/*
RANT: Why did they have to create the components from Jetpack Compose
with a different API for Glance? Now there are things I use in Jetpack Compose
like the filterQuality, which isn't here.
*/
val CURRENT_IMAGE_KEY = booleanPreferencesKey("current_image")
class CharacterDisplayWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
val application = context.applicationContext as VBHelper
val storageRepository = application.container.db
val currentCharacter = storageRepository
.userCharacterDao()
.getActiveCharacter()
val multiplier = 4
provideContent {
var bitmapIdle1 by remember { mutableStateOf<Bitmap?>(null) }
var bitmapIdle2 by remember { mutableStateOf<Bitmap?>(null) }
var dpSize by remember { mutableStateOf(0.dp) }
if (currentCharacter != null) {
val currentCharacterBitmapIdle1 = BitmapData(
currentCharacter.spriteIdle,
currentCharacter.spriteWidth,
currentCharacter.spriteHeight
)
val currentCharacterBitmapIdle2 = BitmapData(
currentCharacter.spriteIdle2,
currentCharacter.spriteWidth,
currentCharacter.spriteHeight
)
bitmapIdle1 = currentCharacterBitmapIdle1.getBitmap()
bitmapIdle2 = currentCharacterBitmapIdle2.getBitmap()
val density: Float = application.resources.displayMetrics.density
dpSize = (currentCharacterBitmapIdle1.width * multiplier / density).dp
}
WidgetContent(
imageBitmapFrame1 = bitmapIdle1,
imageBitmapFrame2 = bitmapIdle2,
dpSize = dpSize
)
}
}
@Composable
private fun WidgetContent(
imageBitmapFrame1: Bitmap?,
imageBitmapFrame2: Bitmap?,
dpSize: Dp
) {
GlanceTheme {
Column (
horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
verticalAlignment = Alignment.Vertical.CenterVertically,
modifier = GlanceModifier
.fillMaxSize()
.background(ColorProvider(MaterialTheme.colorScheme.background))
) {
if (imageBitmapFrame1 != null) {
Image(
provider = ImageProvider(
bitmap = imageBitmapFrame1
),
contentDescription = "Character",
modifier = GlanceModifier
.size(dpSize)
)
} else {
Text(
text = "No character selected"
)
}
}
}
}
}
@@ -0,0 +1,3 @@
package com.github.nacabaro.vbhelper.widget
import androidx.glance.state.PreferencesGlanceStateDefinition
@@ -0,0 +1,8 @@
package com.github.nacabaro.vbhelper.widget
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
class CharacterDisplayWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = CharacterDisplayWidget()
}
@@ -0,0 +1,35 @@
package com.github.nacabaro.vbhelper.widget
import android.content.Context
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.state.updateAppWidgetState
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
class CharacterDisplayWidgetWorker(
appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
// Get all widget IDs for your ImageSwapWidget
val glanceIds = GlanceAppWidgetManager(applicationContext)
.getGlanceIds(CharacterDisplayWidget::class.java)
glanceIds.forEach { glanceId ->
// Update the widget's state (toggle the boolean)
updateAppWidgetState(
context = applicationContext,
glanceId = glanceId
) { prefs ->
val currentImageIsImage1 = prefs[CURRENT_IMAGE_KEY] ?: true
prefs[CURRENT_IMAGE_KEY] = !currentImageIsImage1 // Toggle the boolean
}
// Trigger the widget to redraw itself with the new state
CharacterDisplayWidget().update(applicationContext, glanceId)
}
// Indicate that the work was successful
return Result.success()
}
}
@@ -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="M40,840v-160l80,-80v-240l-80,-80v-160h80v80h80v-80h80v80h80v-80h80v160l-80,80v40h240v-40l-80,-80v-160h80v80h80v-80h80v80h80v-80h80v160l-80,80v240l80,80v160L560,840v-120q0,-33 -23.5,-56.5T480,640q-33,0 -56.5,23.5T400,720v120L40,840ZM120,760h200v-40q0,-66 47,-113t113,-47q66,0 113,47t47,113v40h200v-47l-80,-80v-306l47,-47L633,280l47,47v153L280,480v-153l47,-47L153,280l47,47v306l-80,80v47ZM480,520Z"
android:fillColor="#000000"/>
</vector>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="40dp"
android:minHeight="20dp"
android:minResizeWidth="20dp"
android:previewImage="@drawable/baseline_image_24"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="300000"
android:widgetCategory="home_screen" />