Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ interface FavoriteGameDao {
@Query("SELECT * FROM favorite_games ORDER BY addedAt ASC")
fun getFavoriteGamesOldestFirst(): Flow<List<FavoriteGameEntity>>

// タイトル(A-Z)
@Query("SELECT * FROM favorite_games ORDER BY name ASC")
fun getFavoriteGamesByTitleDesc(): Flow<List<FavoriteGameEntity>>

// タイトル(Z-A)
@Query("SELECT * FROM favorite_games ORDER BY name DESC")
fun getFavoriteGamesByTitleAsc(): Flow<List<FavoriteGameEntity>>

// 評価(高い順)
@Query("SELECT * FROM favorite_games ORDER BY rating DESC")
fun getFavoriteGamesByRatingDesc(): Flow<List<FavoriteGameEntity>>

// 評価(低い順)
@Query("SELECT * FROM favorite_games ORDER BY rating ASC")
fun getFavoriteGamesByRatingAsc(): Flow<List<FavoriteGameEntity>>

// お気に入り判定用
@Query("SELECT EXISTS(SELECT 1 FROM favorite_games WHERE id = :gameId)")
fun isFavorite(gameId: Int): Flow<Boolean>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.lilin.gamelibrary.data.repository
import com.lilin.gamelibrary.data.local.dao.FavoriteGameDao
import com.lilin.gamelibrary.data.local.entity.FavoriteGameEntity
import com.lilin.gamelibrary.domain.model.FavoriteGame
import com.lilin.gamelibrary.domain.model.SortOption
import com.lilin.gamelibrary.domain.model.SortOrder
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
Expand All @@ -20,6 +21,19 @@ class FavoriteGameRepository @Inject constructor(
}
}

fun getFavoriteGames2(order: SortOption): Flow<List<FavoriteGame>> {
return when (order) {
SortOption.ADDED_DATE_DESC -> favoriteGameDao.getFavoriteGamesNewestFirst()
SortOption.ADDED_DATE_ASC -> favoriteGameDao.getFavoriteGamesOldestFirst()
SortOption.TITLE_DESC -> favoriteGameDao.getFavoriteGamesByTitleDesc()
SortOption.TITLE_ASC -> favoriteGameDao.getFavoriteGamesByTitleAsc()
SortOption.RATING_DESC -> favoriteGameDao.getFavoriteGamesByRatingDesc()
SortOption.RATING_ASC -> favoriteGameDao.getFavoriteGamesByRatingAsc()
}.map { entities ->
entities.map { it.toDomainModel() }
}
}

