diff --git a/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterFolderDaoImpl.kt b/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterFolderDaoImpl.kt index 5a8127312..3d628d6e4 100644 --- a/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterFolderDaoImpl.kt +++ b/domain/app/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/database/dao/MonsterFolderDaoImpl.kt @@ -58,6 +58,16 @@ internal class MonsterFolderDaoImpl( queries.getMonstersFromFolder(folderName).executeAsList().groupByMonsterFolder() } + override suspend fun getMonstersFromFolders( + foldersName: List + ): List { + return withContext(dispatcher) { + queries.getMonstersFromFolders(foldersName).executeAsList().map { + it.toMonsterEntity() + } + } + } + override suspend fun removeMonsterFromFolder( folderName: String, monsterIndexes: List @@ -76,28 +86,32 @@ internal class MonsterFolderDaoImpl( folderName = it.folderName, monsterIndex = it.monsterIndex, createdAt = it.createdAt - ) to MonsterEntity( - index = it.index, - type = it.type, - subtype = it.subtype, - group = it.subtitle, - challengeRating = it.challengeRating.toFloat(), - name = it.name, - subtitle = it.subtitle, - imageUrl = it.imageUrl, - backgroundColorLight = it.backgroundColorLight, - backgroundColorDark = it.backgroundColorDark, - isHorizontalImage = it.isHorizontalImage == 1L, - size = it.size, - alignment = it.alignment, - armorClass = it.armorClass.toInt(), - hitPoints = it.hitPoints.toInt(), - hitDice = it.hitDice, - senses = it.senses, - languages = it.languages, - sourceName = it.sourceName, - status = MonsterEntityStatus.entries[it.isClone.toInt()], - ) + ) to it.toMonsterEntity() }.groupBy(keySelector = { it.first }, valueTransform = { it.second }) } + + private fun MonsterFolderCompleteEntityView.toMonsterEntity(): MonsterEntity { + return MonsterEntity( + index = index, + type = type, + subtype = subtype, + group = subtitle, + challengeRating = challengeRating.toFloat(), + name = name, + subtitle = subtitle, + imageUrl = imageUrl, + backgroundColorLight = backgroundColorLight, + backgroundColorDark = backgroundColorDark, + isHorizontalImage = isHorizontalImage == 1L, + size = size, + alignment = alignment, + armorClass = armorClass.toInt(), + hitPoints = hitPoints.toInt(), + hitDice = hitDice, + senses = senses, + languages = languages, + sourceName = sourceName, + status = MonsterEntityStatus.entries[isClone.toInt()], + ) + } } diff --git a/domain/app/data/src/commonMain/sqldelight/br/alexandregpereira/hunter/database/MonsterFolder.sq b/domain/app/data/src/commonMain/sqldelight/br/alexandregpereira/hunter/database/MonsterFolder.sq index 71fbffc73..e89d1177c 100644 --- a/domain/app/data/src/commonMain/sqldelight/br/alexandregpereira/hunter/database/MonsterFolder.sq +++ b/domain/app/data/src/commonMain/sqldelight/br/alexandregpereira/hunter/database/MonsterFolder.sq @@ -19,6 +19,12 @@ WHERE folderName == ? ORDER BY createdAt DESC ; +getMonstersFromFolders: +SELECT * FROM MonsterFolderCompleteEntityView +WHERE folderName IN ? +ORDER BY createdAt DESC +; + removeMonsterFromFolder: DELETE FROM MonsterFolderEntity WHERE folderName == ? AND monsterIndex IN ? diff --git a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/AddMonsterToTemporaryFolderUseCase.kt b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/AddMonsterToTemporaryFolderUseCase.kt index 6a8fef25f..a15186e77 100644 --- a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/AddMonsterToTemporaryFolderUseCase.kt +++ b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/AddMonsterToTemporaryFolderUseCase.kt @@ -23,10 +23,10 @@ class AddMonsterToTemporaryFolderUseCase( private val addMonstersToFolder: AddMonstersToFolderUseCase, ) { - operator fun invoke(index: String): Flow { + operator fun invoke(indexes: List): Flow { return addMonstersToFolder( folderName = TEMPORARY_FOLDER_NAME, - indexes = listOf(index) + indexes = indexes ) } } diff --git a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/GetMonstersByFoldersUseCase.kt b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/GetMonstersByFoldersUseCase.kt new file mode 100644 index 000000000..eaca83691 --- /dev/null +++ b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/GetMonstersByFoldersUseCase.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2022 Alexandre Gomes Pereira + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package br.alexandregpereira.hunter.domain.folder + +import br.alexandregpereira.hunter.domain.folder.model.MonsterPreviewFolder +import kotlinx.coroutines.flow.Flow + +class GetMonstersByFolders( + private val monsterFolderRepository: MonsterFolderRepository +) { + + operator fun invoke(foldersName: List): Flow> { + return monsterFolderRepository.getMonstersFromFolders( + foldersName = foldersName + ) + } +} diff --git a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/MonsterFolderRepository.kt b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/MonsterFolderRepository.kt index 75c4b331f..828a1cfbd 100644 --- a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/MonsterFolderRepository.kt +++ b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/MonsterFolderRepository.kt @@ -17,6 +17,7 @@ package br.alexandregpereira.hunter.domain.folder import br.alexandregpereira.hunter.domain.folder.model.MonsterFolder +import br.alexandregpereira.hunter.domain.folder.model.MonsterPreviewFolder import kotlinx.coroutines.flow.Flow interface MonsterFolderRepository { @@ -25,5 +26,6 @@ interface MonsterFolderRepository { fun removeMonsters(folderName: String, indexes: List): Flow fun getMonsterFolders(): Flow> fun getMonstersFromFolder(folderName: String): Flow + fun getMonstersFromFolders(foldersName: List): Flow> fun removeMonsterFolders(folderNames: List): Flow } diff --git a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/di/DomainModule.kt b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/di/DomainModule.kt index b020421e0..520f93a14 100644 --- a/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/di/DomainModule.kt +++ b/domain/monster-folder/core/src/commonMain/kotlin/br/alexandregpereira/hunter/domain/folder/di/DomainModule.kt @@ -22,6 +22,7 @@ import br.alexandregpereira.hunter.domain.folder.ClearTemporaryFolderUseCase import br.alexandregpereira.hunter.domain.folder.GetFolderMonsterPreviewsByIdsUseCase import br.alexandregpereira.hunter.domain.folder.GetMonsterFoldersUseCase import br.alexandregpereira.hunter.domain.folder.GetMonstersByFolderUseCase +import br.alexandregpereira.hunter.domain.folder.GetMonstersByFolders import br.alexandregpereira.hunter.domain.folder.GetMonstersByTemporaryFolderUseCase import br.alexandregpereira.hunter.domain.folder.RemoveMonsterFoldersUseCase import br.alexandregpereira.hunter.domain.folder.RemoveMonstersFromTemporaryFolderUseCase @@ -37,4 +38,5 @@ val monsterFolderDomainModule = module { factory { GetMonstersByTemporaryFolderUseCase(get()) } factory { RemoveMonsterFoldersUseCase(get()) } factory { RemoveMonstersFromTemporaryFolderUseCase(get()) } + factory { GetMonstersByFolders(get()) } } diff --git a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/DefaultFolderRepository.kt b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/DefaultFolderRepository.kt index 54afd2950..0fca1e844 100644 --- a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/DefaultFolderRepository.kt +++ b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/DefaultFolderRepository.kt @@ -40,6 +40,11 @@ internal class DefaultFolderRepository( return monsterFolderLocalDataSource.getMonstersFromFolder(folderName).map { it?.asDomain() } } + override fun getMonstersFromFolders(foldersName: List): Flow> { + return monsterFolderLocalDataSource.getMonstersFromFolders(foldersName) + .map { it.asDomainMonsterPreviewFolderEntity() } + } + override fun removeMonsters(folderName: String, indexes: List): Flow { return monsterFolderLocalDataSource.removeMonsters(folderName, monsterIndexes = indexes) } diff --git a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/DefaultMonsterFolderLocalDataSource.kt b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/DefaultMonsterFolderLocalDataSource.kt index 247135e7e..62c369b1c 100644 --- a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/DefaultMonsterFolderLocalDataSource.kt +++ b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/DefaultMonsterFolderLocalDataSource.kt @@ -85,4 +85,8 @@ internal class DefaultMonsterFolderLocalDataSource( override fun removeMonsterFolders(folderNames: List): Flow = flow { emit(monsterFolderDao.removeMonsterFolders(folderNames)) } + + override fun getMonstersFromFolders(foldersName: List): Flow> = flow { + emit(monsterFolderDao.getMonstersFromFolders(foldersName)) + } } diff --git a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/MonsterFolderLocalDataSource.kt b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/MonsterFolderLocalDataSource.kt index d4c8113cb..360e269fb 100644 --- a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/MonsterFolderLocalDataSource.kt +++ b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/MonsterFolderLocalDataSource.kt @@ -28,4 +28,5 @@ internal interface MonsterFolderLocalDataSource { fun getMonstersFromFolder(folderName: String): Flow fun getFolderMonsterPreviewsByIds(monsterIndexes: List): Flow> fun removeMonsterFolders(folderNames: List): Flow + fun getMonstersFromFolders(foldersName: List): Flow> } diff --git a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/dao/MonsterFolderDao.kt b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/dao/MonsterFolderDao.kt index 22da15444..7c556a81a 100644 --- a/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/dao/MonsterFolderDao.kt +++ b/domain/monster-folder/data/src/commonMain/kotlin/br/alexandregpereira/hunter/data/monster/folder/local/dao/MonsterFolderDao.kt @@ -30,4 +30,6 @@ interface MonsterFolderDao { suspend fun removeMonsterFromFolder(folderName: String, monsterIndexes: List) suspend fun removeMonsterFolders(folderNames: List) + + suspend fun getMonstersFromFolders(foldersName: List): List } diff --git a/feature/folder-insert/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertFeature.kt b/feature/folder-insert/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertFeature.kt index db3d79e67..7d894a457 100644 --- a/feature/folder-insert/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertFeature.kt +++ b/feature/folder-insert/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertFeature.kt @@ -35,6 +35,7 @@ fun FolderInsertFeature( onFolderSelected = stateHolder::onFolderNameFieldChange, onLongClick = stateHolder::onRemoveMonster, onSave = stateHolder::onSave, - onClose = stateHolder::onClose + onClose = stateHolder::onClose, + onShare = stateHolder::onShare, ) } diff --git a/feature/folder-insert/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/ui/FolderInsertScreen.kt b/feature/folder-insert/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/ui/FolderInsertScreen.kt index a83ba4a94..c1e074481 100644 --- a/feature/folder-insert/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/ui/FolderInsertScreen.kt +++ b/feature/folder-insert/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/ui/FolderInsertScreen.kt @@ -38,38 +38,49 @@ internal fun FolderInsertScreen( onLongClick: (String) -> Unit = {}, onSave: () -> Unit = {}, onClose: () -> Unit = {}, + onShare: () -> Unit = {}, +) = BottomSheet( + opened = state.isOpen, + contentPadding = contentPadding, + topSpaceHeight = 0.dp, + onClose = onClose ) { - BottomSheet(opened = state.isOpen, contentPadding = contentPadding, onClose = onClose) { - ScreenHeader( - title = state.strings.addToFolder, - modifier = Modifier.padding(16.dp) - ) + ScreenHeader( + title = state.strings.addToFolder, + modifier = Modifier.padding(16.dp) + ) - MonsterPreviewRow( - monsters = state.monsterPreviews, - onLongClick = onLongClick, - modifier = Modifier.padding(top = 8.dp) - ) + MonsterPreviewRow( + monsters = state.monsterPreviews, + onLongClick = onLongClick, + modifier = Modifier.padding(top = 8.dp) + ) - AppTextField( - text = state.folderName, - label = state.strings.folderNameLabel, - modifier = Modifier.padding(vertical = 16.dp, horizontal = 16.dp), - onValueChange = onFolderNameFieldChange - ) + AppTextField( + text = state.folderName, + label = state.strings.folderNameLabel, + modifier = Modifier.padding(vertical = 16.dp, horizontal = 16.dp), + onValueChange = onFolderNameFieldChange + ) - MonsterFolderGrid( - folders = state.folders, - onFolderSelected = onFolderSelected, - modifier = Modifier.padding(bottom = 16.dp) - ) + MonsterFolderGrid( + folders = state.folders, + onFolderSelected = onFolderSelected, + modifier = Modifier.padding(bottom = 16.dp) + ) - AppButton( - text = state.strings.save, - modifier = Modifier.padding(16.dp), - onClick = onSave - ) - } + AppButton( + text = state.strings.share, + modifier = Modifier.padding(top = 16.dp, bottom = 8.dp).padding(horizontal = 16.dp), + isPrimary = false, + onClick = onShare + ) + + AppButton( + text = state.strings.save, + modifier = Modifier.padding(bottom = 16.dp, top = 8.dp).padding(horizontal = 16.dp), + onClick = onSave + ) } @Preview diff --git a/feature/folder-insert/state-holder/build.gradle.kts b/feature/folder-insert/state-holder/build.gradle.kts index 038faafb3..6d571985a 100644 --- a/feature/folder-insert/state-holder/build.gradle.kts +++ b/feature/folder-insert/state-holder/build.gradle.kts @@ -9,6 +9,7 @@ multiplatform { api(project(":core:state-holder")) api(project(":domain:monster-folder:core")) implementation(project(":feature:folder-insert:event")) + implementation(project(":feature:share-content:event")) implementation(libs.kotlin.coroutines.core) implementation(libs.koin.core) } diff --git a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertAnalytics.kt b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertAnalytics.kt index bbb81d0f2..cc57672a6 100644 --- a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertAnalytics.kt +++ b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertAnalytics.kt @@ -55,4 +55,10 @@ internal class FolderInsertAnalytics( ) ) } + + fun trackShared() { + analytics.track( + eventName = "Folder Insert - shared", + ) + } } \ No newline at end of file diff --git a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStateHolder.kt b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStateHolder.kt index 5e1e159d1..a9ac8be4d 100644 --- a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStateHolder.kt +++ b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStateHolder.kt @@ -16,6 +16,8 @@ package br.alexandregpereira.hunter.folder.insert +import br.alexadregpereira.hunter.shareContent.event.ShareContentEvent +import br.alexadregpereira.hunter.shareContent.event.ShareContentEventDispatcher import br.alexandregpereira.hunter.domain.folder.AddMonstersToFolderUseCase import br.alexandregpereira.hunter.domain.folder.GetFolderMonsterPreviewsByIdsUseCase import br.alexandregpereira.hunter.domain.folder.GetMonsterFoldersUseCase @@ -40,6 +42,7 @@ class FolderInsertStateHolder internal constructor( private val dispatcher: CoroutineDispatcher, private val analytics: FolderInsertAnalytics, private val appLocalization: AppLocalization, + private val shareContentEventDispatcher: ShareContentEventDispatcher, ) : UiModel(FolderInsertState()) { private val strings: FolderInsertStrings @@ -93,6 +96,14 @@ class FolderInsertStateHolder internal constructor( setState { copy(isOpen = false) } } + fun onShare() { + analytics.trackShared() + onClose() + shareContentEventDispatcher.dispatchEvent( + ShareContentEvent.Export.OnStart(state.value.monsterIndexes) + ) + } + private fun load(monsterIndexes: List, folderName: String = "") { setState { copy( diff --git a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStrings.kt b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStrings.kt index 5cc4e4620..6a1ac4287 100644 --- a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStrings.kt +++ b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/FolderInsertStrings.kt @@ -6,24 +6,28 @@ interface FolderInsertStrings { val addToFolder: String val folderNameLabel: String val save: String + val share: String } internal data class FolderInsertEnStrings( override val addToFolder: String = "Add to folder", override val folderNameLabel: String = "Folder name", override val save: String = "Save", + override val share: String = "Share", ) : FolderInsertStrings internal data class FolderInsertPtStrings( override val addToFolder: String = "Adicionar à pasta", override val folderNameLabel: String = "Nome da pasta", override val save: String = "Salvar", + override val share: String = "Compartilhar", ) : FolderInsertStrings internal data class FolderInsertEmptyStrings( override val addToFolder: String = "", override val folderNameLabel: String = "", override val save: String = "", + override val share: String = "", ) : FolderInsertStrings internal fun getFolderInsertStrings(lang: Language): FolderInsertStrings { diff --git a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/di/Module.kt b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/di/Module.kt index 84aaa89b7..a55454d1e 100644 --- a/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/di/Module.kt +++ b/feature/folder-insert/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/insert/di/Module.kt @@ -37,6 +37,7 @@ val folderInsertModule = module { dispatcher = get(), analytics = FolderInsertAnalytics(get()), appLocalization = get(), + shareContentEventDispatcher = get(), ) } } diff --git a/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListFeature.kt b/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListFeature.kt index 318df55b4..469da6946 100644 --- a/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListFeature.kt +++ b/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListFeature.kt @@ -40,6 +40,7 @@ fun FolderListFeature( onCLick = viewModel::onItemClick, onLongCLick = viewModel::onItemSelect, onItemSelectionClose = viewModel::onItemSelectionClose, - onItemSelectionDeleteClick = viewModel::onItemSelectionDeleteClick + onItemSelectionDeleteClick = viewModel::onItemSelectionDeleteClick, + onItemSelectionAddToPreviewClick = viewModel::onItemSelectionAddToPreviewClick, ) } diff --git a/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/ui/FolderListScreen.kt b/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/ui/FolderListScreen.kt index c908e23b6..26c53c39e 100644 --- a/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/ui/FolderListScreen.kt +++ b/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/ui/FolderListScreen.kt @@ -30,7 +30,8 @@ internal fun FolderListScreen( onCLick: (String) -> Unit = {}, onLongCLick: (String) -> Unit = {}, onItemSelectionClose: () -> Unit = {}, - onItemSelectionDeleteClick: () -> Unit = {} + onItemSelectionDeleteClick: () -> Unit = {}, + onItemSelectionAddToPreviewClick: () -> Unit = {}, ) { Box(Modifier.fillMaxSize()) { FolderCardGrid( @@ -44,9 +45,11 @@ internal fun FolderListScreen( ItemSelection( itemSelectionText = state.strings.itemSelected(state.itemSelectionCount), deleteText = state.strings.delete, + addToPreviewText = state.strings.addToPreview, contentBottomPadding = contentPadding.calculateBottomPadding(), onClose = onItemSelectionClose, onDeleteClick = onItemSelectionDeleteClick, + onAddToPreviewClick = onItemSelectionAddToPreviewClick, isOpen = state.isItemSelectionOpen, ) } diff --git a/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/ui/ItemSelection.kt b/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/ui/ItemSelection.kt index ccad5a84a..ac8fdb560 100644 --- a/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/ui/ItemSelection.kt +++ b/feature/folder-list/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/ui/ItemSelection.kt @@ -39,11 +39,13 @@ import org.jetbrains.compose.ui.tooling.preview.Preview internal fun ItemSelection( itemSelectionText: String, deleteText: String, + addToPreviewText: String, modifier: Modifier = Modifier, isOpen: Boolean = true, contentBottomPadding: Dp = 0.dp, onClose: () -> Unit = {}, - onDeleteClick: () -> Unit = {} + onDeleteClick: () -> Unit = {}, + onAddToPreviewClick: () -> Unit = {}, ) = BottomSheet( opened = isOpen, maxWidth = Dp.Unspecified, @@ -61,8 +63,16 @@ internal fun ItemSelection( ) AppButton( - text = deleteText, + text = addToPreviewText, modifier = Modifier.padding(top = 24.dp), + isPrimary = false, + elevation = 4, + onClick = onAddToPreviewClick + ) + + AppButton( + text = deleteText, + modifier = Modifier.padding(top = 16.dp), onClick = onDeleteClick ) @@ -78,6 +88,7 @@ private fun ItemSelectionPreview() = HunterTheme { ItemSelection( itemSelectionText = "Item Selection", deleteText = "Delete", + addToPreviewText = "Add to Preview", ) } } diff --git a/feature/folder-list/state-holder/build.gradle.kts b/feature/folder-list/state-holder/build.gradle.kts index 5bf9e989d..e652f6f3b 100644 --- a/feature/folder-list/state-holder/build.gradle.kts +++ b/feature/folder-list/state-holder/build.gradle.kts @@ -12,6 +12,7 @@ multiplatform { implementation(project(":feature:folder-insert:event")) implementation(project(":feature:folder-detail:event")) implementation(project(":feature:folder-list:event")) + implementation(project(":feature:folder-preview:event")) implementation(project(":domain:monster:event")) implementation(libs.kotlin.coroutines.core) implementation(libs.koin.core) diff --git a/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListAnalytics.kt b/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListAnalytics.kt index 8e2668f9f..24e34b7dd 100644 --- a/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListAnalytics.kt +++ b/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListAnalytics.kt @@ -49,4 +49,10 @@ internal class FolderListAnalytics( ) ) } + + fun trackItemSelectionAddToPreviewClick() { + analytics.track( + eventName = "Folder List - item selection add to preview click", + ) + } } \ No newline at end of file diff --git a/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListStateHolder.kt b/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListStateHolder.kt index 59a168873..388e44f04 100644 --- a/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListStateHolder.kt +++ b/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListStateHolder.kt @@ -17,12 +17,15 @@ package br.alexandregpereira.hunter.folder.list import br.alexandregpereira.hunter.domain.folder.GetMonsterFoldersUseCase +import br.alexandregpereira.hunter.domain.folder.GetMonstersByFolders import br.alexandregpereira.hunter.domain.folder.RemoveMonsterFoldersUseCase import br.alexandregpereira.hunter.event.folder.detail.FolderDetailEvent.Show import br.alexandregpereira.hunter.event.folder.detail.FolderDetailEventDispatcher import br.alexandregpereira.hunter.event.folder.insert.FolderInsertResult.OnSaved import br.alexandregpereira.hunter.event.folder.insert.FolderInsertResultListener import br.alexandregpereira.hunter.event.folder.list.FolderListResult.OnItemSelectionVisibilityChanges +import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEvent +import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEventDispatcher import br.alexandregpereira.hunter.localization.AppLocalization import br.alexandregpereira.hunter.monster.event.MonsterEventDispatcher import br.alexandregpereira.hunter.monster.event.collectOnMonsterCompendiumChanges @@ -49,6 +52,8 @@ class FolderListStateHolder internal constructor( private val analytics: FolderListAnalytics, private val appLocalization: AppLocalization, private val stateRecovery: StateRecovery, + private val folderPreviewEventDispatcher: FolderPreviewEventDispatcher, + private val getMonstersByFolders: GetMonstersByFolders, ) : UiModel(stateRecovery.getState()) { private val strings: FolderListStrings @@ -97,6 +102,21 @@ class FolderListStateHolder internal constructor( .launchIn(scope) } + fun onItemSelectionAddToPreviewClick() { + analytics.trackItemSelectionAddToPreviewClick() + val folders = state.value.itemSelection + getMonstersByFolders(folders.toList()) + .map { monster -> monster.map { it.index } } + .flowOn(dispatcher) + .onEach { monsterIndexes -> + onItemSelectionClose() + folderPreviewEventDispatcher.dispatchEvent( + FolderPreviewEvent.AddMonster(monsterIndexes) + ) + } + .launchIn(scope) + } + fun onItemSelect(folderName: String) { analytics.trackItemSelect(folderName) setState { diff --git a/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListStrings.kt b/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListStrings.kt index 47e3b9466..bad49c3d6 100644 --- a/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListStrings.kt +++ b/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/FolderListStrings.kt @@ -5,12 +5,14 @@ import br.alexandregpereira.hunter.localization.Language interface FolderListStrings { val title: String val delete: String + val addToPreview: String val itemSelected: (Int) -> String } internal data class FolderListEnStrings( override val title: String = "Folders", override val delete: String = "Delete", + override val addToPreview: String = "Add to Preview", override val itemSelected: (Int) -> String = { count -> if (count == 1) { "$count item selected" @@ -23,6 +25,7 @@ internal data class FolderListEnStrings( internal data class FolderListPtStrings( override val title: String = "Pastas", override val delete: String = "Deletar", + override val addToPreview: String = "Adicionar ao Preview", override val itemSelected: (Int) -> String = { count -> if (count == 1) { "$count item selecionado" @@ -35,6 +38,7 @@ internal data class FolderListPtStrings( internal data class FolderListEmptyStrings( override val title: String = "", override val delete: String = "", + override val addToPreview: String = "", override val itemSelected: (Int) -> String = { _ -> "" } ) : FolderListStrings diff --git a/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/di/Module.kt b/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/di/Module.kt index 9e18cd15d..8a38f2b78 100644 --- a/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/di/Module.kt +++ b/feature/folder-list/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/list/di/Module.kt @@ -44,6 +44,8 @@ val folderListModule = module { analytics = FolderListAnalytics(get()), appLocalization = get(), stateRecovery = get(named(FolderListStateRecoveryQualifier)), + folderPreviewEventDispatcher = get(), + getMonstersByFolders = get(), ) } } diff --git a/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewFeature.kt b/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewFeature.kt index 74e7a757d..e830ee4f0 100644 --- a/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewFeature.kt +++ b/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewFeature.kt @@ -40,6 +40,7 @@ fun FolderPreviewFeature( onClick = stateHolder::onItemClick, onLongClick = stateHolder::onItemLongClick, modifier = modifier, - onSave = stateHolder::onSave + onSave = stateHolder::onSave, + onClear = stateHolder::onClear, ) } diff --git a/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/ui/FolderPreview.kt b/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/ui/FolderPreview.kt index 8490c3320..bc0a82e5e 100644 --- a/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/ui/FolderPreview.kt +++ b/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/ui/FolderPreview.kt @@ -17,24 +17,33 @@ package br.alexandregpereira.hunter.folder.preview.ui import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import br.alexandregpereira.hunter.folder.preview.domain.model.MonsterFolderPreview -import br.alexandregpereira.hunter.ui.compose.AppButton +import br.alexandregpereira.hunter.ui.compose.AppButtonSize +import br.alexandregpereira.hunter.ui.compose.AppCircleButton import br.alexandregpereira.hunter.ui.compose.CircleImage import br.alexandregpereira.hunter.ui.theme.HunterTheme import org.jetbrains.compose.ui.tooling.preview.Preview @@ -43,22 +52,22 @@ import org.jetbrains.compose.ui.tooling.preview.Preview @Composable fun FolderPreview( monsters: List, - saveButtonText: String, modifier: Modifier = Modifier, lazyListState: LazyListState = rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(), onClick: (index: String) -> Unit = {}, onLongClick: (index: String) -> Unit = {}, onSave: () -> Unit = {}, + onClear: () -> Unit = {}, ) { Column(modifier.fillMaxWidth()) { Spacer(modifier = Modifier.height(8.dp)) - Row { + Box(modifier = Modifier.fillMaxWidth()) { LazyRow( - modifier = Modifier.fillMaxWidth(0.7f), + modifier = Modifier.fillMaxWidth(), state = lazyListState, horizontalArrangement = Arrangement.spacedBy(16.dp), - contentPadding = PaddingValues(horizontal = 16.dp) + contentPadding = PaddingValues(start = 16.dp, end = 80.dp) ) { items(monsters, key = { it.index }) { state -> CircleImage( @@ -74,12 +83,22 @@ fun FolderPreview( onLongClick = { onLongClick(state.index) } ) } + item(key = "clear") { + AppCircleButton( + onClick = onClear, + isPrimary = false, + modifier = Modifier.animateItemPlacement() + ) { + Icon( + imageVector = Icons.Filled.Clear, + contentDescription = null + ) + } + } } - AppButton( - text = saveButtonText, - onClick = onSave, - modifier = Modifier.padding(end = 16.dp) + StickerButtonMoreOptions( + onClick = onSave ) } @@ -87,11 +106,33 @@ fun FolderPreview( } } +@Composable +private fun StickerButtonMoreOptions( + onClick: () -> Unit, +) = Box(modifier = Modifier.fillMaxWidth()) { + Box(Modifier.align(Alignment.CenterEnd)) { + Spacer( + modifier = Modifier.height(AppButtonSize.MEDIUM.height.dp) + .width(48.dp) + .background(MaterialTheme.colors.background) + .align(Alignment.CenterEnd) + ) + AppCircleButton( + onClick = onClick, + modifier = Modifier.padding(end = 16.dp) + ) { + Icon( + imageVector = Icons.Filled.MoreVert, + contentDescription = null + ) + } + } +} + @Preview @Composable private fun FolderPreviewPreview() = HunterTheme { FolderPreview( - saveButtonText = "Save", monsters = listOf( MonsterFolderPreview( index = "index1", diff --git a/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/ui/FolderPreviewScreen.kt b/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/ui/FolderPreviewScreen.kt index 855b27f19..b5cf69576 100644 --- a/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/ui/FolderPreviewScreen.kt +++ b/feature/folder-preview/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/ui/FolderPreviewScreen.kt @@ -42,6 +42,7 @@ internal fun FolderPreviewScreen( onClick: (index: String) -> Unit = {}, onLongClick: (index: String) -> Unit = {}, onSave: () -> Unit = {}, + onClear: () -> Unit = {}, ) = Box(modifier = modifier.fillMaxWidth().animateContentSize(animationSpec = spring())) { AnimatedVisibility( visible = state.showPreview, @@ -64,11 +65,11 @@ internal fun FolderPreviewScreen( FolderPreview( monsters = state.monsters, lazyListState = lazyListState, - saveButtonText = state.strings.save, contentPadding = contentPadding, onClick = onClick, onLongClick = onLongClick, onSave = onSave, + onClear = onClear, ) } } diff --git a/feature/folder-preview/event/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/event/FolderPreviewEvent.kt b/feature/folder-preview/event/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/event/FolderPreviewEvent.kt index b935513e5..66e55cdc1 100644 --- a/feature/folder-preview/event/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/event/FolderPreviewEvent.kt +++ b/feature/folder-preview/event/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/event/FolderPreviewEvent.kt @@ -17,5 +17,11 @@ package br.alexandregpereira.hunter.folder.preview.event sealed class FolderPreviewEvent { - data class AddMonster(val index: String) : FolderPreviewEvent() + data class AddMonster(val indexes: List) : FolderPreviewEvent() + + companion object { + fun AddMonster(index: String): AddMonster { + return AddMonster(listOf(index)) + } + } } diff --git a/feature/folder-preview/state-holder/build.gradle.kts b/feature/folder-preview/state-holder/build.gradle.kts index 3bb9d0372..a7d2aab84 100644 --- a/feature/folder-preview/state-holder/build.gradle.kts +++ b/feature/folder-preview/state-holder/build.gradle.kts @@ -5,7 +5,6 @@ plugins { multiplatform { commonMain { implementation(project(":core:analytics")) - implementation(project(":core:localization")) api(project(":core:state-holder")) api(project(":domain:monster-folder:core")) implementation(project(":feature:folder-preview:event")) diff --git a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewAnalytics.kt b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewAnalytics.kt index 27065d409..74aeff56c 100644 --- a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewAnalytics.kt +++ b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewAnalytics.kt @@ -57,11 +57,11 @@ internal class FolderPreviewAnalytics( ) } - fun trackAddMonster(index: String) { + fun trackAddMonster(indexes: List) { analytics.track( eventName = "Folder Preview - add monster", params = mapOf( - "monsterIndex" to index + "monsterIndexes" to indexes ) ) } @@ -77,4 +77,10 @@ internal class FolderPreviewAnalytics( eventName = "Folder Preview - show", ) } + + fun trackClear() { + analytics.track( + eventName = "Folder Preview - clear", + ) + } } \ No newline at end of file diff --git a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewState.kt b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewState.kt index 75c7ffd06..b64ddd7de 100644 --- a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewState.kt +++ b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewState.kt @@ -20,13 +20,10 @@ import br.alexandregpereira.hunter.folder.preview.domain.model.MonsterFolderPrev data class FolderPreviewState( val monsters: List = emptyList(), - val strings: FolderPreviewStrings = FolderPreviewEmptyStrings(), -) { - val showPreview: Boolean = monsters.isNotEmpty() -} - + val showPreview: Boolean = false, +) internal fun FolderPreviewState.changeMonsters( monsters: List ): FolderPreviewState { - return this.copy(monsters = monsters) + return this.copy(monsters = monsters, showPreview = monsters.isNotEmpty()) } diff --git a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewStateHolder.kt b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewStateHolder.kt index cc5b65776..7b2af0a16 100644 --- a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewStateHolder.kt +++ b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewStateHolder.kt @@ -25,7 +25,6 @@ import br.alexandregpereira.hunter.folder.preview.domain.ClearFolderPreviewUseCa import br.alexandregpereira.hunter.folder.preview.domain.GetMonstersFromFolderPreviewUseCase import br.alexandregpereira.hunter.folder.preview.domain.RemoveMonsterFromFolderPreviewUseCase import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEvent.AddMonster -import br.alexandregpereira.hunter.localization.AppLocalization import br.alexandregpereira.hunter.monster.event.MonsterEvent.OnVisibilityChanges.Show import br.alexandregpereira.hunter.monster.event.MonsterEventDispatcher import br.alexandregpereira.hunter.monster.event.collectOnMonsterCompendiumChanges @@ -50,7 +49,6 @@ class FolderPreviewStateHolder internal constructor( private val folderInsertEventDispatcher: FolderInsertEventDispatcher, private val dispatcher: CoroutineDispatcher, private val analytics: FolderPreviewAnalytics, - private val appLocalization: AppLocalization, ) : UiModel(FolderPreviewState()), MutableActionHandler by MutableActionHandler() { @@ -87,13 +85,22 @@ class FolderPreviewStateHolder internal constructor( }.launchIn(scope) } + fun onClear() { + analytics.trackClear() + scope.launch { + setState { copy(showPreview = false) } + delay(300) + clear() + } + } + private fun observeEvents() { scope.launch { folderPreviewEventManager.events.collect { event -> when (event) { is AddMonster -> { - analytics.trackAddMonster(event.index) - addMonster(event.index) + analytics.trackAddMonster(event.indexes) + addMonster(event.indexes) } } } @@ -112,8 +119,8 @@ class FolderPreviewStateHolder internal constructor( .launchIn(scope) } - private fun addMonster(index: String) { - addMonsterToFolderPreview(index) + private fun addMonster(indexes: List) { + addMonsterToFolderPreview(indexes) .flowOn(dispatcher) .onEach { monsters -> val previousMonsterListSize = state.value.monsters.size @@ -135,7 +142,6 @@ class FolderPreviewStateHolder internal constructor( .map { monsters -> setState { changeMonsters(monsters = monsters) - .copy(strings = appLocalization.getStrings()) } } .launchIn(scope) diff --git a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewStrings.kt b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewStrings.kt deleted file mode 100644 index 38d058452..000000000 --- a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/FolderPreviewStrings.kt +++ /dev/null @@ -1,27 +0,0 @@ -package br.alexandregpereira.hunter.folder.preview - -import br.alexandregpereira.hunter.localization.AppLocalization -import br.alexandregpereira.hunter.localization.Language - -interface FolderPreviewStrings { - val save: String -} - -internal data class FolderPreviewEnStrings( - override val save: String = "Save", -) : FolderPreviewStrings - -internal data class FolderPreviewPtStrings( - override val save: String = "Salvar", -) : FolderPreviewStrings - -internal data class FolderPreviewEmptyStrings( - override val save: String = "", -) : FolderPreviewStrings - -internal fun AppLocalization.getStrings(): FolderPreviewStrings { - return when (getLanguage()) { - Language.ENGLISH -> FolderPreviewEnStrings() - Language.PORTUGUESE -> FolderPreviewPtStrings() - } -} diff --git a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/di/Module.kt b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/di/Module.kt index f4e738b1f..71cb0830b 100644 --- a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/di/Module.kt +++ b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/di/Module.kt @@ -45,7 +45,6 @@ val folderPreviewModule = module { folderInsertEventDispatcher = get(), dispatcher = get(), analytics = FolderPreviewAnalytics(get()), - appLocalization = get(), ) } } diff --git a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/domain/AddMonsterToFolderPreviewUseCase.kt b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/domain/AddMonsterToFolderPreviewUseCase.kt index c2fc05f3d..418caf9c5 100644 --- a/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/domain/AddMonsterToFolderPreviewUseCase.kt +++ b/feature/folder-preview/state-holder/src/commonMain/kotlin/br/alexandregpereira/hunter/folder/preview/domain/AddMonsterToFolderPreviewUseCase.kt @@ -28,8 +28,8 @@ internal class AddMonsterToFolderPreviewUseCase( ) { @OptIn(ExperimentalCoroutinesApi::class) - operator fun invoke(index: String): Flow> { - return addMonsterToTemporaryFolder(index).flatMapLatest { + operator fun invoke(indexes: List): Flow> { + return addMonsterToTemporaryFolder(indexes).flatMapLatest { getMonstersFromFolderPreview() } } 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 2ae52f86e..4f1d86a4f 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 @@ -231,7 +231,7 @@ class MonsterDetailStateHolder internal constructor( EXPORT -> { analytics.trackMonsterDetailExportClicked(monsterIndex) shareContentEventDispatcher.dispatchEvent( - ShareContentEvent.Export.OnStart(monsterIndex) + ShareContentEvent.Export.OnStart(listOf(monsterIndex)) ) } } diff --git a/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/SearchScreenFeature.kt b/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/SearchScreenFeature.kt index 2021c5a16..4e4ada6c1 100644 --- a/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/SearchScreenFeature.kt +++ b/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/SearchScreenFeature.kt @@ -35,5 +35,6 @@ fun SearchScreenFeature( onCardClick = viewModel::onItemClick, onCardLongClick = viewModel::onItemLongClick, onSearchKeyClick = viewModel::onSearchKeyClick, + onAddClick = viewModel::onAddClick, ) } diff --git a/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/SearchStateHolder.kt b/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/SearchStateHolder.kt index 3bd8a5860..4321595fb 100644 --- a/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/SearchStateHolder.kt +++ b/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/SearchStateHolder.kt @@ -18,6 +18,7 @@ package br.alexandregpereira.hunter.search import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue +import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEvent import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEvent.AddMonster import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEventDispatcher import br.alexandregpereira.hunter.localization.AppReactiveLocalization @@ -130,7 +131,7 @@ internal class SearchStateHolder( fun onItemLongClick(index: String) { analytics.trackItemLongClick(index, searchQuery.value) - folderPreviewEventDispatcher.dispatchEvent(AddMonster(index)) + folderPreviewEventDispatcher.dispatchEvent(FolderPreviewEvent.AddMonster(index)) } fun onSearchKeyClick(searchKeyIndex: Int) { @@ -143,4 +144,9 @@ internal class SearchStateHolder( } onSearchValueChange(TextFieldValue(newSearchValue, TextRange(newSearchValue.length))) } + + fun onAddClick() { + val monsterIndexes = state.value.monsterRows.map { it.index } + folderPreviewEventDispatcher.dispatchEvent(AddMonster(monsterIndexes)) + } } diff --git a/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/ui/SearchScreen.kt b/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/ui/SearchScreen.kt index a191a2143..036648682 100644 --- a/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/ui/SearchScreen.kt +++ b/feature/search/compose/src/commonMain/kotlin/br/alexandregpereira/hunter/search/ui/SearchScreen.kt @@ -16,20 +16,29 @@ package br.alexandregpereira.hunter.search.ui +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.spring +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -37,6 +46,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp +import br.alexandregpereira.hunter.ui.compose.AppCircleButton import br.alexandregpereira.hunter.ui.compose.ClearFocusWhenScrolling import kotlin.math.absoluteValue @@ -48,67 +58,83 @@ internal fun SearchScreen( onCardClick: (String) -> Unit = {}, onCardLongClick: (String) -> Unit = {}, onSearchKeyClick: (Int) -> Unit = {}, -) { - Box { - val listState = rememberLazyGridState() - val focusManager = LocalFocusManager.current + onAddClick: () -> Unit = {}, +) = Box(modifier = Modifier.fillMaxSize()) { + val listState = rememberLazyGridState() + val focusManager = LocalFocusManager.current - ClearFocusWhenScrolling(listState) + ClearFocusWhenScrolling(listState) - SearchGrid( - monsterRows = state.monsterRows, - totalResults = state.searchResults, - listState = listState, - contentPadding = PaddingValues( - top = contentPaddingValues.calculateTopPadding() + 96.dp + 8.dp, - bottom = contentPaddingValues.calculateBottomPadding() - ), - onCardClick = { - focusManager.clearFocus() - onCardClick(it) + SearchGrid( + monsterRows = state.monsterRows, + totalResults = state.searchResults, + listState = listState, + contentPadding = PaddingValues( + top = contentPaddingValues.calculateTopPadding() + 96.dp + 8.dp, + bottom = contentPaddingValues.calculateBottomPadding() + ), + onCardClick = { + focusManager.clearFocus() + onCardClick(it) + }, + onCardLongClick = { + focusManager.clearFocus() + onCardLongClick(it) + } + ) + + Column { + var isProgrammaticChange by remember { mutableStateOf(false) } + val focusRequester = remember { FocusRequester() } + SearchBar( + text = state.searchValue, + searchLabel = state.searchLabel, + onValueChange = { newValue -> + if (!isProgrammaticChange) { + onSearchValueChange(newValue) + } }, - onCardLongClick = { - focusManager.clearFocus() - onCardLongClick(it) - } + isSearching = state.isSearching, + modifier = Modifier + .focusRequester(focusRequester) + .background(color = MaterialTheme.colors.surface) + .padding(horizontal = 8.dp) + .padding(top = 8.dp + contentPaddingValues.calculateTopPadding()) ) - Column { - var isProgrammaticChange by remember { mutableStateOf(false) } - val focusRequester = remember { FocusRequester() } - SearchBar( - text = state.searchValue, - searchLabel = state.searchLabel, - onValueChange = { newValue -> - if (!isProgrammaticChange) { - onSearchValueChange(newValue) - } - }, - isSearching = state.isSearching, - modifier = Modifier - .focusRequester(focusRequester) - .background(color = MaterialTheme.colors.surface) - .padding(horizontal = 8.dp) - .padding(top = 8.dp + contentPaddingValues.calculateTopPadding()) - ) + val density = LocalDensity.current + val scrollTriggerInPixels = with(density) { 56.dp.toPx() } + Spacer(modifier = Modifier.height(8.dp)) + SearchKeyButtons( + shouldShow = { + val offset: Int = listState.layoutInfo.visibleItemsInfo + .firstOrNull()?.offset?.y?.absoluteValue ?: 0 + offset < scrollTriggerInPixels + }, + modifier = Modifier, + searchKeys = state.searchKeys, + onClick = { + isProgrammaticChange = true + onSearchKeyClick(it) + focusRequester.requestFocus() + isProgrammaticChange = false + }, + ) + } - val density = LocalDensity.current - val scrollTriggerInPixels = with(density) { 56.dp.toPx() } - Spacer(modifier = Modifier.height(8.dp)) - SearchKeyButtons( - shouldShow = { - val offset: Int = listState.layoutInfo.visibleItemsInfo - .firstOrNull()?.offset?.y?.absoluteValue ?: 0 - offset < scrollTriggerInPixels - }, - modifier = Modifier, - searchKeys = state.searchKeys, - onClick = { - isProgrammaticChange = true - onSearchKeyClick(it) - focusRequester.requestFocus() - isProgrammaticChange = false - }, + AnimatedVisibility( + visible = state.monsterRows.isNotEmpty(), + modifier = Modifier.align(Alignment.BottomEnd), + enter = fadeIn(spring()), + exit = fadeOut(spring()), + ) { + AppCircleButton( + modifier = Modifier.padding(vertical = 16.dp, horizontal = 12.dp), + onClick = onAddClick + ) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = null, ) } } 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 67fb69f78..a53b26aef 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,10 +167,6 @@ 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 a3fce1462..6b298dcad 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,7 +17,6 @@ interface SettingsStrings { val defaultLightBackground: String val defaultDarkBackground: String val importContent: String - val exportEditedContent: String } internal data class SettingsEnStrings( @@ -35,7 +34,6 @@ 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( @@ -53,7 +51,6 @@ 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( @@ -71,7 +68,6 @@ 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 bc32155e8..2b7db132f 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,6 +30,4 @@ 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 55a18a13a..cc4280e3d 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 @@ -73,13 +73,6 @@ 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 c7e5d3c4b..237e1cb96 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,14 +28,14 @@ fun ShareContentExportMonsterFeature( var isOpen by rememberSaveable { mutableStateOf(false) } - var monsterIndex: String? by rememberSaveable { - mutableStateOf(null) + var monsterIndexes: List by rememberSaveable { + mutableStateOf(emptyList()) } LaunchedEffect(eventDispatcher.events) { eventDispatcher.exportEvents().collect { event -> isOpen = when (event) { is Export.OnStart -> { - monsterIndex = event.monsterIndex + monsterIndexes = event.monsterIndexes true } is Export.OnFinish -> false @@ -53,9 +53,9 @@ fun ShareContentExportMonsterFeature( ) { val stateHolder = koinInject() val clipboardManager = LocalClipboardManager.current - LaunchedEffect(monsterIndex) { + LaunchedEffect(monsterIndexes) { stateHolder.fetchMonsterContentToExport( - monsterIndex = monsterIndex, + monsterIndexes = monsterIndexes, actualClipboardContent = clipboardManager.getText()?.text ) } 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 8c58bc116..3ed528a54 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 @@ -1,7 +1,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.GetMonstersContentEditedToExport import br.alexandregpereira.hunter.shareContent.domain.GetMonstersContentToExport import br.alexandregpereira.hunter.shareContent.domain.ImportContent import br.alexandregpereira.hunter.shareContent.state.ShareContentStateHolder @@ -10,7 +10,7 @@ import org.koin.dsl.module val featureShareContentModule = module { single { ShareContentEventDispatcher() } factory { ImportContent(get(), get(), get()) } - factory { GetMonsterContentToExport(get(), get(), get()) } - factory { GetMonstersContentToExport(get(), get(), get()) } - single { ShareContentStateHolder(get(), get(), get(), get(), get(), get()) } + factory { GetMonstersContentToExport(get(), get(), get(), get()) } + factory { GetMonstersContentEditedToExport(get(), get(), get()) } + single { ShareContentStateHolder(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 8c0b14387..ab61d7119 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 @@ -2,21 +2,26 @@ package br.alexandregpereira.hunter.shareContent.domain 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.domain.usecase.GetMonstersByIdsUseCase import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -internal fun interface GetMonsterContentToExport { +internal fun interface GetMonstersContentToExport { - operator fun invoke(monsterIndex: String): Flow + operator fun invoke(monsterIndexes: List): Flow } -internal fun GetMonsterContentToExport( - getMonster: GetMonsterUseCase, +internal fun GetMonstersContentToExport( + getMonsters: GetMonstersByIdsUseCase, getMonstersLore: GetMonstersLoreByIdsUseCase, - getSpellsByIds: GetSpellsByIdsUseCase -): GetMonsterContentToExport = GetMonsterContentToExport { monsterIndex -> - getMonster(monsterIndex).map { monster -> - listOf(monster).getContentToExport(getMonstersLore, getSpellsByIds) + getSpellsByIds: GetSpellsByIdsUseCase, + getMonstersContentEditedToExport: GetMonstersContentEditedToExport, +): GetMonstersContentToExport = GetMonstersContentToExport { monsterIndexes -> + if (monsterIndexes.isEmpty()) { + getMonstersContentEditedToExport() + } else { + getMonsters(monsterIndexes).map { monsters -> + monsters.getContentToExport(getMonstersLore, getSpellsByIds) + } } } 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 index 92790c3a8..9a32d27f9 100644 --- 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 @@ -14,16 +14,16 @@ import kotlinx.coroutines.flow.single import kotlinx.coroutines.flow.singleOrNull import kotlinx.serialization.encodeToString -internal fun interface GetMonstersContentToExport { +internal fun interface GetMonstersContentEditedToExport { operator fun invoke(): Flow } -internal fun GetMonstersContentToExport( +internal fun GetMonstersContentEditedToExport( getMonstersByStatus: GetMonstersByStatus, getMonstersLore: GetMonstersLoreByIdsUseCase, getSpellsByIds: GetSpellsByIdsUseCase -): GetMonstersContentToExport = GetMonstersContentToExport { +): GetMonstersContentEditedToExport = GetMonstersContentEditedToExport { val status = setOf(MonsterStatus.Edited, MonsterStatus.Clone, MonsterStatus.Imported) getMonstersByStatus(status).map { monsters -> monsters.getContentToExport(getMonstersLore, getSpellsByIds) 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 e9a804e12..f80341e8e 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 @@ -3,7 +3,6 @@ package br.alexandregpereira.hunter.shareContent.state 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 @@ -22,7 +21,6 @@ internal class ShareContentStateHolder( appLocalization: AppReactiveLocalization, private val eventDispatcher: ShareContentEventDispatcher, private val importContent: ImportContent, - private val getMonsterContentToExport: GetMonsterContentToExport, private val getMonstersContentToExport: GetMonstersContentToExport, ) : UiModel( ShareContentState(strings = appLocalization.getLanguage().getStrings()) @@ -61,9 +59,8 @@ internal class ShareContentStateHolder( copy(contentToImport = content.trim(), importError = null) } - fun fetchMonsterContentToExport(monsterIndex: String?, actualClipboardContent: String?) { - val contentToExportFlow = monsterIndex?.let { getMonsterContentToExport(it) } - ?: getMonstersContentToExport() + fun fetchMonsterContentToExport(monsterIndexes: List, actualClipboardContent: String?) { + val contentToExportFlow = getMonstersContentToExport(monsterIndexes) contentToExportFlow .flowOn(dispatcher) .onEach { contentToExport -> diff --git a/feature/share-content/event/build.gradle.kts b/feature/share-content/event/build.gradle.kts index d162a376c..1b0154fb9 100644 --- a/feature/share-content/event/build.gradle.kts +++ b/feature/share-content/event/build.gradle.kts @@ -4,7 +4,7 @@ plugins { multiplatform { commonMain { - implementation(project(":core:event")) + api(project(":core:event")) implementation(libs.koin.core) implementation(libs.kotlin.coroutines.core) } 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 58cb18525..f0a679bf3 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? = null) : Export() + data class OnStart(val monsterIndexes: List = emptyList()) : Export() data object OnFinish : Export() } } diff --git a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/AppButton.kt b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/AppButton.kt index 93697f5df..d1e445116 100644 --- a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/AppButton.kt +++ b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/AppButton.kt @@ -23,18 +23,24 @@ import androidx.compose.foundation.layout.Column 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.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.jetbrains.compose.ui.tooling.preview.Preview +import kotlin.math.ln @Composable fun AppButton( @@ -42,36 +48,94 @@ fun AppButton( modifier: Modifier = Modifier, size: AppButtonSize = AppButtonSize.MEDIUM, enabled: Boolean = true, + isPrimary: Boolean = true, + elevation: Int = 1, onClick: () -> Unit = {} +) { + AppBasicButton( + modifier = modifier.height(size.height.dp).fillMaxWidth(), + enabled = enabled, + isPrimary = isPrimary, + elevation = elevation, + onClick = onClick + ) { + val fontSizes = when (size) { + AppButtonSize.VERY_SMALL -> 12.sp + AppButtonSize.SMALL, + AppButtonSize.MEDIUM -> 16.sp + } + Text( + text = text, + fontWeight = FontWeight.Normal, + color = LocalContentColor.current, + fontSize = fontSizes, + modifier = Modifier.padding(horizontal = 16.dp) + ) + } +} + +@Composable +fun AppCircleButton( + modifier: Modifier = Modifier, + size: AppButtonSize = AppButtonSize.MEDIUM, + enabled: Boolean = true, + isPrimary: Boolean = true, + onClick: () -> Unit = {}, + content: @Composable () -> Unit +) { + AppBasicButton( + modifier = modifier.size(size.height.dp), + enabled = enabled, + shape = CircleShape, + isPrimary = isPrimary, + onClick = onClick + ) { + content() + } +} + +@Composable +private fun AppBasicButton( + modifier: Modifier = Modifier, + enabled: Boolean = true, + shape: RoundedCornerShape = RoundedCornerShape(24.dp), + isPrimary: Boolean = true, + elevation: Int = 1, + onClick: () -> Unit = {}, + content: @Composable () -> Unit ) { val backgroundColorAlpha by animateFloatAsState( targetValue = if (enabled) 1f else 0.5f ) + val backgroundColor = if (isPrimary) { + MaterialTheme.colors.primary + } else { + val alpha = ((4.5f * ln(elevation.toFloat() + 1)) + 2f) / 100f + MaterialTheme.colors.onSurface.copy(alpha = alpha) + .compositeOver(MaterialTheme.colors.surface) + } + val contentColor = if (isPrimary) { + MaterialTheme.colors.onPrimary + } else { + MaterialTheme.colors.onSurface + } + Box( modifier = modifier - .height(size.height.dp) - .fillMaxWidth() .animatePressed( enabled = enabled, onClick = onClick ) - .clip(RoundedCornerShape(24.dp)) - .background(color = MaterialTheme.colors.primary.copy(alpha = backgroundColorAlpha)), + .clip(shape) + .background(color = backgroundColor.copy(alpha = backgroundColorAlpha)), contentAlignment = Alignment.Center ) { - val fontSizes = when (size) { - AppButtonSize.VERY_SMALL -> 12.sp - AppButtonSize.SMALL, - AppButtonSize.MEDIUM -> 16.sp + CompositionLocalProvider( + LocalContentColor provides contentColor + ) { + content() } - Text( - text = text, - fontWeight = FontWeight.Normal, - color = MaterialTheme.colors.onPrimary.copy(alpha = backgroundColorAlpha), - fontSize = fontSizes, - modifier = Modifier.padding(horizontal = 16.dp) - ) } } diff --git a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/BottomSheet.kt b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/BottomSheet.kt index 4608a55cd..67c6b4051 100644 --- a/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/BottomSheet.kt +++ b/ui/core/src/commonMain/kotlin/br/alexandregpereira/hunter/ui/compose/BottomSheet.kt @@ -49,6 +49,7 @@ fun BottomSheet( maxWidth: Dp = maxBottomSheetWidth, widthFraction: Float = 1f, closeClickingOutside: Boolean = true, + topSpaceHeight: Dp = 288.dp, onClose: () -> Unit = {}, content: @Composable () -> Unit, ) { @@ -72,9 +73,11 @@ fun BottomSheet( } ) { Box(modifier = Modifier.fillMaxSize()) { + val scrollState = rememberScrollState() + ClearFocusWhenScrolling(scrollState) Column( modifier = Modifier - .verticalScroll(state = rememberScrollState()) + .verticalScroll(scrollState) .align(Alignment.BottomCenter) .run { if (closeClickingOutside) { @@ -84,7 +87,6 @@ fun BottomSheet( horizontalAlignment = Alignment.End, ) { if (closeClickingOutside) { - val topSpaceHeight = 288.dp Spacer( modifier = Modifier .fillMaxWidth()