Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ package com.lilin.gamelibrary.domain.model

import com.lilin.gamelibrary.R

enum class SortType {
ADDED_DATE,
TITLE,
RATING,
}

enum class SortDirection {
DESC,
ASC,
}

enum class SortOption {
ADDED_DATE_DESC,
ADDED_DATE_ASC,
Expand All @@ -21,3 +32,45 @@ enum class SortOption {
RATING_ASC -> R.string.favorite_rating_asc
}
}

fun SortOption.toTypeAndDirection(): Pair<SortType, SortDirection> {
return when (this) {
SortOption.ADDED_DATE_DESC -> Pair(SortType.ADDED_DATE, SortDirection.DESC)
SortOption.ADDED_DATE_ASC -> Pair(SortType.ADDED_DATE, SortDirection.ASC)
SortOption.TITLE_DESC -> Pair(SortType.TITLE, SortDirection.DESC)
SortOption.TITLE_ASC -> Pair(SortType.TITLE, SortDirection.ASC)
SortOption.RATING_DESC -> Pair(SortType.RATING, SortDirection.DESC)
SortOption.RATING_ASC -> Pair(SortType.RATING, SortDirection.ASC)
}
}

fun combineSortOption(
type: SortType,
direction: SortDirection,
): SortOption {
return when (type) {
SortType.ADDED_DATE -> {
if (direction == SortDirection.ASC) {
SortOption.ADDED_DATE_ASC
} else {
SortOption.ADDED_DATE_DESC
}
}

SortType.TITLE -> {
if (direction == SortDirection.ASC) {
SortOption.TITLE_ASC
} else {
SortOption.TITLE_DESC
}
}

SortType.RATING -> {
if (direction == SortDirection.ASC) {
SortOption.RATING_ASC
} else {
SortOption.RATING_DESC
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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 @@ -13,18 +12,13 @@ import javax.inject.Inject
* 指定されたソート順でお気に入り登録されたゲームのリストを取得する。
* Flowを返すため、データベースの変更が自動的にUIに反映される。
*
* @param sortOrder ソート順(新しい順/古い順)
* @param sortOption ソート順
* @return お気に入りゲームのリストを流すFlow
*/
class GetFavoriteGamesUseCase @Inject constructor(
private val repository: FavoriteGameRepository,
) {
@Deprecated("use invoke2")
fun invoke(sortOrder: SortOrder): Flow<List<FavoriteGame>> {
return repository.getFavoriteGames(sortOrder)
}

fun invoke2(sortOption: SortOption): Flow<List<FavoriteGame>> {
operator fun invoke(sortOption: SortOption): Flow<List<FavoriteGame>> {
return repository.getFavoriteGames2(sortOption)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import com.lilin.gamelibrary.ui.theme.DetailDescriptionGradientEnd
import com.lilin.gamelibrary.ui.theme.DetailDescriptionGradientStart
import com.lilin.gamelibrary.ui.theme.DetailRatingGradientStart
import com.lilin.gamelibrary.ui.theme.DetailTagGradientStart
import java.util.Locale
import com.lilin.gamelibrary.ui.util.formatCount

@Composable
fun GameDetailMediumExpandedLayout(
Expand Down Expand Up @@ -192,7 +192,11 @@ private fun BasicInfoSection(
InfoRow(
icon = Icons.Outlined.Star,
label = stringResource(R.string.detail_label_rating),
value = stringResource(R.string.game_rating_format, rating, formatCount(ratingsCount)),
value = stringResource(
R.string.game_rating_format,
rating,
formatCount(ratingsCount),
),
)
}
}
Expand Down Expand Up @@ -353,19 +357,6 @@ private fun PlatformChipsRow(
}
}

/**
* 数値をフォーマット(例:1234 → 1.2k)
*/
private fun formatCount(count: Int): String {
return when {
count >= 1000 -> {
String.format(Locale.ROOT, "%.1f k", count / 1000.0)
}

else -> count.toString()
}
}

@Preview(
name = "Game Detail Medium Expanded Layout",
showBackground = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ fun FavoriteScreen(
viewModel: FavoriteViewModel = hiltViewModel(),
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val sortOrder by viewModel.sortOrder.collectAsStateWithLifecycle()
val sortOption by viewModel.sortOption.collectAsStateWithLifecycle()
val isExpanded by viewModel.isExpanded.collectAsStateWithLifecycle()

val expandedUiState by viewModel.expandedUiState.collectAsStateWithLifecycle()
val showDeleteDialog by viewModel.showDeleteDialog.collectAsStateWithLifecycle()
Expand All @@ -103,8 +104,10 @@ fun FavoriteScreen(
floatingActionButton = {
if (!isAtLeastMedium) {
FavoriteSortFabMenu(
sortOrder = sortOrder,
onSortClick = viewModel::toggleSortOrder,
currentSortOption = sortOption,
isExpanded = isExpanded,
onClickFab = viewModel::toggleFabMenu,
onSortChange = viewModel::toggleSortOrder,
)
}
},
Expand Down Expand Up @@ -163,7 +166,7 @@ private fun FavoriteScreen(
is FavoriteUiState.Success -> {
val listState = rememberLazyListState()

LaunchedEffect(uiState.sortOrder) {
LaunchedEffect(uiState.sortOption) {
listState.scrollToItem(0)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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 {
data object Empty : FavoriteUiState
Expand All @@ -12,7 +11,7 @@ sealed interface FavoriteUiState {

data class Success(
val games: List<FavoriteGame>,
val sortOrder: SortOrder,
val sortOption: SortOption,
) : FavoriteUiState

data class Error(val message: String) : FavoriteUiState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope
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
import com.lilin.gamelibrary.domain.usecase.favorite.GetFavoriteGamesUseCase
import com.lilin.gamelibrary.domain.usecase.favorite.RemoveFavoriteGameUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -28,34 +27,36 @@ class FavoriteViewModel @Inject constructor(
MutableStateFlow<FavoriteExpandedUiState>(FavoriteExpandedUiState.Empty)
val expandedUiState: StateFlow<FavoriteExpandedUiState> = _expandedUiState.asStateFlow()

private val _sortOrder = MutableStateFlow(SortOrder.NEWEST_FIRST)
val sortOrder: StateFlow<SortOrder> = _sortOrder.asStateFlow()

private val _showDeleteDialog = MutableStateFlow(false)
val showDeleteDialog: StateFlow<Boolean> = _showDeleteDialog.asStateFlow()

private val _selectedGameId = MutableStateFlow(0)
val selectedGameId: StateFlow<Int> = _selectedGameId.asStateFlow()

private val _selectedGameName = MutableStateFlow("")
val selectedGameName: StateFlow<String> = _selectedGameName.asStateFlow()

private val _sortOption = MutableStateFlow(SortOption.ADDED_DATE_DESC)
val sortOption: StateFlow<SortOption> = _sortOption.asStateFlow()

private val _isExpanded = MutableStateFlow(false)
val isExpanded: StateFlow<Boolean> = _isExpanded.asStateFlow()

init {
getFavoriteGames(SortOrder.NEWEST_FIRST)
getFavoriteGames(SortOption.ADDED_DATE_DESC)
loadFavoriteGamesExpanded(SortOption.ADDED_DATE_DESC)
}

private fun getFavoriteGames(sortOrder: SortOrder) {
private fun getFavoriteGames(sortOption: SortOption) {
_uiState.value = FavoriteUiState.Loading

viewModelScope.launch {
runCatching {
getFavoriteGamesUseCase.invoke(sortOrder).collect { games ->
getFavoriteGamesUseCase(sortOption).collect { games ->
_uiState.value = if (games.isEmpty()) {
FavoriteUiState.Empty
} else {
_sortOrder.value = sortOrder
FavoriteUiState.Success(games, sortOrder)
_sortOption.value = sortOption
FavoriteUiState.Success(games, sortOption)
}
}
}.onFailure {
Expand All @@ -64,25 +65,12 @@ class FavoriteViewModel @Inject constructor(
}
}

fun toggleSortOrder() {
val currentState = _uiState.value
if (currentState is FavoriteUiState.Success) {
val newOrder = when (currentState.sortOrder) {
SortOrder.NEWEST_FIRST -> SortOrder.OLDEST_FIRST
SortOrder.OLDEST_FIRST -> SortOrder.NEWEST_FIRST
}
getFavoriteGames(newOrder)
}
fun toggleFabMenu() {
_isExpanded.value = !_isExpanded.value
}

fun removeFavoriteGame(gameId: Int) {
viewModelScope.launch {
runCatching {
removeFavoriteGameUseCase(gameId)
}.onFailure {
_uiState.value = FavoriteUiState.Error(it.message ?: "Unknown error")
}
}
fun toggleSortOrder(sortOption: SortOption) {
getFavoriteGames(sortOption)
}

// Expanded
Expand All @@ -91,11 +79,10 @@ class FavoriteViewModel @Inject constructor(

viewModelScope.launch {
runCatching {
getFavoriteGamesUseCase.invoke2(sortOption).collect { games ->
getFavoriteGamesUseCase(sortOption).collect { games ->
if (games.isEmpty()) {
_expandedUiState.value = FavoriteExpandedUiState.Empty
} else {
// 統計計算
val statistics = calculateStatistics(games)

_expandedUiState.value = FavoriteExpandedUiState.Success(
Expand All @@ -117,11 +104,13 @@ class FavoriteViewModel @Inject constructor(
is FavoriteExpandedUiState.Success -> {
state.games.find { it.id == gameId }?.name ?: ""
}

else -> {
when (val compactState = _uiState.value) {
is FavoriteUiState.Success -> {
compactState.games.find { it.id == gameId }?.name ?: ""
}

else -> ""
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.lilin.gamelibrary.ui.component

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun BottomBarTemplate(
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit,
) {
Box(
modifier = modifier
.fillMaxWidth()
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
MaterialTheme.colorScheme.surface.copy(alpha = 0.8f),
MaterialTheme.colorScheme.surface,
),
),
),
) {
BottomAppBar(
modifier = Modifier
.defaultMinSize(80.dp)
.padding(bottom = 10.dp),
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onSurface,
tonalElevation = 0.dp,
) {
Box(
modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth()
.shadow(
elevation = 8.dp,
shape = RoundedCornerShape(90.dp),
)
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(90.dp),
),
contentAlignment = Alignment.Center,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 25.dp),
verticalAlignment = Alignment.CenterVertically,
content = content,
)
}
}
}
}
Loading
Loading