Skip to content

Commit

Permalink
Implement export edited content feature (#316)
Browse files Browse the repository at this point in the history
* Show proportion on monster image form

* Fix back handler stack

* Fix monster detail pager animation

* Implement export edited content feature
  • Loading branch information
alexandregpereira authored Aug 14, 2024
1 parent b7992c2 commit 1af970d
Show file tree
Hide file tree
Showing 32 changed files with 260 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -172,6 +173,15 @@ internal class MonsterDaoImpl(
}
}

override suspend fun getMonstersByStatus(
status: Set<MonsterEntityStatus>
): List<MonsterCompleteEntity> {
return withContext(dispatcher) {
monsterQueries.getMonstersByStatus(status.map { it.toStatusInt() }).executeAsList()
.queryMonsterCompleteEntities()
}
}

private fun deleteAllEntries(monsters: List<MonsterCompleteEntity>) {
val monsterIndexes = monsters.map { it.monster.index }
val actionsIds = monsters.mapAndReduce { actions.map { it.action.id } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ SELECT * FROM MonsterEntity;

getMonstersEdited:
SELECT * FROM MonsterEntity WHERE isClone > 0;

getMonstersByStatus:
SELECT * FROM MonsterEntity WHERE isClone IN ?;
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -29,4 +30,5 @@ interface MonsterLocalRepository {
fun getMonster(index: String): Flow<Monster>
fun getMonstersByQuery(query: String): Flow<List<Monster>>
fun deleteMonster(index: String): Flow<Unit>
fun getMonstersByStatus(status: Set<MonsterStatus>): Flow<List<Monster>>
}
Original file line number Diff line number Diff line change
@@ -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<MonsterStatus>): Flow<List<Monster>>
}

internal fun GetMonstersByStatus(
repository: MonsterLocalRepository,
): GetMonstersByStatus = GetMonstersByStatus { status ->
repository.getMonstersByStatus(status)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -60,4 +62,9 @@ internal class DefaultMonsterLocalRepository(
override fun deleteMonster(index: String): Flow<Unit> {
return localDataSource.deleteMonster(index)
}

override fun getMonstersByStatus(status: Set<MonsterStatus>): Flow<List<Monster>> {
return localDataSource.getMonstersByStatus(status.map { it.toEntityStatus() }.toSet())
.map { it.toDomain() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -79,4 +80,10 @@ internal class DefaultMonsterLocalDataSource(
override fun deleteMonster(index: String): Flow<Unit> = flow {
emit(monsterDao.deleteMonster(index))
}

override fun getMonstersByStatus(
status: Set<MonsterEntityStatus>
): Flow<List<MonsterCompleteEntity>> = flow {
emit(monsterDao.getMonstersByStatus(status))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -30,4 +31,5 @@ internal interface MonsterLocalDataSource {
fun saveMonsters(monsters: List<MonsterCompleteEntity>, isSync: Boolean): Flow<Unit>
fun getMonster(index: String): Flow<MonsterCompleteEntity>
fun deleteMonster(index: String): Flow<Unit>
fun getMonstersByStatus(status: Set<MonsterEntityStatus>): Flow<List<MonsterCompleteEntity>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -38,4 +39,6 @@ interface MonsterDao {
suspend fun insert(monsters: List<MonsterCompleteEntity>, deleteAll: Boolean)

suspend fun deleteMonster(index: String)

suspend fun getMonstersByStatus(status: Set<MonsterEntityStatus>): List<MonsterCompleteEntity>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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<Monster>.toEntity(): List<MonsterCompleteEntity> {
return this.map { it.toEntity() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ internal fun MonsterContentPreviewScreen(
) = AppScreen(
isOpen = state.isOpen,
contentPaddingValues = contentPadding,
backHandlerEnabled = false,
onClose = onClose
) {
LoadingScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<MonsterState>,
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
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -95,16 +90,11 @@ import kotlin.math.absoluteValue
@Composable
internal fun MonsterDetailScreen(
monsters: List<MonsterState>,
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 = {},
Expand Down Expand Up @@ -147,7 +137,7 @@ internal fun MonsterDetailScreen(
item(key = "MonsterImageCompose") {
Box(
modifier = Modifier
.monsterAspectRatio(heightFraction = 0.9f, maxHeight = getImageHeightInDp())
.monsterAspectRatio(maxHeight = getImageHeightInDp())
.transitionHorizontalScrollable(pagerState)
.animateItemPlacement()
)
Expand Down Expand Up @@ -194,7 +184,6 @@ internal fun MonsterDetailScreen(
scrollState.animateScrollToItem(0)
}
)
OnMonsterChanged(monsters, initialMonsterIndex, pagerState, onMonsterChanged)
}

@Composable
Expand Down Expand Up @@ -257,30 +246,6 @@ private fun ScrollableBackground(
}
}

@Composable
private fun OnMonsterChanged(
monsters: List<MonsterState>,
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<MonsterState>,
Expand Down Expand Up @@ -482,7 +447,6 @@ private fun MonsterDetailPreview() = Window {
reactions = listOf()
)
},
initialMonsterIndex = 2
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MonsterState> = emptyList(),
Expand Down
Loading

0 comments on commit 1af970d

Please sign in to comment.