From 17084cb8ad40d05adcea8058fe306e171c625c45 Mon Sep 17 00:00:00 2001 From: Alexandre G Pereira Date: Tue, 13 Aug 2024 21:12:39 -0300 Subject: [PATCH 1/4] Show proportion on monster image form --- .../hunter/detail/ui/MonsterDetailScreen.kt | 2 +- .../registration/ui/form/MonsterImageForm.kt | 21 +++++++++++++++---- .../MonsterRegistrationStrings.kt | 7 +++++-- .../hunter/ui/compose/MonsterImage.kt | 20 ++++++++++++++++-- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt b/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt index 3b003252..b6a8f02f 100644 --- a/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt +++ b/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt @@ -147,7 +147,7 @@ internal fun MonsterDetailScreen( item(key = "MonsterImageCompose") { Box( modifier = Modifier - .monsterAspectRatio(heightFraction = 0.9f, maxHeight = getImageHeightInDp()) + .monsterAspectRatio(maxHeight = getImageHeightInDp()) .transitionHorizontalScrollable(pagerState) .animateItemPlacement() ) diff --git a/feature/monster-registration/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/ui/form/MonsterImageForm.kt b/feature/monster-registration/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/ui/form/MonsterImageForm.kt index b602f2ae..93677e3b 100644 --- a/feature/monster-registration/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/ui/form/MonsterImageForm.kt +++ b/feature/monster-registration/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/ui/form/MonsterImageForm.kt @@ -3,19 +3,24 @@ package br.alexandregpereira.hunter.monster.registration.ui.form import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment.Companion.TopCenter +import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import br.alexandregpereira.hunter.monster.registration.MonsterInfoState import br.alexandregpereira.hunter.monster.registration.ui.strings import br.alexandregpereira.hunter.ui.compose.AppSwitch @@ -23,6 +28,7 @@ import br.alexandregpereira.hunter.ui.compose.AppTextField import br.alexandregpereira.hunter.ui.compose.ColorTextField import br.alexandregpereira.hunter.ui.compose.Form import br.alexandregpereira.hunter.ui.compose.MonsterCoilImage +import br.alexandregpereira.hunter.ui.compose.getMonsterImageAspectRatio import br.alexandregpereira.hunter.ui.compose.monsterAspectRatio import br.alexandregpereira.hunter.ui.util.toColor @@ -49,9 +55,9 @@ internal fun LazyListScope.MonsterImageForm( lightColor } ) - Box( + Column( modifier = Modifier.fillMaxWidth(), - contentAlignment = TopCenter + horizontalAlignment = CenterHorizontally, ) { val widthFraction by animateFloatAsState( targetValue = if (infoState.isImageHorizontal) .8f else .4f @@ -67,6 +73,13 @@ internal fun LazyListScope.MonsterImageForm( widthFraction = widthFraction, ), ) + Spacer(modifier = Modifier.padding(4.dp)) + val aspectRatio = getMonsterImageAspectRatio(infoState.isImageHorizontal) + Text( + text = strings.imageProportion(aspectRatio.toString()), + fontWeight = FontWeight.Light, + fontSize = 14.sp, + ) } AppSwitch( checked = isDarkThemeMutable, diff --git a/feature/monster-registration/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/MonsterRegistrationStrings.kt b/feature/monster-registration/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/MonsterRegistrationStrings.kt index 7f194d3b..297a6b62 100644 --- a/feature/monster-registration/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/MonsterRegistrationStrings.kt +++ b/feature/monster-registration/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/MonsterRegistrationStrings.kt @@ -113,6 +113,7 @@ interface MonsterRegistrationStrings { val imageFormTitle: String val imageHorizontalSwitchLabel: String val darkThemeSwitchLabel: String + val imageProportion: (String) -> String } internal data class MonsterRegistrationEnStrings( @@ -224,7 +225,8 @@ internal data class MonsterRegistrationEnStrings( override val remove: String = "Remove", override val imageFormTitle: String = "Image", override val imageHorizontalSwitchLabel: String = "Horizontal Image", - override val darkThemeSwitchLabel: String = "Dark Theme", + override val darkThemeSwitchLabel: String = "Preview Dark Theme", + override val imageProportion: (String) -> String = { "Proportion - $it" }, ) : MonsterRegistrationStrings internal data class MonsterRegistrationPtStrings( @@ -336,7 +338,8 @@ internal data class MonsterRegistrationPtStrings( override val remove: String = "Remover", override val imageFormTitle: String = "Imagem", override val imageHorizontalSwitchLabel: String = "Imagem Horizontal", - override val darkThemeSwitchLabel: String = "Tema Escuro", + override val darkThemeSwitchLabel: String = "Pré visualizar em Tema Escuro", + override val imageProportion: (String) -> String = { "Proporção - $it" }, ) : MonsterRegistrationStrings fun MonsterRegistrationStrings(): MonsterRegistrationStrings = MonsterRegistrationEnStrings() diff --git a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterImage.kt b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterImage.kt index a7bb70c6..5ed57a13 100644 --- a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterImage.kt +++ b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/MonsterImage.kt @@ -78,17 +78,33 @@ fun MonsterImage( @Composable fun Modifier.monsterAspectRatio( isHorizontal: Boolean = false, - heightFraction: Float = 1f, widthFraction: Float = 1f, maxHeight: Dp = LocalScreenSize.current.heightInDp, ): Modifier { val aspectRatio by animateFloatAsState( - targetValue = if (isHorizontal) 18.84f / 16f else 9 / (16f * heightFraction) + targetValue = getMonsterImageAspectRatio(isHorizontal).value ) return fillMaxWidth(widthFraction).heightIn(max = maxHeight) .aspectRatio(aspectRatio) } +fun getMonsterImageAspectRatio(isHorizontal: Boolean): MonsterImageAspectRatio { + return if (isHorizontal) { + MonsterImageAspectRatio(width = 179f, height = 152f) + } else { + MonsterImageAspectRatio(width = 9f, height = 16f) + } +} + +data class MonsterImageAspectRatio internal constructor( + val width: Float, + val height: Float, +) { + val value: Float = width / height + + override fun toString(): String = "${width.toInt()}:${height.toInt()}" +} + @Preview @Composable fun MonsterImagePreview() = HunterTheme { From 594dcc959d1d87859436c32d165a80361257f8e0 Mon Sep 17 00:00:00 2001 From: Alexandre G Pereira Date: Tue, 13 Aug 2024 21:22:14 -0300 Subject: [PATCH 2/4] Fix back handler stack --- .../monster/content/preview/ui/MonsterContentPreviewScreen.kt | 1 - .../monster/registration/ui/MonsterRegistrationScreen.kt | 1 - .../kotlin/br/alexandregpereira/hunter/ui/compose/Closeable.kt | 3 +-- .../kotlin/br/alexandregpereira/hunter/ui/compose/Screen.kt | 2 -- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/feature/monster-content-manager/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/content/preview/ui/MonsterContentPreviewScreen.kt b/feature/monster-content-manager/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/content/preview/ui/MonsterContentPreviewScreen.kt index bae8f61a..a6e19b4f 100644 --- a/feature/monster-content-manager/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/content/preview/ui/MonsterContentPreviewScreen.kt +++ b/feature/monster-content-manager/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/content/preview/ui/MonsterContentPreviewScreen.kt @@ -66,7 +66,6 @@ internal fun MonsterContentPreviewScreen( ) = AppScreen( isOpen = state.isOpen, contentPaddingValues = contentPadding, - backHandlerEnabled = false, onClose = onClose ) { LoadingScreen( diff --git a/feature/monster-registration/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/ui/MonsterRegistrationScreen.kt b/feature/monster-registration/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/ui/MonsterRegistrationScreen.kt index 61310954..b54c5042 100644 --- a/feature/monster-registration/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/ui/MonsterRegistrationScreen.kt +++ b/feature/monster-registration/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/registration/ui/MonsterRegistrationScreen.kt @@ -38,7 +38,6 @@ internal fun MonsterRegistrationScreen( modifier = Modifier.widthIn( max = maxBottomSheetWidth.takeIf { screenSize.isLandscape } ?: Dp.Unspecified ), - backHandlerEnabled = false, onClose = intent::onClose ) { CompositionLocalProvider(LocalStrings provides state.strings) { diff --git a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/Closeable.kt b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/Closeable.kt index d31d0f13..42ea6f35 100644 --- a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/Closeable.kt +++ b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/Closeable.kt @@ -32,11 +32,10 @@ import kotlin.math.absoluteValue fun Closeable( isOpen: Boolean, modifier: Modifier = Modifier, - backHandlerEnabled: Boolean = isOpen, getScrollOffset: () -> Int = { 0 }, onClosed: () -> Unit, ) { - BackHandler(enabled = backHandlerEnabled, onBack = onClosed) + BackHandler(enabled = isOpen, onBack = onClosed) AnimatedVisibility( modifier = modifier, diff --git a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/Screen.kt b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/Screen.kt index 13299ce2..ffb3d300 100644 --- a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/Screen.kt +++ b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/Screen.kt @@ -35,7 +35,6 @@ fun AppScreen( modifier: Modifier = Modifier, showCloseButton: Boolean = true, backgroundColor: Color = MaterialTheme.colors.surface, - backHandlerEnabled: Boolean = isOpen, level: Int = 1, onClose: () -> Unit, content: @Composable () -> Unit @@ -44,7 +43,6 @@ fun AppScreen( val swipeVerticalState: SwipeVerticalState = rememberSwipeVerticalState(key = enterExitState) Closeable( isOpen = isOpen, - backHandlerEnabled = backHandlerEnabled, onClosed = onClose, getScrollOffset = { swipeVerticalState.offset.toInt() } ) From 0d041529453d9e60f15c1c881a2408543bbd551b Mon Sep 17 00:00:00 2001 From: Alexandre G Pereira Date: Tue, 13 Aug 2024 23:42:57 -0300 Subject: [PATCH 3/4] Fix monster detail pager animation --- .../hunter/detail/MonsterDetailFeature.kt | 58 +++++++++++++++++-- .../hunter/detail/ui/MonsterDetailScreen.kt | 42 +------------- .../monster/detail/MonsterDetailState.kt | 1 - .../detail/MonsterDetailStateHolder.kt | 3 +- 4 files changed, 56 insertions(+), 48 deletions(-) diff --git a/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/MonsterDetailFeature.kt b/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/MonsterDetailFeature.kt index 5c282311..43232b36 100644 --- a/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/MonsterDetailFeature.kt +++ b/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/MonsterDetailFeature.kt @@ -17,15 +17,24 @@ package br.alexandregpereira.hunter.detail import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.unit.dp import br.alexandregpereira.hunter.detail.ui.LocalStrings import br.alexandregpereira.hunter.detail.ui.MonsterDetailScreen import br.alexandregpereira.hunter.monster.detail.MonsterDetailStateHolder +import br.alexandregpereira.hunter.monster.detail.MonsterState import br.alexandregpereira.hunter.monster.detail.di.MonsterDetailStateRecoveryQualifier import br.alexandregpereira.hunter.ui.compose.AppScreen import br.alexandregpereira.hunter.ui.compose.LoadingScreen @@ -61,18 +70,55 @@ fun MonsterDetailFeature( CompositionLocalProvider( LocalStrings provides viewState.strings, ) { + val monsters = viewState.monsters + val pagerState = key(monsters) { + rememberPagerState( + initialPage = initialMonsterIndex, + pageCount = { monsters.size } + ) + } MonsterDetailScreen( - viewState.monsters, - initialMonsterIndex, - contentPadding, - onMonsterChanged = { monster -> - viewModel.onMonsterChanged(monster.index) - }, + monsters = monsters, + contentPadding = contentPadding, + pagerState = pagerState, onOptionsClicked = viewModel::onShowOptionsClicked, onSpellClicked = viewModel::onSpellClicked, onLoreClicked = viewModel::onLoreClicked, onClose = viewModel::onClose, ) + + OnMonsterChanged( + monsters, + initialMonsterIndex, + pagerState, + onMonsterChanged = remember(viewModel) { + { monster -> viewModel.onMonsterChanged(monster.index) } + } + ) + } + } + } +} + +@Composable +private fun OnMonsterChanged( + monsters: List, + initialMonsterIndex: Int, + pagerState: PagerState, + onMonsterChanged: (monster: MonsterState) -> Unit +) { + var initialMonsterIndexState by remember { mutableIntStateOf(initialMonsterIndex) } + + LaunchedEffect(key1 = initialMonsterIndex) { + pagerState.scrollToPage(initialMonsterIndex) + } + + LaunchedEffect(pagerState, monsters) { + snapshotFlow { pagerState.currentPage }.collect { page -> + if (initialMonsterIndexState == initialMonsterIndex) { + onMonsterChanged(monsters[page]) + } else { + initialMonsterIndexState = initialMonsterIndex } } } diff --git a/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt b/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt index b6a8f02f..e4da3c13 100644 --- a/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt +++ b/feature/monster-detail/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/detail/ui/MonsterDetailScreen.kt @@ -45,15 +45,10 @@ import androidx.compose.material.Surface import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed @@ -95,16 +90,11 @@ import kotlin.math.absoluteValue @Composable internal fun MonsterDetailScreen( monsters: List, - initialMonsterIndex: Int, contentPadding: PaddingValues = PaddingValues(0.dp), - pagerState: PagerState = key(monsters) { - rememberPagerState( - initialPage = initialMonsterIndex, - pageCount = { monsters.size } - ) - }, + pagerState: PagerState = rememberPagerState( + pageCount = { monsters.size } + ), scrollState: LazyListState = rememberLazyListState(), - onMonsterChanged: (monster: MonsterState) -> Unit = {}, onOptionsClicked: () -> Unit = {}, onSpellClicked: (String) -> Unit = {}, onLoreClicked: (String) -> Unit = {}, @@ -194,7 +184,6 @@ internal fun MonsterDetailScreen( scrollState.animateScrollToItem(0) } ) - OnMonsterChanged(monsters, initialMonsterIndex, pagerState, onMonsterChanged) } @Composable @@ -257,30 +246,6 @@ private fun ScrollableBackground( } } -@Composable -private fun OnMonsterChanged( - monsters: List, - initialMonsterIndex: Int, - pagerState: PagerState, - onMonsterChanged: (monster: MonsterState) -> Unit -) { - var initialMonsterIndexState by remember { mutableIntStateOf(initialMonsterIndex) } - - LaunchedEffect(key1 = initialMonsterIndex) { - pagerState.scrollToPage(initialMonsterIndex) - } - - LaunchedEffect(pagerState, monsters) { - snapshotFlow { pagerState.currentPage }.collect { page -> - if (initialMonsterIndexState == initialMonsterIndex) { - onMonsterChanged(monsters[page]) - } else { - initialMonsterIndexState = initialMonsterIndex - } - } - } -} - @Composable private fun MonsterImageCompose( monsters: List, @@ -482,7 +447,6 @@ private fun MonsterDetailPreview() = Window { reactions = listOf() ) }, - initialMonsterIndex = 2 ) } diff --git a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailState.kt b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailState.kt index b41855c3..b8389593 100644 --- a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailState.kt +++ b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailState.kt @@ -24,7 +24,6 @@ import br.alexandregpereira.hunter.domain.model.SpeedType import br.alexandregpereira.hunter.domain.monster.spell.model.SchoolOfMagic import kotlin.native.ObjCName -@ObjCName(name = "MonsterDetailState", exact = true) data class MonsterDetailState( val isLoading: Boolean = true, val monsters: List = emptyList(), diff --git a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateHolder.kt b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateHolder.kt index 0defb833..57b6df56 100644 --- a/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateHolder.kt +++ b/feature/monster-detail/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/monster/detail/MonsterDetailStateHolder.kt @@ -186,12 +186,11 @@ class MonsterDetailStateHolder internal constructor( } } stateRecovery.saveMonsterIndex(monsterIndex) - setState { changeOptions() } } fun onShowOptionsClicked() { analytics.trackMonsterDetailOptionsShown() - setState { ShowOptions } + setState { ShowOptions.changeOptions() } } fun onShowOptionsClosed() { From aec3c3da99c5da62d84ead482fb301f1cd13a23b Mon Sep 17 00:00:00 2001 From: Alexandre G Pereira Date: Wed, 14 Aug 2024 00:52:46 -0300 Subject: [PATCH 4/4] Implement export edited content feature --- .../data/database/dao/MonsterDaoImpl.kt | 10 ++++ .../data/database/dao/MonsterDaoMapper.kt | 16 ++++-- .../hunter/database/Monster.sq | 3 + .../hunter/domain/di/MonsterDomainModule.kt | 2 + .../repository/MonsterLocalRepository.kt | 2 + .../domain/usecase/GetMonstersByStatus.kt | 17 ++++++ .../monster/DefaultMonsterLocalRepository.kt | 7 +++ .../local/DefaultMonsterLocalDataSource.kt | 7 +++ .../monster/local/MonsterLocalDataSource.kt | 2 + .../data/monster/local/dao/MonsterDao.kt | 3 + .../local/mapper/MonsterEntityMapper.kt | 16 ++++-- .../hunter/settings/SettingsStateHolder.kt | 4 ++ .../hunter/settings/SettingsStrings.kt | 5 +- .../hunter/settings/SettingsViewIntent.kt | 2 + .../hunter/settings/ui/MenuScreen.kt | 8 ++- .../ShareContentExportMonsterFeature.kt | 4 +- .../hunter/shareContent/di.kt | 4 +- .../GetMonsterContentToExportUseCase.kt | 27 +-------- .../GetMonstersContentToExportUseCase.kt | 55 +++++++++++++++++++ .../state/ShareContentStateHolder.kt | 12 ++-- .../event/ShareContentEventDispatcher.kt | 2 +- 21 files changed, 162 insertions(+), 46 deletions(-) create mode 100644 domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/usecase/GetMonstersByStatus.kt create mode 100644 feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/domain/GetMonstersContentToExportUseCase.kt diff --git a/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterDaoImpl.kt b/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterDaoImpl.kt index 10a88d4c..ee9b87ff 100644 --- a/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterDaoImpl.kt +++ b/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterDaoImpl.kt @@ -19,6 +19,7 @@ package br.alexandregpereira.hunter.data.database.dao import br.alexandregpereira.hunter.data.monster.local.dao.MonsterDao import br.alexandregpereira.hunter.data.monster.local.entity.MonsterCompleteEntity import br.alexandregpereira.hunter.data.monster.local.entity.MonsterEntity +import br.alexandregpereira.hunter.data.monster.local.entity.MonsterEntityStatus import br.alexandregpereira.hunter.data.monster.spell.local.model.SpellUsageSpellCrossRefEntity import br.alexandregpereira.hunter.data.monster.spell.local.model.SpellcastingSpellUsageCrossRefEntity import br.alexandregpereira.hunter.database.AbilityScoreQueries @@ -172,6 +173,15 @@ internal class MonsterDaoImpl( } } + override suspend fun getMonstersByStatus( + status: Set + ): List { + return withContext(dispatcher) { + monsterQueries.getMonstersByStatus(status.map { it.toStatusInt() }).executeAsList() + .queryMonsterCompleteEntities() + } + } + private fun deleteAllEntries(monsters: List) { val monsterIndexes = monsters.map { it.monster.index } val actionsIds = monsters.mapAndReduce { actions.map { it.action.id } } diff --git a/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterDaoMapper.kt b/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterDaoMapper.kt index 013e0c2a..98db050c 100644 --- a/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterDaoMapper.kt +++ b/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterDaoMapper.kt @@ -115,15 +115,19 @@ internal fun MonsterEntity.toDatabaseEntity(): MonsterDatabaseEntity { senses = this.senses, languages = this.languages, sourceName = this.sourceName, - isClone = when (this.status) { - MonsterEntityStatus.Original -> 0L - MonsterEntityStatus.Clone -> 1L - MonsterEntityStatus.Edited -> 2L - MonsterEntityStatus.Imported -> 3L - } + isClone = this.status.toStatusInt() ) } +internal fun MonsterEntityStatus.toStatusInt(): Long { + return when (this) { + MonsterEntityStatus.Original -> 0L + MonsterEntityStatus.Clone -> 1L + MonsterEntityStatus.Edited -> 2L + MonsterEntityStatus.Imported -> 3L + } +} + internal fun ReactionEntity.toDatabaseEntity(): ReactionDatabaseEntity { return ReactionDatabaseEntity( name = this.name, diff --git a/domain/app/data/src/commonMain/sqldelight/br/alexandregpereira/hunter/database/Monster.sq b/domain/app/data/src/commonMain/sqldelight/br/alexandregpereira/hunter/database/Monster.sq index 2ac97a51..ce2d696b 100644 --- a/domain/app/data/src/commonMain/sqldelight/br/alexandregpereira/hunter/database/Monster.sq +++ b/domain/app/data/src/commonMain/sqldelight/br/alexandregpereira/hunter/database/Monster.sq @@ -26,3 +26,6 @@ SELECT * FROM MonsterEntity; getMonstersEdited: SELECT * FROM MonsterEntity WHERE isClone > 0; + +getMonstersByStatus: +SELECT * FROM MonsterEntity WHERE isClone IN ?; diff --git a/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/di/MonsterDomainModule.kt b/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/di/MonsterDomainModule.kt index ddfa196e..4c0ab796 100644 --- a/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/di/MonsterDomainModule.kt +++ b/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/di/MonsterDomainModule.kt @@ -25,6 +25,7 @@ import br.alexandregpereira.hunter.domain.usecase.GetMonsterPreviewsUseCase import br.alexandregpereira.hunter.domain.usecase.GetMonsterUseCase import br.alexandregpereira.hunter.domain.usecase.GetMonstersAroundIndexUseCase import br.alexandregpereira.hunter.domain.usecase.GetMonstersByIdsUseCase +import br.alexandregpereira.hunter.domain.usecase.GetMonstersByStatus import br.alexandregpereira.hunter.domain.usecase.GetMonstersUseCase import br.alexandregpereira.hunter.domain.usecase.GetRemoteMonstersBySourceUseCase import br.alexandregpereira.hunter.domain.usecase.SaveCompendiumScrollItemPositionUseCase @@ -49,4 +50,5 @@ val monsterDomainModule = module { factory { SaveMonstersUseCase(get(), get(), get()) } factory { SyncMonstersUseCase(get(), get(), get(), get(), get(), get(), get()) } factory { GetRemoteMonstersBySourceUseCase(get(), get()) } + factory { GetMonstersByStatus(get()) } } diff --git a/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/repository/MonsterLocalRepository.kt b/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/repository/MonsterLocalRepository.kt index af9ef62c..b38cb786 100644 --- a/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/repository/MonsterLocalRepository.kt +++ b/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/repository/MonsterLocalRepository.kt @@ -17,6 +17,7 @@ package br.alexandregpereira.hunter.domain.repository import br.alexandregpereira.hunter.domain.model.Monster +import br.alexandregpereira.hunter.domain.model.MonsterStatus import kotlinx.coroutines.flow.Flow interface MonsterLocalRepository { @@ -29,4 +30,5 @@ interface MonsterLocalRepository { fun getMonster(index: String): Flow fun getMonstersByQuery(query: String): Flow> fun deleteMonster(index: String): Flow + fun getMonstersByStatus(status: Set): Flow> } diff --git a/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/usecase/GetMonstersByStatus.kt b/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/usecase/GetMonstersByStatus.kt new file mode 100644 index 00000000..e25ebb32 --- /dev/null +++ b/domain/monster/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/usecase/GetMonstersByStatus.kt @@ -0,0 +1,17 @@ +package br.alexandregpereira.hunter.domain.usecase + +import br.alexandregpereira.hunter.domain.model.Monster +import br.alexandregpereira.hunter.domain.model.MonsterStatus +import br.alexandregpereira.hunter.domain.repository.MonsterLocalRepository +import kotlinx.coroutines.flow.Flow + +fun interface GetMonstersByStatus { + + operator fun invoke(status: Set): Flow> +} + +internal fun GetMonstersByStatus( + repository: MonsterLocalRepository, +): GetMonstersByStatus = GetMonstersByStatus { status -> + repository.getMonstersByStatus(status) +} diff --git a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/DefaultMonsterLocalRepository.kt b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/DefaultMonsterLocalRepository.kt index 32a8b980..145521d6 100644 --- a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/DefaultMonsterLocalRepository.kt +++ b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/DefaultMonsterLocalRepository.kt @@ -20,7 +20,9 @@ import br.alexandregpereira.hunter.data.monster.local.MonsterLocalDataSource import br.alexandregpereira.hunter.data.monster.local.mapper.toDomain import br.alexandregpereira.hunter.data.monster.local.mapper.toDomainMonsterEntity import br.alexandregpereira.hunter.data.monster.local.mapper.toEntity +import br.alexandregpereira.hunter.data.monster.local.mapper.toEntityStatus import br.alexandregpereira.hunter.domain.model.Monster +import br.alexandregpereira.hunter.domain.model.MonsterStatus import br.alexandregpereira.hunter.domain.repository.MonsterLocalRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -60,4 +62,9 @@ internal class DefaultMonsterLocalRepository( override fun deleteMonster(index: String): Flow { return localDataSource.deleteMonster(index) } + + override fun getMonstersByStatus(status: Set): Flow> { + return localDataSource.getMonstersByStatus(status.map { it.toEntityStatus() }.toSet()) + .map { it.toDomain() } + } } diff --git a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/DefaultMonsterLocalDataSource.kt b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/DefaultMonsterLocalDataSource.kt index 7afcea57..ce7b8be8 100644 --- a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/DefaultMonsterLocalDataSource.kt +++ b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/DefaultMonsterLocalDataSource.kt @@ -19,6 +19,7 @@ package br.alexandregpereira.hunter.data.monster.local import br.alexandregpereira.hunter.data.monster.local.dao.MonsterDao import br.alexandregpereira.hunter.data.monster.local.entity.MonsterCompleteEntity import br.alexandregpereira.hunter.data.monster.local.entity.MonsterEntity +import br.alexandregpereira.hunter.data.monster.local.entity.MonsterEntityStatus import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.sync.Mutex @@ -79,4 +80,10 @@ internal class DefaultMonsterLocalDataSource( override fun deleteMonster(index: String): Flow = flow { emit(monsterDao.deleteMonster(index)) } + + override fun getMonstersByStatus( + status: Set + ): Flow> = flow { + emit(monsterDao.getMonstersByStatus(status)) + } } diff --git a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/MonsterLocalDataSource.kt b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/MonsterLocalDataSource.kt index ead09ad1..4e2c988d 100644 --- a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/MonsterLocalDataSource.kt +++ b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/MonsterLocalDataSource.kt @@ -18,6 +18,7 @@ package br.alexandregpereira.hunter.data.monster.local import br.alexandregpereira.hunter.data.monster.local.entity.MonsterCompleteEntity import br.alexandregpereira.hunter.data.monster.local.entity.MonsterEntity +import br.alexandregpereira.hunter.data.monster.local.entity.MonsterEntityStatus import kotlinx.coroutines.flow.Flow internal interface MonsterLocalDataSource { @@ -30,4 +31,5 @@ internal interface MonsterLocalDataSource { fun saveMonsters(monsters: List, isSync: Boolean): Flow fun getMonster(index: String): Flow fun deleteMonster(index: String): Flow + fun getMonstersByStatus(status: Set): Flow> } diff --git a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/dao/MonsterDao.kt b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/dao/MonsterDao.kt index b20fe5af..1dde2bb7 100644 --- a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/dao/MonsterDao.kt +++ b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/dao/MonsterDao.kt @@ -18,6 +18,7 @@ package br.alexandregpereira.hunter.data.monster.local.dao import br.alexandregpereira.hunter.data.monster.local.entity.MonsterCompleteEntity import br.alexandregpereira.hunter.data.monster.local.entity.MonsterEntity +import br.alexandregpereira.hunter.data.monster.local.entity.MonsterEntityStatus interface MonsterDao { @@ -38,4 +39,6 @@ interface MonsterDao { suspend fun insert(monsters: List, deleteAll: Boolean) suspend fun deleteMonster(index: String) + + suspend fun getMonstersByStatus(status: Set): List } diff --git a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/mapper/MonsterEntityMapper.kt b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/mapper/MonsterEntityMapper.kt index 25279ce4..e36930d9 100644 --- a/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/mapper/MonsterEntityMapper.kt +++ b/domain/monster/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/local/mapper/MonsterEntityMapper.kt @@ -82,12 +82,7 @@ internal fun Monster.toEntity(): MonsterCompleteEntity { senses = senses.joinToString(), languages = languages, sourceName = sourceName, - status = when (status) { - MonsterStatus.Original -> MonsterEntityStatus.Original - MonsterStatus.Clone -> MonsterEntityStatus.Clone - MonsterStatus.Edited -> MonsterEntityStatus.Edited - MonsterStatus.Imported -> MonsterEntityStatus.Imported - }, + status = status.toEntityStatus(), ), speed = speed.toEntity(index), abilityScores = toAbilityScoreEntity(), @@ -105,6 +100,15 @@ internal fun Monster.toEntity(): MonsterCompleteEntity { ) } +internal fun MonsterStatus.toEntityStatus(): MonsterEntityStatus { + return when (this) { + MonsterStatus.Original -> MonsterEntityStatus.Original + MonsterStatus.Clone -> MonsterEntityStatus.Clone + MonsterStatus.Edited -> MonsterEntityStatus.Edited + MonsterStatus.Imported -> MonsterEntityStatus.Imported + } +} + internal fun List.toEntity(): List { return this.map { it.toEntity() } } diff --git a/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsStateHolder.kt b/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsStateHolder.kt index b9bf09bc..278a28b2 100644 --- a/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsStateHolder.kt +++ b/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsStateHolder.kt @@ -167,6 +167,10 @@ internal class SettingsStateHolder( shareContentEventDispatcher.dispatchEvent(ShareContentEvent.Import.OnStart) } + override fun onExportContent() { + shareContentEventDispatcher.dispatchEvent(ShareContentEvent.Export.OnStart()) + } + private fun load() { getMonsterImageJsonUrl() .zip(getAlternativeSourceJsonUrl()) { imageBaseUrl, alternativeSourceBaseUrl -> diff --git a/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsStrings.kt b/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsStrings.kt index 52c238db..a3fce146 100644 --- a/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsStrings.kt +++ b/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsStrings.kt @@ -17,9 +17,9 @@ interface SettingsStrings { val defaultLightBackground: String val defaultDarkBackground: String val importContent: String + val exportEditedContent: String } - internal data class SettingsEnStrings( override val additionalContent: String = "Additional Content", override val monsterImagesJson: String = "Monster Images JSON URL", @@ -35,6 +35,7 @@ internal data class SettingsEnStrings( override val defaultLightBackground: String = "Default Image Light Background Color", override val defaultDarkBackground: String = "Default Image Dark Background Color", override val importContent: String = "Import Shared Content", + override val exportEditedContent: String = "Export Edited Content", ) : SettingsStrings internal data class SettingsPtStrings( @@ -52,6 +53,7 @@ internal data class SettingsPtStrings( override val defaultLightBackground: String = "Cor Padrão de Fundo das Imagens Light", override val defaultDarkBackground: String = "Cor Padrão de Fundo das Imagens Dark", override val importContent: String = "Importar Conteúdo Compartilhado", + override val exportEditedContent: String = "Exportar Conteúdo Editado", ) : SettingsStrings internal data class SettingsEmptyStrings( @@ -69,6 +71,7 @@ internal data class SettingsEmptyStrings( override val defaultLightBackground: String = "", override val defaultDarkBackground: String = "", override val importContent: String = "", + override val exportEditedContent: String = "", ) : SettingsStrings internal fun getSettingsStrings(lang: Language): SettingsStrings { diff --git a/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsViewIntent.kt b/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsViewIntent.kt index 2b7db132..bc32155e 100644 --- a/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsViewIntent.kt +++ b/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/SettingsViewIntent.kt @@ -30,4 +30,6 @@ internal interface SettingsViewIntent { fun onAppearanceChange(appearance: AppearanceSettingsState) fun onImport() + + fun onExportContent() } diff --git a/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/ui/MenuScreen.kt b/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/ui/MenuScreen.kt index 91f148e1..55a18a13 100644 --- a/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/ui/MenuScreen.kt +++ b/feature/settings/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/settings/ui/MenuScreen.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import br.alexandregpereira.hunter.settings.SettingsViewIntent import br.alexandregpereira.hunter.settings.SettingsViewState -import br.alexandregpereira.hunter.ui.compose.BottomSheet @Composable internal fun MenuScreen( @@ -74,6 +73,13 @@ internal fun MenuScreen( Divider() + MenuItem( + text = state.strings.exportEditedContent, + onClick = viewIntent::onExportContent + ) + + Divider() + MenuItem( text = state.strings.manageMonsterContent, onClick = viewIntent::onManageMonsterContentClick diff --git a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/ShareContentExportMonsterFeature.kt b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/ShareContentExportMonsterFeature.kt index b704f0c9..c7e5d3c4 100644 --- a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/ShareContentExportMonsterFeature.kt +++ b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/ShareContentExportMonsterFeature.kt @@ -28,8 +28,8 @@ fun ShareContentExportMonsterFeature( var isOpen by rememberSaveable { mutableStateOf(false) } - var monsterIndex by rememberSaveable { - mutableStateOf("") + var monsterIndex: String? by rememberSaveable { + mutableStateOf(null) } LaunchedEffect(eventDispatcher.events) { eventDispatcher.exportEvents().collect { event -> diff --git a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/di.kt b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/di.kt index 46ddfb16..8c58bc11 100644 --- a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/di.kt +++ b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/di.kt @@ -2,6 +2,7 @@ package br.alexandregpereira.hunter.shareContent import br.alexadregpereira.hunter.shareContent.event.ShareContentEventDispatcher import br.alexandregpereira.hunter.shareContent.domain.GetMonsterContentToExport +import br.alexandregpereira.hunter.shareContent.domain.GetMonstersContentToExport import br.alexandregpereira.hunter.shareContent.domain.ImportContent import br.alexandregpereira.hunter.shareContent.state.ShareContentStateHolder import org.koin.dsl.module @@ -10,5 +11,6 @@ val featureShareContentModule = module { single { ShareContentEventDispatcher() } factory { ImportContent(get(), get(), get()) } factory { GetMonsterContentToExport(get(), get(), get()) } - single { ShareContentStateHolder(get(), get(), get(), get(), get()) } + factory { GetMonstersContentToExport(get(), get(), get()) } + single { ShareContentStateHolder(get(), get(), get(), get(), get(), get()) } } diff --git a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/domain/GetMonsterContentToExportUseCase.kt b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/domain/GetMonsterContentToExportUseCase.kt index 483d8e10..8c0b1438 100644 --- a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/domain/GetMonsterContentToExportUseCase.kt +++ b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/domain/GetMonsterContentToExportUseCase.kt @@ -1,17 +1,10 @@ package br.alexandregpereira.hunter.shareContent.domain -import br.alexandregpereira.hunter.domain.model.Monster -import br.alexandregpereira.hunter.domain.monster.lore.GetMonsterLoreUseCase +import br.alexandregpereira.hunter.domain.monster.lore.GetMonstersLoreByIdsUseCase import br.alexandregpereira.hunter.domain.spell.GetSpellsByIdsUseCase import br.alexandregpereira.hunter.domain.usecase.GetMonsterUseCase -import br.alexandregpereira.hunter.shareContent.domain.mapper.toShareMonster -import br.alexandregpereira.hunter.shareContent.domain.mapper.toShareMonsterLore -import br.alexandregpereira.hunter.shareContent.domain.mapper.toShareSpell import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.single -import kotlinx.coroutines.flow.singleOrNull -import kotlinx.serialization.encodeToString internal fun interface GetMonsterContentToExport { @@ -20,24 +13,10 @@ internal fun interface GetMonsterContentToExport { internal fun GetMonsterContentToExport( getMonster: GetMonsterUseCase, - getMonsterLore: GetMonsterLoreUseCase, + getMonstersLore: GetMonstersLoreByIdsUseCase, getSpellsByIds: GetSpellsByIdsUseCase ): GetMonsterContentToExport = GetMonsterContentToExport { monsterIndex -> getMonster(monsterIndex).map { monster -> - val monsterLore = getMonsterLore(monsterIndex).singleOrNull() - val spells = getSpellsByIds(monster.getSpellIndexes()).single().takeIf { it.isNotEmpty() } - - val shareContent = ShareContent( - monsters = listOf(monster.toShareMonster()), - monstersLore = listOfNotNull(monsterLore?.toShareMonsterLore()), - spells = spells?.map { it.toShareSpell() }, - ) - json.encodeToString(shareContent) + listOf(monster).getContentToExport(getMonstersLore, getSpellsByIds) } } - -private fun Monster.getSpellIndexes(): List { - return spellcastings.asSequence().map { - it.usages - }.flatten().map { it.spells }.flatten().map { it.index }.toList() -} diff --git a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/domain/GetMonstersContentToExportUseCase.kt b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/domain/GetMonstersContentToExportUseCase.kt new file mode 100644 index 00000000..92790c3a --- /dev/null +++ b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/domain/GetMonstersContentToExportUseCase.kt @@ -0,0 +1,55 @@ +package br.alexandregpereira.hunter.shareContent.domain + +import br.alexandregpereira.hunter.domain.model.Monster +import br.alexandregpereira.hunter.domain.model.MonsterStatus +import br.alexandregpereira.hunter.domain.monster.lore.GetMonstersLoreByIdsUseCase +import br.alexandregpereira.hunter.domain.spell.GetSpellsByIdsUseCase +import br.alexandregpereira.hunter.domain.usecase.GetMonstersByStatus +import br.alexandregpereira.hunter.shareContent.domain.mapper.toShareMonster +import br.alexandregpereira.hunter.shareContent.domain.mapper.toShareMonsterLore +import br.alexandregpereira.hunter.shareContent.domain.mapper.toShareSpell +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.single +import kotlinx.coroutines.flow.singleOrNull +import kotlinx.serialization.encodeToString + +internal fun interface GetMonstersContentToExport { + + operator fun invoke(): Flow +} + +internal fun GetMonstersContentToExport( + getMonstersByStatus: GetMonstersByStatus, + getMonstersLore: GetMonstersLoreByIdsUseCase, + getSpellsByIds: GetSpellsByIdsUseCase +): GetMonstersContentToExport = GetMonstersContentToExport { + val status = setOf(MonsterStatus.Edited, MonsterStatus.Clone, MonsterStatus.Imported) + getMonstersByStatus(status).map { monsters -> + monsters.getContentToExport(getMonstersLore, getSpellsByIds) + } +} + +internal suspend fun List.getContentToExport( + getMonstersLore: GetMonstersLoreByIdsUseCase, + getSpellsByIds: GetSpellsByIdsUseCase +): String { + val monsters = this + val monsterIndexes = monsters.map { it.index } + val monstersLore = getMonstersLore(monsterIndexes).singleOrNull() + val spellIndexes = monsters.flatMap { it.getSpellIndexes() } + val spells = getSpellsByIds(spellIndexes).single().takeIf { it.isNotEmpty() } + + val shareContent = ShareContent( + monsters = monsters.map { it.toShareMonster() }, + monstersLore = monstersLore?.map { it.toShareMonsterLore() }, + spells = spells?.map { it.toShareSpell() }, + ) + return json.encodeToString(shareContent) +} + +private fun Monster.getSpellIndexes(): List { + return spellcastings.asSequence().map { + it.usages + }.flatten().map { it.spells }.flatten().map { it.index }.toList() +} diff --git a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/state/ShareContentStateHolder.kt b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/state/ShareContentStateHolder.kt index 0d48fdc1..e9a804e1 100644 --- a/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/state/ShareContentStateHolder.kt +++ b/feature/share-content/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/shareContent/state/ShareContentStateHolder.kt @@ -1,12 +1,13 @@ package br.alexandregpereira.hunter.shareContent.state -import br.alexadregpereira.hunter.shareContent.event.ShareContentEvent.* +import br.alexadregpereira.hunter.shareContent.event.ShareContentEvent.Import import br.alexadregpereira.hunter.shareContent.event.ShareContentEventDispatcher import br.alexandregpereira.hunter.localization.AppReactiveLocalization import br.alexandregpereira.hunter.shareContent.domain.GetMonsterContentToExport +import br.alexandregpereira.hunter.shareContent.domain.GetMonstersContentToExport import br.alexandregpereira.hunter.shareContent.domain.ImportContent import br.alexandregpereira.hunter.shareContent.domain.ImportContentException -import br.alexandregpereira.hunter.shareContent.state.ShareContentImportError.* +import br.alexandregpereira.hunter.shareContent.state.ShareContentImportError.InvalidContent import br.alexandregpereira.hunter.state.MutableActionHandler import br.alexandregpereira.hunter.state.UiModel import kotlinx.coroutines.CoroutineDispatcher @@ -22,6 +23,7 @@ internal class ShareContentStateHolder( private val eventDispatcher: ShareContentEventDispatcher, private val importContent: ImportContent, private val getMonsterContentToExport: GetMonsterContentToExport, + private val getMonstersContentToExport: GetMonstersContentToExport, ) : UiModel( ShareContentState(strings = appLocalization.getLanguage().getStrings()) ), MutableActionHandler by MutableActionHandler() { @@ -59,8 +61,10 @@ internal class ShareContentStateHolder( copy(contentToImport = content.trim(), importError = null) } - fun fetchMonsterContentToExport(monsterIndex: String, actualClipboardContent: String?) { - getMonsterContentToExport(monsterIndex) + fun fetchMonsterContentToExport(monsterIndex: String?, actualClipboardContent: String?) { + val contentToExportFlow = monsterIndex?.let { getMonsterContentToExport(it) } + ?: getMonstersContentToExport() + contentToExportFlow .flowOn(dispatcher) .onEach { contentToExport -> setState { diff --git a/feature/share-content/event/src/commonMain/kotlin/br/alexadregpereira/hunter/shareContent/event/ShareContentEventDispatcher.kt b/feature/share-content/event/src/commonMain/kotlin/br/alexadregpereira/hunter/shareContent/event/ShareContentEventDispatcher.kt index 99865f95..58cb1852 100644 --- a/feature/share-content/event/src/commonMain/kotlin/br/alexadregpereira/hunter/shareContent/event/ShareContentEventDispatcher.kt +++ b/feature/share-content/event/src/commonMain/kotlin/br/alexadregpereira/hunter/shareContent/event/ShareContentEventDispatcher.kt @@ -13,7 +13,7 @@ sealed class ShareContentEvent { data object OnFinish : Import() } sealed class Export : ShareContentEvent() { - data class OnStart(val monsterIndex: String) : Export() + data class OnStart(val monsterIndex: String? = null) : Export() data object OnFinish : Export() } }