From 7f6a86b2809a649db0840c9c161ead85e42461b3 Mon Sep 17 00:00:00 2001 From: lilin Date: Mon, 26 Jan 2026 23:12:35 +0900 Subject: [PATCH 01/11] =?UTF-8?q?DiscoveryBottomBar=E3=81=AE=E3=82=B0?= =?UTF-8?q?=E3=83=A9=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E3=81=8C=E4=B8=8A=E3=81=A7=E6=AD=A2=E3=81=BE?= =?UTF-8?q?=E3=81=A3=E3=81=A6=E3=81=97=E3=81=BE=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=81=A7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/discovery/DiscoveryBottomBar.kt | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/DiscoveryBottomBar.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/DiscoveryBottomBar.kt index 62774d1..ab3cff6 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/DiscoveryBottomBar.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/DiscoveryBottomBar.kt @@ -5,9 +5,11 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape @@ -55,24 +57,23 @@ fun DiscoveryBottomBar( val interactionSourceHighRated = remember { MutableInteractionSource() } val interactionSourceNewRelease = remember { MutableInteractionSource() } - Box(modifier = modifier) { - Box( - modifier = Modifier - .fillMaxWidth() - .defaultMinSize(minHeight = 80.dp) - .background( - brush = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), - MaterialTheme.colorScheme.surface, - ), + 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(minHeight = 80.dp), + modifier = Modifier + .defaultMinSize(minHeight = 80.dp) + .padding(bottom = 10.dp), containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.onSurface, tonalElevation = 0.dp, From 61bfa6d541328bf455dd239ebda63b1c698ae401 Mon Sep 17 00:00:00 2001 From: lilin Date: Mon, 26 Jan 2026 23:17:29 +0900 Subject: [PATCH 02/11] =?UTF-8?q?Formatter=E3=81=B8=E9=9B=86=E7=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/GameDetailMediumExpandedLayout.kt | 21 ++++++------------- .../detail/GameDetailSectionCards.kt | 15 +------------ .../ui/component/discovery/GameCard.kt | 15 +------------ .../ui/component/search/SearchComponents.kt | 14 +------------ .../component/sectiondetail/GameGridCard.kt | 15 +------------ .../lilin/gamelibrary/ui/util/Formatter.kt | 15 +++++++++++++ 6 files changed, 25 insertions(+), 70 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/feature/detail/GameDetailMediumExpandedLayout.kt b/app/src/main/kotlin/com/lilin/gamelibrary/feature/detail/GameDetailMediumExpandedLayout.kt index babcbee..b0fc71f 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/feature/detail/GameDetailMediumExpandedLayout.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/feature/detail/GameDetailMediumExpandedLayout.kt @@ -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( @@ -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), + ), ) } } @@ -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, diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/detail/GameDetailSectionCards.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/detail/GameDetailSectionCards.kt index e9c22e1..0a65da6 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/detail/GameDetailSectionCards.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/detail/GameDetailSectionCards.kt @@ -57,7 +57,7 @@ import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import com.lilin.gamelibrary.R import com.lilin.gamelibrary.ui.component.discovery.getMetacriticColor -import java.util.Locale +import com.lilin.gamelibrary.ui.util.formatCount @Composable fun GameBackgroundImage( @@ -429,19 +429,6 @@ fun GameTags( } } -/** - * 数値をフォーマット(例:1234 → 1.2k) - */ -private fun formatCount(count: Int): String { - return when { - count >= 1000 -> { - String.format(Locale.ROOT, "%.1fk", count / 1000.0) - } - - else -> count.toString() - } -} - @Preview @Composable private fun GameBackgroundImage90ScorePreview() { diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/GameCard.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/GameCard.kt index bdcd256..e4e5657 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/GameCard.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/GameCard.kt @@ -51,7 +51,7 @@ import com.lilin.gamelibrary.ui.theme.RatingGold import com.lilin.gamelibrary.ui.theme.RatingSilver import com.lilin.gamelibrary.ui.theme.TrendingGradientEnd import com.lilin.gamelibrary.ui.theme.TrendingGradientStart -import java.util.Locale +import com.lilin.gamelibrary.ui.util.formatCount private const val METACRITIC_GOLD_THRESHOLD = 90 private const val METACRITIC_SILVER_THRESHOLD = 70 @@ -548,19 +548,6 @@ fun getMetacriticColor(score: Int): Color { } } -/** - * 数値をフォーマット(例:1234 → 1.2k) - */ -fun formatCount(count: Int): String { - return when { - count >= 1000 -> { - String.format(Locale.ROOT, "%.1fk", count / 1000.0) - } - - else -> count.toString() - } -} - @Preview @Composable private fun TrendingGameCardPreview() { diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/search/SearchComponents.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/search/SearchComponents.kt index e7d0374..ec624c0 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/search/SearchComponents.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/search/SearchComponents.kt @@ -55,7 +55,7 @@ import com.lilin.gamelibrary.ui.theme.RatingBronze import com.lilin.gamelibrary.ui.theme.RatingEmpty import com.lilin.gamelibrary.ui.theme.RatingGold import com.lilin.gamelibrary.ui.theme.RatingSilver -import java.util.Locale +import com.lilin.gamelibrary.ui.util.formatCount private const val METACRITIC_GOLD_THRESHOLD = 90 private const val METACRITIC_SILVER_THRESHOLD = 70 @@ -312,18 +312,6 @@ private fun getMetacriticColor(score: Int): Color { } } -/** - * 数値をフォーマット(例:1234 → 1.2k) - */ -private fun formatCount(count: Int): String { - return when { - count >= 1000 -> { - String.format(Locale.ROOT, "%.1fk", count / 1000.0) - } - else -> count.toString() - } -} - @Preview @Composable private fun SearchResultCardHighScorePreview() { diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/sectiondetail/GameGridCard.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/sectiondetail/GameGridCard.kt index 43ee1b4..0110c26 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/sectiondetail/GameGridCard.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/sectiondetail/GameGridCard.kt @@ -60,7 +60,7 @@ import com.lilin.gamelibrary.ui.theme.RatingGold import com.lilin.gamelibrary.ui.theme.RatingSilver import com.lilin.gamelibrary.ui.theme.TrendingGradientEnd import com.lilin.gamelibrary.ui.theme.TrendingGradientStart -import java.util.Locale +import com.lilin.gamelibrary.ui.util.formatCount private const val METACRITIC_GOLD_THRESHOLD = 90 private const val METACRITIC_SILVER_THRESHOLD = 70 @@ -581,19 +581,6 @@ fun getMetacriticColor(score: Int): Color { } } -/** - * 数値をフォーマット(例:1234 → 1.2k) - */ -fun formatCount(count: Int): String { - return when { - count >= 1000 -> { - String.format(Locale.ROOT, "%.1fk", count / 1000.0) - } - - else -> count.toString() - } -} - @Preview @Composable private fun TrendingGameCardPreview() { diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/util/Formatter.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/util/Formatter.kt index 1efacbd..eb31a07 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/util/Formatter.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/util/Formatter.kt @@ -1,5 +1,20 @@ package com.lilin.gamelibrary.ui.util +import java.util.Locale + +/** + * 数値をフォーマット(例:1234 → 1.2k) + */ +fun formatCount(count: Int): String { + return when { + count >= 1000 -> { + String.format(Locale.ROOT, "%.1f k", count / 1000.0) + } + + else -> count.toString() + } +} + fun Double.formatAvgRating(): String { return if (this > 0) { "%.1f".format(this) From bfa695babba2187396cc4ad9a7d5c47413d20ca4 Mon Sep 17 00:00:00 2001 From: lilin Date: Thu, 29 Jan 2026 10:17:11 +0900 Subject: [PATCH 03/11] =?UTF-8?q?Compac=20Screen=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gamelibrary/domain/model/SortOption.kt | 55 ++++++ .../feature/favorite/FavoriteScreen.kt | 11 +- .../feature/favorite/FavoriteUiState.kt | 2 +- .../feature/favorite/FavoriteViewModel.kt | 42 ++--- .../component/favorite/FavoriteSortFabMenu.kt | 159 ++++++++++-------- app/src/main/res/values/strings.xml | 4 + 6 files changed, 170 insertions(+), 103 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/domain/model/SortOption.kt b/app/src/main/kotlin/com/lilin/gamelibrary/domain/model/SortOption.kt index 14f0c83..be4ba1d 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/domain/model/SortOption.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/domain/model/SortOption.kt @@ -2,6 +2,19 @@ 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, @@ -21,3 +34,45 @@ enum class SortOption { RATING_ASC -> R.string.favorite_rating_asc } } + +fun SortOption.toTypeAndDirection(): Pair { + 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 + } + } + } +} diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreen.kt b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreen.kt index 828faa3..00dc58e 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreen.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreen.kt @@ -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() @@ -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, ) } }, @@ -163,7 +166,7 @@ private fun FavoriteScreen( is FavoriteUiState.Success -> { val listState = rememberLazyListState() - LaunchedEffect(uiState.sortOrder) { + LaunchedEffect(uiState.sortOption) { listState.scrollToItem(0) } diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteUiState.kt b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteUiState.kt index 916a778..95a06e5 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteUiState.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteUiState.kt @@ -12,7 +12,7 @@ sealed interface FavoriteUiState { data class Success( val games: List, - val sortOrder: SortOrder, + val sortOption: SortOption, ) : FavoriteUiState data class Error(val message: String) : FavoriteUiState diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModel.kt b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModel.kt index d7df2c1..ae1701c 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModel.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModel.kt @@ -28,34 +28,36 @@ class FavoriteViewModel @Inject constructor( MutableStateFlow(FavoriteExpandedUiState.Empty) val expandedUiState: StateFlow = _expandedUiState.asStateFlow() - private val _sortOrder = MutableStateFlow(SortOrder.NEWEST_FIRST) - val sortOrder: StateFlow = _sortOrder.asStateFlow() - private val _showDeleteDialog = MutableStateFlow(false) val showDeleteDialog: StateFlow = _showDeleteDialog.asStateFlow() private val _selectedGameId = MutableStateFlow(0) - val selectedGameId: StateFlow = _selectedGameId.asStateFlow() private val _selectedGameName = MutableStateFlow("") val selectedGameName: StateFlow = _selectedGameName.asStateFlow() + private val _sortOption = MutableStateFlow(SortOption.ADDED_DATE_DESC) + val sortOption: StateFlow = _sortOption.asStateFlow() + + private val _isExpanded = MutableStateFlow(false) + val isExpanded: StateFlow = _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.invoke2(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 { @@ -64,25 +66,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 @@ -95,7 +84,6 @@ class FavoriteViewModel @Inject constructor( if (games.isEmpty()) { _expandedUiState.value = FavoriteExpandedUiState.Empty } else { - // 統計計算 val statistics = calculateStatistics(games) _expandedUiState.value = FavoriteExpandedUiState.Success( diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt index 6a5f6dd..3c492b5 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt @@ -19,6 +19,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Sort @@ -30,10 +32,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text 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.graphics.Color @@ -42,25 +40,31 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.lilin.gamelibrary.R -import com.lilin.gamelibrary.domain.model.SortOrder +import com.lilin.gamelibrary.domain.model.SortDirection +import com.lilin.gamelibrary.domain.model.SortOption +import com.lilin.gamelibrary.domain.model.SortType +import com.lilin.gamelibrary.domain.model.combineSortOption +import com.lilin.gamelibrary.domain.model.toTypeAndDirection import com.lilin.gamelibrary.ui.theme.FavoriteGradientStart @Composable fun FavoriteSortFabMenu( - sortOrder: SortOrder, - onSortClick: () -> Unit, + currentSortOption: SortOption, + isExpanded: Boolean, + onClickFab: () -> Unit, + onSortChange: (SortOption) -> Unit, modifier: Modifier = Modifier, ) { - var isExpanded by remember { mutableStateOf(false) } + val (currentType, currentDirection) = currentSortOption.toTypeAndDirection() - Box(modifier = modifier.fillMaxSize()) { + Box(modifier = modifier) { if (isExpanded) { Box( modifier = Modifier .fillMaxSize() .pointerInput(Unit) { detectTapGestures( - onTap = { isExpanded = false }, + onTap = { onClickFab() }, ) }, ) @@ -76,22 +80,47 @@ fun FavoriteSortFabMenu( enter = fadeIn(tween(200)) + slideInVertically(tween(200)) { it / 2 }, exit = fadeOut(tween(200)) + slideOutVertically(tween(200)) { it / 2 }, ) { - FavoriteSortMenuItem( - sortOrder = sortOrder, - onClick = onSortClick, - ) - } + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + SortType.entries.forEach { sortType -> + item { + FavoriteSortMenuItem( + sortType = sortType, + currentDirection = if (currentType == sortType) { + currentDirection + } else { + SortDirection.DESC + }, + isSelected = currentType == sortType, + onClick = { + val newDirection = if (currentType == sortType) { + if (currentDirection == SortDirection.DESC) { + SortDirection.ASC + } else { + SortDirection.DESC + } + } else { + SortDirection.DESC + } + onSortChange(combineSortOption(sortType, newDirection)) + }, + ) + } + } + } - FloatingActionButton( - onClick = { isExpanded = !isExpanded }, - containerColor = FavoriteGradientStart, - contentColor = Color.White, - modifier = Modifier.size(56.dp), - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.Sort, - contentDescription = "Sort Menu", - ) + FloatingActionButton( + onClick = onClickFab, + containerColor = FavoriteGradientStart, + contentColor = Color.White, + modifier = Modifier.size(56.dp), + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.Sort, + contentDescription = "Sort Menu", + ) + } } } } @@ -99,33 +128,38 @@ fun FavoriteSortFabMenu( @Composable private fun FavoriteSortMenuItem( - sortOrder: SortOrder, + sortType: SortType, + currentDirection: SortDirection, + isSelected: Boolean, onClick: () -> Unit, + modifier: Modifier = Modifier, ) { Surface( onClick = onClick, shape = RoundedCornerShape(12.dp), color = FavoriteGradientStart, - tonalElevation = 6.dp, - modifier = Modifier.widthIn(min = 140.dp), + tonalElevation = if (isSelected) 8.dp else 6.dp, + modifier = modifier + .widthIn(min = 140.dp), ) { Row( - modifier = Modifier.padding(horizontal = 20.dp, vertical = 16.dp), + modifier = Modifier + .padding(horizontal = 20.dp, vertical = 16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically, ) { AnimatedContent( - targetState = sortOrder, + targetState = currentDirection, transitionSpec = { fadeIn(tween(150)) + scaleIn(tween(150)) togetherWith fadeOut(tween(150)) + scaleOut(tween(150)) }, label = "sort_icon_animation", - ) { order -> + ) { direction -> Icon( - imageVector = when (order) { - SortOrder.NEWEST_FIRST -> Icons.Default.ArrowUpward - SortOrder.OLDEST_FIRST -> Icons.Default.ArrowDownward + imageVector = when (direction) { + SortDirection.ASC -> Icons.Default.ArrowUpward + SortDirection.DESC -> Icons.Default.ArrowDownward }, contentDescription = null, modifier = Modifier.size(24.dp), @@ -134,16 +168,17 @@ private fun FavoriteSortMenuItem( } AnimatedContent( - targetState = sortOrder, + targetState = sortType, transitionSpec = { fadeIn(tween(150)) togetherWith fadeOut(tween(150)) }, label = "sort_text_animation", - ) { order -> + ) { sortType -> Text( - text = when (order) { - SortOrder.NEWEST_FIRST -> stringResource(R.string.favorite_sort_newest) - SortOrder.OLDEST_FIRST -> stringResource(R.string.favorite_sort_oldest) + text = when (sortType) { + SortType.ADDED_DATE -> stringResource(R.string.favorite_sort_added_date) + SortType.TITLE -> stringResource(R.string.favorite_sort_title) + SortType.RATING -> stringResource(R.string.favorite_sort_rating) }, color = Color.White, style = MaterialTheme.typography.titleMedium, @@ -153,48 +188,30 @@ private fun FavoriteSortMenuItem( } } -@Preview(showBackground = true) -@Composable -private fun FavoriteSortFabMenuClosedPreview() { - FavoriteSortFabMenu( - sortOrder = SortOrder.NEWEST_FIRST, - onSortClick = {}, - ) -} - -@Preview(showBackground = true, name = "Expanded - Newest First") +@Preview(showBackground = true, name = "Expanded - Added Date DESC") @Composable -private fun FavoriteSortFabMenuExpandedNewestPreview() { +private fun FavoriteSortFabMenuExpandedPreview() { Column( horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(16.dp), ) { FavoriteSortMenuItem( - sortOrder = SortOrder.NEWEST_FIRST, + sortType = SortType.ADDED_DATE, + currentDirection = SortDirection.DESC, + isSelected = true, onClick = {}, ) - FloatingActionButton( + FavoriteSortMenuItem( + sortType = SortType.TITLE, + currentDirection = SortDirection.DESC, + isSelected = false, onClick = {}, - containerColor = FavoriteGradientStart, - contentColor = Color.White, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.Sort, - contentDescription = null, - ) - } - } -} - -@Preview(showBackground = true, name = "Expanded - Oldest First") -@Composable -private fun FavoriteSortFabMenuExpandedOldestPreview() { - Column( - horizontalAlignment = Alignment.End, - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { + ) FavoriteSortMenuItem( - sortOrder = SortOrder.OLDEST_FIRST, + sortType = SortType.RATING, + currentDirection = SortDirection.DESC, + isSelected = false, onClick = {}, ) FloatingActionButton( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 84b988f..fc588ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -88,4 +88,8 @@ 合計 平均評価 最新追加 + + 追加日 + タイトル + 評価 From 4cab8bad059420eba74fe0c955c8f79ac46e3626 Mon Sep 17 00:00:00 2001 From: lilin Date: Sat, 31 Jan 2026 22:28:19 +0900 Subject: [PATCH 04/11] =?UTF-8?q?=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/favorite/FavoriteSortFabMenu.kt | 83 ++++++++++++++----- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt index 3c492b5..5137c2a 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt @@ -10,17 +10,19 @@ import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Sort @@ -46,6 +48,7 @@ import com.lilin.gamelibrary.domain.model.SortType import com.lilin.gamelibrary.domain.model.combineSortOption import com.lilin.gamelibrary.domain.model.toTypeAndDirection import com.lilin.gamelibrary.ui.theme.FavoriteGradientStart +import com.lilin.gamelibrary.ui.theme.InfoColor @Composable fun FavoriteSortFabMenu( @@ -109,7 +112,9 @@ fun FavoriteSortFabMenu( } } } + } + Box { FloatingActionButton( onClick = onClickFab, containerColor = FavoriteGradientStart, @@ -121,6 +126,29 @@ fun FavoriteSortFabMenu( contentDescription = "Sort Menu", ) } + + if (!isExpanded) { + Box( + modifier = Modifier + .align(Alignment.CenterEnd) + .offset(x = 10.dp) + .background( + color = InfoColor, + shape = CircleShape, + ), + ) { + Icon( + imageVector = when (currentDirection) { + SortDirection.ASC -> Icons.Default.ArrowUpward + SortDirection.DESC -> Icons.Default.ArrowDownward + }, + contentDescription = null, + modifier = Modifier + .size(24.dp), + tint = Color.White, + ) + } + } } } } @@ -145,28 +173,9 @@ private fun FavoriteSortMenuItem( Row( modifier = Modifier .padding(horizontal = 20.dp, vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - AnimatedContent( - targetState = currentDirection, - transitionSpec = { - fadeIn(tween(150)) + scaleIn(tween(150)) togetherWith - fadeOut(tween(150)) + scaleOut(tween(150)) - }, - label = "sort_icon_animation", - ) { direction -> - Icon( - imageVector = when (direction) { - SortDirection.ASC -> Icons.Default.ArrowUpward - SortDirection.DESC -> Icons.Default.ArrowDownward - }, - contentDescription = null, - modifier = Modifier.size(24.dp), - tint = Color.White, - ) - } - AnimatedContent( targetState = sortType, transitionSpec = { @@ -184,6 +193,27 @@ private fun FavoriteSortMenuItem( style = MaterialTheme.typography.titleMedium, ) } + + AnimatedContent( + targetState = currentDirection, + transitionSpec = { + fadeIn(tween(150)) + scaleIn(tween(150)) togetherWith + fadeOut(tween(150)) + scaleOut(tween(150)) + }, + label = "sort_icon_animation", + ) { direction -> + if (isSelected) { + Icon( + imageVector = when (direction) { + SortDirection.ASC -> Icons.Default.ArrowUpward + SortDirection.DESC -> Icons.Default.ArrowDownward + }, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = Color.White, + ) + } + } } } } @@ -226,3 +256,14 @@ private fun FavoriteSortFabMenuExpandedPreview() { } } } + +@Preview(showBackground = true, name = "Not Expanded") +@Composable +private fun FavoriteSortFabMenuExpanded1Preview() { + FavoriteSortFabMenu( + currentSortOption = SortOption.ADDED_DATE_DESC, + isExpanded = false, + onClickFab = {}, + onSortChange = {}, + ) +} From 829743d249b2b00a8a3868fda2dac855826727db Mon Sep 17 00:00:00 2001 From: lilin Date: Sun, 1 Feb 2026 18:51:58 +0900 Subject: [PATCH 05/11] =?UTF-8?q?badgedBox=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/favorite/FavoriteSortFabMenu.kt | 50 +++++++++---------- .../com/lilin/gamelibrary/ui/theme/Color.kt | 1 + 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt index 5137c2a..b919f4c 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt @@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn @@ -28,6 +27,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Sort import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.material.icons.filled.ArrowUpward +import androidx.compose.material3.BadgedBox import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -47,8 +47,8 @@ import com.lilin.gamelibrary.domain.model.SortOption import com.lilin.gamelibrary.domain.model.SortType import com.lilin.gamelibrary.domain.model.combineSortOption import com.lilin.gamelibrary.domain.model.toTypeAndDirection +import com.lilin.gamelibrary.ui.theme.BadgeColor import com.lilin.gamelibrary.ui.theme.FavoriteGradientStart -import com.lilin.gamelibrary.ui.theme.InfoColor @Composable fun FavoriteSortFabMenu( @@ -114,7 +114,28 @@ fun FavoriteSortFabMenu( } } - Box { + BadgedBox( + badge = { + if (!isExpanded) { + Box( + modifier = Modifier + .background( + color = BadgeColor, + shape = CircleShape, + ) + .padding(2.dp), + ) { + Icon( + imageVector = if (currentDirection == SortDirection.ASC) + Icons.Default.ArrowUpward else Icons.Default.ArrowDownward, + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = Color.White, + ) + } + } + }, + ) { FloatingActionButton( onClick = onClickFab, containerColor = FavoriteGradientStart, @@ -126,29 +147,6 @@ fun FavoriteSortFabMenu( contentDescription = "Sort Menu", ) } - - if (!isExpanded) { - Box( - modifier = Modifier - .align(Alignment.CenterEnd) - .offset(x = 10.dp) - .background( - color = InfoColor, - shape = CircleShape, - ), - ) { - Icon( - imageVector = when (currentDirection) { - SortDirection.ASC -> Icons.Default.ArrowUpward - SortDirection.DESC -> Icons.Default.ArrowDownward - }, - contentDescription = null, - modifier = Modifier - .size(24.dp), - tint = Color.White, - ) - } - } } } } diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/theme/Color.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/theme/Color.kt index 7451aff..483ebba 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/theme/Color.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/theme/Color.kt @@ -112,3 +112,4 @@ val IconTint = Color(0xFF7C4DFF) // FavoriteSection val FavoriteGradientStart = Color(0xFF7C4DFF) val FavoriteGradientEnd = Color(0xFFFFD700) +val BadgeColor = Color(0xFFFFB74D) From e56acaf6ff239c273cadc0929d4c0898ea01cd03 Mon Sep 17 00:00:00 2001 From: lilin Date: Sun, 1 Feb 2026 21:12:45 +0900 Subject: [PATCH 06/11] =?UTF-8?q?Color=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/lilin/gamelibrary/ui/theme/Color.kt | 94 ++++++++++--------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/theme/Color.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/theme/Color.kt index 483ebba..ad42278 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/theme/Color.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/theme/Color.kt @@ -69,47 +69,53 @@ val OnSurfaceVariantDark = Color(0xFFC4C6CF) val OutlineDark = Color(0xFF938F99) val OutlineVariantDark = Color(0xFF49454F) -// 評価用カラー -val RatingGold = Color(0xFFFFB300) -val RatingSilver = Color(0xFFC0C0C0) -val RatingBronze = Color(0xFFCD7F32) -val RatingEmpty = Color(0xFFE0E0E0) - -// Trending Section Colors -val TrendingGradientStart = Color(0xFFFF6B6B) -val TrendingGradientEnd = Color(0xFFFF8E53) - -// High Rated Section Colors -val HighRatedGradientStart = Color(0xFFFFD700) -val HighRatedGradientEnd = Color(0xFFFFA500) - -// New Release Section Colors -val NewReleaseGradientStart = Color(0xFF26C6DA) -val NewReleaseGradientEnd = Color(0xFF00BFA5) - -// その他の機能的なカラー -val SuccessColor = Color(0xFF00C853) -val WarningColor = Color(0xFFFF9800) -val InfoColor = Color(0xFF0066CC) -val LoadingColor = Color(0xFFCCCCCC) - -// 評価セクション -val DetailRatingGradientStart = Color(0xFF7C4DFF) -val DetailRatingGradientEnd = Color(0xFFB47CFF) - -// 説明セクション -val DetailDescriptionGradientStart = Color(0xFFFF6B6B) -val DetailDescriptionGradientEnd = Color(0xFFFFB74D) - -// タグセクション -val DetailTagGradientStart = Color(0xFF26C6DA) -val DetailTagGradientEnd = Color(0xFF7C4DFF) - -// フォーカス時 -val OutlineColor = Color(0xFF7C4DFF) -val IconTint = Color(0xFF7C4DFF) - -// FavoriteSection -val FavoriteGradientStart = Color(0xFF7C4DFF) -val FavoriteGradientEnd = Color(0xFFFFD700) -val BadgeColor = Color(0xFFFFB74D) +/* Color Palette */ +// Purple +val Purple500 = Color(0xFF7C4DFF) +val Purple300 = Color(0xFFB47CFF) + +// Coral & Orange +val Coral500 = Color(0xFFFF6B6B) +val Orange500 = Color(0xFFFFB74D) +val Orange600 = Color(0xFFFF8E53) +val Orange700 = Color(0xFFFFA500) + +// Teal +val Teal500 = Color(0xFF26C6DA) +val Teal700 = Color(0xFF00BFA5) + +// Gold +val Gold500 = Color(0xFFFFD700) +val Gold600 = Color(0xFFFFB300) + +// Gray +val Gray300 = Color(0xFFE0E0E0) +val Silver = Color(0xFFC0C0C0) +val Bronze = Color(0xFFCD7F32) + +/* Semantic Colors - Discovery Section */ +val TrendingGradientStart = Coral500 +val TrendingGradientEnd = Orange600 +val HighRatedGradientStart = Gold500 +val HighRatedGradientEnd = Orange700 +val NewReleaseGradientStart = Teal500 +val NewReleaseGradientEnd = Teal700 + +/* Semantic Colors - Detail Section */ +val DetailRatingGradientStart = Purple500 +val DetailRatingGradientEnd = Purple300 +val DetailDescriptionGradientStart = Coral500 +val DetailDescriptionGradientEnd = Orange500 +val DetailTagGradientStart = Teal500 +val DetailTagGradientEnd = Purple500 + +/* Semantic Colors - Favorite Section */ +val FavoriteGradientStart = Purple500 +val FavoriteGradientEnd = Gold500 +val BadgeColor = Orange500 + +/* Semantic Colors - Rating */ +val RatingGold = Gold600 +val RatingSilver = Silver +val RatingBronze = Bronze +val RatingEmpty = Gray300 From c3749fc13a9d243a4d0175c30ed91fb8cf534c43 Mon Sep 17 00:00:00 2001 From: lilin Date: Sun, 1 Feb 2026 22:59:18 +0900 Subject: [PATCH 07/11] =?UTF-8?q?BottomBar=E3=81=AEtemplate=E4=BD=9C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/component/BottomBarTemplate.kt | 71 +++++++ .../component/discovery/DiscoveryBottomBar.kt | 174 +++++++----------- 2 files changed, 135 insertions(+), 110 deletions(-) create mode 100644 app/src/main/kotlin/com/lilin/gamelibrary/ui/component/BottomBarTemplate.kt diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/BottomBarTemplate.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/BottomBarTemplate.kt new file mode 100644 index 0000000..e193b56 --- /dev/null +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/BottomBarTemplate.kt @@ -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, + ) + } + } + } +} diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/DiscoveryBottomBar.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/DiscoveryBottomBar.kt index ab3cff6..ff654cf 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/DiscoveryBottomBar.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/discovery/DiscoveryBottomBar.kt @@ -3,13 +3,8 @@ package com.lilin.gamelibrary.ui.component.discovery import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape @@ -18,7 +13,6 @@ import androidx.compose.material.icons.rounded.LocalFireDepartment import androidx.compose.material.icons.rounded.NewReleases import androidx.compose.material.icons.rounded.Stars import androidx.compose.material.ripple.RippleAlpha -import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalRippleConfiguration @@ -27,15 +21,12 @@ import androidx.compose.material3.RippleConfiguration import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember -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.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.lilin.gamelibrary.R +import com.lilin.gamelibrary.ui.component.BottomBarTemplate import com.lilin.gamelibrary.ui.theme.DetailDescriptionGradientEnd import com.lilin.gamelibrary.ui.theme.HighRatedGradientStart import com.lilin.gamelibrary.ui.theme.HighRatedRippleConfiguration @@ -57,114 +48,77 @@ fun DiscoveryBottomBar( val interactionSourceHighRated = remember { MutableInteractionSource() } val interactionSourceNewRelease = remember { MutableInteractionSource() } - Box( - modifier = modifier - .fillMaxWidth() - .background( - brush = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), - MaterialTheme.colorScheme.surface, - ), - ), - ), - ) { - BottomAppBar( + BottomBarTemplate(modifier = modifier) { + Row( modifier = Modifier - .defaultMinSize(minHeight = 80.dp) - .padding(bottom = 10.dp), - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, + .fillMaxWidth() + .padding(vertical = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween, ) { - Box( + CustomRippleIconButton( + interactionSource = interactionSourceTrending, + rippleConfiguration = TrendingRippleConfiguration, + onClick = onClickTrendingTab, modifier = Modifier - .padding(horizontal = 20.dp) - .fillMaxSize() - .shadow( - elevation = 8.dp, - shape = RoundedCornerShape(90.dp), - ) + .size(80.dp, 44.dp) .background( - color = MaterialTheme.colorScheme.surface, - shape = RoundedCornerShape(90.dp), + color = if (selectedTab == SectionType.TRENDING) { + TrendingGradientEnd.copy(alpha = 0.15f) + } else { + MaterialTheme.colorScheme.surface + }, + shape = RoundedCornerShape(32.dp), ), - contentAlignment = Alignment.Center, ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 25.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - CustomRippleIconButton( - interactionSource = interactionSourceTrending, - rippleConfiguration = TrendingRippleConfiguration, - onClick = onClickTrendingTab, - modifier = Modifier - .size(80.dp, 44.dp) - .background( - color = if (selectedTab == SectionType.TRENDING) { - TrendingGradientEnd.copy(alpha = 0.15f) - } else { - MaterialTheme.colorScheme.surface - }, - shape = RoundedCornerShape(32.dp), - ), - ) { - Icon( - imageVector = Icons.Rounded.LocalFireDepartment, - contentDescription = stringResource(R.string.detail_bottom_bar_back), - tint = TrendingGradientStart, - ) - } + Icon( + imageVector = Icons.Rounded.LocalFireDepartment, + contentDescription = stringResource(R.string.detail_bottom_bar_back), + tint = TrendingGradientStart, + ) + } - CustomRippleIconButton( - interactionSource = interactionSourceHighRated, - rippleConfiguration = HighRatedRippleConfiguration, - onClick = onClickHighRatedTab, - modifier = Modifier - .size(80.dp, 44.dp) - .background( - color = if (selectedTab == SectionType.HIGH_RATED) { - DetailDescriptionGradientEnd.copy(alpha = 0.25f) - } else { - MaterialTheme.colorScheme.surface - }, - shape = RoundedCornerShape(32.dp), - ), - ) { - Icon( - imageVector = Icons.Rounded.Stars, - contentDescription = stringResource(R.string.detail_bottom_bar_back), - tint = HighRatedGradientStart, - ) - } + CustomRippleIconButton( + interactionSource = interactionSourceHighRated, + rippleConfiguration = HighRatedRippleConfiguration, + onClick = onClickHighRatedTab, + modifier = Modifier + .size(80.dp, 44.dp) + .background( + color = if (selectedTab == SectionType.HIGH_RATED) { + DetailDescriptionGradientEnd.copy(alpha = 0.25f) + } else { + MaterialTheme.colorScheme.surface + }, + shape = RoundedCornerShape(32.dp), + ), + ) { + Icon( + imageVector = Icons.Rounded.Stars, + contentDescription = stringResource(R.string.detail_bottom_bar_back), + tint = HighRatedGradientStart, + ) + } - CustomRippleIconButton( - interactionSource = interactionSourceNewRelease, - rippleConfiguration = NewReleaseRippleConfiguration, - onClick = onClickNewReleaseTab, - modifier = Modifier - .size(80.dp, 44.dp) - .background( - color = if (selectedTab == SectionType.NEW_RELEASE) { - NewReleaseGradientStart.copy(alpha = 0.15f) - } else { - MaterialTheme.colorScheme.surface - }, - shape = RoundedCornerShape(32.dp), - ), - ) { - Icon( - imageVector = Icons.Rounded.NewReleases, - contentDescription = stringResource(R.string.detail_bottom_bar_back), - tint = NewReleaseGradientStart, - ) - } - } + CustomRippleIconButton( + interactionSource = interactionSourceNewRelease, + rippleConfiguration = NewReleaseRippleConfiguration, + onClick = onClickNewReleaseTab, + modifier = Modifier + .size(80.dp, 44.dp) + .background( + color = if (selectedTab == SectionType.NEW_RELEASE) { + NewReleaseGradientStart.copy(alpha = 0.15f) + } else { + MaterialTheme.colorScheme.surface + }, + shape = RoundedCornerShape(32.dp), + ), + ) { + Icon( + imageVector = Icons.Rounded.NewReleases, + contentDescription = stringResource(R.string.detail_bottom_bar_back), + tint = NewReleaseGradientStart, + ) } } } From 7e6865c7c764f059aa37c2ceb5ecb0ca8618aa81 Mon Sep 17 00:00:00 2001 From: lilin Date: Sun, 1 Feb 2026 23:05:45 +0900 Subject: [PATCH 08/11] =?UTF-8?q?GameDetailScreen,=20SectionDetailScreen?= =?UTF-8?q?=E3=81=AEbottombar=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/detail/GameDetailBottomBar.kt | 143 ++++++------------ .../sectiondetail/SectionDetailBottomBar.kt | 127 +++++----------- 2 files changed, 90 insertions(+), 180 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/detail/GameDetailBottomBar.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/detail/GameDetailBottomBar.kt index 8ceabd7..21e852b 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/detail/GameDetailBottomBar.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/detail/GameDetailBottomBar.kt @@ -1,34 +1,26 @@ package com.lilin.gamelibrary.ui.component.detail -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Favorite import androidx.compose.material.icons.rounded.FavoriteBorder import androidx.compose.material.icons.rounded.Share -import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton 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.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.lilin.gamelibrary.R +import com.lilin.gamelibrary.ui.component.BottomBarTemplate @Composable fun GameDetailBottomBar( @@ -39,96 +31,59 @@ fun GameDetailBottomBar( isFavorite: Boolean, modifier: Modifier = Modifier, ) { - Box(modifier = modifier) { - Box( + BottomBarTemplate(modifier = modifier) { + Row( modifier = Modifier .fillMaxWidth() - .height(90.dp) - .background( - brush = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), - MaterialTheme.colorScheme.surface, - ), - ), - ), - ) - - BottomAppBar( - modifier = Modifier.height(90.dp), - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, + .padding(vertical = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { - Box( - modifier = Modifier - .padding(horizontal = 60.dp) - .fillMaxSize() - .shadow( - elevation = 8.dp, - shape = RoundedCornerShape(32.dp), - ) - .background( - color = MaterialTheme.colorScheme.surface, - shape = RoundedCornerShape(32.dp), - ), - contentAlignment = Alignment.Center, - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - IconButton(onClick = onBackClick) { - Icon( - imageVector = Icons.AutoMirrored.Rounded.ArrowBack, - contentDescription = stringResource(R.string.detail_bottom_bar_back), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = stringResource(R.string.detail_bottom_bar_back), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } - Spacer(modifier = Modifier.weight(1f)) - IconButton( - onClick = onFavoriteClick, - enabled = isSuccessState, - ) { - Icon( - imageVector = if (isFavorite) { - Icons.Rounded.Favorite - } else { - Icons.Rounded.FavoriteBorder - }, - contentDescription = stringResource(R.string.detail_bottom_bar_favorite), - tint = if (isSuccessState) { - if (isFavorite) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onSurfaceVariant - } - } else { - MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) - }, - ) - } + Spacer(modifier = Modifier.weight(1f)) + IconButton( + onClick = onFavoriteClick, + enabled = isSuccessState, + ) { + Icon( + imageVector = if (isFavorite) { + Icons.Rounded.Favorite + } else { + Icons.Rounded.FavoriteBorder + }, + contentDescription = stringResource(R.string.detail_bottom_bar_favorite), + tint = if (isSuccessState) { + if (isFavorite) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + } + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) + }, + ) + } - IconButton( - onClick = onShareClick, - enabled = isSuccessState, - ) { - Icon( - imageVector = Icons.Rounded.Share, - contentDescription = stringResource(R.string.detail_bottom_bar_share), - tint = if (isSuccessState) { - MaterialTheme.colorScheme.onSurfaceVariant - } else { - MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) - }, - ) - } - } + IconButton( + onClick = onShareClick, + enabled = isSuccessState, + ) { + Icon( + imageVector = Icons.Rounded.Share, + contentDescription = stringResource(R.string.detail_bottom_bar_share), + tint = if (isSuccessState) { + MaterialTheme.colorScheme.onSurfaceVariant + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) + }, + ) } } } diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/sectiondetail/SectionDetailBottomBar.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/sectiondetail/SectionDetailBottomBar.kt index 53632b8..8bd5af7 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/sectiondetail/SectionDetailBottomBar.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/sectiondetail/SectionDetailBottomBar.kt @@ -1,34 +1,26 @@ package com.lilin.gamelibrary.ui.component.sectiondetail -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ViewList import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.filled.GridView -import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton 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.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.lilin.gamelibrary.R import com.lilin.gamelibrary.feature.sectiondetail.DisplayMode +import com.lilin.gamelibrary.ui.component.BottomBarTemplate @Composable fun SectionDetailBottomBar( @@ -37,89 +29,52 @@ fun SectionDetailBottomBar( onDisplayModeChange: (DisplayMode) -> Unit, modifier: Modifier = Modifier, ) { - Box(modifier = modifier) { - Box( + BottomBarTemplate(modifier = modifier) { + Row( modifier = Modifier .fillMaxWidth() - .height(100.dp) - .background( - brush = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), - MaterialTheme.colorScheme.surface, - ), - ), - ), - ) - - BottomAppBar( - modifier = Modifier.height(90.dp), - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, + .padding(vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, ) { - Box( - modifier = Modifier - .padding(horizontal = 60.dp) - .fillMaxSize() - .shadow( - elevation = 8.dp, - shape = RoundedCornerShape(32.dp), - ) - .background( - color = MaterialTheme.colorScheme.surface, - shape = RoundedCornerShape(32.dp), - ), - contentAlignment = Alignment.Center, - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - IconButton(onClick = onBackClick) { - Icon( - imageVector = Icons.AutoMirrored.Rounded.ArrowBack, - contentDescription = stringResource(R.string.detail_bottom_bar_back), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = stringResource(R.string.detail_bottom_bar_back), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.weight(1f)) - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - IconButton( - onClick = { onDisplayModeChange(DisplayMode.GRID_COLUMN) }, - ) { - Icon( - imageVector = Icons.Filled.GridView, - contentDescription = "2列グリッド", - tint = if (currentDisplayMode == DisplayMode.GRID_COLUMN) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onSurfaceVariant - }, - ) - } + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + IconButton( + onClick = { onDisplayModeChange(DisplayMode.GRID_COLUMN) }, + ) { + Icon( + imageVector = Icons.Filled.GridView, + contentDescription = "2列グリッド", + tint = if (currentDisplayMode == DisplayMode.GRID_COLUMN) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + ) + } - IconButton( - onClick = { onDisplayModeChange(DisplayMode.LIST) }, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ViewList, - contentDescription = "リスト表示", - tint = if (currentDisplayMode == DisplayMode.LIST) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onSurfaceVariant - }, - ) - } - } + IconButton( + onClick = { onDisplayModeChange(DisplayMode.LIST) }, + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ViewList, + contentDescription = "リスト表示", + tint = if (currentDisplayMode == DisplayMode.LIST) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + ) } } } From fd53b29daf7ec5243650842a6ae51e8a57975698 Mon Sep 17 00:00:00 2001 From: lilin Date: Sun, 1 Feb 2026 23:16:32 +0900 Subject: [PATCH 09/11] =?UTF-8?q?SortOrder=E3=81=8B=E3=82=89=E3=81=AE?= =?UTF-8?q?=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../favorite/GetFavoriteGamesUseCase.kt | 9 ++------- .../feature/favorite/FavoriteViewModel.kt | 7 ++++--- .../favorite/GetFavoriteGamesUseCaseTest.kt | 18 +++++++++--------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCase.kt b/app/src/main/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCase.kt index 51957a9..51d8401 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCase.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCase.kt @@ -13,18 +13,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> { - return repository.getFavoriteGames(sortOrder) - } - - fun invoke2(sortOption: SortOption): Flow> { + operator fun invoke(sortOption: SortOption): Flow> { return repository.getFavoriteGames2(sortOption) } } diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModel.kt b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModel.kt index ae1701c..a4a7a94 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModel.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModel.kt @@ -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 @@ -52,7 +51,7 @@ class FavoriteViewModel @Inject constructor( viewModelScope.launch { runCatching { - getFavoriteGamesUseCase.invoke2(sortOption).collect { games -> + getFavoriteGamesUseCase(sortOption).collect { games -> _uiState.value = if (games.isEmpty()) { FavoriteUiState.Empty } else { @@ -80,7 +79,7 @@ class FavoriteViewModel @Inject constructor( viewModelScope.launch { runCatching { - getFavoriteGamesUseCase.invoke2(sortOption).collect { games -> + getFavoriteGamesUseCase(sortOption).collect { games -> if (games.isEmpty()) { _expandedUiState.value = FavoriteExpandedUiState.Empty } else { @@ -105,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 -> "" } } diff --git a/app/src/test/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCaseTest.kt b/app/src/test/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCaseTest.kt index 51047fb..7c0f4b9 100644 --- a/app/src/test/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCaseTest.kt +++ b/app/src/test/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCaseTest.kt @@ -2,7 +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.SortOrder +import com.lilin.gamelibrary.domain.model.SortOption import io.mockk.coEvery import io.mockk.mockk import kotlinx.coroutines.flow.first @@ -23,12 +23,12 @@ class GetFavoriteGamesUseCaseTest { } @Test - fun invoke_withNewestFirstOrder_returnsCorrectFlow() = runTest { - val order = SortOrder.NEWEST_FIRST + fun invoke_withAddedDateDescOrder_returnsCorrectFlow() = runTest { + val order = SortOption.ADDED_DATE_DESC val games = newestGames() coEvery { - repository.getFavoriteGames(order) + repository.getFavoriteGames2(order) } returns flowOf(games) val result = useCase.invoke(order) @@ -38,12 +38,12 @@ class GetFavoriteGamesUseCaseTest { } @Test - fun invoke_withOldestFirstOrder_returnsCorrectFlow() = runTest { - val order = SortOrder.OLDEST_FIRST + fun invoke_withAddedDateAscOrder_returnsCorrectFlow() = runTest { + val order = SortOption.ADDED_DATE_ASC val games = oldestGames() coEvery { - repository.getFavoriteGames(order) + repository.getFavoriteGames2(order) } returns flowOf(games) val result = useCase.invoke(order) @@ -54,11 +54,11 @@ class GetFavoriteGamesUseCaseTest { @Test fun invoke_withEmptyList_returnsEmptyFlow() = runTest { - val order = SortOrder.NEWEST_FIRST + val order = SortOption.ADDED_DATE_DESC val emptyGames = emptyList() coEvery { - repository.getFavoriteGames(order) + repository.getFavoriteGames2(order) } returns flowOf(emptyGames) val result = useCase.invoke(order) From 312be96578d8b3a52e724ae92a0c3536b542c578 Mon Sep 17 00:00:00 2001 From: lilin Date: Sun, 1 Feb 2026 23:20:26 +0900 Subject: [PATCH 10/11] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../favorite/FavoriteScreenShotTest.kt | 5 +- .../feature/favorite/FavoriteViewModelTest.kt | 50 ++++++++++--------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreenShotTest.kt b/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreenShotTest.kt index 91204e0..a2ee7e7 100644 --- a/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreenShotTest.kt +++ b/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreenShotTest.kt @@ -27,6 +27,7 @@ import coil3.asImage import coil3.test.FakeImageLoaderEngine import com.lilin.gamelibrary.R import com.lilin.gamelibrary.domain.model.FavoriteGame +import com.lilin.gamelibrary.domain.model.SortOption import com.lilin.gamelibrary.domain.model.SortOrder import com.lilin.gamelibrary.navigation.TOP_LEVEL_ROUTES import com.lilin.gamelibrary.ui.component.GameLibraryNavigationBar @@ -95,7 +96,7 @@ class FavoriteScreenShotTest { FavoriteScreenSample( uiState = FavoriteUiState.Success( games = FAVORITE_GAMES, - sortOrder = SortOrder.NEWEST_FIRST, + sortOption = SortOption.ADDED_DATE_DESC, ), modifier = Modifier.padding(paddingValues), ) @@ -168,7 +169,7 @@ class FavoriteScreenShotTest { FavoriteScreenSample( uiState = FavoriteUiState.Success( games = FAVORITE_GAMES, - sortOrder = SortOrder.OLDEST_FIRST, + sortOption = SortOption.TITLE_DESC, ), modifier = Modifier.padding(paddingValues), ) diff --git a/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModelTest.kt b/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModelTest.kt index f5d6ee9..15135aa 100644 --- a/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModelTest.kt +++ b/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteViewModelTest.kt @@ -2,7 +2,7 @@ package com.lilin.gamelibrary.feature.favorite import app.cash.turbine.test import com.lilin.gamelibrary.domain.model.FavoriteGame -import com.lilin.gamelibrary.domain.model.SortOrder +import com.lilin.gamelibrary.domain.model.SortOption import com.lilin.gamelibrary.domain.usecase.favorite.GetFavoriteGamesUseCase import com.lilin.gamelibrary.domain.usecase.favorite.RemoveFavoriteGameUseCase import io.mockk.every @@ -19,7 +19,6 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import kotlin.collections.reversed @ExperimentalCoroutinesApi class FavoriteViewModelTest { @@ -42,7 +41,7 @@ class FavoriteViewModelTest { } @Test - fun toggleSortOrder_withNewestFirstOrder_updatesToOldestFirst() = + fun toggleSortOrder_withAddedDateDescOrder_updatesToAddedDateAsc() = runTest { val testGames = listOf( FavoriteGame( @@ -65,8 +64,8 @@ class FavoriteViewModelTest { ), ) - every { getFavoriteGamesUseCase.invoke(SortOrder.NEWEST_FIRST) } returns flowOf(testGames) - every { getFavoriteGamesUseCase.invoke(SortOrder.OLDEST_FIRST) } returns flowOf(testGames.reversed()) + every { getFavoriteGamesUseCase.invoke(SortOption.ADDED_DATE_DESC) } returns flowOf(testGames) + every { getFavoriteGamesUseCase.invoke(SortOption.ADDED_DATE_ASC) } returns flowOf(testGames.reversed()) viewModel = FavoriteViewModel(getFavoriteGamesUseCase, removeFavoriteGameUseCase) @@ -77,12 +76,12 @@ class FavoriteViewModelTest { val initialSuccessState = awaitItem() assertEquals(true, initialSuccessState is FavoriteUiState.Success) assertEquals( - SortOrder.NEWEST_FIRST, - (initialSuccessState as FavoriteUiState.Success).sortOrder, + SortOption.ADDED_DATE_DESC, + (initialSuccessState as FavoriteUiState.Success).sortOption, ) assertEquals(testGames, initialSuccessState.games) - viewModel.toggleSortOrder() + viewModel.toggleSortOrder(SortOption.ADDED_DATE_ASC) assertEquals(FavoriteUiState.Loading, awaitItem()) @@ -91,15 +90,15 @@ class FavoriteViewModelTest { val toggledSuccessState = awaitItem() assertEquals(true, toggledSuccessState is FavoriteUiState.Success) assertEquals( - SortOrder.OLDEST_FIRST, - (toggledSuccessState as FavoriteUiState.Success).sortOrder, + SortOption.ADDED_DATE_ASC, + (toggledSuccessState as FavoriteUiState.Success).sortOption, ) assertEquals(testGames.reversed(), toggledSuccessState.games) } } @Test - fun toggleSortOrder_withOldestFirstOrder_updatesToNewestFirst() = + fun toggleSortOrder_withAddedDateAscOrder_updatesToAddedDateDesc() = runTest { val testGames = listOf( FavoriteGame( @@ -113,24 +112,24 @@ class FavoriteViewModelTest { ), ) - every { getFavoriteGamesUseCase.invoke(SortOrder.NEWEST_FIRST) } returns flowOf(testGames) - every { getFavoriteGamesUseCase.invoke(SortOrder.OLDEST_FIRST) } returns flowOf(testGames) + every { getFavoriteGamesUseCase.invoke(SortOption.ADDED_DATE_DESC) } returns flowOf(testGames) + every { getFavoriteGamesUseCase.invoke(SortOption.ADDED_DATE_ASC) } returns flowOf(testGames) viewModel = FavoriteViewModel(getFavoriteGamesUseCase, removeFavoriteGameUseCase) advanceUntilIdle() - viewModel.toggleSortOrder() + viewModel.toggleSortOrder(SortOption.ADDED_DATE_ASC) advanceUntilIdle() viewModel.uiState.test { val currentState = awaitItem() assertEquals(true, currentState is FavoriteUiState.Success) assertEquals( - SortOrder.OLDEST_FIRST, - (currentState as FavoriteUiState.Success).sortOrder, + SortOption.ADDED_DATE_ASC, + (currentState as FavoriteUiState.Success).sortOption, ) - viewModel.toggleSortOrder() + viewModel.toggleSortOrder(SortOption.ADDED_DATE_DESC) assertEquals(FavoriteUiState.Loading, awaitItem()) @@ -139,16 +138,17 @@ class FavoriteViewModelTest { val toggledSuccessState = awaitItem() assertEquals(true, toggledSuccessState is FavoriteUiState.Success) assertEquals( - SortOrder.NEWEST_FIRST, - (toggledSuccessState as FavoriteUiState.Success).sortOrder, + SortOption.ADDED_DATE_DESC, + (toggledSuccessState as FavoriteUiState.Success).sortOption, ) } } @Test - fun toggleSortOrder_withNonSuccessState_stateRemainsUnchanged() = + fun toggleSortOrder_withEmptyState_transitionsToLoadingThenEmpty() = runTest { - every { getFavoriteGamesUseCase.invoke(SortOrder.NEWEST_FIRST) } returns flowOf(emptyList()) + every { getFavoriteGamesUseCase.invoke(SortOption.ADDED_DATE_DESC) } returns flowOf(emptyList()) + every { getFavoriteGamesUseCase.invoke(SortOption.ADDED_DATE_ASC) } returns flowOf(emptyList()) viewModel = FavoriteViewModel(getFavoriteGamesUseCase, removeFavoriteGameUseCase) @@ -159,9 +159,13 @@ class FavoriteViewModelTest { assertEquals(FavoriteUiState.Empty, awaitItem()) - viewModel.toggleSortOrder() + viewModel.toggleSortOrder(SortOption.ADDED_DATE_ASC) - expectNoEvents() + assertEquals(FavoriteUiState.Loading, awaitItem()) + + advanceUntilIdle() + + assertEquals(FavoriteUiState.Empty, awaitItem()) } } } From 9b85ea41d0aaa73983eeab3519180abd82a8361a Mon Sep 17 00:00:00 2001 From: lilin Date: Sun, 1 Feb 2026 23:26:14 +0900 Subject: [PATCH 11/11] =?UTF-8?q?detekt=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/lilin/gamelibrary/domain/model/SortOption.kt | 2 -- .../domain/usecase/favorite/GetFavoriteGamesUseCase.kt | 1 - .../lilin/gamelibrary/feature/favorite/FavoriteUiState.kt | 1 - .../ui/component/favorite/FavoriteSortFabMenu.kt | 7 +++++-- .../gamelibrary/feature/favorite/FavoriteScreenShotTest.kt | 1 - 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/domain/model/SortOption.kt b/app/src/main/kotlin/com/lilin/gamelibrary/domain/model/SortOption.kt index be4ba1d..977af19 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/domain/model/SortOption.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/domain/model/SortOption.kt @@ -6,13 +6,11 @@ enum class SortType { ADDED_DATE, TITLE, RATING, - ; } enum class SortDirection { DESC, ASC, - ; } enum class SortOption { diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCase.kt b/app/src/main/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCase.kt index 51d8401..4c75637 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCase.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/domain/usecase/favorite/GetFavoriteGamesUseCase.kt @@ -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 diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteUiState.kt b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteUiState.kt index 95a06e5..bcee027 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteUiState.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteUiState.kt @@ -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 diff --git a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt index b919f4c..8df1116 100644 --- a/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt +++ b/app/src/main/kotlin/com/lilin/gamelibrary/ui/component/favorite/FavoriteSortFabMenu.kt @@ -126,8 +126,11 @@ fun FavoriteSortFabMenu( .padding(2.dp), ) { Icon( - imageVector = if (currentDirection == SortDirection.ASC) - Icons.Default.ArrowUpward else Icons.Default.ArrowDownward, + imageVector = if (currentDirection == SortDirection.ASC) { + Icons.Default.ArrowUpward + } else { + Icons.Default.ArrowDownward + }, contentDescription = null, modifier = Modifier.size(16.dp), tint = Color.White, diff --git a/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreenShotTest.kt b/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreenShotTest.kt index a2ee7e7..2558787 100644 --- a/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreenShotTest.kt +++ b/app/src/test/kotlin/com/lilin/gamelibrary/feature/favorite/FavoriteScreenShotTest.kt @@ -28,7 +28,6 @@ import coil3.test.FakeImageLoaderEngine import com.lilin.gamelibrary.R import com.lilin.gamelibrary.domain.model.FavoriteGame import com.lilin.gamelibrary.domain.model.SortOption -import com.lilin.gamelibrary.domain.model.SortOrder import com.lilin.gamelibrary.navigation.TOP_LEVEL_ROUTES import com.lilin.gamelibrary.ui.component.GameLibraryNavigationBar import com.lilin.gamelibrary.util.captureMultiDevice