fun isFavorite(gameId: Int): Flow<Boolean> {
return favoriteGameDao.isFavorite(gameId)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.lilin.gamelibrary.domain.model

data class FavoriteStatistics(
val totalCount: Int,
val avgRating: Double,
val latestAdded: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.lilin.gamelibrary.domain.model

import com.lilin.gamelibrary.R

enum class SortOption {
ADDED_DATE_DESC,
ADDED_DATE_ASC,
TITLE_ASC,
TITLE_DESC,
RATING_DESC,
RATING_ASC,
;

val label: Int
get() = when (this) {
ADDED_DATE_DESC -> R.string.favorite_added_date_desc
ADDED_DATE_ASC -> R.string.favorite_added_date_asc
TITLE_ASC -> R.string.favorite_title_asc
TITLE_DESC -> R.string.favorite_title_desc
RATING_DESC -> R.string.favorite_rating_desc
RATING_ASC -> R.string.favorite_rating_asc
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.lilin.gamelibrary.domain.usecase.favorite

import com.lilin.gamelibrary.data.repository.FavoriteGameRepository
import com.lilin.gamelibrary.domain.model.FavoriteGame
import com.lilin.gamelibrary.domain.model.SortOption
import com.lilin.gamelibrary.domain.model.SortOrder
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
Expand All @@ -18,7 +19,12 @@ import javax.inject.Inject
class GetFavoriteGamesUseCase @Inject constructor(
private val repository: FavoriteGameRepository,
) {
operator fun invoke(sortOrder: SortOrder): Flow<List<FavoriteGame>> {
@Deprecated("use invoke2")
fun invoke(sortOrder: SortOrder): Flow<List<FavoriteGame>> {
return repository.getFavoriteGames(sortOrder)
}

fun invoke2(sortOption: SortOption): Flow<List<FavoriteGame>> {
return repository.getFavoriteGames2(sortOption)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.lilin.gamelibrary.R
import com.lilin.gamelibrary.ui.component.favorite.FavoriteGameCard
import com.lilin.gamelibrary.feature.favorite.expanded.FavoriteExpandedLayout
import com.lilin.gamelibrary.ui.component.favorite.DeleteConfirmDialog
import com.lilin.gamelibrary.ui.component.favorite.FavoriteGameCompactCard
import com.lilin.gamelibrary.ui.component.favorite.FavoriteScreenHeader
import com.lilin.gamelibrary.ui.component.favorite.FavoriteSortFabMenu
import com.lilin.gamelibrary.ui.theme.FavoriteGradientEnd
Expand All @@ -56,10 +58,12 @@ import kotlinx.serialization.Serializable
object FavoriteScreen

fun NavGraphBuilder.navigateFavoriteScreen(
isAtLeastMedium: Boolean,
navigateToDetail: (Int) -> Unit,
) {
composable<FavoriteScreen> {
FavoriteScreen(
isAtLeastMedium = isAtLeastMedium,
navigateToDetail = { gameId ->
navigateToDetail(gameId)
},
Expand All @@ -70,42 +74,71 @@ fun NavGraphBuilder.navigateFavoriteScreen(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FavoriteScreen(
isAtLeastMedium: Boolean,
navigateToDetail: (Int) -> Unit,
modifier: Modifier = Modifier,
viewModel: FavoriteViewModel = hiltViewModel(),
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val sortOrder by viewModel.sortOrder.collectAsStateWithLifecycle()

val expandedUiState by viewModel.expandedUiState.collectAsStateWithLifecycle()
val showDeleteDialog by viewModel.showDeleteDialog.collectAsStateWithLifecycle()
val selectedGameName by viewModel.selectedGameName.collectAsStateWithLifecycle()

Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
text = stringResource(R.string.favorite_screen_title),
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
)
},
)
if (!isAtLeastMedium) {
CenterAlignedTopAppBar(
title = {
Text(
text = stringResource(R.string.favorite_screen_title),
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
)
},
)
}
},
floatingActionButton = {
FavoriteSortFabMenu(
sortOrder = sortOrder,
onSortClick = viewModel::toggleSortOrder,
)
if (!isAtLeastMedium) {
FavoriteSortFabMenu(
sortOrder = sortOrder,
onSortClick = viewModel::toggleSortOrder,
)
}
},
contentWindowInsets = WindowInsets.navigationBars,
modifier = modifier,
) { paddingValues ->
FavoriteScreen(
uiState = uiState,
navigateToDetail = { gameId ->
navigateToDetail(gameId)
},
onClickDelete = viewModel::removeFavoriteGame,
modifier = Modifier.padding(paddingValues),
)
if (isAtLeastMedium) {
FavoriteExpandedLayout(
uiState = expandedUiState,
bottomBarPadding = paddingValues.calculateBottomPadding(),
onSortChange = viewModel::onSortChange,
onGameClick = { gameId ->
navigateToDetail(gameId)
},
onDeleteClick = viewModel::showDeleteConfirmDialog,
)
} else {
FavoriteScreen(
uiState = uiState,
navigateToDetail = { gameId ->
navigateToDetail(gameId)
},
onClickDelete = viewModel::showDeleteConfirmDialog,
modifier = Modifier.padding(paddingValues),
)
}

if (showDeleteDialog) {
DeleteConfirmDialog(
gameName = selectedGameName,
onDismiss = viewModel::dismissDeleteDialog,
onConfirm = viewModel::confirmDelete,
)
}
}
}

Expand All @@ -124,16 +157,7 @@ private fun FavoriteScreen(
}

is FavoriteUiState.Loading -> {
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp),
color = FavoriteGradientEnd,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
}
FavoriteGridLoading()
}

is FavoriteUiState.Success -> {
Expand Down Expand Up @@ -163,7 +187,7 @@ private fun FavoriteScreen(
items = uiState.games,
key = { it.id },
) {
FavoriteGameCard(
FavoriteGameCompactCard(
game = it,
onClick = { gameId ->
navigateToDetail(gameId)
Expand All @@ -178,31 +202,31 @@ private fun FavoriteScreen(
}

is FavoriteUiState.Error -> {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Icon(
imageVector = Icons.Outlined.ErrorOutline,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.error,
)

Spacer(modifier = Modifier.height(16.dp))
Text(
text = uiState.message,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
)
}
FavoriteGridError(
message = uiState.message,
)
}
}
}

@Composable
private fun FavoriteEmptyContent(
fun FavoriteGridLoading(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp),
color = FavoriteGradientEnd,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
}
}

@Composable
fun FavoriteEmptyContent(
modifier: Modifier = Modifier,
) {
Column(
Expand Down Expand Up @@ -252,6 +276,32 @@ private fun FavoriteEmptyContent(
}
}

@Composable
fun FavoriteGridError(
message: String,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Icon(
imageVector = Icons.Outlined.ErrorOutline,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.error,
)

Spacer(modifier = Modifier.height(16.dp))
Text(
text = message,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
)
}
}

@Preview
@Composable
private fun FavoriteEmptyContentPreview() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.lilin.gamelibrary.feature.favorite

import com.lilin.gamelibrary.domain.model.FavoriteGame
import com.lilin.gamelibrary.domain.model.FavoriteStatistics
import com.lilin.gamelibrary.domain.model.SortOption
import com.lilin.gamelibrary.domain.model.SortOrder

sealed interface FavoriteUiState {
Expand All @@ -15,3 +17,17 @@ sealed interface FavoriteUiState {

data class Error(val message: String) : FavoriteUiState
}

sealed interface FavoriteExpandedUiState {
data object Empty : FavoriteExpandedUiState

data object Loading : FavoriteExpandedUiState

data class Success(
val games: List<FavoriteGame>,
val statistics: FavoriteStatistics,
val sortOption: SortOption,
) : FavoriteExpandedUiState

data class Error(val message: String) : FavoriteExpandedUiState
}
Loading
Loading