diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7fc8766..2e86b43 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -66,6 +66,7 @@ android { proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + signingConfig = signingConfigs.getByName("debug") } debug { buildConfigField( @@ -138,7 +139,7 @@ dependencies { //---------------User Interface---------------// //Core UI libraries - api(platform(libs.compose.bom)) + api(platform(libs.compose.bom.canary)) //Accompanist libraries implementation(libs.bundles.accompanist) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 41e0daf..07f364d 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -62,6 +62,21 @@ volatile ; } +# Keep all classes in the specified package +-keep class com.bobbyesp.metadator.presentation.common.** { *; } + +# Keep all serialization-related annotations +-keepattributes *Annotation* + +# Keep classes and members information for serialization +-keepclassmembers class com.bobbyesp.metadator.presentation.common.** { + @kotlinx.serialization.Serializable ; + @kotlinx.serialization.Serializable ; +} + +# Keep classes and members in the specified package +-keepnames class com.bobbyesp.metadator.presentation.common.** { *; } + # Only used in `kotlinx.coroutines.internal.ExceptionsConstructor`. # The case when it is not available is hidden in a `try`-`catch`, as well as a check for Android. -dontwarn java.lang.ClassValue diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt index 5f476b9..e5c4dcf 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/Navigation.kt @@ -59,9 +59,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastAny -import androidx.compose.ui.util.fastForEach import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.NavHost import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.navigation @@ -75,8 +76,8 @@ import com.bobbyesp.metadator.presentation.common.LocalNavController import com.bobbyesp.metadator.presentation.common.LocalPlayerAwareWindowInsets import com.bobbyesp.metadator.presentation.common.LocalSnackbarHostState import com.bobbyesp.metadator.presentation.common.Route +import com.bobbyesp.metadator.presentation.common.mainNavigators import com.bobbyesp.metadator.presentation.common.qualifiedName -import com.bobbyesp.metadator.presentation.common.routesToNavigate import com.bobbyesp.metadator.presentation.pages.MediaStorePageViewModel import com.bobbyesp.metadator.presentation.pages.home.HomePage import com.bobbyesp.metadator.presentation.pages.mediaplayer.MediaplayerPage @@ -102,7 +103,7 @@ fun Navigator() { val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentRootRoute = rememberSaveable(navBackStackEntry, key = "currentRootRoute") { + val currentNavigator = rememberSaveable(navBackStackEntry, key = "currentRootRoute") { mutableStateOf( navBackStackEntry?.destination?.parent?.route ) @@ -119,7 +120,7 @@ fun Navigator() { //able to open drawer when the user is in one of the main routes (root routes) val canOpenDrawer by remember(currentRoute) { - mutableStateOf(routesToNavigate.fastAny { it.qualifiedName() == currentRootRoute.value }) + mutableStateOf(mainNavigators.fastAny { navigator -> navigator.qualifiedName() == currentNavigator.value }) } val mediaStoreViewModel = hiltViewModel() @@ -192,11 +193,12 @@ fun Navigator() { rememberScrollState() ) ) { - routesToNavigate.fastForEach { route -> - val formattedRoute = route.qualifiedName() - val isSelected = remember(currentRootRoute.value, formattedRoute) { - currentRootRoute.value == formattedRoute - } + mainNavigators.forEach { route -> + val actualNavigator = route.qualifiedName() +// val isSelected by remember(currentNavigator, actualNavigator) { +// mutableStateOf(currentNavigator.value == actualNavigator) +// } + val isSelected = currentNavigator.value == actualNavigator val destinationInfo = DestinationInfo.fromRoute(route) NavigationDrawerItem( @@ -213,7 +215,6 @@ fun Navigator() { scope.launch { drawerState.close() } - return@NavigationDrawerItem } else { navController.navigate(route) { popUpTo(navController.graph.findStartDestination().id) { @@ -238,7 +239,6 @@ fun Navigator() { ) } } - Spacer(modifier = Modifier.weight(1f)) OutlinedCard( modifier = Modifier @@ -334,7 +334,9 @@ fun Navigator() { startDestination = Route.MetadatorNavigator.Home, ) { animatedComposable { - HomePage(viewModel = mediaStoreViewModel) + val songsState = + mediaStoreViewModel.songs.collectAsStateWithLifecycle() + HomePage(songs = songsState) } } @@ -364,20 +366,36 @@ fun Navigator() { } } - navigation( - startDestination = Route.SettingsNavigator.Settings, - ) { - animatedComposable { - SettingsPage( - onBackPressed = { - navController.popBackStack() - } - ) - } - } + settingsNavigation { navController.popBackStack() } } } } } } +} + +fun NavGraphBuilder.settingsNavigation( + onNavigateBack: () -> Unit +) { + navigation( + startDestination = Route.SettingsNavigator.Settings, + ) { + animatedComposable { + SettingsPage( + onBackPressed = onNavigateBack + ) + } + + animatedComposable { + Text("General") + } + + animatedComposable { + Text("Appearance") + } + + animatedComposable { + Text("About") + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/common/DestinationInfo.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/common/DestinationInfo.kt index f4312b3..6a17c77 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/common/DestinationInfo.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/common/DestinationInfo.kt @@ -4,9 +4,11 @@ import androidx.annotation.StringRes import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Home import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.vector.ImageVector import com.bobbyesp.metadator.R +@Immutable enum class DestinationInfo( val icon: ImageVector, @StringRes val title: Int, @@ -20,9 +22,6 @@ enum class DestinationInfo( title = R.string.mediaplayer ); - val qualifiedName - get() = this::class.qualifiedName.toString() - companion object { fun fromRoute(route: Route): DestinationInfo? { return when (route) { diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/common/Route.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/common/Route.kt index 2e25574..2f7087a 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/common/Route.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/common/Route.kt @@ -29,11 +29,20 @@ sealed interface Route { @Serializable data object SettingsNavigator : Route { @Serializable - data object Settings : Route + data object Settings : Route { + @Serializable + data object General : Route + + @Serializable + data object Appearance : Route + + @Serializable + data object About : Route + } } } -val routesToNavigate = listOf( +val mainNavigators = listOf( Route.MetadatorNavigator, Route.MediaplayerNavigator ) diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStorePage.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStorePage.kt index f79e6d5..0c07c63 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStorePage.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStorePage.kt @@ -15,9 +15,9 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bobbyesp.metadator.presentation.components.cards.songs.HorizontalSongCard import com.bobbyesp.metadator.presentation.components.cards.songs.VerticalSongCard import com.bobbyesp.metadator.presentation.components.others.status.EmptyMediaStore @@ -32,21 +32,19 @@ import my.nanihadesuka.compose.ScrollbarSettings @Composable fun MediaStorePage( modifier: Modifier = Modifier, - viewModel: MediaStorePageViewModel, + songs: State>>, lazyGridState: LazyGridState, lazyListState: LazyListState, desiredLayout: LayoutType, onItemClicked: (Song) -> Unit ) { - val songsState = viewModel.songs.collectAsStateWithLifecycle().value - Box( modifier = modifier.fillMaxSize() ) { Crossfade( targetState = desiredLayout, label = "List item transition", animationSpec = tween(200) ) { type -> - when (songsState) { + when (songs.value) { is ResourceState.Loading -> { CircularProgressIndicator() } @@ -56,12 +54,12 @@ fun MediaStorePage( } is ResourceState.Success -> { - if (songsState.data!!.isEmpty()) { + if (songs.value.data!!.isEmpty()) { EmptyMediaStore( modifier = Modifier.fillMaxSize() ) } else { - val songs = songsState.data!! + val songsList = songs.value.data!! when (type) { LayoutType.Grid -> { LazyVerticalGridScrollbar( @@ -82,10 +80,10 @@ fun MediaStorePage( .background(MaterialTheme.colorScheme.background), state = lazyGridState ) { - items(count = songs.size, - key = { index -> songs[index].id }, - contentType = { index -> songs[index].id.toString() }) { index -> - val song = songs[index] + items(count = songsList.size, + key = { index -> songsList[index].id }, + contentType = { index -> songsList[index].id.toString() }) { index -> + val song = songsList[index] VerticalSongCard(song = song, modifier = Modifier.animateItem( fadeInSpec = null, fadeOutSpec = null @@ -113,10 +111,10 @@ fun MediaStorePage( .background(MaterialTheme.colorScheme.background), state = lazyListState, ) { - items(count = songs.size, - key = { index -> songs[index].id }, - contentType = { index -> songs[index].id.toString() }) { index -> - val song = songs[index] + items(count = songsList.size, + key = { index -> songsList[index].id }, + contentType = { index -> songsList[index].id.toString() }) { index -> + val song = songsList[index] HorizontalSongCard( song = song, modifier = Modifier.animateItem( diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStoreViewModel.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStoreViewModel.kt index 8e3b822..51460db 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStoreViewModel.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/MediaStoreViewModel.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -29,7 +30,16 @@ class MediaStorePageViewModel @Inject constructor( init { viewModelScope.launch(Dispatchers.IO) { mediaStoreSongsFlow.collectLatest { songs -> - _songs.value = ResourceState.Success(songs) + _songs.update { ResourceState.Success(songs) } + } + } + } + + fun reloadMediaStore() { + viewModelScope.launch(Dispatchers.IO) { + _songs.update { ResourceState.Loading() } + mediaStoreSongsFlow.collectLatest { songs -> + _songs.update { ResourceState.Success(songs) } } } } diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/home/HomePage.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/home/HomePage.kt index e4e8234..b6fa4ab 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/home/HomePage.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/home/HomePage.kt @@ -34,6 +34,7 @@ import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -56,12 +57,13 @@ import com.bobbyesp.metadator.presentation.common.LocalDrawerState import com.bobbyesp.metadator.presentation.common.LocalNavController import com.bobbyesp.metadator.presentation.common.Route import com.bobbyesp.metadator.presentation.pages.MediaStorePage -import com.bobbyesp.metadator.presentation.pages.MediaStorePageViewModel import com.bobbyesp.ui.components.dropdown.AnimatedDropdownMenu import com.bobbyesp.ui.components.dropdown.DropdownItemContainer import com.bobbyesp.ui.components.text.AutoResizableText +import com.bobbyesp.utilities.model.Song import com.bobbyesp.utilities.preferences.Preferences import com.bobbyesp.utilities.preferences.PreferencesKeys.DESIRED_OVERLAY +import com.bobbyesp.utilities.states.ResourceState import com.bobbyesp.utilities.ui.permission.PermissionNotGrantedDialog import com.bobbyesp.utilities.ui.permission.PermissionRequestHandler import com.bobbyesp.utilities.ui.permission.toPermissionType @@ -74,7 +76,7 @@ import okhttp3.internal.toImmutableList @OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class) @Composable fun HomePage( - modifier: Modifier = Modifier, viewModel: MediaStorePageViewModel + modifier: Modifier = Modifier, songs: State>> ) { val context = LocalContext.current as Activity val currentApiVersion = Build.VERSION.SDK_INT @@ -123,7 +125,7 @@ fun HomePage( } }, title = { Column( - horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally, + horizontalAlignment = Alignment.CenterHorizontally, ) { Text( text = stringResource(id = R.string.app_name).uppercase(), @@ -225,8 +227,9 @@ fun HomePage( ) }, content = { - MediaStorePage(modifier = Modifier.padding(paddingValues = paddingValues), - viewModel = viewModel, + MediaStorePage( + modifier = Modifier.padding(paddingValues = paddingValues), + songs = songs, lazyGridState = mediaStoreLazyGridState, lazyListState = mediaStoreLazyColumnState, desiredLayout = desiredLayout, diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/settings/SettingsPage.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/settings/SettingsPage.kt index 7e38348..a2bfcce 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/settings/SettingsPage.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/settings/SettingsPage.kt @@ -4,6 +4,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Palette import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.LargeTopAppBar @@ -19,7 +21,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.bobbyesp.metadator.R +import com.bobbyesp.metadator.presentation.common.LocalNavController +import com.bobbyesp.metadator.presentation.common.Route import com.bobbyesp.ui.components.button.BackButton +import com.bobbyesp.ui.components.preferences.SettingItem @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -27,7 +32,7 @@ fun SettingsPage( onBackPressed: () -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - + val navController = LocalNavController.current Scaffold( topBar = { LargeTopAppBar( @@ -52,6 +57,16 @@ fun SettingsPage( .nestedScroll(scrollBehavior.nestedScrollConnection), contentPadding = paddingValues ) { + item { + SettingItem( + title = stringResource(id = R.string.appearance), + description = stringResource(id = R.string.appearance_description), + icon = Icons.Rounded.Palette, + onClick = { + navController.navigate(Route.SettingsNavigator.Settings.Appearance) + } + ) + } item { HorizontalDivider(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) Text( diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 20fa92c..1957b0c 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -67,4 +67,6 @@ Volver a buscar Metadatos de Spotify Hecho con 💖 por BobbyESP + Apariencia + Personaliza como se ve la aplicación \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab08042..4eaea3a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,5 +66,7 @@ Retry search Spotify metadata Made with 💖 by BobbyESP + Appearance + Customize how the app looks like \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 978aa6f..7990f2c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ leakcanary = "2.13" #Compose compose-bom = "2024.06.00" -compose-bom-canary = "2024.05.00-alpha03" +compose-bom-canary = "2024.07.00-alpha02" constraintLayout = "1.1.0-alpha13" androidx-compose-material3 = "1.2.1" compose-util = "1.6.8"