diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 3dca2a74..e710498b 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -17,17 +17,14 @@ kotlin { implementation(project(":core:syncer")) implementation(project(":core:platform")) - implementation(project(":feature:common")) - implementation(project(":feature:drawer")) - implementation(project(":feature:message")) - implementation(project(":feature:home")) - implementation(project(":feature:player")) - implementation(project(":feature:customtab")) + implementation(project(":ui:common")) + implementation(project(":ui:components")) implementation(libs.koin.core.viewmodel) implementation(libs.koin.compose.viewmodel) implementation(libs.navigation.compose) + implementation(libs.reorderable) } androidMain.dependencies { diff --git a/composeApp/src/androidMain/kotlin/com/andannn/melodify/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/andannn/melodify/MainActivity.kt index cb3fe929..3c4d62ad 100644 --- a/composeApp/src/androidMain/kotlin/com/andannn/melodify/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/com/andannn/melodify/MainActivity.kt @@ -24,12 +24,12 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.andannn.melodify.feature.common.dialog.ConnectFailedAlertDialog -import com.andannn.melodify.feature.common.theme.MelodifyTheme +import com.andannn.melodify.ui.common.dialog.ConnectFailedAlertDialog +import com.andannn.melodify.ui.common.theme.MelodifyTheme import android.graphics.Color import com.andannn.melodify.core.syncer.MediaLibrarySyncer import com.andannn.melodify.core.syncer.SyncJobService -import com.andannn.melodify.feature.drawer.DrawerController +import com.andannn.melodify.ui.components.drawer.DrawerController import io.github.aakira.napier.Napier import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch diff --git a/composeApp/src/androidMain/kotlin/com/andannn/melodify/Modules.android.kt b/composeApp/src/androidMain/kotlin/com/andannn/melodify/Modules.android.kt index 88cc0892..58d2c767 100644 --- a/composeApp/src/androidMain/kotlin/com/andannn/melodify/Modules.android.kt +++ b/composeApp/src/androidMain/kotlin/com/andannn/melodify/Modules.android.kt @@ -1,9 +1,9 @@ package com.andannn.melodify -import com.andannn.melodify.feature.drawer.DrawerController -import com.andannn.melodify.feature.drawer.DrawerControllerImpl -import com.andannn.melodify.feature.message.MessageController -import com.andannn.melodify.feature.message.MessageControllerImpl +import com.andannn.melodify.ui.components.drawer.DrawerController +import com.andannn.melodify.ui.components.drawer.DrawerControllerImpl +import com.andannn.melodify.ui.components.message.MessageController +import com.andannn.melodify.ui.components.message.MessageControllerImpl import org.koin.core.module.Module import org.koin.core.module.dsl.scopedOf import org.koin.dsl.bind diff --git a/composeApp/src/commonMain/kotlin/com/andannn/melodify/MelodifyAppState.kt b/composeApp/src/commonMain/kotlin/com/andannn/melodify/MelodifyAppState.kt index c9a9c79d..97b01a12 100644 --- a/composeApp/src/commonMain/kotlin/com/andannn/melodify/MelodifyAppState.kt +++ b/composeApp/src/commonMain/kotlin/com/andannn/melodify/MelodifyAppState.kt @@ -10,12 +10,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController -import com.andannn.melodify.feature.common.util.getUiRetainedScope -import com.andannn.melodify.feature.drawer.DrawerController -import com.andannn.melodify.feature.message.MessageController -import com.andannn.melodify.feature.message.dialog.Dialog -import com.andannn.melodify.feature.message.dialog.InteractionResult -import com.andannn.melodify.feature.message.dialog.navigateToDialog +import com.andannn.melodify.ui.components.drawer.DrawerController +import com.andannn.melodify.ui.components.message.MessageController +import com.andannn.melodify.ui.components.message.dialog.Dialog +import com.andannn.melodify.ui.components.message.dialog.InteractionResult +import com.andannn.melodify.navigation.routes.navigateToDialog +import com.andannn.melodify.ui.common.util.getUiRetainedScope import io.github.aakira.napier.Napier import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope diff --git a/composeApp/src/commonMain/kotlin/com/andannn/melodify/MelodifyMobileApp.kt b/composeApp/src/commonMain/kotlin/com/andannn/melodify/MelodifyMobileApp.kt index 0bdfa099..83921aa7 100644 --- a/composeApp/src/commonMain/kotlin/com/andannn/melodify/MelodifyMobileApp.kt +++ b/composeApp/src/commonMain/kotlin/com/andannn/melodify/MelodifyMobileApp.kt @@ -18,25 +18,18 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.andannn.melodify.feature.customtab.CustomTabSelector -import com.andannn.melodify.feature.drawer.BottomDrawerContainer -import com.andannn.melodify.feature.player.PlayerAreaView -import com.andannn.melodify.feature.player.PlayerStateViewModel -import com.andannn.melodify.feature.player.PlayerUiState import com.andannn.melodify.navigation.MelodifyNavHost -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.library_title +import com.andannn.melodify.ui.components.drawer.BottomDrawerContainer +import com.andannn.melodify.ui.components.tabselector.CustomTabSelector +import com.andannn.melodify.ui.components.playcontrol.ui.PlayerAreaView +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.library_title import org.jetbrains.compose.resources.stringResource -import org.koin.compose.viewmodel.koinViewModel -import org.koin.core.parameter.parametersOf @Composable fun MelodifyMobileApp( modifier: Modifier = Modifier, appState: MelodifyAppState = rememberAppState(), - playerStateViewModel: PlayerStateViewModel = koinViewModel { - parametersOf(appState.drawerController) - }, ) { ModalDrawer( drawerState = appState.drawerState, @@ -57,13 +50,7 @@ fun MelodifyMobileApp( onDialogResult = appState::onDialogResult, ) - val playerUiState by playerStateViewModel.playerUiStateFlow.collectAsState() - if (playerUiState is PlayerUiState.Active) { - PlayerAreaView( - state = playerUiState as PlayerUiState.Active, - onEvent = playerStateViewModel::onEvent, - ) - } + PlayerAreaView() val drawerController = appState.drawerController val bottomSheetModel by drawerController.bottomSheetModel.collectAsState(null) diff --git a/composeApp/src/commonMain/kotlin/com/andannn/melodify/Modules.kt b/composeApp/src/commonMain/kotlin/com/andannn/melodify/Modules.kt index ccede509..eaaf5953 100644 --- a/composeApp/src/commonMain/kotlin/com/andannn/melodify/Modules.kt +++ b/composeApp/src/commonMain/kotlin/com/andannn/melodify/Modules.kt @@ -3,8 +3,6 @@ package com.andannn.melodify import com.andannn.melodify.core.data.di.dataModule import com.andannn.melodify.core.platform.platformModule import com.andannn.melodify.core.syncer.di.syncerModule -import com.andannn.melodify.feature.home.di.homeFeatureModule -import com.andannn.melodify.feature.player.di.playerFeatureModule import org.koin.core.module.Module expect val uiScopedModule: Module @@ -15,7 +13,4 @@ val modules: List = listOf( dataModule, syncerModule, platformModule, - - homeFeatureModule, - playerFeatureModule, ) diff --git a/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/MelodifyNavHost.kt b/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/MelodifyNavHost.kt index 0a534070..1e654812 100644 --- a/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/MelodifyNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/MelodifyNavHost.kt @@ -4,13 +4,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost -import com.andannn.melodify.feature.customtab.navigation.customTabSetting -import com.andannn.melodify.feature.customtab.navigation.navigateToCustomTabSetting -import com.andannn.melodify.feature.home.navigation.HOME_ROUTE -import com.andannn.melodify.feature.home.navigation.homeScreen -import com.andannn.melodify.feature.message.dialog.Dialog -import com.andannn.melodify.feature.message.dialog.InteractionResult -import com.andannn.melodify.feature.message.dialog.melodifyDialog +import com.andannn.melodify.ui.components.message.dialog.Dialog +import com.andannn.melodify.ui.components.message.dialog.InteractionResult +import com.andannn.melodify.navigation.routes.melodifyDialog +import com.andannn.melodify.navigation.routes.HOME_ROUTE +import com.andannn.melodify.navigation.routes.customTabSetting +import com.andannn.melodify.navigation.routes.homeScreen +import com.andannn.melodify.navigation.routes.navigateToCustomTabSetting @Composable fun MelodifyNavHost( diff --git a/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomTabSettingScreen.kt b/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/CustomTabSettingRoute.kt similarity index 91% rename from feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomTabSettingScreen.kt rename to composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/CustomTabSettingRoute.kt index 4bc313b3..326586b8 100644 --- a/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomTabSettingScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/CustomTabSettingRoute.kt @@ -1,4 +1,5 @@ -package com.andannn.melodify.feature.customtab +package com.andannn.melodify.navigation.routes + import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateColorAsState @@ -44,13 +45,34 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable import com.andannn.melodify.core.data.model.CustomTab -import com.andannn.melodify.feature.common.util.getCategoryResource -import com.andannn.melodify.feature.common.util.rememberSwapListState +import com.andannn.melodify.ui.common.util.getCategoryResource +import com.andannn.melodify.ui.common.util.rememberSwapListState +import com.andannn.melodify.ui.components.tabselector.CustomTabSettingViewStateHolder +import com.andannn.melodify.ui.components.tabselector.TabUiState +import com.andannn.melodify.ui.components.tabselector.UiEvent +import com.andannn.melodify.ui.components.tabselector.rememberCustomTabSettingViewStateHolder import kotlinx.collections.immutable.toImmutableList import org.jetbrains.compose.resources.stringResource import sh.calvin.reorderable.ReorderableItem +const val CUSTOM_TAB_SETTING_ROUTE = "custom_tab_setting_route" + +fun NavController.navigateToCustomTabSetting() { + this.navigate(CUSTOM_TAB_SETTING_ROUTE) +} + +fun NavGraphBuilder.customTabSetting(onBackPressed: () -> Unit) { + composable( + route = CUSTOM_TAB_SETTING_ROUTE, + ) { + CustomTabSettingScreen(onBackPressed = onBackPressed) + } +} + @Composable internal fun CustomTabSettingScreen( stateHolder: CustomTabSettingViewStateHolder = rememberCustomTabSettingViewStateHolder(), diff --git a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/DialogNavigation.kt b/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/DialogNavigation.kt similarity index 79% rename from feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/DialogNavigation.kt rename to composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/DialogNavigation.kt index d533df3b..8866ceba 100644 --- a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/DialogNavigation.kt +++ b/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/DialogNavigation.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.message.dialog +package com.andannn.melodify.navigation.routes import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -9,15 +9,17 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.dialog -import com.andannn.melodify.feature.message.dialog.ui.AlertMessageDialog -import com.andannn.melodify.feature.message.dialog.ui.NewPlayListDialog +import com.andannn.melodify.ui.components.message.dialog.Dialog +import com.andannn.melodify.ui.components.message.dialog.InteractionResult +import com.andannn.melodify.ui.components.message.dialog.ui.AlertMessageDialog +import com.andannn.melodify.ui.components.message.dialog.ui.NewPlayListDialog import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map internal const val DIALOG_ROUTE_PREFIX = "alert_dialog_route_" fun NavController.navigateToDialog(dialog: Dialog) { - this.navigate("$DIALOG_ROUTE_PREFIX${dialog.id}") + this.navigate("${DIALOG_ROUTE_PREFIX}${dialog.id}") } fun NavGraphBuilder.melodifyDialog( @@ -27,7 +29,7 @@ fun NavGraphBuilder.melodifyDialog( onResult: (Dialog, InteractionResult) -> Unit ) { dialog( - route = "$DIALOG_ROUTE_PREFIX${dialog.id}", + route = "${DIALOG_ROUTE_PREFIX}${dialog.id}", dialogProperties = dialog.dialogProperties ) { entry -> var interaction by remember { diff --git a/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/HomeRoute.kt b/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/HomeRoute.kt new file mode 100644 index 00000000..b14960fc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/andannn/melodify/navigation/routes/HomeRoute.kt @@ -0,0 +1,118 @@ +package com.andannn.melodify.navigation.routes + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Settings +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarDefaults.enterAlwaysScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import com.andannn.melodify.ui.components.tab.ReactiveTab +import com.andannn.melodify.ui.components.tab.TabUiStateHolder +import com.andannn.melodify.ui.components.tab.rememberTabUiStateHolder +import com.andannn.melodify.ui.components.tabcontent.TabContent +import com.andannn.melodify.ui.components.tabcontent.TabContentStateHolder +import com.andannn.melodify.ui.components.tabcontent.rememberTabContentStateHolder + +const val HOME_ROUTE = "home_route" + +fun NavGraphBuilder.homeScreen( + onNavigateCustomTabSetting: () -> Unit +) { + composable(route = HOME_ROUTE) { + HomeRoute( + onNavigateCustomTabSetting = onNavigateCustomTabSetting, + ) + } +} + +@Composable +fun HomeRoute( + modifier: Modifier = Modifier, + onNavigateCustomTabSetting: () -> Unit = {}, +) { + HomeScreen( + modifier = modifier, + onSettingButtonClick = onNavigateCustomTabSetting + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun HomeScreen( + modifier: Modifier = Modifier, + onSettingButtonClick: () -> Unit = {}, +) { + val scrollBehavior = enterAlwaysScrollBehavior() + val scope = rememberCoroutineScope() + val tabUiStateHolder = rememberTabUiStateHolder( + scope = scope + ) + val tabContentStateHolder = rememberTabContentStateHolder( + scope = scope, + selectedTab = tabUiStateHolder.state.selectedTab + ) + + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + colors = + TopAppBarDefaults.centerAlignedTopAppBarColors().run { + copy(scrolledContainerColor = containerColor) + }, + title = { + Text(text = "Melodify") + }, + actions = { + IconButton( + onClick = onSettingButtonClick, + content = { + Icon(Icons.Rounded.Settings, contentDescription = "") + } + ) + }, + scrollBehavior = scrollBehavior, + ) + }, + ) { padding -> + TabWithContent( + modifier = Modifier.padding(padding) + .nestedScroll(scrollBehavior.nestedScrollConnection) + .fillMaxSize(), + tabUiStateHolder = tabUiStateHolder, + tabContentStateHolder = tabContentStateHolder + ) + } +} + +@Composable +fun TabWithContent( + modifier: Modifier = Modifier, + tabUiStateHolder: TabUiStateHolder, + tabContentStateHolder: TabContentStateHolder, +) { + Column( + modifier = modifier + ) { + ReactiveTab( + stateHolder = tabUiStateHolder + ) + + TabContent( + stateHolder = tabContentStateHolder + ) + } +} diff --git a/composeApp/src/desktopMain/kotlin/com/andannn/melodify/MelodifyDesktopApp.kt b/composeApp/src/desktopMain/kotlin/com/andannn/melodify/MelodifyDesktopApp.kt index 9875f427..6a55ac0f 100644 --- a/composeApp/src/desktopMain/kotlin/com/andannn/melodify/MelodifyDesktopApp.kt +++ b/composeApp/src/desktopMain/kotlin/com/andannn/melodify/MelodifyDesktopApp.kt @@ -3,27 +3,21 @@ package com.andannn.melodify import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Surface import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.compose.ui.window.MenuBar import androidx.compose.ui.window.Window -import com.andannn.melodify.core.data.model.AudioItemModel import com.andannn.melodify.core.syncer.MediaLibrarySyncer -import com.andannn.melodify.feature.customtab.CustomTabSelector -import com.andannn.melodify.feature.home.TabWithContent -import com.andannn.melodify.feature.home.HomeUiEvent -import com.andannn.melodify.feature.home.HomeViewModel -import com.andannn.melodify.feature.player.PlayerSector -import org.koin.compose.viewmodel.koinViewModel +import com.andannn.melodify.ui.components.tabselector.CustomTabSelector +import com.andannn.melodify.ui.components.tab.rememberTabUiStateHolder +import com.andannn.melodify.ui.components.tabcontent.rememberTabContentStateHolder +import com.andannn.melodify.navigation.routes.TabWithContent +import com.andannn.melodify.ui.components.playcontrol.Player import org.koin.java.KoinJavaComponent.getKoin @Composable @@ -68,7 +62,7 @@ fun MainWindowContent( HorizontalDivider() - PlayerSector( + Player( modifier = Modifier ) } @@ -77,22 +71,22 @@ fun MainWindowContent( @Composable private fun TabWithContentSector( modifier: Modifier = Modifier, - homeViewModel: HomeViewModel = koinViewModel(), ) { - val state by homeViewModel.state.collectAsState() - + val scope = rememberCoroutineScope() + val tabUiStateHolder = rememberTabUiStateHolder( + scope = scope + ) + val tabContentStateHolder = rememberTabContentStateHolder( + scope = scope, + selectedTab = tabUiStateHolder.state.selectedTab + ) Surface( modifier = modifier ) { TabWithContent( modifier = Modifier, - uiState = state, - onEvent = homeViewModel::onEvent, - onMediaItemClick = { - if (it is AudioItemModel) { - homeViewModel.onEvent(HomeUiEvent.OnMusicItemClick(it)) - } - } + tabUiStateHolder = tabUiStateHolder, + tabContentStateHolder = tabContentStateHolder ) } } diff --git a/composeApp/src/desktopMain/kotlin/com/andannn/melodify/Modules.desktop.kt b/composeApp/src/desktopMain/kotlin/com/andannn/melodify/Modules.desktop.kt index ec060b66..b48160db 100644 --- a/composeApp/src/desktopMain/kotlin/com/andannn/melodify/Modules.desktop.kt +++ b/composeApp/src/desktopMain/kotlin/com/andannn/melodify/Modules.desktop.kt @@ -1,9 +1,9 @@ package com.andannn.melodify -import com.andannn.melodify.feature.drawer.DrawerController -import com.andannn.melodify.feature.drawer.DrawerControllerImpl -import com.andannn.melodify.feature.message.MessageController -import com.andannn.melodify.feature.message.MessageControllerImpl +import com.andannn.melodify.ui.components.drawer.DrawerController +import com.andannn.melodify.ui.components.drawer.DrawerControllerImpl +import com.andannn.melodify.ui.components.message.MessageController +import com.andannn.melodify.ui.components.message.MessageControllerImpl import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind import org.koin.dsl.module diff --git a/composeApp/src/iosMain/kotlin/com/andannn/melodify/MainViewController.kt b/composeApp/src/iosMain/kotlin/com/andannn/melodify/MainViewController.kt index e4481ae4..377acc13 100644 --- a/composeApp/src/iosMain/kotlin/com/andannn/melodify/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/com/andannn/melodify/MainViewController.kt @@ -6,8 +6,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.ComposeUIViewController -import com.andannn.melodify.feature.drawer.DrawerController -import com.andannn.melodify.feature.drawer.DrawerControllerImpl +import com.andannn.melodify.ui.common.components.drawer.DrawerController +import com.andannn.melodify.ui.common.components.drawer.DrawerControllerImpl import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier import org.koin.core.context.startKoin diff --git a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/model/CustomTab.kt b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/model/CustomTab.kt index 174d7968..5af43164 100644 --- a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/model/CustomTab.kt +++ b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/model/CustomTab.kt @@ -6,33 +6,13 @@ import kotlinx.serialization.Serializable @Serializable sealed interface CustomTab { - fun Repository.contentFlow(): Flow> + fun Repository.contentFlow(): Flow> @Serializable data object AllMusic : CustomTab { override fun Repository.contentFlow() = mediaContentRepository.getAllMediaItemsFlow() } - @Serializable - data object AllAlbum : CustomTab { - override fun Repository.contentFlow() = mediaContentRepository.getAllAlbumsFlow() - } - - @Serializable - data object AllArtist : CustomTab { - override fun Repository.contentFlow() = mediaContentRepository.getAllArtistFlow() - } - - @Serializable - data object AllGenre : CustomTab { - override fun Repository.contentFlow() = mediaContentRepository.getAllGenreFlow() - } - - @Serializable - data object AllPlayList : CustomTab { - override fun Repository.contentFlow() = playListRepository.getAllPlayListFlow() - } - @Serializable data class AlbumDetail(val albumId: String, val label: String) : CustomTab { override fun Repository.contentFlow() = mediaContentRepository.getAudiosOfAlbumFlow(albumId) diff --git a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepositoryImpl.kt b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepositoryImpl.kt index 5a259705..9960fdfd 100644 --- a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/repository/UserPreferenceRepositoryImpl.kt @@ -15,10 +15,6 @@ import kotlinx.coroutines.flow.map val DefaultCustomTabs = CurrentCustomTabs( listOf( CustomTab.AllMusic, - CustomTab.AllAlbum, - CustomTab.AllPlayList, - CustomTab.AllGenre, - CustomTab.AllArtist, ) ) diff --git a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/util/EntityMapper.kt b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/util/EntityMapper.kt index 883ceab8..d5508270 100644 --- a/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/util/EntityMapper.kt +++ b/core/data/src/commonMain/kotlin/com/andannn/melodify/core/data/util/EntityMapper.kt @@ -102,10 +102,6 @@ internal fun List.mapToCustomTabModel() = map { internal fun CustomTabEntity.toAppItem() = when (type) { CustomTabType.ALL_MUSIC -> CustomTab.AllMusic - CustomTabType.ALL_ALBUM -> CustomTab.AllAlbum - CustomTabType.ALL_PLAYLIST -> CustomTab.AllPlayList - CustomTabType.ALL_GENRE -> CustomTab.AllGenre - CustomTabType.ALL_ARTIST -> CustomTab.AllArtist CustomTabType.ALBUM_DETAIL -> CustomTab.AlbumDetail(externalId!!, name!!) CustomTabType.ARTIST_DETAIL -> CustomTab.ArtistDetail(externalId!!, name!!) CustomTabType.GENRE_DETAIL -> CustomTab.GenreDetail(externalId!!, name!!) @@ -115,11 +111,7 @@ internal fun CustomTabEntity.toAppItem() = when (type) { } internal fun CustomTab.toEntity() = when (this) { - CustomTab.AllAlbum -> CustomTabEntity(type = CustomTabType.ALL_ALBUM, externalId = "") - CustomTab.AllArtist -> CustomTabEntity(type = CustomTabType.ALL_ARTIST, externalId = "") - CustomTab.AllGenre -> CustomTabEntity(type = CustomTabType.ALL_GENRE, externalId = "") CustomTab.AllMusic -> CustomTabEntity(type = CustomTabType.ALL_MUSIC, externalId = "") - CustomTab.AllPlayList -> CustomTabEntity(type = CustomTabType.ALL_PLAYLIST, externalId = "") is CustomTab.ArtistDetail -> CustomTabEntity( type = CustomTabType.ARTIST_DETAIL, externalId = artistId, diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index 8f75ba46..09f55ab1 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } android { - namespace = "com.andannn.melodify.feature.database" + namespace = "com.andannn.melodify.ui.database" defaultConfig { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/core/database/src/commonMain/kotlin/com/andannn/melodify/core/database/entity/CustomTabEntity.kt b/core/database/src/commonMain/kotlin/com/andannn/melodify/core/database/entity/CustomTabEntity.kt index 8ab9b4b5..54991b0a 100644 --- a/core/database/src/commonMain/kotlin/com/andannn/melodify/core/database/entity/CustomTabEntity.kt +++ b/core/database/src/commonMain/kotlin/com/andannn/melodify/core/database/entity/CustomTabEntity.kt @@ -15,10 +15,6 @@ internal object CustomTabColumns { object CustomTabType { const val ALL_MUSIC = "all_music" - const val ALL_ALBUM = "all_album" - const val ALL_PLAYLIST = "all_playlist" - const val ALL_GENRE = "all_genre" - const val ALL_ARTIST = "all_artist" const val ALBUM_DETAIL = "album_detail" const val ARTIST_DETAIL = "artist_detail" const val GENRE_DETAIL = "genre_detail" diff --git a/core/player/build.gradle.kts b/core/player/build.gradle.kts index db055ea8..74df9dd4 100644 --- a/core/player/build.gradle.kts +++ b/core/player/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } android { - namespace = "com.andannn.melodify.feature.core.player" + namespace = "com.andannn.melodify.ui.core.player" } kotlin { diff --git a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/MediaLibraryScanner.desktop.kt b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/MediaLibraryScanner.desktop.kt index 52a321c3..977dd60b 100644 --- a/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/MediaLibraryScanner.desktop.kt +++ b/core/syncer/src/desktopMain/kotlin/com/andannn/melodify/core/syncer/MediaLibraryScanner.desktop.kt @@ -41,7 +41,7 @@ class MediaLibraryScannerImpl( // TODO Get Path from DataStore after implement library path setting feature. // val libraryPathSet = userSettingPreferences.userDate.first().libraryPath val libraryPathSet = setOf( - "/Volumes/PS2000/Music", + "/Users/jiangqn/Documents/Music" ) // TODO: Scan file in worker thread. diff --git a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/PlayListCardPreview.kt b/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/PlayListCardPreview.kt deleted file mode 100644 index 4a29a7cd..00000000 --- a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/PlayListCardPreview.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.andannn.melodify.feature.common.component - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.FavoriteBorder -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview - -@Preview -@Composable -private fun PlayListCardPreview() { - PlayListCard( - albumArtUri = "", - title = "Title", - coverImage = Icons.Rounded.FavoriteBorder, - trackCount = 0 - ) -} \ No newline at end of file diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/PlayListCard.kt b/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/PlayListCard.kt deleted file mode 100644 index f425e60e..00000000 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/PlayListCard.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.andannn.melodify.feature.common.component - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -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.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material.icons.rounded.FavoriteBorder -import androidx.compose.material3.Card -import androidx.compose.material3.CardColors -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.vector.ImageVector -import org.jetbrains.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import coil3.compose.AsyncImage - -@Composable -fun PlayListCard( - modifier: Modifier = Modifier, - colors: CardColors = CardDefaults.cardColors(), - albumArtUri: String = "", - coverImage: ImageVector? = null, - title: String = "", - trackCount: Int = 0, - onPlayListItemClick: () -> Unit = {}, - onOptionButtonClick: () -> Unit = {} -) { - Card( - modifier = modifier.fillMaxWidth(), - colors = colors, - shape = MaterialTheme.shapes.medium, - onClick = onPlayListItemClick - ) { - Row( - modifier = Modifier - .padding(10.dp) - .height(IntrinsicSize.Min), - verticalAlignment = Alignment.CenterVertically - ) { - Box(modifier = Modifier.size(50.dp)) { - AsyncImage( - modifier = Modifier - .fillMaxSize() - .clip(MaterialTheme.shapes.extraSmall), - model = albumArtUri, - contentDescription = "" - ) - coverImage?.let { - Image( - modifier = Modifier - .background( - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.2f), - shape = RoundedCornerShape(5.dp) - ) - .padding(4.dp) - .fillMaxSize(), - imageVector = coverImage, - contentDescription = "" - ) - } - } - - Spacer(modifier = Modifier.width(10.dp)) - - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = title, - style = MaterialTheme.typography.bodyLarge - ) - Spacer(modifier = Modifier.height(10.dp)) - Text( - text = "$trackCount songs", - style = MaterialTheme.typography.bodySmall - ) - } - - IconButton( - modifier = Modifier, - onClick = onOptionButtonClick - ) { - Icon(imageVector = Icons.Filled.MoreVert, contentDescription = "menu") - } - } - } -} \ No newline at end of file diff --git a/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomTabSettingViewStateHolder.kt b/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomTabSettingViewStateHolder.kt deleted file mode 100644 index 52043dd3..00000000 --- a/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomTabSettingViewStateHolder.kt +++ /dev/null @@ -1,174 +0,0 @@ -package com.andannn.melodify.feature.customtab - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import com.andannn.melodify.core.data.model.CustomTab -import com.andannn.melodify.core.data.repository.DefaultCustomTabs -import com.andannn.melodify.core.data.repository.MediaContentRepository -import com.andannn.melodify.core.data.repository.PlayListRepository -import com.andannn.melodify.core.data.repository.UserPreferenceRepository -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.album_page_title -import melodify.feature.common.generated.resources.artist_page_title -import melodify.feature.common.generated.resources.genre_title -import melodify.feature.common.generated.resources.playlist_page_title -import org.jetbrains.compose.resources.StringResource -import org.koin.compose.getKoin - -@Composable -fun rememberCustomTabSettingViewStateHolder( - playListRepository: PlayListRepository = getKoin().get(), - contentRepository: MediaContentRepository = getKoin().get(), - userPreferenceRepository: UserPreferenceRepository = getKoin().get(), - scope: CoroutineScope = rememberCoroutineScope(), -) = remember( - playListRepository, - contentRepository, - userPreferenceRepository -) { - CustomTabSettingViewStateHolder( - playListRepository, - contentRepository, - userPreferenceRepository, - scope, - ) -} - -sealed interface UiEvent { - data class OnSelectedChange(val tab: CustomTab, val isSelected: Boolean) : UiEvent - data class OnUpdateTabs(val newTabs: List) : UiEvent - data object OnResetClick : UiEvent -} - -private const val TAG = "CustomTabSettingViewState" - -class CustomTabSettingViewStateHolder( - private val playListRepository: PlayListRepository, - private val contentRepository: MediaContentRepository, - private val userPreferenceRepository: UserPreferenceRepository, - scope: CoroutineScope -) : CoroutineScope by scope { - - val state = combine( - userPreferenceRepository.currentCustomTabsFlow, - contentRepository.getAllAlbumsFlow(), - contentRepository.getAllArtistFlow(), - contentRepository.getAllGenreFlow(), - playListRepository.getAllPlayListFlow(), - ) { tabs, albums, artists, genre, playlist -> - TabUiState( - currentTabs = tabs, - allAvailableTabSectors = mutableListOf() - .apply { -// add( -// TabSector( -// Res.string.home, -// listOf( -// CustomTab.AllMusic, -// CustomTab.AllPlayList, -// CustomTab.AllAlbum, -// CustomTab.AllArtist, -// CustomTab.AllGenre, -// ) -// ) -// ) - - - val albumTabs = albums.map { - CustomTab.AlbumDetail(it.id, it.name) - } - add( - TabSector( - Res.string.album_page_title, - albumTabs - ) - ) - - val playListTabs = playlist.map { - CustomTab.PlayListDetail(it.id, it.name) - } - add( - TabSector( - Res.string.playlist_page_title, - playListTabs - ) - ) - - val artistTabs = artists.map { - CustomTab.ArtistDetail(it.id, it.name) - } - add( - TabSector( - Res.string.artist_page_title, - artistTabs - ) - ) - - val genreTabs = genre.map { - CustomTab.GenreDetail(it.id, it.name) - } - add( - TabSector( - Res.string.genre_title, - genreTabs - ) - ) - } - .toList() - ) - }.stateIn(scope = scope, SharingStarted.WhileSubscribed(), TabUiState()) - - - fun onEvent(event: UiEvent) { - val state = state.value - - when (event) { - is UiEvent.OnSelectedChange -> { - val (tab, selected) = event - launch { - if (selected) { - userPreferenceRepository.updateCurrentCustomTabs( - state.currentTabs + tab - ) - } else { - userPreferenceRepository.updateCurrentCustomTabs( - state.currentTabs - tab - ) - } - } - } - - is UiEvent.OnUpdateTabs -> { - launch { - userPreferenceRepository.updateCurrentCustomTabs( - event.newTabs - ) - } - } - - UiEvent.OnResetClick -> { - launch { - userPreferenceRepository.updateCurrentCustomTabs( - DefaultCustomTabs.customTabs - ) - } - } - } - } -} - -data class TabUiState( - val currentTabs: List = emptyList(), - val allAvailableTabSectors: List = emptyList(), -) - -data class TabSector( - val sectorTitle: StringResource, - val sectorContent: List -) diff --git a/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/navigation/CustomTabSettingNavigation.kt b/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/navigation/CustomTabSettingNavigation.kt deleted file mode 100644 index 56a40c35..00000000 --- a/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/navigation/CustomTabSettingNavigation.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.andannn.melodify.feature.customtab.navigation - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.andannn.melodify.feature.customtab.CustomTabSettingScreen - -const val CUSTOM_TAB_SETTING_ROUTE = "custom_tab_setting_route" - -fun NavController.navigateToCustomTabSetting() { - this.navigate(CUSTOM_TAB_SETTING_ROUTE) -} - -fun NavGraphBuilder.customTabSetting(onBackPressed: () -> Unit) { - composable( - route = CUSTOM_TAB_SETTING_ROUTE, - ) { - CustomTabSettingScreen(onBackPressed = onBackPressed) - } -} diff --git a/feature/drawer/.gitignore b/feature/drawer/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/feature/drawer/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/feature/drawer/build.gradle.kts b/feature/drawer/build.gradle.kts deleted file mode 100644 index 0546bba7..00000000 --- a/feature/drawer/build.gradle.kts +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - id("melodify.kmp.library") - id("melodify.compose.multiplatform.library") -} - -kotlin { - sourceSets { - commonMain.dependencies { - implementation(project(":feature:common")) - implementation(project(":feature:message")) - implementation(project(":core:data")) - - implementation(libs.coil3.compose) - implementation(libs.koin.compose.viewmodel) - implementation(libs.navigation.compose) - } - } -} - -android { - namespace = "com.andannn.melodify.feature.drawer" -} diff --git a/feature/drawer/src/androidMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheetContentPreview.kt b/feature/drawer/src/androidMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheetContentPreview.kt deleted file mode 100644 index 1ea3d057..00000000 --- a/feature/drawer/src/androidMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheetContentPreview.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.andannn.melodify.feature.drawer.sheet - -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Surface -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview -import com.andannn.melodify.core.data.model.AudioItemModel -import com.andannn.melodify.core.data.model.PlayListItemModel -import com.andannn.melodify.feature.common.theme.MelodifyTheme - -@Preview -@Composable -fun AddToPlayListRequestSheetContentPreview() { - MelodifyTheme { - Surface { - AddToPlayListRequestSheetContent( - audioList = List(10) { - AudioItemModel(it.toString(), "name $it", "", 0, "", "0", "", "0", 0, 0, "") - }, - playLists = List(10) { - PlayListItemModel( - id = "$it", - name = "PlayList $it", - artWorkUri = "", - trackCount = 0 - ) - }, - ) - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Preview -@Composable -fun AddToPlayListRequestSheetPreview() { - MelodifyTheme { - ModalBottomSheet( - sheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true - ), - onDismissRequest = { - }, - ) { - AddToPlayListRequestSheetContent( - audioList = listOf( - AudioItemModel.DEFAULT - ), - playLists = List(10) { - PlayListItemModel( - id = "$it", - name = "PlayList $it", - artWorkUri = "", - trackCount = 0 - ) - }, - ) - } - } -} \ No newline at end of file diff --git a/feature/home/.gitignore b/feature/home/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/feature/home/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts deleted file mode 100644 index b861f1a1..00000000 --- a/feature/home/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id("melodify.kmp.library") - id("melodify.compose.multiplatform.library") -} - -kotlin { - sourceSets { - commonMain.dependencies { - implementation(project(":feature:common")) - implementation(project(":feature:drawer")) - implementation(project(":feature:message")) - implementation(project(":core:data")) - - implementation(libs.koin.core.viewmodel) - implementation(libs.koin.compose.viewmodel) - implementation(libs.navigation.compose) - } - } -} - -android { - namespace = "com.andannn.melodify.feature.home" -} diff --git a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeScreen.kt b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeScreen.kt deleted file mode 100644 index 4362c6f0..00000000 --- a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeScreen.kt +++ /dev/null @@ -1,277 +0,0 @@ -@file:OptIn(ExperimentalFoundationApi::class) - -package com.andannn.melodify.feature.home - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -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.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.rounded.List -import androidx.compose.material.icons.rounded.Apps -import androidx.compose.material.icons.rounded.Settings -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.ScrollableTabRow -import androidx.compose.material3.Tab -import androidx.compose.material3.TabRowDefaults -import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarDefaults.enterAlwaysScrollBehavior -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.unit.dp -import com.andannn.melodify.core.data.model.AlbumItemModel -import com.andannn.melodify.core.data.model.ArtistItemModel -import com.andannn.melodify.core.data.model.AudioItemModel -import com.andannn.melodify.core.data.model.GenreItemModel -import com.andannn.melodify.core.data.model.MediaItemModel -import com.andannn.melodify.core.data.model.PlayListItemModel -import com.andannn.melodify.core.data.model.browsableOrPlayable -import com.andannn.melodify.core.data.model.key -import com.andannn.melodify.feature.common.component.ExtraPaddingBottom -import com.andannn.melodify.feature.common.component.ListTileItemView -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import com.andannn.melodify.feature.common.util.getCategoryResource -import com.andannn.melodify.feature.common.util.getUiRetainedScope -import com.andannn.melodify.feature.drawer.DrawerController -import com.andannn.melodify.feature.message.MessageController -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.track_count -import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.ui.tooling.preview.Preview -import org.koin.compose.viewmodel.koinViewModel -import org.koin.core.parameter.parametersOf -import org.koin.core.scope.Scope - -@Composable -fun HomeRoute( - modifier: Modifier = Modifier, - scope: Scope? = getUiRetainedScope(), - homeViewModel: HomeViewModel = koinViewModel { - parametersOf( - scope?.get(), - scope?.get(), - ) - }, - onNavigateCustomTabSetting: () -> Unit = {}, -) { - fun onMediaItemClick(mediaItem: MediaItemModel) { - when (mediaItem) { - is AudioItemModel -> { - homeViewModel.onEvent(HomeUiEvent.OnMusicItemClick(mediaItem)) - } - - else -> {} - } - } - - val state by homeViewModel.state.collectAsState() - - HomeScreen( - state = state, - modifier = modifier, - onEvent = homeViewModel::onEvent, - onMediaItemClick = ::onMediaItemClick, - onSettingButtonClick = onNavigateCustomTabSetting - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun HomeScreen( - state: HomeUiState, - modifier: Modifier = Modifier, - onMediaItemClick: (MediaItemModel) -> Unit = {}, - onSettingButtonClick: () -> Unit = {}, - onEvent: (HomeUiEvent) -> Unit = {}, -) { - val uiState by rememberUpdatedState(state) - - val scrollBehavior = enterAlwaysScrollBehavior() - Scaffold( - modifier = modifier, - topBar = { - TopAppBar( - colors = - TopAppBarDefaults.centerAlignedTopAppBarColors().run { - copy(scrolledContainerColor = containerColor) - }, - title = { - Text(text = "Melodify") - }, - actions = { - IconButton( - onClick = onSettingButtonClick, - content = { - Icon(Icons.Rounded.Settings, contentDescription = "") - } - ) - }, - scrollBehavior = scrollBehavior, - ) - }, - ) { padding -> - TabWithContent( - modifier = Modifier.padding(padding) - .nestedScroll(scrollBehavior.nestedScrollConnection) - .fillMaxSize(), - uiState = uiState, - onMediaItemClick = onMediaItemClick, - onEvent = onEvent, - ) - } -} - -@Composable -fun TabWithContent( - modifier: Modifier = Modifier, - uiState: HomeUiState, - onMediaItemClick: (MediaItemModel) -> Unit = {}, - onEvent: (HomeUiEvent) -> Unit = {}, -) { - val tabs by rememberUpdatedState(uiState.customTabList) - val selectedIndex by rememberUpdatedState(uiState.selectedIndex) - Column( - modifier = modifier - ) { - if (tabs.isNotEmpty()) { - ScrollableTabRow( - modifier = Modifier.fillMaxWidth(), - selectedTabIndex = selectedIndex, - indicator = - @Composable { tabPositions -> - TabRowDefaults.SecondaryIndicator( - Modifier.tabIndicatorOffset(tabPositions.getOrElse(selectedIndex) { tabPositions.last() }) - ) - }, - ) { - tabs.forEachIndexed { index, item -> - Tab( - selected = index == selectedIndex, - selectedContentColor = MaterialTheme.colorScheme.primary, - unselectedContentColor = MaterialTheme.colorScheme.onSurface, - text = @Composable { - Text( - text = getCategoryResource(item), - ) - }, - onClick = { - onEvent(HomeUiEvent.OnSelectedCategoryChanged(index)) - }, - ) - } - } - } - - val mediaItems by rememberUpdatedState(uiState.mediaItems) - val playingItem by rememberUpdatedState(uiState.playingItem) - - val listLayoutState = - rememberSaveable(selectedIndex, saver = LazyListState.Saver) { - LazyListState() - } - LazyListContent( - modifier = - Modifier.fillMaxSize(), - state = listLayoutState, - mediaItems = mediaItems, - playingItem = playingItem, - onMusicItemClick = onMediaItemClick, - onShowMusicItemOption = { - onEvent(HomeUiEvent.OnShowItemOption(it)) - } - ) - } -} - -@Composable -private fun LazyListContent( - - mediaItems: ImmutableList, - modifier: Modifier = Modifier, - playingItem: AudioItemModel? = null, - state: LazyListState = rememberLazyListState(), - onMusicItemClick: (T) -> Unit = {}, - onShowMusicItemOption: (T) -> Unit = {}, -) { - LazyColumn( - state = state, - modifier = modifier, - contentPadding = PaddingValues(horizontal = 5.dp), - ) { - items( - items = mediaItems, - key = { it.key }, - ) { item -> - ListTileItemView( - modifier = - Modifier.animateItem(), - playable = item.browsableOrPlayable, - isActive = item.id == playingItem?.id, - albumArtUri = item.artWorkUri, - title = item.name, - subTitle = subTitle(item), - onMusicItemClick = { - onMusicItemClick.invoke(item) - }, - onOptionButtonClick = { - onShowMusicItemOption(item) - }, - ) - } - - item { ExtraPaddingBottom() } - } -} - -@Composable -private fun subTitle( - model: MediaItemModel -): String = when (model) { - is AudioItemModel -> model.artist - is AlbumItemModel, - is PlayListItemModel, - is ArtistItemModel -> stringResource(Res.string.track_count, model.trackCount.toString()) - - is GenreItemModel -> "" -} - -@Preview -@Composable -private fun HomeScreenPreview() { - MelodifyTheme(darkTheme = true) { - HomeScreen( - state = HomeUiState( - mediaItems = (1..4).map { - AlbumItemModel( - id = it.toString(), - name = "Album $it", - artWorkUri = "", - trackCount = 10 - ) - }.toImmutableList(), - ), - ) - } -} diff --git a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeViewModel.kt b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeViewModel.kt deleted file mode 100644 index 0d1b93f2..00000000 --- a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/HomeViewModel.kt +++ /dev/null @@ -1,217 +0,0 @@ -package com.andannn.melodify.feature.home - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.andannn.melodify.core.data.model.MediaItemModel -import com.andannn.melodify.core.data.model.AudioItemModel -import com.andannn.melodify.core.data.Repository -import com.andannn.melodify.core.data.model.CustomTab -import com.andannn.melodify.feature.drawer.DrawerController -import com.andannn.melodify.feature.drawer.DrawerEvent -import com.andannn.melodify.feature.drawer.model.SheetModel -import com.andannn.melodify.feature.message.MessageController -import com.andannn.melodify.feature.message.dialog.Dialog -import com.andannn.melodify.feature.message.dialog.InteractionResult -import io.github.aakira.napier.Napier -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.scan -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch - -private const val TAG = "HomeViewModel" - -sealed interface HomeUiEvent { - data class OnSelectedCategoryChanged(val tabIndex: Int) : HomeUiEvent - data class OnMusicItemClick(val mediaItem: AudioItemModel) : HomeUiEvent - data class OnShowItemOption(val audioItemModel: MediaItemModel) : HomeUiEvent -} - -@OptIn(ExperimentalCoroutinesApi::class) -class HomeViewModel( - private val repository: Repository, - private val drawerController: DrawerController, - private val messageController: MessageController, -) : ViewModel() { - private val mediaControllerRepository = repository.mediaControllerRepository - private val userPreferenceRepository = repository.userPreferenceRepository - private val playerStateMonitoryRepository = repository.playerStateMonitoryRepository - private val playListRepository = repository.playListRepository - private val playlistCreatedEventChannel = drawerController.playlistCreatedEventChannel - - private val _userSettingFlow = userPreferenceRepository.userSettingFlow - private val _currentCustomTabsFlow = userPreferenceRepository.currentCustomTabsFlow - private val _selectedTabIndexFlow = MutableStateFlow(0) - - private val _tabStatusFlow = combine( - _selectedTabIndexFlow, - _currentCustomTabsFlow - ) { selectedIndex, customTabs -> - TabStatus( - selectedIndex = selectedIndex.coerceAtMost(customTabs.size - 1), - customTabList = customTabs - ) - } - - init { - // Ensure selected index is valid - viewModelScope.launch { - _currentCustomTabsFlow - .scan, Pair?, List?>>(null to null) { pre, next -> - pre.second to next - } - .collect { (pre, next) -> - if (pre == null || next == null) { - return@collect - } - - val currentIndex = _selectedTabIndexFlow.value - - val newIndex: Int = if (next.size < pre.size) { - // new tab list is smaller than the previous one. - // 1. select the previous tab - // 2. if the current tab is removed, select the previous tab - next.indexOf(pre.getOrNull(currentIndex)) - .takeIf { it != -1 } ?: (currentIndex - 1).coerceAtLeast(0) - } else if (next.size > pre.size) { - // always select the new created tab - next.indexOf(next.firstOrNull { it !in pre }) - } else { - next.indexOf(pre.getOrNull(currentIndex)) - } - if (newIndex != -1) { - _selectedTabIndexFlow.value = newIndex - } - } - } - } - - private val _mediaContentFlow = _tabStatusFlow - .map { it.selectedTab } - .distinctUntilChanged() - .flatMapLatest { tab -> - if (tab == null) { - return@flatMapLatest flow { emit(emptyList()) } - } - with(tab) { - repository.contentFlow() - } - } - - val state = combine( - _tabStatusFlow, - _mediaContentFlow, - playerStateMonitoryRepository.playingMediaStateFlow, - ) { tabStatus, mediaContents, playingItem -> - HomeUiState( - selectedIndex = tabStatus.selectedIndex, - customTabList = tabStatus.customTabList, - mediaItems = mediaContents.toImmutableList(), - playingItem = playingItem - ) - } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), HomeUiState()) - - init { - viewModelScope.launch { - for (createdPlayListId in playlistCreatedEventChannel) { - Napier.d(tag = TAG) { "new playlist created $createdPlayListId" } - val playList = playListRepository.getPlayListById(createdPlayListId) - ?: error("no such playlist") - val currentCustomTabs = userPreferenceRepository.currentCustomTabsFlow.first() - userPreferenceRepository.updateCurrentCustomTabs( - listOf( - *currentCustomTabs.toTypedArray(), - CustomTab.PlayListDetail(playList.id, playList.name), - ) - ) - } - } - } - - fun onEvent(event: HomeUiEvent) { - when (event) { - is HomeUiEvent.OnSelectedCategoryChanged -> onSelectedCategoryChanged(event.tabIndex) - is HomeUiEvent.OnMusicItemClick -> playMusic(event.mediaItem) - is HomeUiEvent.OnShowItemOption -> onShowMusicItemOption(event.audioItemModel) - } - } - - private fun onSelectedCategoryChanged(category: Int) { - _selectedTabIndexFlow.value = category - } - - private fun playMusic(mediaItem: AudioItemModel) { - if (mediaItem.isValid()) { - val mediaItems = state.value.mediaItems.toList() as? List - ?: error("invalid state") - - mediaControllerRepository.playMediaList( - mediaItems.toList(), - mediaItems.indexOf(mediaItem) - ) - } else { - Napier.d(tag = TAG) { "invalid media item click $mediaItem" } - viewModelScope.launch { - val result = - messageController.showMessageDialogAndWaitResult(Dialog.ConfirmDeletePlaylist) - Napier.d(tag = TAG) { "ConfirmDeletePlaylist result: $result" } - if (result == InteractionResult.AlertDialog.ACCEPT) { - val playListId = (state.value.currentTab as CustomTab.PlayListDetail).playListId - val mediaId = mediaItem.id.substringAfter(AudioItemModel.INVALID_ID_PREFIX) - - playListRepository.removeMusicFromPlayList(playListId.toLong(), listOf(mediaId)) - } - } - } - } - - private fun onShowMusicItemOption(mediaItemModel: MediaItemModel) { - val currentTab = state.value.currentTab - if (mediaItemModel is AudioItemModel && currentTab is CustomTab.PlayListDetail) { - drawerController.onEvent( - DrawerEvent.OnShowBottomDrawer( - SheetModel.AudioOptionInPlayListSheet( - playListId = currentTab.playListId, - mediaItemModel - ) - ) - ) - } else { - drawerController.onEvent( - DrawerEvent.OnShowBottomDrawer( - SheetModel.MediaOptionSheet.fromMediaModel( - item = mediaItemModel, - ) - ) - ) - } - } -} - -private data class TabStatus( - val selectedIndex: Int = 0, - val customTabList: List = emptyList() -) { - val selectedTab: CustomTab? - get() = customTabList.getOrNull(selectedIndex) -} - -data class HomeUiState( - val selectedIndex: Int = 0, - val customTabList: List = emptyList(), - val mediaItems: ImmutableList = emptyList().toImmutableList(), - val playingItem: AudioItemModel? = null -) { - val currentTab: CustomTab - get() = customTabList[selectedIndex] -} \ No newline at end of file diff --git a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/di/HomeFeatureModule.kt b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/di/HomeFeatureModule.kt deleted file mode 100644 index 16548bce..00000000 --- a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/di/HomeFeatureModule.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.andannn.melodify.feature.home.di - -import com.andannn.melodify.feature.home.HomeViewModel -import org.koin.core.module.dsl.viewModelOf -import org.koin.dsl.module - -val homeFeatureModule = module { - viewModelOf(::HomeViewModel) -} diff --git a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/navigation/HomeNavigation.kt b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/navigation/HomeNavigation.kt deleted file mode 100644 index 1ca4bdec..00000000 --- a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/navigation/HomeNavigation.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.andannn.melodify.feature.home.navigation - -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.andannn.melodify.core.data.model.MediaListSource -import com.andannn.melodify.feature.home.HomeRoute - -const val HOME_ROUTE = "home_route" - -fun NavGraphBuilder.homeScreen( - onNavigateCustomTabSetting: () -> Unit -) { - composable(route = HOME_ROUTE) { - HomeRoute( - onNavigateCustomTabSetting = onNavigateCustomTabSetting, - ) - } -} diff --git a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/util/MusicSortOrder.kt b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/util/MusicSortOrder.kt deleted file mode 100644 index 3bdf0e18..00000000 --- a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/util/MusicSortOrder.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.andannn.melodify.feature.home.util - -sealed class MusicSortOrder( - open val orderType: OrderType -) { - data class Name(override val orderType: OrderType) : MusicSortOrder(orderType) - data class Date(override val orderType: OrderType) : MusicSortOrder(orderType) - data class Size(override val orderType: OrderType) : MusicSortOrder(orderType) - data class Length(override val orderType: OrderType) : MusicSortOrder(orderType) -} diff --git a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/util/OrderType.kt b/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/util/OrderType.kt deleted file mode 100644 index d56eb659..00000000 --- a/feature/home/src/commonMain/kotlin/com/andannn/melodify/feature/home/util/OrderType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.andannn.melodify.feature.home.util - -sealed interface OrderType { - object Ascending : OrderType - object Descending : OrderType -} diff --git a/feature/message/.gitignore b/feature/message/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/feature/message/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/feature/message/build.gradle.kts b/feature/message/build.gradle.kts deleted file mode 100644 index abce6dda..00000000 --- a/feature/message/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id("melodify.kmp.library") - id("melodify.compose.multiplatform.library") -} - -kotlin { - sourceSets { - commonMain.dependencies { - implementation(project(":feature:common")) - - implementation(libs.navigation.compose) - } - - commonTest.dependencies { - implementation(libs.kotlin.test) - implementation(libs.kotlinx.coroutines.test) - } - } -} - -android { - namespace = "com.andannn.melodify.feature.message" -} diff --git a/feature/message/src/androidMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/NewPlayListDialog.android.kt b/feature/message/src/androidMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/NewPlayListDialog.android.kt deleted file mode 100644 index 6a0c36a9..00000000 --- a/feature/message/src/androidMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/NewPlayListDialog.android.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.andannn.melodify.feature.message.dialog.ui - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview -import com.andannn.melodify.feature.common.theme.MelodifyTheme - - -@Preview -@Composable -internal fun NewPlayListDialogPreview() { - MelodifyTheme { - NewPlayListDialog() - } -} diff --git a/feature/message/src/commonTest/kotlin/com/andannn/melodify/feature/message/MessageControllerTest.kt b/feature/message/src/commonTest/kotlin/com/andannn/melodify/feature/message/MessageControllerTest.kt deleted file mode 100644 index 93de44d0..00000000 --- a/feature/message/src/commonTest/kotlin/com/andannn/melodify/feature/message/MessageControllerTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.andannn.melodify.feature.message - -import com.andannn.melodify.feature.message.dialog.Dialog -import com.andannn.melodify.feature.message.dialog.InteractionResult -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.delay -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runTest -import kotlin.test.Test -import kotlin.test.assertEquals - -class MessageControllerTest { - private val testScope = TestScope() - private val messageController = MessageControllerImpl() - - @Test - fun message_controller_test() = testScope.runTest { - var dialog: Dialog? = null - launch { - dialog = messageController.sendDialogChannel.receive() - - messageController.onResult(dialog!!, InteractionResult.AlertDialog.ACCEPT) - } - - val result = messageController.showMessageDialogAndWaitResult(Dialog.ConfirmDeletePlaylist) - - assertEquals(Dialog.ConfirmDeletePlaylist, dialog) - assertEquals(InteractionResult.AlertDialog.ACCEPT, result) - } - - @Test - fun message_controller_cancel_test() = testScope.runTest { - var dialog: Dialog? = null - val job1 = launch( - start = CoroutineStart.UNDISPATCHED - ) { - dialog = messageController.sendDialogChannel.receive() - - delay(10) - - messageController.onResult(dialog!!, InteractionResult.AlertDialog.ACCEPT) - } - - var result: InteractionResult? = null - val job2 = launch( - start = CoroutineStart.UNDISPATCHED - ) { - result = messageController.showMessageDialogAndWaitResult(Dialog.ConfirmDeletePlaylist) - } - job2.cancel() - joinAll(job1, job2) - - assertEquals(Dialog.ConfirmDeletePlaylist, dialog) - assertEquals(null, result) - } -} \ No newline at end of file diff --git a/feature/player/.gitignore b/feature/player/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/feature/player/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/feature/player/build.gradle.kts b/feature/player/build.gradle.kts deleted file mode 100644 index 4ea2ca00..00000000 --- a/feature/player/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id("melodify.kmp.library") - id("melodify.compose.multiplatform.library") -} - -kotlin { - sourceSets { - commonMain.dependencies { - implementation(project(":feature:common")) - implementation(project(":feature:drawer")) - implementation(project(":core:data")) - - implementation(libs.coil3.compose) - implementation(libs.koin.compose.viewmodel) - implementation(libs.reorderable) - implementation(libs.navigation.compose) - } - } -} - -android { - namespace = "com.andannn.melodify.feature.player" -} diff --git a/feature/player/src/androidMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/MiniPlayerLayoutPreview.kt b/feature/player/src/androidMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/MiniPlayerLayoutPreview.kt deleted file mode 100644 index 3a93df6a..00000000 --- a/feature/player/src/androidMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/MiniPlayerLayoutPreview.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.andannn.melodify.feature.player.ui.shrinkable - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import com.andannn.melodify.feature.player.ui.ShrinkPlayerHeight - -@Preview -@Composable -private fun MiniPlayerLayoutPreview() { - MelodifyTheme { - Surface { - MiniPlayerLayout( - modifier = Modifier - .fillMaxWidth() - .height(ShrinkPlayerHeight), - title = "BBBBB", - artist = "AAAAA", - isPlaying = true, - isFavorite = false, - enabled = true, - ) - } - } -} diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaView.kt b/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaView.kt deleted file mode 100644 index f113242f..00000000 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaView.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.andannn.melodify.feature.player - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.andannn.melodify.feature.player.ui.PlayerView - -@Composable -fun PlayerAreaView( - state: PlayerUiState.Active, - modifier: Modifier = Modifier, - onEvent: (PlayerUiEvent) -> Unit, -) { - PlayerView( - state = state, - onEvent = onEvent, - modifier = modifier - ) -} \ No newline at end of file diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/di/PlayerFeatureModule.kt b/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/di/PlayerFeatureModule.kt deleted file mode 100644 index 4331ad30..00000000 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/di/PlayerFeatureModule.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.andannn.melodify.feature.player.di - -import com.andannn.melodify.feature.player.PlayerStateViewModel -import org.koin.core.module.dsl.viewModelOf -import org.koin.dsl.module - -val playerFeatureModule = module { - viewModelOf(::PlayerStateViewModel) -} \ No newline at end of file diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/LyricsView.kt b/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/LyricsView.kt deleted file mode 100644 index 77c5ef1e..00000000 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/LyricsView.kt +++ /dev/null @@ -1,167 +0,0 @@ -package com.andannn.melodify.feature.player.ui.shrinkable.bottom.lyrics - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import com.andannn.melodify.core.data.model.LyricModel -import com.andannn.melodify.feature.player.LyricState -import org.jetbrains.compose.ui.tooling.preview.Preview - -@Composable -internal fun LyricsView( - lyricState: LyricState, - modifier: Modifier = Modifier, - currentPositionMs: Long = 0L, - onRequestSeek: (timeMs: Long) -> Unit = {} -) { - when (lyricState) { - is LyricState.Loaded -> { - val lyricModel = lyricState.lyric - if (lyricModel == null) { - Box(modifier = Modifier.fillMaxSize()) { - Text( - modifier = Modifier.align(Alignment.Center), - text = "No lyrics found", - style = MaterialTheme.typography.headlineSmall - ) - } - } else { - if (lyricModel.syncedLyrics.isNotBlank()) { - SyncedLyricsView( - modifier = modifier, - syncedLyric = lyricModel.syncedLyrics, - currentPositionMs = currentPositionMs, - onRequestSeek = onRequestSeek - ) - } else if (lyricModel.plainLyrics.isNotBlank()) { - PlainLyricsView( - modifier = modifier, - lyric = lyricModel.plainLyrics - ) - } - } - } - LyricState.Loading -> { - Box(modifier = Modifier.fillMaxSize()) { - CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) - } - } - } -} - -@Composable -private fun PlainLyricsView( - lyric: String, - modifier: Modifier = Modifier -) { - Text( - modifier = - modifier - .verticalScroll(rememberScrollState()) - .padding(8.dp), - text = lyric, - ) -} - - -@Preview -@Composable -private fun PlainLyricsViewPreview() { - MelodifyTheme { - Surface { - LyricsView( - lyricState = LyricState.Loaded( - LyricModel( - plainLyrics = "正しさとは 愚かさとは\n" + - "それが何か見せつけてやる\n" + - "\n" + - "ちっちゃな頃から優等生\n" + - "気づいたら大人になっていた\n" + - "ナイフの様な思考回路\n" + - "持ち合わせる訳もなく\n" + - "\n" + - "でも遊び足りない 何か足りない\n" + - "困っちまうこれは誰かのせい\n" + - "あてもなくただ混乱するエイデイ\n" + - "\n" + - "それもそっか\n" + - "最新の流行は当然の把握\n" + - "経済の動向も通勤時チェック\n" + - "純情な精神で入社しワーク\n" + - "社会人じゃ当然のルールです\n" + - "\n" + - "はぁ?うっせぇうっせぇうっせぇわ\n" + - "あなたが思うより健康です\n" + - "一切合切凡庸な\n" + - "あなたじゃ分からないかもね\n" + - "嗚呼よく似合う\n" + - "その可もなく不可もないメロディー\n" + - "うっせぇうっせぇうっせぇわ\n" + - "頭の出来が違うので問題はナシ\n" + - "\n" + - "つっても私模範人間\n" + - "殴ったりするのはノーセンキュー\n" + - "だったら言葉の銃口を\n" + - "その頭に突きつけて撃てば\n" + - "\n" + - "マジヤバない?止まれやしない\n" + - "不平不満垂れて成れの果て\n" + - "サディスティックに変貌する精神\n" + - "\n" + - "クソだりぃな\n" + - "酒が空いたグラスあれば直ぐに注ぎなさい\n" + - "皆がつまみ易いように串外しなさい\n" + - "会計や注文は先陣を切る\n" + - "不文律最低限のマナーです\n" + - "\n" + - "はぁ?うっせぇうっせぇうっせぇわ\n" + - "くせぇ口塞げや限界です\n" + - "絶対絶対現代の代弁者は私やろがい\n" + - "もう見飽きたわ\n" + - "二番煎じ言い換えのパロディ\n" + - "うっせぇうっせぇうっせぇわ\n" + - "丸々と肉付いたその顔面にバツ\n" + - "\n" + - "うっせぇうっせぇうっせぇわ\n" + - "うっせぇうっせぇうっせぇわ\n" + - "私が俗に言う天才です\n" + - "\n" + - "うっせぇうっせぇうっせぇわ\n" + - "あなたが思うより健康です\n" + - "一切合切凡庸な\n" + - "あなたじゃ分からないかもね\n" + - "嗚呼つまらねぇ\n" + - "何回聞かせるんだそのメモリー\n" + - "うっせぇうっせぇうっせぇわ\n" + - "アタシも大概だけど\n" + - "どうだっていいぜ問題はナシ", - syncedLyrics = "" - ) - ), - ) - } - } -} - -@Preview -@Composable -private fun LyricsViewLoadingPreview() { - MelodifyTheme { - Surface { - LyricsView( - lyricState = LyricState.Loading - ) - } - } -} \ No newline at end of file diff --git a/feature/player/src/commonTest/kotlin/com/andannn/melodify/feature/player/lyrics/SyncedLyricsStateTest.kt b/feature/player/src/commonTest/kotlin/com/andannn/melodify/feature/player/lyrics/SyncedLyricsStateTest.kt deleted file mode 100644 index 84c6c48a..00000000 --- a/feature/player/src/commonTest/kotlin/com/andannn/melodify/feature/player/lyrics/SyncedLyricsStateTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.andannn.melodify.feature.player.lyrics - -import com.andannn.melodify.feature.player.ui.shrinkable.bottom.lyrics.parseSyncedLyrics -import kotlin.test.Test - -class ParseSyncLyricsTest { - private val dummy = "[00:00.55] 正しさとは 愚かさとは\n" + - "[00:03.72] それが何か見せつけてやる\n" + - "[00:08.78] \n" + - "[00:16.80] ちっちゃな頃から優等生\n" - - @Test - fun parse_sync_lyrics() { - val result = parseSyncedLyrics(dummy) - println(result) - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index f40cace3..285b47d8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,11 +20,7 @@ include(":core:player") include(":core:datastore") include(":core:network") include(":core:database") -include(":feature:common") -include(":feature:home") -include(":feature:player") -include(":feature:customtab") -include(":feature:drawer") -include(":feature:message") include(":core:syncer") include(":core:platform") +include(":ui:common") +include(":ui:components") diff --git a/feature/common/.gitignore b/ui/common/.gitignore similarity index 100% rename from feature/common/.gitignore rename to ui/common/.gitignore diff --git a/feature/common/build.gradle.kts b/ui/common/build.gradle.kts similarity index 100% rename from feature/common/build.gradle.kts rename to ui/common/build.gradle.kts diff --git a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.android.kt b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.android.kt similarity index 99% rename from feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.android.kt rename to ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.android.kt index 2c382fec..20f06d67 100644 --- a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.android.kt +++ b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.android.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.dynamic_theming +package com.andannn.melodify.ui.common.dynamic_theming import androidx.compose.material3.ColorScheme import androidx.compose.ui.graphics.Color diff --git a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.android.kt b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.android.kt similarity index 98% rename from feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.android.kt rename to ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.android.kt index e32789d2..c964e98e 100644 --- a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.android.kt +++ b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.android.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.dynamic_theming +package com.andannn.melodify.ui.common.dynamic_theming import android.content.Context import androidx.collection.LruCache diff --git a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.android.kt b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.android.kt similarity index 94% rename from feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.android.kt rename to ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.android.kt index a2fbd3b0..7dc18dd8 100644 --- a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.android.kt +++ b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.android.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.theme +package com.andannn.melodify.ui.common.theme import android.os.Build import androidx.compose.material3.ColorScheme diff --git a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.android.kt b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.android.kt similarity index 90% rename from feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.android.kt rename to ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.android.kt index 88e88814..9341d21b 100644 --- a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.android.kt +++ b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.android.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.util +package com.andannn.melodify.ui.common.util import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable diff --git a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.android.kt b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.android.kt similarity index 80% rename from feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.android.kt rename to ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.android.kt index d2a446d8..36596a0b 100644 --- a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.android.kt +++ b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.android.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable diff --git a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/ListTileItemViewPreview.kt b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/widgets/ListTileItemViewPreview.kt similarity index 94% rename from feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/ListTileItemViewPreview.kt rename to ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/widgets/ListTileItemViewPreview.kt index cbf581f3..b0573c96 100644 --- a/feature/common/src/androidMain/kotlin/com/andannn/melodify/feature/common/component/ListTileItemViewPreview.kt +++ b/ui/common/src/androidMain/kotlin/com/andannn/melodify/ui/common/widgets/ListTileItemViewPreview.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable diff --git a/feature/common/src/commonMain/composeResources/drawable/default_image_icon.png b/ui/common/src/commonMain/composeResources/drawable/default_image_icon.png similarity index 100% rename from feature/common/src/commonMain/composeResources/drawable/default_image_icon.png rename to ui/common/src/commonMain/composeResources/drawable/default_image_icon.png diff --git a/feature/common/src/commonMain/composeResources/values-de/strings.xml b/ui/common/src/commonMain/composeResources/values-de/strings.xml similarity index 100% rename from feature/common/src/commonMain/composeResources/values-de/strings.xml rename to ui/common/src/commonMain/composeResources/values-de/strings.xml diff --git a/feature/common/src/commonMain/composeResources/values-es/strings.xml b/ui/common/src/commonMain/composeResources/values-es/strings.xml similarity index 100% rename from feature/common/src/commonMain/composeResources/values-es/strings.xml rename to ui/common/src/commonMain/composeResources/values-es/strings.xml diff --git a/feature/common/src/commonMain/composeResources/values-fr/strings.xml b/ui/common/src/commonMain/composeResources/values-fr/strings.xml similarity index 100% rename from feature/common/src/commonMain/composeResources/values-fr/strings.xml rename to ui/common/src/commonMain/composeResources/values-fr/strings.xml diff --git a/feature/common/src/commonMain/composeResources/values-ja/strings.xml b/ui/common/src/commonMain/composeResources/values-ja/strings.xml similarity index 100% rename from feature/common/src/commonMain/composeResources/values-ja/strings.xml rename to ui/common/src/commonMain/composeResources/values-ja/strings.xml diff --git a/feature/common/src/commonMain/composeResources/values-ko/strings.xml b/ui/common/src/commonMain/composeResources/values-ko/strings.xml similarity index 100% rename from feature/common/src/commonMain/composeResources/values-ko/strings.xml rename to ui/common/src/commonMain/composeResources/values-ko/strings.xml diff --git a/feature/common/src/commonMain/composeResources/values-pt/strings.xml b/ui/common/src/commonMain/composeResources/values-pt/strings.xml similarity index 100% rename from feature/common/src/commonMain/composeResources/values-pt/strings.xml rename to ui/common/src/commonMain/composeResources/values-pt/strings.xml diff --git a/feature/common/src/commonMain/composeResources/values-ru/strings.xml b/ui/common/src/commonMain/composeResources/values-ru/strings.xml similarity index 100% rename from feature/common/src/commonMain/composeResources/values-ru/strings.xml rename to ui/common/src/commonMain/composeResources/values-ru/strings.xml diff --git a/feature/common/src/commonMain/composeResources/values/strings.xml b/ui/common/src/commonMain/composeResources/values/strings.xml similarity index 100% rename from feature/common/src/commonMain/composeResources/values/strings.xml rename to ui/common/src/commonMain/composeResources/values/strings.xml diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dialog/AlertDialogs.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dialog/AlertDialogs.kt similarity index 97% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dialog/AlertDialogs.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dialog/AlertDialogs.kt index e8f6ec36..880a31c2 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dialog/AlertDialogs.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dialog/AlertDialogs.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.dialog +package com.andannn.melodify.ui.common.dialog import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.kt similarity index 97% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.kt index cbb97a59..15019d81 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.dynamic_theming +package com.andannn.melodify.ui.common.dynamic_theming import androidx.compose.material3.ColorScheme import androidx.compose.ui.graphics.Color diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.kt similarity index 97% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.kt index a83c6f9c..972231fe 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.dynamic_theming +package com.andannn.melodify.ui.common.dynamic_theming import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Spring diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/icons/SimpleMusicIcons.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/icons/SimpleMusicIcons.kt similarity index 97% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/icons/SimpleMusicIcons.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/icons/SimpleMusicIcons.kt index b16803f0..1093d706 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/icons/SimpleMusicIcons.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/icons/SimpleMusicIcons.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.icons +package com.andannn.melodify.ui.common.icons import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.PlaylistAdd diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Color.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Color.kt similarity index 85% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Color.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Color.kt index 60807903..bda82f86 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Color.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Color.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.theme +package com.andannn.melodify.ui.common.theme /** * This is the minimum amount of calculated contrast for a color to be used on top of the diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Shape.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Shape.kt similarity index 84% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Shape.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Shape.kt index 3e1e4dbe..5528abd0 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Shape.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Shape.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.theme +package com.andannn.melodify.ui.common.theme import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Shapes diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.kt similarity index 94% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.kt index 640898ef..ce43ee6f 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.theme +package com.andannn.melodify.ui.common.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.ColorScheme diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Type.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Type.kt similarity index 93% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Type.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Type.kt index ef3b1d8c..4794c9e2 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/theme/Type.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/theme/Type.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.theme +package com.andannn.melodify.ui.common.theme import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/Colors.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/Colors.kt similarity index 95% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/Colors.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/Colors.kt index 84b29c85..6766aa45 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/Colors.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/Colors.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.andannn.melodify.feature.common.util +package com.andannn.melodify.ui.common.util import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/GradientScrim.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/GradientScrim.kt similarity index 98% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/GradientScrim.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/GradientScrim.kt index d5218ade..4007f63b 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/GradientScrim.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/GradientScrim.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.andannn.melodify.feature.common.util +package com.andannn.melodify.ui.common.util import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/util/ResourceUtil.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/ImageResourceUtil.kt similarity index 52% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/util/ResourceUtil.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/ImageResourceUtil.kt index 3a20712c..566ebc3f 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/util/ResourceUtil.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/ImageResourceUtil.kt @@ -1,14 +1,13 @@ -package com.andannn.melodify.feature.player.util +package com.andannn.melodify.ui.common.util import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Repeat import androidx.compose.material.icons.rounded.RepeatOn import androidx.compose.material.icons.rounded.RepeatOneOn import com.andannn.melodify.core.data.model.PlayMode -import com.andannn.melodify.feature.player.ui.shrinkable.bottom.SheetTab -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.lyrics -import melodify.feature.common.generated.resources.play_queue +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.lyrics +import melodify.ui.common.generated.resources.play_queue fun PlayMode.getIcon() = when (this) { @@ -16,8 +15,3 @@ fun PlayMode.getIcon() = PlayMode.REPEAT_OFF -> Icons.Rounded.Repeat PlayMode.REPEAT_ALL -> Icons.Rounded.RepeatOn } - -fun SheetTab.getLabel() = when(this) { - SheetTab.NEXT_SONG -> Res.string.play_queue - SheetTab.LYRICS -> Res.string.lyrics -} \ No newline at end of file diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.kt similarity index 72% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.kt index 59ca4cf7..0c4a8955 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.util +package com.andannn.melodify.ui.common.util import androidx.compose.runtime.Composable import org.koin.core.scope.Scope diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/StringResUtil.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/StringResUtil.kt similarity index 59% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/StringResUtil.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/StringResUtil.kt index 095ad4b7..4512ccab 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/StringResUtil.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/StringResUtil.kt @@ -1,16 +1,12 @@ -package com.andannn.melodify.feature.common.util +package com.andannn.melodify.ui.common.util import androidx.compose.runtime.Composable import com.andannn.melodify.core.data.model.CustomTab -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.album_page_title -import melodify.feature.common.generated.resources.artist_page_title -import melodify.feature.common.generated.resources.audio_page_title -import melodify.feature.common.generated.resources.genre_title -import melodify.feature.common.generated.resources.number_hours -import melodify.feature.common.generated.resources.number_minutes -import melodify.feature.common.generated.resources.number_seconds -import melodify.feature.common.generated.resources.playlist_page_title +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.audio_page_title +import melodify.ui.common.generated.resources.number_hours +import melodify.ui.common.generated.resources.number_minutes +import melodify.ui.common.generated.resources.number_seconds import org.jetbrains.compose.resources.stringResource import kotlin.time.Duration @@ -44,11 +40,7 @@ fun durationString(duration: Duration): String { @Composable fun getCategoryResource(category: CustomTab): String { return when (category) { - CustomTab.AllAlbum -> stringResource(Res.string.album_page_title) - CustomTab.AllArtist -> stringResource(Res.string.artist_page_title) - CustomTab.AllGenre -> stringResource(Res.string.genre_title) CustomTab.AllMusic -> stringResource(Res.string.audio_page_title) - CustomTab.AllPlayList -> stringResource(Res.string.playlist_page_title) is CustomTab.AlbumDetail -> category.label is CustomTab.ArtistDetail -> category.label is CustomTab.GenreDetail -> category.label diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/SwapListStateHelper.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/SwapListStateHelper.kt similarity index 85% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/SwapListStateHelper.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/SwapListStateHelper.kt index fc5363eb..daf88958 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/util/SwapListStateHelper.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/util/SwapListStateHelper.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.util +package com.andannn.melodify.ui.common.util import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState @@ -23,12 +23,13 @@ fun rememberSwapListState( ): SwapListStateHolder { val onSwapFinishedState = rememberUpdatedState(onSwapFinished) val onDeleteFinishedState = rememberUpdatedState(onDeleteFinished) - val playQueueState = remember { - SwapListStateImpl( - onSwapFinishedState = onSwapFinishedState, - onDeleteFinishedState = onDeleteFinishedState, - ) - } + val playQueueState = + remember { + SwapListStateImpl( + onSwapFinishedState = onSwapFinishedState, + onDeleteFinishedState = onDeleteFinishedState, + ) + } val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -36,10 +37,10 @@ fun rememberSwapListState( } return remember { - SwapListStateHolder( + SwapListStateHolder( lazyListState = lazyListState, swapListState = playQueueState, - reorderableLazyListState = reorderableLazyListState + reorderableLazyListState = reorderableLazyListState, ) } } @@ -50,11 +51,18 @@ class SwapListStateHolder( val reorderableLazyListState: ReorderableLazyListState, ) : SwapListState by swapListState -interface SwapListState { +interface SwapListState { val itemList: SnapshotStateList + fun onStopDrag() - fun onSwapItem(from: Int, to: Int) + + fun onSwapItem( + from: Int, + to: Int, + ) + fun onApplyNewList(list: ImmutableList) + fun onDeleteItem(item: T) } @@ -80,7 +88,10 @@ class SwapListStateImpl( toDragIndex = null } - override fun onSwapItem(from: Int, to: Int) { + override fun onSwapItem( + from: Int, + to: Int, + ) { Napier.d(tag = TAG) { "PlayQueueView: onSwapItem from $from to $to" } toDragIndex = to if (fromDragIndex == null) { @@ -108,4 +119,4 @@ class SwapListStateImpl( onDeleteFinishedState.value(index, itemList.toList()) } } -} \ No newline at end of file +} diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.kt similarity index 72% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.kt index e2acb0de..a46bdf00 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.runtime.Composable diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/AutoResizedText.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/AutoResizedText.kt similarity index 96% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/AutoResizedText.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/AutoResizedText.kt index 9a25be0a..becd24ab 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/AutoResizedText.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/AutoResizedText.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/CircleBoderImage.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/CircleBoderImage.kt similarity index 93% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/CircleBoderImage.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/CircleBoderImage.kt index bb99cc64..5c02ee35 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/CircleBoderImage.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/CircleBoderImage.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.border diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/ExtraPaddingBottom.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/ExtraPaddingBottom.kt similarity index 84% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/ExtraPaddingBottom.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/ExtraPaddingBottom.kt index 3d1d803a..471c8db8 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/ExtraPaddingBottom.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/ExtraPaddingBottom.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/FavoriteIconButton.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/FavoriteIconButton.kt similarity index 94% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/FavoriteIconButton.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/FavoriteIconButton.kt index 4514dc5e..2027e7e4 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/FavoriteIconButton.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/FavoriteIconButton.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Favorite diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/LargePreviewCard.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/LargePreviewCard.kt similarity index 94% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/LargePreviewCard.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/LargePreviewCard.kt index 89711d4b..b2b07024 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/LargePreviewCard.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/LargePreviewCard.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -21,9 +21,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.default_image_icon +import com.andannn.melodify.ui.common.theme.MelodifyTheme +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.default_image_icon import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.ui.tooling.preview.Preview diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/ListTileItemView.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/ListTileItemView.kt similarity index 96% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/ListTileItemView.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/ListTileItemView.kt index 2982f9f2..5aa49fea 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/ListTileItemView.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/ListTileItemView.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -29,8 +29,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.default_image_icon +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.default_image_icon import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.painterResource diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/SmpIconButton.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/SmpIconButton.kt similarity index 97% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/SmpIconButton.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/SmpIconButton.kt index 85f3439a..9b1311ac 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/SmpIconButton.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/SmpIconButton.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.PlayArrow diff --git a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/SmpTextButton.kt b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/SmpTextButton.kt similarity index 96% rename from feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/SmpTextButton.kt rename to ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/SmpTextButton.kt index b6f6292c..1b13a016 100644 --- a/feature/common/src/commonMain/kotlin/com/andannn/melodify/feature/common/component/SmpTextButton.kt +++ b/ui/common/src/commonMain/kotlin/com/andannn/melodify/ui/common/widgets/SmpTextButton.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.width diff --git a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.desktop.kt b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.desktop.kt similarity index 88% rename from feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.desktop.kt rename to ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.desktop.kt index 0fdca3a1..5d7502c5 100644 --- a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/ColorSchemeUtil.desktop.kt +++ b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/ColorSchemeUtil.desktop.kt @@ -1,11 +1,11 @@ -package com.andannn.melodify.feature.common.dynamic_theming +package com.andannn.melodify.ui.common.dynamic_theming import androidx.compose.material3.ColorScheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.ui.graphics.Color -import com.andannn.melodify.feature.common.theme.DarkColorPalette -import com.andannn.melodify.feature.common.theme.LightColorPalette +import com.andannn.melodify.ui.common.theme.DarkColorPalette +import com.andannn.melodify.ui.common.theme.LightColorPalette /** * Not support dynamic color in desktop app. diff --git a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.desktop.kt b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.desktop.kt similarity index 89% rename from feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.desktop.kt rename to ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.desktop.kt index 2ef1ef2e..481f5fa9 100644 --- a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/dynamic_theming/DynamicTheming.desktop.kt +++ b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/dynamic_theming/DynamicTheming.desktop.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.dynamic_theming +package com.andannn.melodify.ui.common.dynamic_theming import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color diff --git a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.desktop.kt b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.desktop.kt similarity index 88% rename from feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.desktop.kt rename to ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.desktop.kt index 198ecea5..0e7424e8 100644 --- a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.desktop.kt +++ b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.desktop.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.theme +package com.andannn.melodify.ui.common.theme import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme diff --git a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.desktop.kt b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.desktop.kt similarity index 83% rename from feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.desktop.kt rename to ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.desktop.kt index ae6373d1..3f88b1d5 100644 --- a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.desktop.kt +++ b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.desktop.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.util +package com.andannn.melodify.ui.common.util import androidx.compose.runtime.Composable import org.koin.core.scope.Scope diff --git a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.desktop.kt b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.desktop.kt similarity index 70% rename from feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.desktop.kt rename to ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.desktop.kt index 7c8e1357..a80a300f 100644 --- a/feature/common/src/desktopMain/kotlin/com/andannn/melodify/feature/common/component/AndroidBackHandler.desktop.kt +++ b/ui/common/src/desktopMain/kotlin/com/andannn/melodify/ui/common/widgets/AndroidBackHandler.desktop.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.component +package com.andannn.melodify.ui.common.widgets import androidx.compose.runtime.Composable diff --git a/feature/common/src/iosMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.ios.kt b/ui/common/src/iosMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.ios.kt similarity index 84% rename from feature/common/src/iosMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.ios.kt rename to ui/common/src/iosMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.ios.kt index 2506d54c..ad409cb0 100644 --- a/feature/common/src/iosMain/kotlin/com/andannn/melodify/feature/common/theme/Theme.ios.kt +++ b/ui/common/src/iosMain/kotlin/com/andannn/melodify/ui/common/theme/Theme.ios.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.theme +package com.andannn.melodify.ui.common.theme import androidx.compose.material3.ColorScheme import androidx.compose.runtime.Composable diff --git a/feature/common/src/iosMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.ios.kt b/ui/common/src/iosMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.ios.kt similarity index 75% rename from feature/common/src/iosMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.ios.kt rename to ui/common/src/iosMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.ios.kt index 37973ef9..fde6c730 100644 --- a/feature/common/src/iosMain/kotlin/com/andannn/melodify/feature/common/util/ScopeResolver.ios.kt +++ b/ui/common/src/iosMain/kotlin/com/andannn/melodify/ui/common/util/ScopeResolver.ios.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.common.util +package com.andannn.melodify.ui.common.util import androidx.compose.runtime.Composable import org.koin.core.scope.Scope diff --git a/feature/customtab/.gitignore b/ui/components/.gitignore similarity index 100% rename from feature/customtab/.gitignore rename to ui/components/.gitignore diff --git a/feature/customtab/build.gradle.kts b/ui/components/build.gradle.kts similarity index 55% rename from feature/customtab/build.gradle.kts rename to ui/components/build.gradle.kts index 2a2d8b0b..dd223fdc 100644 --- a/feature/customtab/build.gradle.kts +++ b/ui/components/build.gradle.kts @@ -6,16 +6,16 @@ plugins { kotlin { sourceSets { commonMain.dependencies { - implementation(project(":feature:common")) implementation(project(":core:data")) + api(project(":core:platform")) + implementation(project(":ui:common")) - implementation(libs.koin.compose.viewmodel) - implementation(libs.navigation.compose) + implementation(libs.coil3.compose) implementation(libs.reorderable) } } } android { - namespace = "com.andannn.melodify.feature.customtab" -} \ No newline at end of file + namespace = "com.andannn.melodify.ui.components" +} diff --git a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/BottomDrawerContainer.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/BottomDrawerContainer.kt similarity index 83% rename from feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/BottomDrawerContainer.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/BottomDrawerContainer.kt index 47470597..4a7ef7b8 100644 --- a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/BottomDrawerContainer.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/BottomDrawerContainer.kt @@ -1,11 +1,11 @@ -package com.andannn.melodify.feature.drawer +package com.andannn.melodify.ui.components.drawer import androidx.compose.runtime.Composable -import com.andannn.melodify.feature.drawer.model.SheetModel -import com.andannn.melodify.feature.drawer.sheet.AddToPlayListRequestSheet -import com.andannn.melodify.feature.drawer.sheet.MediaOptionBottomSheet -import com.andannn.melodify.feature.drawer.sheet.SleepTimerCountingBottomSheet -import com.andannn.melodify.feature.drawer.sheet.SleepTimerOptionBottomSheet +import com.andannn.melodify.ui.components.drawer.model.SheetModel +import com.andannn.melodify.ui.components.drawer.sheet.AddToPlayListRequestSheet +import com.andannn.melodify.ui.components.drawer.sheet.MediaOptionBottomSheet +import com.andannn.melodify.ui.components.drawer.sheet.SleepTimerCountingBottomSheet +import com.andannn.melodify.ui.components.drawer.sheet.SleepTimerOptionBottomSheet @Composable fun BottomDrawerContainer( diff --git a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/DrawerController.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/DrawerController.kt similarity index 95% rename from feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/DrawerController.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/DrawerController.kt index 16f75c26..990e657d 100644 --- a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/DrawerController.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/DrawerController.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.drawer +package com.andannn.melodify.ui.components.drawer import com.andannn.melodify.core.data.Repository import com.andannn.melodify.core.data.getAudios @@ -7,13 +7,13 @@ import com.andannn.melodify.core.data.model.MediaItemModel import com.andannn.melodify.core.data.model.PlayListItemModel import com.andannn.melodify.core.data.repository.MediaControllerRepository import com.andannn.melodify.core.data.repository.PlayerStateMonitoryRepository -import com.andannn.melodify.feature.drawer.model.SheetModel -import com.andannn.melodify.feature.drawer.model.SheetOptionItem -import com.andannn.melodify.feature.drawer.model.SleepTimerOption -import com.andannn.melodify.feature.message.MessageController -import com.andannn.melodify.feature.message.dialog.Dialog -import com.andannn.melodify.feature.message.dialog.InteractionResult -import com.andannn.melodify.feature.message.snackbar.SnackBarMessage +import com.andannn.melodify.ui.components.drawer.model.SheetModel +import com.andannn.melodify.ui.components.drawer.model.SheetOptionItem +import com.andannn.melodify.ui.components.drawer.model.SleepTimerOption +import com.andannn.melodify.ui.components.message.MessageController +import com.andannn.melodify.ui.components.message.dialog.Dialog +import com.andannn.melodify.ui.components.message.dialog.InteractionResult +import com.andannn.melodify.ui.components.message.snackbar.SnackBarMessage import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/model/BottomSheet.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/model/BottomSheet.kt similarity index 94% rename from feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/model/BottomSheet.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/model/BottomSheet.kt index 600f15ba..52f77313 100644 --- a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/model/BottomSheet.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/model/BottomSheet.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.drawer.model +package com.andannn.melodify.ui.components.drawer.model import com.andannn.melodify.core.data.model.AlbumItemModel import com.andannn.melodify.core.data.model.ArtistItemModel @@ -6,10 +6,10 @@ import com.andannn.melodify.core.data.model.AudioItemModel import com.andannn.melodify.core.data.model.GenreItemModel import com.andannn.melodify.core.data.model.MediaItemModel import com.andannn.melodify.core.data.model.PlayListItemModel -import com.andannn.melodify.feature.common.icons.SimpleMusicIcons -import com.andannn.melodify.feature.common.icons.SmpIcon -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.* +import com.andannn.melodify.ui.common.icons.SimpleMusicIcons +import com.andannn.melodify.ui.common.icons.SmpIcon +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.* import org.jetbrains.compose.resources.StringResource import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes diff --git a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheet.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/AddToPlayListRequestSheet.kt similarity index 88% rename from feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheet.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/AddToPlayListRequestSheet.kt index 7302eccc..4f7cbbc5 100644 --- a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheet.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/AddToPlayListRequestSheet.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.drawer.sheet +package com.andannn.melodify.ui.components.drawer.sheet import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -17,8 +17,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.Close -import androidx.compose.material.icons.rounded.PlayArrow -import androidx.compose.material.icons.rounded.PlusOne import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -36,17 +34,17 @@ import androidx.compose.ui.unit.dp import com.andannn.melodify.core.data.model.AudioItemModel import com.andannn.melodify.core.data.model.PlayListItemModel import com.andannn.melodify.core.data.model.key -import com.andannn.melodify.feature.common.component.ActionType -import com.andannn.melodify.feature.common.component.LargePreviewCard -import com.andannn.melodify.feature.common.component.ListTileItemView -import com.andannn.melodify.feature.common.component.SmpTextButton -import com.andannn.melodify.feature.drawer.model.SheetModel -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.all_playlists -import melodify.feature.common.generated.resources.all_to_playlist_page_title -import melodify.feature.common.generated.resources.new_playlist_dialog_title -import melodify.feature.common.generated.resources.selected_songs -import melodify.feature.common.generated.resources.track_count +import com.andannn.melodify.ui.common.widgets.ActionType +import com.andannn.melodify.ui.common.widgets.LargePreviewCard +import com.andannn.melodify.ui.common.widgets.ListTileItemView +import com.andannn.melodify.ui.common.widgets.SmpTextButton +import com.andannn.melodify.ui.components.drawer.model.SheetModel +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.all_playlists +import melodify.ui.common.generated.resources.all_to_playlist_page_title +import melodify.ui.common.generated.resources.new_playlist_dialog_title +import melodify.ui.common.generated.resources.selected_songs +import melodify.ui.common.generated.resources.track_count import org.jetbrains.compose.resources.stringResource @OptIn(ExperimentalMaterial3Api::class) diff --git a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheetState.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/AddToPlayListRequestSheetState.kt similarity index 97% rename from feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheetState.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/AddToPlayListRequestSheetState.kt index 9f7d2301..1f20732f 100644 --- a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/AddToPlayListRequestSheetState.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/AddToPlayListRequestSheetState.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.drawer.sheet +package com.andannn.melodify.ui.components.drawer.sheet import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetState diff --git a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/MediaOptionBottomSheet.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/MediaOptionBottomSheet.kt similarity index 93% rename from feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/MediaOptionBottomSheet.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/MediaOptionBottomSheet.kt index 4a855eb1..1a9f7701 100644 --- a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/MediaOptionBottomSheet.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/MediaOptionBottomSheet.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.drawer.sheet +package com.andannn.melodify.ui.components.drawer.sheet import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -41,18 +41,18 @@ import com.andannn.melodify.core.data.model.AudioItemModel import com.andannn.melodify.core.data.model.GenreItemModel import com.andannn.melodify.core.data.model.MediaItemModel import com.andannn.melodify.core.data.model.PlayListItemModel -import com.andannn.melodify.feature.common.component.FavoriteIconButton -import com.andannn.melodify.feature.common.icons.SmpIcon -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import com.andannn.melodify.feature.drawer.model.SheetModel -import com.andannn.melodify.feature.drawer.model.SheetOptionItem -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.default_image_icon +import com.andannn.melodify.ui.common.widgets.FavoriteIconButton +import com.andannn.melodify.ui.common.icons.SmpIcon +import com.andannn.melodify.ui.common.theme.MelodifyTheme +import com.andannn.melodify.ui.components.drawer.model.SheetModel +import com.andannn.melodify.ui.components.drawer.model.SheetOptionItem +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.default_image_icon import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview -import org.koin.compose.getKoin +import org.koin.mp.KoinPlatform.getKoin @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/SleepTimerCountingSheet.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/SleepTimerCountingSheet.kt similarity index 88% rename from feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/SleepTimerCountingSheet.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/SleepTimerCountingSheet.kt index b10b977c..d6a598fb 100644 --- a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/SleepTimerCountingSheet.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/SleepTimerCountingSheet.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.drawer.sheet +package com.andannn.melodify.ui.components.drawer.sheet import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -13,22 +13,20 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.andannn.melodify.core.data.repository.MediaControllerRepository -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import com.andannn.melodify.feature.common.util.durationString -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.cancel_timer -import melodify.feature.common.generated.resources.sleep_timer +import com.andannn.melodify.ui.common.theme.MelodifyTheme +import com.andannn.melodify.ui.common.util.durationString +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.cancel_timer +import melodify.ui.common.generated.resources.sleep_timer import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview -import org.koin.compose.getKoin +import org.koin.mp.KoinPlatform.getKoin import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds diff --git a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/SleepTimerOptionSheet.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/SleepTimerOptionSheet.kt similarity index 89% rename from feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/SleepTimerOptionSheet.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/SleepTimerOptionSheet.kt index d6e14696..125c9533 100644 --- a/feature/drawer/src/commonMain/kotlin/com/andannn/melodify/feature/drawer/sheet/SleepTimerOptionSheet.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/drawer/sheet/SleepTimerOptionSheet.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.drawer.sheet +package com.andannn.melodify.ui.components.drawer.sheet import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -23,17 +23,15 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import com.andannn.melodify.feature.common.util.durationString -import com.andannn.melodify.feature.drawer.model.SleepTimerOption +import com.andannn.melodify.ui.common.theme.MelodifyTheme +import com.andannn.melodify.ui.common.util.durationString +import com.andannn.melodify.ui.components.drawer.model.SleepTimerOption import kotlinx.coroutines.CoroutineScope -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.end_of_song -import melodify.feature.common.generated.resources.sleep_timer +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.end_of_song +import melodify.ui.common.generated.resources.sleep_timer import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/LyricState.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/LyricState.kt new file mode 100644 index 00000000..ae549e86 --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/LyricState.kt @@ -0,0 +1,64 @@ +package com.andannn.melodify.ui.components.lyrics + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import com.andannn.melodify.core.data.Repository +import com.andannn.melodify.core.data.model.AudioItemModel +import com.andannn.melodify.core.data.model.LyricModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.koin.mp.KoinPlatform.getKoin + +@Composable +fun rememberLyricStateHolder( + source: AudioItemModel?, + scope: CoroutineScope = rememberCoroutineScope(), + repository: Repository = getKoin().get(), +) = remember( + scope, + source, + repository, +) { + LyricStateHolder( + scope = scope, + source = source, + repository = repository, + ) +} + +class LyricStateHolder( + scope: CoroutineScope, + private val source: AudioItemModel?, + private val repository: Repository, +) { + var state by mutableStateOf(LyricState.Loading) + + init { + if (source != null) { + scope.launch { + repository.lyricRepository.tryGetLyricOrIgnore( + mediaId = source.id, + trackName = source.name, + artistName = source.artist, + albumName = source.album, + ) + } + + scope.launch { + repository.lyricRepository.getLyricByMediaIdFlow(source.id).collect { lyricOrNull -> + state = LyricState.Loaded(lyricOrNull) + } + } + } + } +} + +sealed class LyricState { + data object Loading : LyricState() + + data class Loaded(val lyric: LyricModel?) : LyricState() +} diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/LyricsView.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/LyricsView.kt new file mode 100644 index 00000000..ae8fc95e --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/LyricsView.kt @@ -0,0 +1,177 @@ +package com.andannn.melodify.ui.components.lyrics + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.andannn.melodify.core.data.model.AudioItemModel +import com.andannn.melodify.core.data.model.LyricModel +import com.andannn.melodify.ui.common.theme.MelodifyTheme +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun LyricsView( + source: AudioItemModel? = null, + lyricStateHolder: LyricStateHolder = rememberLyricStateHolder(source), + modifier: Modifier = Modifier, +) { + LyricsViewContent( + lyricState = lyricStateHolder.state, + modifier = modifier, + ) +} + +@Composable +private fun LyricsViewContent( + lyricState: LyricState, + modifier: Modifier = Modifier, +) { + when (lyricState) { + is LyricState.Loaded -> { + val lyricModel = lyricState.lyric + if (lyricModel == null) { + Box(modifier = Modifier.fillMaxSize()) { + Text( + modifier = Modifier.align(Alignment.Center), + text = "No lyrics found", + style = MaterialTheme.typography.headlineSmall, + ) + } + } else { + if (lyricModel.syncedLyrics.isNotBlank()) { + SyncedLyrics( + modifier = modifier, + syncedLyric = lyricModel.syncedLyrics, + ) + } else if (lyricModel.plainLyrics.isNotBlank()) { + PlainLyricsView( + modifier = modifier, + lyric = lyricModel.plainLyrics, + ) + } + } + } + + LyricState.Loading -> { + Box(modifier = Modifier.fillMaxSize()) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } + } + } +} + +@Composable +private fun PlainLyricsView( + lyric: String, + modifier: Modifier = Modifier, +) { + Text( + modifier = + modifier + .verticalScroll(rememberScrollState()) + .padding(8.dp), + text = lyric, + ) +} + +@Preview +@Composable +private fun PlainLyricsViewPreview() { + MelodifyTheme { + Surface { + LyricsViewContent( + lyricState = + LyricState.Loaded( + LyricModel( + plainLyrics = + "正しさとは 愚かさとは\n" + + "それが何か見せつけてやる\n" + + "\n" + + "ちっちゃな頃から優等生\n" + + "気づいたら大人になっていた\n" + + "ナイフの様な思考回路\n" + + "持ち合わせる訳もなく\n" + + "\n" + + "でも遊び足りない 何か足りない\n" + + "困っちまうこれは誰かのせい\n" + + "あてもなくただ混乱するエイデイ\n" + + "\n" + + "それもそっか\n" + + "最新の流行は当然の把握\n" + + "経済の動向も通勤時チェック\n" + + "純情な精神で入社しワーク\n" + + "社会人じゃ当然のルールです\n" + + "\n" + + "はぁ?うっせぇうっせぇうっせぇわ\n" + + "あなたが思うより健康です\n" + + "一切合切凡庸な\n" + + "あなたじゃ分からないかもね\n" + + "嗚呼よく似合う\n" + + "その可もなく不可もないメロディー\n" + + "うっせぇうっせぇうっせぇわ\n" + + "頭の出来が違うので問題はナシ\n" + + "\n" + + "つっても私模範人間\n" + + "殴ったりするのはノーセンキュー\n" + + "だったら言葉の銃口を\n" + + "その頭に突きつけて撃てば\n" + + "\n" + + "マジヤバない?止まれやしない\n" + + "不平不満垂れて成れの果て\n" + + "サディスティックに変貌する精神\n" + + "\n" + + "クソだりぃな\n" + + "酒が空いたグラスあれば直ぐに注ぎなさい\n" + + "皆がつまみ易いように串外しなさい\n" + + "会計や注文は先陣を切る\n" + + "不文律最低限のマナーです\n" + + "\n" + + "はぁ?うっせぇうっせぇうっせぇわ\n" + + "くせぇ口塞げや限界です\n" + + "絶対絶対現代の代弁者は私やろがい\n" + + "もう見飽きたわ\n" + + "二番煎じ言い換えのパロディ\n" + + "うっせぇうっせぇうっせぇわ\n" + + "丸々と肉付いたその顔面にバツ\n" + + "\n" + + "うっせぇうっせぇうっせぇわ\n" + + "うっせぇうっせぇうっせぇわ\n" + + "私が俗に言う天才です\n" + + "\n" + + "うっせぇうっせぇうっせぇわ\n" + + "あなたが思うより健康です\n" + + "一切合切凡庸な\n" + + "あなたじゃ分からないかもね\n" + + "嗚呼つまらねぇ\n" + + "何回聞かせるんだそのメモリー\n" + + "うっせぇうっせぇうっせぇわ\n" + + "アタシも大概だけど\n" + + "どうだっていいぜ問題はナシ", + syncedLyrics = "", + ), + ), + ) + } + } +} + +@Preview +@Composable +private fun LyricsViewLoadingPreview() { + MelodifyTheme { + Surface { + LyricsViewContent( + lyricState = LyricState.Loading, + ) + } + } +} diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/SyncedLyricsState.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/SyncedLyricsState.kt similarity index 81% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/SyncedLyricsState.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/SyncedLyricsState.kt index 6c4b18e0..928beaaf 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/SyncedLyricsState.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/SyncedLyricsState.kt @@ -1,38 +1,45 @@ -package com.andannn.melodify.feature.player.ui.shrinkable.bottom.lyrics +package com.andannn.melodify.ui.components.lyrics import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable -import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import com.andannn.melodify.core.data.Repository +import com.andannn.melodify.core.data.repository.MediaControllerRepository +import com.andannn.melodify.core.data.repository.PlayerStateMonitoryRepository import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.koin.mp.KoinPlatform.getKoin @Composable fun rememberSyncedLyricsState( syncedLyrics: String, lazyListState: LazyListState = rememberLazyListState(), coroutineScope: CoroutineScope = rememberCoroutineScope(), - onRequestSeek: (timeMs: Long) -> Unit = { }, + repository: Repository = getKoin().get(), ): SyncedLyricsState { - val onRequestSeekState = rememberUpdatedState(onRequestSeek) - return remember(syncedLyrics, lazyListState) { + return remember( + syncedLyrics, + lazyListState, + coroutineScope, + repository, + ) { SyncedLyricsState( syncedLyrics = syncedLyrics, + playControlRepository = repository.mediaControllerRepository, + playerStateMonitoryRepository = repository.playerStateMonitoryRepository, lazyListState = lazyListState, coroutineScope = coroutineScope, - onRequestSeekState = onRequestSeekState ) } } @@ -55,7 +62,8 @@ class SyncedLyricsState( syncedLyrics: String, val lazyListState: LazyListState, private val coroutineScope: CoroutineScope, - private val onRequestSeekState: State<(timeMs: Long) -> Unit>, + private val playControlRepository: MediaControllerRepository, + private val playerStateMonitoryRepository: PlayerStateMonitoryRepository, ) : CoroutineScope by coroutineScope { var syncedLyricsLines by mutableStateOf(parseSyncedLyrics(syncedLyrics)) var lyricsState by mutableStateOf(LyricsState.AutoScrolling) @@ -64,6 +72,13 @@ class SyncedLyricsState( private var waitingToCancelSeekJob: Job? = null init { + launch { + playerStateMonitoryRepository.observeProgressFactor() + .collect { + onPositionChanged(playerStateMonitoryRepository.currentPositionMs) + } + } + launch { lazyListState.interactionSource.interactions.collect { when (it) { @@ -109,7 +124,7 @@ class SyncedLyricsState( ) } - fun onPositionChanged(currentPositionMs: Long) { + private fun onPositionChanged(currentPositionMs: Long) { val state = lyricsState if (state is LyricsState.WaitingSeekingResult) { if (state.requestTimeMs != currentPositionMs) { @@ -133,7 +148,7 @@ class SyncedLyricsState( Napier.d(tag = TAG) { "onSeekTimeClick: $time" } lyricsState = LyricsState.WaitingSeekingResult(time) - onRequestSeekState.value(time) + playControlRepository.seekToTime(time) } } diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/SyncedLyricsView.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/SyncedLyricsView.kt similarity index 55% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/SyncedLyricsView.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/SyncedLyricsView.kt index aaff7577..1d8977e6 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/lyrics/SyncedLyricsView.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/lyrics/SyncedLyricsView.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player.ui.shrinkable.bottom.lyrics +package com.andannn.melodify.ui.components.lyrics import androidx.compose.animation.animateColor import androidx.compose.animation.core.animateFloat @@ -32,7 +32,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.unit.dp import com.andannn.melodify.core.platform.formatTime -import com.andannn.melodify.feature.common.theme.MelodifyTheme +import com.andannn.melodify.ui.common.theme.MelodifyTheme import io.github.aakira.napier.Napier import org.jetbrains.compose.ui.tooling.preview.Preview import kotlin.math.roundToInt @@ -41,17 +41,21 @@ import kotlin.time.Duration.Companion.milliseconds private const val TAG = "SyncedLyricsView" @Composable -fun SyncedLyricsView( - syncedLyric: String, +fun SyncedLyrics( modifier: Modifier = Modifier, - onRequestSeek: (timeMs: Long) -> Unit = {}, - currentPositionMs: Long = 0L, + syncedLyric: String, ) { - val state = rememberSyncedLyricsState( - syncedLyric, - onRequestSeek = onRequestSeek + SyncedLyricsContent( + modifier = modifier, + state = rememberSyncedLyricsState(syncedLyrics = syncedLyric), ) +} +@Composable +private fun SyncedLyricsContent( + modifier: Modifier = Modifier, + state: SyncedLyricsState, +) { val activeIndex by remember { derivedStateOf { when (val lyricsState = state.lyricsState) { @@ -62,10 +66,6 @@ fun SyncedLyricsView( } } - LaunchedEffect(currentPositionMs) { - state.onPositionChanged(currentPositionMs) - } - LaunchedEffect(state.lazyListState, state.currentPlayingIndex, state.lyricsState) { if (state.lyricsState !is LyricsState.AutoScrolling) { return@LaunchedEffect @@ -194,90 +194,3 @@ private fun LyricLine( } } } - -@Preview -@Composable -private fun SyncLyricsViewPreview() { - MelodifyTheme { - Surface { - SyncedLyricsView( - syncedLyric = "[00:00.55] 正しさとは 愚かさとは\n" + - "[00:03.72] それが何か見せつけてやる\n" + - "[00:08.78] \n" + - "[00:16.80] ちっちゃな頃から優等生\n" + - "[00:19.55] 気づいたら大人になっていた\n" + - "[00:21.81] ナイフの様な思考回路\n" + - "[00:24.67] 持ち合わせる訳もなく\n" + - "[00:27.31] でも遊び足りない 何か足りない\n" + - "[00:30.36] 困っちまうこれは誰かのせい\n" + - "[00:32.78] あてもなくただ混乱するエイデイ\n" + - "[00:37.29] それもそっか\n" + - "[00:38.97] 最新の流行は当然の把握\n" + - "[00:41.10] 経済の動向も通勤時チェック\n" + - "[00:43.53] 純情な精神で入社しワーク\n" + - "[00:46.61] 社会人じゃ当然のルールです\n" + - "[00:51.21] はぁ?うっせぇうっせぇうっせぇわ\n" + - "[00:54.38] あなたが思うより健康です\n" + - "[00:56.70] 一切合切凡庸な\n" + - "[00:59.85] あなたじゃ分からないかもね\n" + - "[01:02.85] 嗚呼よく似合う\n" + - "[01:04.65] その可もなく不可もないメロディー\n" + - "[01:08.35] うっせぇうっせぇうっせぇわ\n" + - "[01:10.89] 頭の出来が違うので問題はナシ\n" + - "[01:16.41] \n" + - "[01:25.32] つっても私模範人間\n" + - "[01:28.12] 殴ったりするのはノーセンキュー\n" + - "[01:30.73] だったら言葉の銃口を\n" + - "[01:33.58] その頭に突きつけて撃てば\n" + - "[01:36.43] マジヤバない?止まれやしない\n" + - "[01:38.80] 不平不満垂れて成れの果て\n" + - "[01:41.72] サディスティックに変貌する精神\n" + - "[01:46.03] クソだりぃな\n" + - "[01:47.38] 酒が空いたグラスあれば直ぐに注ぎなさい\n" + - "[01:49.87] 皆がつまみ易いように串外しなさい\n" + - "[01:52.25] 会計や注文は先陣を切る\n" + - "[01:55.39] 不文律最低限のマナーです\n" + - "[02:00.23] はぁ?うっせぇうっせぇうっせぇわ\n" + - "[02:03.17] くせぇ口塞げや限界です\n" + - "[02:05.96] 絶対絶対現代の代弁者は私やろがい\n" + - "[02:11.65] もう見飽きたわ\n" + - "[02:13.43] 二番煎じ言い換えのパロディ\n" + - "[02:16.65] うっせぇうっせぇうっせぇわ\n" + - "[02:19.25] 丸々と肉付いたその顔面にバツ\n" + - "[02:24.69] \n" + - "[02:34.40] うっせぇうっせぇうっせぇわ\n" + - "[02:39.96] うっせぇうっせぇうっせぇわ\n" + - "[02:42.98] 私が俗に言う天才です\n" + - "[02:45.51] うっせぇうっせぇうっせぇわ\n" + - "[02:48.08] あなたが思うより健康です\n" + - "[02:50.55] 一切合切凡庸な\n" + - "[02:53.11] あなたじゃ分からないかもね\n" + - "[02:56.33] 嗚呼つまらねぇ\n" + - "[02:58.11] 何回聞かせるんだそのメモリー\n" + - "[03:01.77] うっせぇうっせぇうっせぇわ\n" + - "[03:04.29] アタシも大概だけど\n" + - "[03:06.29] どうだっていいぜ問題はナシ\n" + - "[03:08.50] ", - currentPositionMs = 1239, - ) - } - } -} - - -@Preview -@Composable -private fun PlayingLinePreview() { - MelodifyTheme(darkTheme = true) { - Surface { - LyricLine( - isPlaying = true, - isActive = true, - lyricsLine = SyncedLyricsLine( - startTimeMs = 12349L, - lyrics = "どうだっていいぜ問題はナシ" - ) - ) - } - } -} \ No newline at end of file diff --git a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/MessageController.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/MessageController.kt similarity index 91% rename from feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/MessageController.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/MessageController.kt index fabc8350..81684c0c 100644 --- a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/MessageController.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/MessageController.kt @@ -1,10 +1,10 @@ -package com.andannn.melodify.feature.message +package com.andannn.melodify.ui.components.message import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarVisuals -import com.andannn.melodify.feature.message.dialog.Dialog -import com.andannn.melodify.feature.message.dialog.InteractionResult -import com.andannn.melodify.feature.message.snackbar.SnackBarMessage +import com.andannn.melodify.ui.components.message.dialog.Dialog +import com.andannn.melodify.ui.components.message.dialog.InteractionResult +import com.andannn.melodify.ui.components.message.snackbar.SnackBarMessage import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel.Factory.BUFFERED diff --git a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/Dialog.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/Dialog.kt similarity index 74% rename from feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/Dialog.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/Dialog.kt index 5d62ceaf..bc3f3104 100644 --- a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/Dialog.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/Dialog.kt @@ -1,15 +1,15 @@ -package com.andannn.melodify.feature.message.dialog +package com.andannn.melodify.ui.components.message.dialog import androidx.compose.ui.window.DialogProperties -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.confirm_delete_playlist_item -import melodify.feature.common.generated.resources.decline -import melodify.feature.common.generated.resources.duplicated_alert_dialog_title -import melodify.feature.common.generated.resources.having_registered_track_in_playlist -import melodify.feature.common.generated.resources.new_playlist_dialog_input_hint -import melodify.feature.common.generated.resources.new_playlist_dialog_title -import melodify.feature.common.generated.resources.ok -import melodify.feature.common.generated.resources.skip_registered_songs +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.confirm_delete_playlist_item +import melodify.ui.common.generated.resources.decline +import melodify.ui.common.generated.resources.duplicated_alert_dialog_title +import melodify.ui.common.generated.resources.having_registered_track_in_playlist +import melodify.ui.common.generated.resources.new_playlist_dialog_input_hint +import melodify.ui.common.generated.resources.new_playlist_dialog_title +import melodify.ui.common.generated.resources.ok +import melodify.ui.common.generated.resources.skip_registered_songs import org.jetbrains.compose.resources.StringResource sealed interface InteractionResult { diff --git a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/AlertMessageDialog.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/ui/AlertMessageDialog.kt similarity index 92% rename from feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/AlertMessageDialog.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/ui/AlertMessageDialog.kt index 663c3f71..6102d4a1 100644 --- a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/AlertMessageDialog.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/ui/AlertMessageDialog.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.message.dialog.ui +package com.andannn.melodify.ui.components.message.dialog.ui import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -15,8 +15,8 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.andannn.melodify.feature.message.dialog.Dialog -import com.andannn.melodify.feature.message.dialog.InteractionResult +import com.andannn.melodify.ui.components.message.dialog.Dialog +import com.andannn.melodify.ui.components.message.dialog.InteractionResult import org.jetbrains.compose.resources.stringResource @Composable diff --git a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/NewPlayListDialog.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/ui/NewPlayListDialog.kt similarity index 93% rename from feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/NewPlayListDialog.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/ui/NewPlayListDialog.kt index 2fd2c0fa..cf20cb96 100644 --- a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/dialog/ui/NewPlayListDialog.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/dialog/ui/NewPlayListDialog.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.message.dialog.ui +package com.andannn.melodify.ui.components.message.dialog.ui import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -23,8 +23,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.andannn.melodify.feature.message.dialog.Dialog -import com.andannn.melodify.feature.message.dialog.InteractionResult +import com.andannn.melodify.ui.components.message.dialog.Dialog +import com.andannn.melodify.ui.components.message.dialog.InteractionResult import org.jetbrains.compose.resources.stringResource @Composable diff --git a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/snackbar/SnackBarMessage.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/snackbar/SnackBarMessage.kt similarity index 82% rename from feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/snackbar/SnackBarMessage.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/snackbar/SnackBarMessage.kt index c46f97ed..d1b717a1 100644 --- a/feature/message/src/commonMain/kotlin/com/andannn/melodify/feature/message/snackbar/SnackBarMessage.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/message/snackbar/SnackBarMessage.kt @@ -1,10 +1,10 @@ -package com.andannn.melodify.feature.message.snackbar +package com.andannn.melodify.ui.components.message.snackbar import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarVisuals -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.add_to_playlist_failed_message -import melodify.feature.common.generated.resources.add_to_playlist_success_message +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.add_to_playlist_failed_message +import melodify.ui.common.generated.resources.add_to_playlist_success_message import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.getString diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaViewModel.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/PlayStateHolder.kt similarity index 53% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaViewModel.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/PlayStateHolder.kt index c8b724d4..3a3b15d9 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/PlayerAreaViewModel.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/PlayStateHolder.kt @@ -1,33 +1,52 @@ -package com.andannn.melodify.feature.player - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +package com.andannn.melodify.ui.components.playcontrol + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import com.andannn.melodify.core.data.Repository import com.andannn.melodify.core.data.model.AudioItemModel -import com.andannn.melodify.core.data.repository.MediaControllerRepository -import com.andannn.melodify.core.data.repository.PlayerStateMonitoryRepository import com.andannn.melodify.core.data.model.PlayMode -import com.andannn.melodify.core.data.model.LyricModel -import com.andannn.melodify.core.data.repository.LyricRepository -import com.andannn.melodify.core.data.repository.PlayListRepository import com.andannn.melodify.core.data.model.next -import com.andannn.melodify.core.data.util.combine6 -import com.andannn.melodify.feature.drawer.DrawerController -import com.andannn.melodify.feature.drawer.DrawerEvent -import com.andannn.melodify.feature.drawer.model.SheetModel +import com.andannn.melodify.core.data.repository.MediaControllerRepository +import com.andannn.melodify.core.data.repository.PlayListRepository +import com.andannn.melodify.core.data.repository.PlayerStateMonitoryRepository +import com.andannn.melodify.ui.common.util.getUiRetainedScope +import com.andannn.melodify.ui.components.drawer.DrawerController +import com.andannn.melodify.ui.components.drawer.DrawerEvent +import com.andannn.melodify.ui.components.drawer.model.SheetModel import io.github.aakira.napier.Napier +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import org.koin.mp.KoinPlatform.getKoin + +@Composable +fun rememberPlayStateHolder( + scope: CoroutineScope = rememberCoroutineScope(), + repository: Repository = getKoin().get(), + drawerController: DrawerController = getUiRetainedScope()?.get() ?: getKoin().get() +) = remember( + scope, + repository, + drawerController +) { + PlayStateHolder( + scope = scope, + playListRepository = repository.playListRepository, + mediaControllerRepository = repository.mediaControllerRepository, + playerStateMonitoryRepository = repository.playerStateMonitoryRepository, + drawerController = drawerController + ) +} sealed interface PlayerUiEvent { data object OnFavoriteButtonClick : PlayerUiEvent @@ -46,104 +65,77 @@ sealed interface PlayerUiEvent { data object OnShuffleButtonClick : PlayerUiEvent - data class OnProgressChange(val progress: Float) : PlayerUiEvent - - data class OnSwapPlayQueue(val from: Int, val to: Int) : PlayerUiEvent - - data class OnDeleteMediaItem(val index: Int) : PlayerUiEvent - - data class OnItemClickInQueue(val item: AudioItemModel) : PlayerUiEvent - - data class OnSeekLyrics(val timeMs: Long) : PlayerUiEvent + data class OnProgressChange( + val progress: Float, + ) : PlayerUiEvent data object OnTimerIconClick : PlayerUiEvent } private const val TAG = "PlayerStateViewModel" -class PlayerStateViewModel( +class PlayStateHolder( + private val scope: CoroutineScope, private val playListRepository: PlayListRepository, private val mediaControllerRepository: MediaControllerRepository, - private val lyricRepository: LyricRepository, private val playerStateMonitoryRepository: PlayerStateMonitoryRepository, - private val drawerController: DrawerController -) : ViewModel() { + private val drawerController: DrawerController, +) { private val interactingMusicItem = playerStateMonitoryRepository.playingMediaStateFlow - private val playStateFlow = combine( - playerStateMonitoryRepository.observeIsPlaying(), - playerStateMonitoryRepository.observeProgressFactor(), - playerStateMonitoryRepository.observePlayMode(), - playerStateMonitoryRepository.observeIsShuffle() - ) { isPlaying, progress, playMode, isShuffle -> - PlayState(isPlaying, progress, playMode, isShuffle) - } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), PlayState()) - - @OptIn(ExperimentalCoroutinesApi::class) - private val lyricFlow: Flow = interactingMusicItem - .filterNotNull() - .flatMapLatest { - lyricRepository.getLyricByMediaIdFlow(it.id) - .map { lyricOrNull -> LyricState.Loaded(lyricOrNull) } - .onStart { emit(LyricState.Loading) } - } + private val playStateFlow = + combine( + playerStateMonitoryRepository.observeIsPlaying(), + playerStateMonitoryRepository.observeProgressFactor(), + playerStateMonitoryRepository.observePlayMode(), + playerStateMonitoryRepository.observeIsShuffle(), + ) { isPlaying, progress, playMode, isShuffle -> + PlayState(isPlaying, progress, playMode, isShuffle) + }.stateIn(scope, SharingStarted.WhileSubscribed(), PlayState()) private val isCountingFlow = mediaControllerRepository.observeIsCounting() - private val playListQueueFlow = playerStateMonitoryRepository.playListQueueStateFlow @OptIn(ExperimentalCoroutinesApi::class) - private val isCurrentMediaFavoriteFlow = interactingMusicItem - .distinctUntilChanged() - .flatMapLatest { - if (it == null) { - return@flatMapLatest flowOf(false) - } - playListRepository.isMediaInFavoritePlayListFlow(it.id) - } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false) + private val isCurrentMediaFavoriteFlow = + interactingMusicItem + .distinctUntilChanged() + .flatMapLatest { + if (it == null) { + return@flatMapLatest flowOf(false) + } + playListRepository.isMediaInFavoritePlayListFlow(it.id) + }.stateIn(scope, SharingStarted.WhileSubscribed(), false) + + var state by mutableStateOf(PlayerUiState.Inactive) + private set - val playerUiStateFlow = - combine6( + private val playerUiStateFlow = + combine( interactingMusicItem, playStateFlow, - playListQueueFlow, - lyricFlow, isCurrentMediaFavoriteFlow, isCountingFlow, - ) { interactingMusicItem, state, playListQueue, lyric, isFavorite, isCounting -> + ) { interactingMusicItem, state, isFavorite, isCounting -> if (interactingMusicItem == null) { PlayerUiState.Inactive } else { PlayerUiState.Active( - lyric = lyric, mediaItem = interactingMusicItem, duration = mediaControllerRepository.currentDuration ?: 0L, playMode = state.playMode, isShuffle = state.isShuffle, isFavorite = isFavorite, - playListQueue = playListQueue, isPlaying = state.isPlaying, progress = state.playProgress, - isCounting = isCounting + isCounting = isCounting, ) } } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), PlayerUiState.Inactive) init { - viewModelScope.launch { - interactingMusicItem - .filterNotNull() - .distinctUntilChanged() - .onEach { audio -> - lyricRepository.tryGetLyricOrIgnore( - mediaId = audio.id, - trackName = audio.name, - artistName = audio.artist, - albumName = audio.album, - ) - } - .collect {} + scope.launch { + playerUiStateFlow.collect { + state = it + } } } @@ -152,11 +144,11 @@ class PlayerStateViewModel( when (event) { PlayerUiEvent.OnFavoriteButtonClick -> { val current = - (playerUiStateFlow.value as? PlayerUiState.Active)?.mediaItem + (state as? PlayerUiState.Active)?.mediaItem Napier.d(tag = TAG) { "currentId: $current" } if (current == null) return - viewModelScope.launch { + scope.launch { onToggleFavoriteState(current) } } @@ -174,40 +166,21 @@ class PlayerStateViewModel( } is PlayerUiEvent.OnOptionIconClick -> { - viewModelScope.launch { + scope.launch { drawerController.onEvent( - DrawerEvent.OnShowBottomDrawer(SheetModel.PlayerOptionSheet(event.mediaItem)) + DrawerEvent.OnShowBottomDrawer(SheetModel.PlayerOptionSheet(event.mediaItem)), ) } } is PlayerUiEvent.OnProgressChange -> { val time = - with((playerUiStateFlow.value as PlayerUiState.Active)) { + with((state as PlayerUiState.Active)) { duration.times(event.progress).toLong() } seekToTime(time) } - is PlayerUiEvent.OnSwapPlayQueue -> { - mediaControllerRepository.moveMediaItem(event.from, event.to) - } - - is PlayerUiEvent.OnItemClickInQueue -> { - val state = playerUiStateFlow.value as PlayerUiState.Active - mediaControllerRepository.seekMediaItem( - mediaItemIndex = state.playListQueue.indexOf(event.item) - ) - } - - is PlayerUiEvent.OnDeleteMediaItem -> { - mediaControllerRepository.removeMediaItem(event.index) - } - - is PlayerUiEvent.OnSeekLyrics -> { - seekToTime(event.timeMs) - } - PlayerUiEvent.OnTimerIconClick -> { drawerController.onEvent(DrawerEvent.OnShowTimerSheet) } @@ -219,9 +192,9 @@ class PlayerStateViewModel( } private fun togglePlayState() { - val state = playerUiStateFlow.value + val state = state if (state is PlayerUiState.Active) { - playerUiStateFlow.value.let { + state.let { if (state.isPlaying) { mediaControllerRepository.pause() } else { @@ -231,7 +204,7 @@ class PlayerStateViewModel( } } - fun next() { + private fun next() { mediaControllerRepository.seekToNext() } @@ -248,26 +221,18 @@ private data class PlayState( val isPlaying: Boolean = false, val playProgress: Float = 0f, val playMode: PlayMode = PlayMode.REPEAT_ALL, - val isShuffle: Boolean = false + val isShuffle: Boolean = false, ) -sealed class LyricState { - data object Loading : LyricState() - - data class Loaded(val lyric: LyricModel?) : LyricState() -} - sealed class PlayerUiState { data object Inactive : PlayerUiState() data class Active( - val lyric: LyricState = LyricState.Loading, val isShuffle: Boolean = false, val duration: Long = 0L, val isFavorite: Boolean = false, val playMode: PlayMode = PlayMode.REPEAT_ALL, val mediaItem: AudioItemModel, - val playListQueue: List, val progress: Float, val isPlaying: Boolean, val isCounting: Boolean, diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.kt new file mode 100644 index 00000000..8239f435 --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.kt @@ -0,0 +1,10 @@ +package com.andannn.melodify.ui.components.playcontrol + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +expect fun Player( + modifier: Modifier = Modifier, + stateHolder: PlayStateHolder = rememberPlayStateHolder() +) \ No newline at end of file diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/PlayerView.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/PlayerView.kt similarity index 56% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/PlayerView.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/PlayerView.kt index 6f8fdc13..703575b3 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/PlayerView.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/PlayerView.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player.ui +package com.andannn.melodify.ui.components.playcontrol.ui import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable @@ -21,18 +21,35 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Size import androidx.compose.ui.platform.LocalDensity -import com.andannn.melodify.feature.common.component.AndroidBackHandler -import com.andannn.melodify.feature.common.dynamic_theming.DynamicThemePrimaryColorsFromImage -import com.andannn.melodify.feature.common.theme.MinContrastOfPrimaryVsSurface -import com.andannn.melodify.feature.common.dynamic_theming.rememberDominantColorState -import com.andannn.melodify.feature.common.util.contrastAgainst -import com.andannn.melodify.feature.player.PlayerUiEvent -import com.andannn.melodify.feature.player.PlayerUiState -import com.andannn.melodify.feature.player.ui.shrinkable.FlexiblePlayerLayout +import com.andannn.melodify.ui.common.widgets.AndroidBackHandler +import com.andannn.melodify.ui.common.dynamic_theming.DynamicThemePrimaryColorsFromImage +import com.andannn.melodify.ui.common.dynamic_theming.rememberDominantColorState +import com.andannn.melodify.ui.common.theme.MinContrastOfPrimaryVsSurface +import com.andannn.melodify.ui.common.util.contrastAgainst +import com.andannn.melodify.ui.components.playcontrol.PlayStateHolder +import com.andannn.melodify.ui.components.playcontrol.PlayerUiEvent +import com.andannn.melodify.ui.components.playcontrol.PlayerUiState +import com.andannn.melodify.ui.components.playcontrol.ui.shrinkable.FlexiblePlayerLayout +import com.andannn.melodify.ui.components.playcontrol.rememberPlayStateHolder + +@Composable +fun PlayerAreaView( + modifier: Modifier = Modifier, + stateHolder: PlayStateHolder = rememberPlayStateHolder() +) { + val state = stateHolder.state + if (state is PlayerUiState.Active) { + PlayerViewContent( + state = state, + onEvent = stateHolder::onEvent, + modifier = modifier + ) + } +} @OptIn(ExperimentalFoundationApi::class) @Composable -internal fun PlayerView( +internal fun PlayerViewContent( state: PlayerUiState.Active, modifier: Modifier = Modifier, onEvent: (PlayerUiEvent) -> Unit, @@ -44,22 +61,24 @@ internal fun PlayerView( BoxWithConstraints( modifier = modifier.fillMaxSize(), ) { - val layoutState: PlayerViewState = rememberPlayerViewState( - screenSize = Size( - width = constraints.maxWidth.toFloat(), - height = constraints.maxHeight.toFloat() - ), - navigationBarHeightPx = navigationBarHeight, - statusBarHeightPx = statusBarHeight, - density = density - ) + val layoutState: PlayerViewState = + rememberPlayerViewState( + screenSize = + Size( + width = constraints.maxWidth.toFloat(), + height = constraints.maxHeight.toFloat(), + ), + navigationBarHeightPx = navigationBarHeight, + statusBarHeightPx = statusBarHeight, + density = density, + ) val isPlayerDraggable by - remember { - derivedStateOf { - !layoutState.isBottomSheetExpanding + remember { + derivedStateOf { + !layoutState.isBottomSheetExpanding + } } - } AndroidBackHandler( enabled = layoutState.playerState == PlayerState.Expand, @@ -85,35 +104,31 @@ internal fun PlayerView( FlexiblePlayerLayout( modifier = - Modifier - .height(with(LocalDensity.current) { layoutState.playerExpandState.offset.toDp() }) - .align(Alignment.BottomCenter) - .anchoredDraggable( - layoutState.playerExpandState, - enabled = isPlayerDraggable, - orientation = Orientation.Vertical, - reverseDirection = true, - ) - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() }, - enabled = layoutState.playerState == PlayerState.Shrink, - onClick = layoutState::expandPlayerLayout, - ), + Modifier + .height(with(LocalDensity.current) { layoutState.playerExpandState.offset.toDp() }) + .align(Alignment.BottomCenter) + .anchoredDraggable( + layoutState.playerExpandState, + enabled = isPlayerDraggable, + orientation = Orientation.Vertical, + reverseDirection = true, + ).clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + enabled = layoutState.playerState == PlayerState.Shrink, + onClick = layoutState::expandPlayerLayout, + ), layoutState = layoutState, coverUri = state.mediaItem.artWorkUri, playMode = state.playMode, isShuffle = state.isShuffle, isPlaying = state.isPlaying, isFavorite = state.isFavorite, - playListQueue = state.playListQueue, activeMediaItem = state.mediaItem, isCounting = state.isCounting, title = state.mediaItem.name, artist = state.mediaItem.artist, progress = state.progress, - duration = state.duration, - lyricState = state.lyric, onShrinkButtonClick = layoutState::shrinkPlayerLayout, onEvent = onEvent, ) diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/PlayerViewState.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/PlayerViewState.kt similarity index 68% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/PlayerViewState.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/PlayerViewState.kt index 9e216dca..5af9bf3d 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/PlayerViewState.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/PlayerViewState.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player.ui +package com.andannn.melodify.ui.components.playcontrol.ui import androidx.compose.animation.core.exponentialDecay import androidx.compose.animation.core.spring @@ -17,10 +17,10 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp -import com.andannn.melodify.feature.player.ui.shrinkable.BottomSheetDragAreaHeight -import com.andannn.melodify.feature.player.ui.shrinkable.MinFadeoutWithExpandAreaPaddingTop -import com.andannn.melodify.feature.player.ui.shrinkable.MinImagePaddingStart -import com.andannn.melodify.feature.player.ui.shrinkable.MinImagePaddingTop +import com.andannn.melodify.ui.components.playcontrol.ui.shrinkable.BottomSheetDragAreaHeight +import com.andannn.melodify.ui.components.playcontrol.ui.shrinkable.MinFadeoutWithExpandAreaPaddingTop +import com.andannn.melodify.ui.components.playcontrol.ui.shrinkable.MinImagePaddingStart +import com.andannn.melodify.ui.components.playcontrol.ui.shrinkable.MinImagePaddingTop import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -37,20 +37,20 @@ internal fun rememberPlayerViewState( navigationBarHeightPx: Int, statusBarHeightPx: Int, density: Density, - animaScope: CoroutineScope = rememberCoroutineScope() + animaScope: CoroutineScope = rememberCoroutineScope(), ) = remember( screenSize, navigationBarHeightPx, statusBarHeightPx, density, - animaScope + animaScope, ) { PlayerViewState( screenSize = screenSize, navigationBarHeightPx = navigationBarHeightPx, statusBarHeightPx = statusBarHeightPx.toFloat(), density = density, - animaScope = animaScope + animaScope = animaScope, ) } @@ -72,10 +72,11 @@ PlayerViewState( val bottomSheetState = AnchoredDraggableState( initialValue = BottomSheetState.Shrink, - anchors = DraggableAnchors { - BottomSheetState.Shrink at bottomSheetHeight - bottomSheetDragAreaHeightPx - BottomSheetState.Expand at 0f - }, + anchors = + DraggableAnchors { + BottomSheetState.Shrink at bottomSheetHeight - bottomSheetDragAreaHeightPx + BottomSheetState.Expand at 0f + }, positionalThreshold = { 300f }, velocityThreshold = { 400f }, snapAnimationSpec = spring(), @@ -95,10 +96,11 @@ PlayerViewState( val playerExpandState = AnchoredDraggableState( initialValue = PlayerState.Shrink, - anchors = DraggableAnchors { - PlayerState.Shrink at shrinkPlayerHeight - PlayerState.Expand at screenSize.height - }, + anchors = + DraggableAnchors { + PlayerState.Shrink at shrinkPlayerHeight + PlayerState.Expand at screenSize.height + }, positionalThreshold = { with(density) { 26.dp.toPx() } }, velocityThreshold = { with(density) { 20.dp.toPx() } }, snapAnimationSpec = spring(), @@ -117,53 +119,56 @@ PlayerViewState( } val playerState: PlayerState by - derivedStateOf { - playerExpandState.currentValue - } + derivedStateOf { + playerExpandState.currentValue + } // - when bottom sheet is expanding: 0f when sheet fully expanded, 1f when sheet shrink. // - when player is expanding: 0f when player fully expanded, 1f when player shrink. val imageTransactionFactor by - derivedStateOf { - if (isBottomSheetExpanding) bottomSheetOffsetFactor else playerExpandFactor - } + derivedStateOf { + if (isBottomSheetExpanding) bottomSheetOffsetFactor else playerExpandFactor + } private val maxImageSize = screenSize.height.toDp().div(2.5f) private val imagePaddingTopWhenFillExpandDp = screenSize.height.toDp().div(8) private val imagePaddingHorizontalWhenFillExpandDp = - screenSize.width.toDp().minus(maxImageSize).div(2) + screenSize.width + .toDp() + .minus(maxImageSize) + .div(2) val imageSizeDp: Dp by - derivedStateOf { - lerp(start = MinImageSize, stop = maxImageSize, imageTransactionFactor) - } + derivedStateOf { + lerp(start = MinImageSize, stop = maxImageSize, imageTransactionFactor) + } val imagePaddingTopDp: Dp by - derivedStateOf { - lerp( - start = MinImagePaddingTop + if (isBottomSheetExpanding) statusBarHeightPx.toDp() else 0.dp, - stop = imagePaddingTopWhenFillExpandDp, - imageTransactionFactor, - ) - } + derivedStateOf { + lerp( + start = MinImagePaddingTop + if (isBottomSheetExpanding) statusBarHeightPx.toDp() else 0.dp, + stop = imagePaddingTopWhenFillExpandDp, + imageTransactionFactor, + ) + } val imagePaddingStartDp: Dp by - derivedStateOf { - lerp( - start = MinImagePaddingStart, - stop = imagePaddingHorizontalWhenFillExpandDp, - imageTransactionFactor - ) - } + derivedStateOf { + lerp( + start = MinImagePaddingStart, + stop = imagePaddingHorizontalWhenFillExpandDp, + imageTransactionFactor, + ) + } val miniPlayerPaddingTopDp: Dp by - derivedStateOf { - lerp( - start = MinFadeoutWithExpandAreaPaddingTop + if (isBottomSheetExpanding) statusBarHeightPx.toDp() else 0.dp, - stop = 0.dp, - imageTransactionFactor, - ) - } + derivedStateOf { + lerp( + start = MinFadeoutWithExpandAreaPaddingTop + if (isBottomSheetExpanding) statusBarHeightPx.toDp() else 0.dp, + stop = 0.dp, + imageTransactionFactor, + ) + } fun shrinkPlayerLayout() { animaScope.launch { diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/FlexiblePlayerLayout.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/FlexiblePlayerLayout.kt similarity index 70% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/FlexiblePlayerLayout.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/FlexiblePlayerLayout.kt index a21ca5d9..66bf39f7 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/FlexiblePlayerLayout.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/FlexiblePlayerLayout.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player.ui.shrinkable +package com.andannn.melodify.ui.components.playcontrol.ui.shrinkable import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -34,17 +34,14 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.andannn.melodify.core.data.model.AudioItemModel -import com.andannn.melodify.feature.common.component.CircleBorderImage import com.andannn.melodify.core.data.model.PlayMode -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import com.andannn.melodify.feature.common.util.verticalGradientScrim -import com.andannn.melodify.feature.player.LyricState -import com.andannn.melodify.feature.player.ui.shrinkable.bottom.PlayerBottomSheetView -import com.andannn.melodify.feature.player.PlayerUiEvent -import com.andannn.melodify.feature.player.ui.MinImageSize -import com.andannn.melodify.feature.player.ui.PlayerViewState -import com.andannn.melodify.feature.player.ui.shrinkable.header.PlayerHeader -import kotlinx.collections.immutable.toImmutableList +import com.andannn.melodify.ui.common.widgets.CircleBorderImage +import com.andannn.melodify.ui.common.theme.MelodifyTheme +import com.andannn.melodify.ui.common.util.verticalGradientScrim +import com.andannn.melodify.ui.components.playcontrol.PlayerUiEvent +import com.andannn.melodify.ui.components.playcontrol.ui.MinImageSize +import com.andannn.melodify.ui.components.playcontrol.ui.PlayerViewState +import com.andannn.melodify.ui.components.playcontrol.ui.shrinkable.bottom.PlayerBottomSheetView import org.jetbrains.compose.ui.tooling.preview.Preview val MinImagePaddingTop = 5.dp @@ -62,10 +59,8 @@ internal fun FlexiblePlayerLayout( layoutState: PlayerViewState, coverUri: String, activeMediaItem: AudioItemModel, - playListQueue: List, modifier: Modifier = Modifier, playMode: PlayMode = PlayMode.REPEAT_ALL, - lyricState: LyricState = LyricState.Loading, isShuffle: Boolean = false, isPlaying: Boolean = false, isFavorite: Boolean = false, @@ -73,7 +68,6 @@ internal fun FlexiblePlayerLayout( title: String = "", artist: String = "", progress: Float = 1f, - duration: Long = 0L, onEvent: (PlayerUiEvent) -> Unit = {}, onShrinkButtonClick: () -> Unit = {}, ) { @@ -85,7 +79,7 @@ internal fun FlexiblePlayerLayout( Surface( modifier = - modifier.fillMaxWidth(), + modifier.fillMaxWidth(), shadowElevation = 10.dp, ) { val primaryColor = MaterialTheme.colorScheme.primary @@ -102,10 +96,10 @@ internal fun FlexiblePlayerLayout( Box( modifier = - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)) - .then(backGroundModifier), + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)) + .then(backGroundModifier), ) { val fadeInAreaAlpha by remember { derivedStateOf { @@ -119,15 +113,14 @@ internal fun FlexiblePlayerLayout( } MiniPlayerLayout( modifier = - Modifier - .graphicsLayer { - alpha = fadeoutAreaAlpha - } - .fillMaxWidth() - .padding( - top = layoutState.miniPlayerPaddingTopDp, - start = MinImagePaddingStart * 2 + MinImageSize, - ), + Modifier + .graphicsLayer { + alpha = fadeoutAreaAlpha + }.fillMaxWidth() + .padding( + top = layoutState.miniPlayerPaddingTopDp, + start = MinImagePaddingStart * 2 + MinImageSize, + ), enabled = fadeoutAreaAlpha == 1f, title = title, artist = artist, @@ -139,11 +132,11 @@ internal fun FlexiblePlayerLayout( if (fadeInAreaAlpha != 0f) { PlayerHeader( modifier = - Modifier - .padding(top = statusBarHeight) - .graphicsLayer { - alpha = fadeInAreaAlpha - }, + Modifier + .padding(top = statusBarHeight) + .graphicsLayer { + alpha = fadeInAreaAlpha + }, showTimerIcon = isCounting, onShrinkButtonClick = onShrinkButtonClick, onTimerIconClick = { @@ -157,30 +150,29 @@ internal fun FlexiblePlayerLayout( CircleBorderImage( modifier = - Modifier - .padding( - top = layoutState.imagePaddingTopDp, - start = layoutState.imagePaddingStartDp - ) - .width(layoutState.imageSizeDp) - .aspectRatio(1f), + Modifier + .padding( + top = layoutState.imagePaddingTopDp, + start = layoutState.imagePaddingStartDp, + ).width(layoutState.imageSizeDp) + .aspectRatio(1f), model = coverUriState.value, ) Column( modifier = - Modifier.fillMaxSize() + Modifier.fillMaxSize(), ) { Spacer(modifier = Modifier.height(layoutState.imagePaddingTopDp + layoutState.imageSizeDp)) LargePlayerControlArea( modifier = - Modifier - .fillMaxWidth() - .weight(1f) - .graphicsLayer { - alpha = fadeInAreaAlpha - }, + Modifier + .fillMaxWidth() + .weight(1f) + .graphicsLayer { + alpha = fadeInAreaAlpha + }, isPlaying = isPlaying, playMode = playMode, enable = layoutState.isFullExpanded, @@ -195,7 +187,7 @@ internal fun FlexiblePlayerLayout( AnimatedVisibility( modifier = - Modifier.align(Alignment.BottomCenter), + Modifier.align(Alignment.BottomCenter), enter = fadeIn(), exit = fadeOut(), visible = layoutState.isFullExpanded, @@ -204,35 +196,31 @@ internal fun FlexiblePlayerLayout( modifier = Modifier.height(with(LocalDensity.current) { layoutState.bottomSheetHeight.toDp() }), state = layoutState.bottomSheetState, activeMediaItem = activeMediaItem, - playListQueue = playListQueue.toImmutableList(), - lyricState = lyricState, - currentPositionMs = (progress * duration).toLong(), - onEvent = onEvent, onRequestExpandSheet = { layoutState.expandBottomSheet() - } + }, ) } if (!layoutState.isPlayerExpanding) { Spacer( modifier = - Modifier - .fillMaxWidth(fraction = progress) - .align(Alignment.BottomStart) - .padding(bottom = with(LocalDensity.current) { layoutState.navigationBarHeightPx.toDp() }) - .height(3.dp) - .background( - brush = - Brush.horizontalGradient( - colors = - listOf( - MaterialTheme.colorScheme.tertiaryContainer, - MaterialTheme.colorScheme.inversePrimary, - MaterialTheme.colorScheme.primary, - ), + Modifier + .fillMaxWidth(fraction = progress) + .align(Alignment.BottomStart) + .padding(bottom = with(LocalDensity.current) { layoutState.navigationBarHeightPx.toDp() }) + .height(3.dp) + .background( + brush = + Brush.horizontalGradient( + colors = + listOf( + MaterialTheme.colorScheme.tertiaryContainer, + MaterialTheme.colorScheme.inversePrimary, + MaterialTheme.colorScheme.primary, + ), + ), ), - ), ) } } @@ -255,7 +243,6 @@ private fun FlexiblePlayerLayoutExpandPreview() { layoutState = layoutState, coverUri = "", activeMediaItem = AudioItemModel.DEFAULT, - playListQueue = emptyList(), modifier = Modifier.height(870.dp), isPlaying = true, isFavorite = true, @@ -281,7 +268,6 @@ private fun FlexiblePlayerLayoutShrinkPreview() { layoutState = layoutState, coverUri = "", activeMediaItem = AudioItemModel.DEFAULT, - playListQueue = emptyList(), modifier = Modifier.height(70.dp), title = "Song name", artist = "Artist name", diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/LargePlayerControlArea.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/LargePlayerControlArea.kt similarity index 91% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/LargePlayerControlArea.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/LargePlayerControlArea.kt index f2a328b4..592987c0 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/LargePlayerControlArea.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/LargePlayerControlArea.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player.ui.shrinkable +package com.andannn.melodify.ui.components.playcontrol.ui.shrinkable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -24,13 +24,13 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.andannn.melodify.feature.common.component.AutoResizedText -import com.andannn.melodify.feature.common.component.SmpMainIconButton -import com.andannn.melodify.feature.common.component.SmpSubIconButton +import com.andannn.melodify.ui.common.widgets.AutoResizedText +import com.andannn.melodify.ui.common.widgets.SmpMainIconButton +import com.andannn.melodify.ui.common.widgets.SmpSubIconButton import com.andannn.melodify.core.data.model.PlayMode -import com.andannn.melodify.feature.common.theme.MelodifyTheme -import com.andannn.melodify.feature.player.PlayerUiEvent -import com.andannn.melodify.feature.player.util.getIcon +import com.andannn.melodify.ui.common.theme.MelodifyTheme +import com.andannn.melodify.ui.common.util.getIcon +import com.andannn.melodify.ui.components.playcontrol.PlayerUiEvent import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/MiniPlayerLayout.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/MiniPlayerLayout.kt similarity index 91% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/MiniPlayerLayout.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/MiniPlayerLayout.kt index 7e88dc12..cb617676 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/MiniPlayerLayout.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/MiniPlayerLayout.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player.ui.shrinkable +package com.andannn.melodify.ui.components.playcontrol.ui.shrinkable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Favorite -import androidx.compose.material.icons.rounded.FavoriteBorder import androidx.compose.material.icons.rounded.Pause import androidx.compose.material.icons.rounded.PlayArrow import androidx.compose.material.icons.rounded.SkipPrevious @@ -24,11 +22,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.andannn.melodify.feature.common.component.FavoriteIconButton -import com.andannn.melodify.feature.player.PlayerUiEvent +import com.andannn.melodify.ui.common.widgets.FavoriteIconButton +import com.andannn.melodify.ui.components.playcontrol.PlayerUiEvent @Composable internal fun MiniPlayerLayout( diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/header/PlayerHeader.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/PlayerHeader.kt similarity index 96% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/header/PlayerHeader.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/PlayerHeader.kt index 897c0a24..35482553 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/header/PlayerHeader.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/PlayerHeader.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player.ui.shrinkable.header +package com.andannn.melodify.ui.components.playcontrol.ui.shrinkable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/PlayerBottomSheetView.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/bottom/PlayerBottomSheetView.kt similarity index 53% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/PlayerBottomSheetView.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/bottom/PlayerBottomSheetView.kt index 345232c8..471f0bba 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/PlayerBottomSheetView.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/bottom/PlayerBottomSheetView.kt @@ -1,10 +1,7 @@ -package com.andannn.melodify.feature.player.ui.shrinkable.bottom +package com.andannn.melodify.ui.components.playcontrol.ui.shrinkable.bottom -import androidx.compose.animation.core.exponentialDecay -import androidx.compose.animation.core.spring import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.AnchoredDraggableState -import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.animateTo @@ -37,25 +34,21 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp -import com.andannn.melodify.feature.common.theme.MelodifyTheme import com.andannn.melodify.core.data.model.AudioItemModel -import com.andannn.melodify.feature.common.component.AndroidBackHandler -import com.andannn.melodify.feature.player.ui.BottomSheetState -import com.andannn.melodify.feature.player.LyricState -import com.andannn.melodify.feature.player.PlayerUiEvent -import com.andannn.melodify.feature.player.ui.shrinkable.bottom.lyrics.LyricsView -import com.andannn.melodify.feature.player.ui.shrinkable.bottom.queue.PlayQueue -import com.andannn.melodify.feature.player.util.getLabel -import io.github.aakira.napier.Napier +import com.andannn.melodify.ui.common.widgets.AndroidBackHandler +import com.andannn.melodify.ui.components.lyrics.LyricsView +import com.andannn.melodify.ui.components.queue.PlayQueue +import com.andannn.melodify.ui.components.playcontrol.ui.BottomSheetState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.lyrics +import melodify.ui.common.generated.resources.play_queue import org.jetbrains.compose.resources.stringResource -import org.jetbrains.compose.ui.tooling.preview.Preview import kotlin.math.roundToInt private const val TAG = "PlayQueueView" @@ -64,14 +57,10 @@ private const val TAG = "PlayQueueView" @Composable internal fun PlayerBottomSheetView( state: AnchoredDraggableState, - playListQueue: ImmutableList, activeMediaItem: AudioItemModel, modifier: Modifier = Modifier, - currentPositionMs: Long = 0L, - lyricState: LyricState = LyricState.Loading, scope: CoroutineScope = rememberCoroutineScope(), - onEvent: (PlayerUiEvent) -> Unit = {}, - onRequestExpandSheet: () -> Unit = {} + onRequestExpandSheet: () -> Unit = {}, ) { val shrinkOffset = state.anchors.positionOf(BottomSheetState.Shrink) val expandOffset = state.anchors.positionOf(BottomSheetState.Expand) @@ -98,27 +87,28 @@ internal fun PlayerBottomSheetView( Surface( modifier = - modifier - .fillMaxSize() - .offset { - IntOffset( - 0, - state - .requireOffset() - .roundToInt(), - ) - }, + modifier + .fillMaxSize() + .offset { + IntOffset( + 0, + state + .requireOffset() + .roundToInt(), + ) + }, color = MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = expandFactor), - shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp) + shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp), ) { Column( - modifier = Modifier.fillMaxHeight() + modifier = Modifier.fillMaxHeight(), ) { Spacer(modifier = Modifier.height(20.dp)) TabBar( - modifier = Modifier - .padding(horizontal = 16.dp) - .anchoredDraggable(state, orientation = Orientation.Vertical), + modifier = + Modifier + .padding(horizontal = 16.dp) + .anchoredDraggable(state, orientation = Orientation.Vertical), isExpand = isExpand, items = sheetState.sheetItems.toImmutableList(), selectedTabIndex = sheetState.selectedIndex, @@ -128,47 +118,31 @@ internal fun PlayerBottomSheetView( onItemClick = { sheetState.onSelectItem(it) onRequestExpandSheet() - } + }, ) Spacer(modifier = Modifier.height(3.dp)) - Surface( - modifier = - Modifier - .weight(1f) - .graphicsLayer { alpha = expandFactor } - .padding(horizontal = 8.dp), - color = MaterialTheme.colorScheme.surfaceContainerHighest, - shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp) - ) { - when (sheetState.selectedTab) { - SheetTab.NEXT_SONG -> { - PlayQueue( - onSwapFinished = { from, to -> - Napier.d(tag = TAG) { "PlayQueueView: drag stopped from $from to $to" } - onEvent(PlayerUiEvent.OnSwapPlayQueue(from, to)) - }, - onDeleteFinished = { - Napier.d(tag = TAG) { "onDeleteFinished $it" } - onEvent(PlayerUiEvent.OnDeleteMediaItem(it)) - }, - onItemClick = { - onEvent(PlayerUiEvent.OnItemClickInQueue(it)) - }, - activeMediaItem = activeMediaItem, - playListQueue = playListQueue, - ) - } - - SheetTab.LYRICS -> { - LyricsView( - modifier = Modifier, - currentPositionMs = currentPositionMs, - lyricState = lyricState, - onRequestSeek = { - onEvent(PlayerUiEvent.OnSeekLyrics(it)) - } - ) + if (expandFactor != 0f) { + Surface( + modifier = + Modifier + .weight(1f) + .graphicsLayer { alpha = expandFactor } + .padding(horizontal = 8.dp), + color = MaterialTheme.colorScheme.surfaceContainerHighest, + shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp), + ) { + when (sheetState.selectedTab) { + SheetTab.NEXT_SONG -> { + PlayQueue() + } + + SheetTab.LYRICS -> { + LyricsView( + modifier = Modifier, + source = activeMediaItem, + ) + } } } } @@ -187,20 +161,21 @@ private fun TabBar( selectedTabIndex: Int, onItemPressed: (SheetTab) -> Unit, modifier: Modifier = Modifier, - onItemClick: (SheetTab) -> Unit + onItemClick: (SheetTab) -> Unit, ) { val defaultIndicator = @Composable { tabPositions: List -> if (selectedTabIndex < tabPositions.size) { TabRowDefaults.SecondaryIndicator( - Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]) + Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]), ) } } TabRow( - modifier = modifier - .fillMaxWidth(), + modifier = + modifier + .fillMaxWidth(), selectedTabIndex = selectedTabIndex, containerColor = Color.Transparent, indicator = if (isExpand) defaultIndicator else emptyIndicator, @@ -222,8 +197,11 @@ private fun TabBar( Tab( selected = index == selectedTabIndex, selectedContentColor = - if (isExpand) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.onSurfaceVariant, + if (isExpand) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant, text = @Composable { Text( @@ -233,57 +211,13 @@ private fun TabBar( interactionSource = source, onClick = { onItemClick(item) - } + }, ) } } } -@ExperimentalFoundationApi -@Preview -@Composable -private fun BottomPlayQueueSheetPreview() { - MelodifyTheme { - val density = LocalDensity.current - val anchors = - with(LocalDensity.current) { - DraggableAnchors { - BottomSheetState.Shrink at 120.dp.toPx() - BottomSheetState.Expand at 0f - } - } - - val state = - remember { - AnchoredDraggableState( - initialValue = BottomSheetState.Expand, - anchors = anchors, - positionalThreshold = { with(density) { 26.dp.toPx() } }, - velocityThreshold = { with(density) { 20.dp.toPx() } }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay(), - ) - } - - - PlayerBottomSheetView( - state = state, - playListQueue = listOf( - AudioItemModel( - id = "0", - name = "Song 1", - modifiedDate = 0, - album = "Album 1", - albumId = "0", - artist = "Artist 1", - artistId = "0", - cdTrackNumber = 1, - discNumber = 0, - artWorkUri = "", - source = "" - ) - ).toImmutableList(), - activeMediaItem = AudioItemModel.DEFAULT, - ) - } +private fun SheetTab.getLabel() = when(this) { + SheetTab.NEXT_SONG -> Res.string.play_queue + SheetTab.LYRICS -> Res.string.lyrics } diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/PlayerBottomSheetViewState.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/bottom/PlayerBottomSheetViewState.kt similarity index 77% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/PlayerBottomSheetViewState.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/bottom/PlayerBottomSheetViewState.kt index cb931ff8..b37b0d03 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/PlayerBottomSheetViewState.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/playcontrol/ui/shrinkable/bottom/PlayerBottomSheetViewState.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player.ui.shrinkable.bottom +package com.andannn.melodify.ui.components.playcontrol.ui.shrinkable.bottom import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -12,9 +12,10 @@ enum class SheetTab { } @Composable -internal fun rememberPlayerBottomSheetState() = remember { - PlayerBottomSheetState() -} +internal fun rememberPlayerBottomSheetState() = + remember { + PlayerBottomSheetState() + } internal class PlayerBottomSheetState { var sheetItems by mutableStateOf(SheetTab.entries.toTypedArray()) @@ -27,4 +28,4 @@ internal class PlayerBottomSheetState { fun onSelectItem(item: SheetTab) { selectedTab = item } -} \ No newline at end of file +} diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/queue/PlayQueueState.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/queue/PlayQueueState.kt new file mode 100644 index 00000000..aca700eb --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/queue/PlayQueueState.kt @@ -0,0 +1,72 @@ +package com.andannn.melodify.ui.components.queue + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import com.andannn.melodify.core.data.Repository +import com.andannn.melodify.core.data.model.AudioItemModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.launch +import org.koin.mp.KoinPlatform.getKoin + +@Composable +fun rememberPlayQueueStateHolder( + scope: CoroutineScope = rememberCoroutineScope(), + repository: Repository = getKoin().get(), +) = remember( + scope, + repository, +) { + PlayQueueStateHolder( + scope = scope, + repository = repository, + ) +} + +class PlayQueueStateHolder( + scope: CoroutineScope, + private val repository: Repository, +) { + var playListQueue: List by mutableStateOf(value = emptyList()) + private set + + var interactingMusicItem: AudioItemModel by mutableStateOf(AudioItemModel.DEFAULT) + private set + + init { + scope.launch { + repository.playerStateMonitoryRepository.playListQueueStateFlow.collect { + playListQueue = it + } + } + + scope.launch { + repository.playerStateMonitoryRepository.playingMediaStateFlow + .filterNotNull() + .collect { + interactingMusicItem = it + } + } + } + + fun onItemClick(item: AudioItemModel) { + repository.mediaControllerRepository.seekMediaItem( + mediaItemIndex = playListQueue.indexOf(item), + ) + } + + fun onSwapFinished( + from: Int, + to: Int, + ) { + repository.mediaControllerRepository.moveMediaItem(from, to) + } + + fun onDeleteFinished(deleted: Int) { + repository.mediaControllerRepository.removeMediaItem(deleted) + } +} diff --git a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/queue/PlayQueueView.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/queue/PlayQueueView.kt similarity index 67% rename from feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/queue/PlayQueueView.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/queue/PlayQueueView.kt index 67d2e860..913cd9eb 100644 --- a/feature/player/src/commonMain/kotlin/com/andannn/melodify/feature/player/ui/shrinkable/bottom/queue/PlayQueueView.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/queue/PlayQueueView.kt @@ -1,6 +1,5 @@ -package com.andannn.melodify.feature.player.ui.shrinkable.bottom.queue +package com.andannn.melodify.ui.components.queue -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn @@ -16,46 +15,63 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import com.andannn.melodify.feature.common.component.ListTileItemView import com.andannn.melodify.core.data.model.AudioItemModel -import com.andannn.melodify.feature.common.component.ActionType -import com.andannn.melodify.feature.common.util.rememberSwapListState +import com.andannn.melodify.ui.common.widgets.ActionType +import com.andannn.melodify.ui.common.widgets.ListTileItemView +import com.andannn.melodify.ui.common.util.rememberSwapListState import io.github.aakira.napier.Napier import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import sh.calvin.reorderable.ReorderableCollectionItemScope import sh.calvin.reorderable.ReorderableItem private const val TAG = "PlayQueueView" -@OptIn(ExperimentalFoundationApi::class) @Composable -internal fun PlayQueue( +fun PlayQueue( + modifier: Modifier = Modifier, + stateHolder: PlayQueueStateHolder = rememberPlayQueueStateHolder(), +) { + PlayQueueContent( + modifier = modifier, + onItemClick = stateHolder::onItemClick, + onSwapFinished = stateHolder::onSwapFinished, + onDeleteFinished = stateHolder::onDeleteFinished, + playListQueue = stateHolder.playListQueue.toImmutableList(), + activeMediaItem = stateHolder.interactingMusicItem, + ) +} + +@Composable +private fun PlayQueueContent( onItemClick: (AudioItemModel) -> Unit, onSwapFinished: (from: Int, to: Int) -> Unit, onDeleteFinished: (Int) -> Unit, playListQueue: ImmutableList, activeMediaItem: AudioItemModel, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { - val playQueueState = rememberSwapListState( - onSwapFinished = { from, to, _ -> - Napier.d(tag = TAG) { "PlayQueueView: drag stopped from $from to $to" } - onSwapFinished(from, to) - }, - onDeleteFinished = { index, _ -> - Napier.d(tag = TAG) { "onDeleteFinished $index" } - onDeleteFinished(index) - } - ) + val playQueueState = + rememberSwapListState( + onSwapFinished = { from, to, _ -> + Napier.d(tag = TAG) { "PlayQueueView: drag stopped from $from to $to" } + onSwapFinished(from, to) + }, + onDeleteFinished = { index, _ -> + Napier.d(tag = TAG) { "onDeleteFinished $index" } + onDeleteFinished(index) + }, + ) LaunchedEffect(playListQueue) { playQueueState.onApplyNewList(playListQueue) } LazyColumn( - modifier = modifier - .fillMaxWidth(), - state = playQueueState.lazyListState + modifier = + modifier + .fillMaxWidth(), + state = playQueueState.lazyListState, ) { items( items = playQueueState.itemList, @@ -63,7 +79,7 @@ internal fun PlayQueue( ) { item -> ReorderableItem( state = playQueueState.reorderableLazyListState, - key = item.hashCode() + key = item.hashCode(), ) { _ -> QueueItem( item = item, @@ -76,7 +92,7 @@ internal fun PlayQueue( }, onDismissFinish = { playQueueState.onDeleteItem(item) - } + }, ) } } @@ -90,7 +106,7 @@ private fun ReorderableCollectionItemScope.QueueItem( modifier: Modifier = Modifier, onSwapFinish: () -> Unit = {}, onClick: () -> Unit = {}, - onDismissFinish: () -> Unit = {} + onDismissFinish: () -> Unit = {}, ) { val dismissState = rememberSwipeToDismissBoxState() var dismissed by remember { @@ -110,13 +126,14 @@ private fun ReorderableCollectionItemScope.QueueItem( state = dismissState, backgroundContent = { Spacer(modifier = Modifier) - } + }, ) { ListTileItemView( modifier = modifier, - swapIconModifier = Modifier.draggableHandle( - onDragStopped = onSwapFinish - ), + swapIconModifier = + Modifier.draggableHandle( + onDragStopped = onSwapFinish, + ), isActive = isActive, defaultColor = MaterialTheme.colorScheme.surfaceContainerHighest, albumArtUri = item.artWorkUri, diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tab/ReactiveTab.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tab/ReactiveTab.kt new file mode 100644 index 00000000..6828dc25 --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tab/ReactiveTab.kt @@ -0,0 +1,58 @@ +package com.andannn.melodify.ui.components.tab + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Modifier +import com.andannn.melodify.ui.common.util.getCategoryResource + +@Composable +fun ReactiveTab( + modifier: Modifier = Modifier, + stateHolder: TabUiStateHolder = rememberTabUiStateHolder() +) { + val state = stateHolder.state + val tabs by rememberUpdatedState(state.customTabList) + val selectedIndex by rememberUpdatedState(state.selectedIndex) + + Column( + modifier = modifier + ) { + if (tabs.isNotEmpty()) { + ScrollableTabRow( + modifier = Modifier.fillMaxWidth(), + selectedTabIndex = selectedIndex, + indicator = + @Composable { tabPositions -> + TabRowDefaults.SecondaryIndicator( + Modifier.tabIndicatorOffset(tabPositions.getOrElse(selectedIndex) { tabPositions.last() }) + ) + }, + ) { + tabs.forEachIndexed { index, item -> + Tab( + selected = index == selectedIndex, + selectedContentColor = MaterialTheme.colorScheme.primary, + unselectedContentColor = MaterialTheme.colorScheme.onSurface, + text = @Composable { + Text( + text = getCategoryResource(item), + ) + }, + onClick = { + stateHolder.onClickTab(index) + }, + ) + } + } + } + } +} \ No newline at end of file diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tab/TabUiState.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tab/TabUiState.kt new file mode 100644 index 00000000..81dac4b4 --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tab/TabUiState.kt @@ -0,0 +1,104 @@ +package com.andannn.melodify.ui.components.tab + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import com.andannn.melodify.core.data.model.CustomTab +import com.andannn.melodify.core.data.repository.UserPreferenceRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.launch +import org.koin.mp.KoinPlatform.getKoin + + +@Composable +fun rememberTabUiStateHolder( + scope: CoroutineScope = rememberCoroutineScope(), + userPreferenceRepository: UserPreferenceRepository = getKoin().get(), +) = remember( + scope, + userPreferenceRepository +) { + TabUiStateHolder( + scope, + userPreferenceRepository + ) +} + +class TabUiStateHolder( + scope: CoroutineScope, + userPreferenceRepository: UserPreferenceRepository, +) { + private val _currentCustomTabsFlow = userPreferenceRepository.currentCustomTabsFlow + private val _selectedTabIndexFlow = MutableStateFlow(0) + + var state by mutableStateOf(TabUiState()) + private set + + private val _stateFlow = combine( + _selectedTabIndexFlow, + _currentCustomTabsFlow + ) { selectedIndex, customTabs -> + TabUiState( + selectedIndex = selectedIndex.coerceAtMost(customTabs.size - 1), + customTabList = customTabs + ) + } + + init { + scope.launch { + _stateFlow.collect { + state = it + } + } + + // Ensure selected index is valid + scope.launch { + _currentCustomTabsFlow + .scan, Pair?, List?>>(null to null) { pre, next -> + pre.second to next + } + .collect { (pre, next) -> + if (pre == null || next == null) { + return@collect + } + + val currentIndex = _selectedTabIndexFlow.value + + val newIndex: Int = if (next.size < pre.size) { + // new tab list is smaller than the previous one. + // 1. select the previous tab + // 2. if the current tab is removed, select the previous tab + next.indexOf(pre.getOrNull(currentIndex)) + .takeIf { it != -1 } ?: (currentIndex - 1).coerceAtLeast(0) + } else if (next.size > pre.size) { + // always select the new created tab + next.indexOf(next.firstOrNull { it !in pre }) + } else { + next.indexOf(pre.getOrNull(currentIndex)) + } + if (newIndex != -1) { + _selectedTabIndexFlow.value = newIndex + } + } + } + } + + fun onClickTab(index: Int) { + _selectedTabIndexFlow.value = index + } +} + + +data class TabUiState( + val selectedIndex: Int = 0, + val customTabList: List = emptyList() +) { + val selectedTab: CustomTab? + get() = customTabList.getOrNull(selectedIndex) +} diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabcontent/TabContent.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabcontent/TabContent.kt new file mode 100644 index 00000000..3da0d74a --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabcontent/TabContent.kt @@ -0,0 +1,96 @@ +package com.andannn.melodify.ui.components.tabcontent + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.andannn.melodify.core.data.model.AlbumItemModel +import com.andannn.melodify.core.data.model.ArtistItemModel +import com.andannn.melodify.core.data.model.AudioItemModel +import com.andannn.melodify.core.data.model.GenreItemModel +import com.andannn.melodify.core.data.model.MediaItemModel +import com.andannn.melodify.core.data.model.PlayListItemModel +import com.andannn.melodify.core.data.model.browsableOrPlayable +import com.andannn.melodify.core.data.model.key +import com.andannn.melodify.ui.common.widgets.ExtraPaddingBottom +import com.andannn.melodify.ui.common.widgets.ListTileItemView +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.track_count +import org.jetbrains.compose.resources.stringResource + +@Composable +fun TabContent( + stateHolder: TabContentStateHolder +) { + val listLayoutState = +// TODO: add selectedIndex + rememberSaveable(saver = LazyListState.Saver) { + LazyListState() + } + LazyListContent( + modifier = + Modifier.fillMaxSize(), + state = listLayoutState, + mediaItems = stateHolder.state.itemList.toImmutableList(), + onMusicItemClick = stateHolder::playMusic, + onShowMusicItemOption = stateHolder::onShowMusicItemOption + ) +} + +@Composable +private fun LazyListContent( + mediaItems: ImmutableList, + modifier: Modifier = Modifier, + state: LazyListState = rememberLazyListState(), + onMusicItemClick: (AudioItemModel) -> Unit = {}, + onShowMusicItemOption: (AudioItemModel) -> Unit = {}, +) { + LazyColumn( + state = state, + modifier = modifier, + contentPadding = PaddingValues(horizontal = 5.dp), + ) { + items( + items = mediaItems, + key = { it.key }, + ) { item -> + ListTileItemView( + modifier = + Modifier.animateItem(), + playable = item.browsableOrPlayable, + isActive = false, + albumArtUri = item.artWorkUri, + title = item.name, + subTitle = subTitle(item), + onMusicItemClick = { + onMusicItemClick.invoke(item) + }, + onOptionButtonClick = { + onShowMusicItemOption(item) + }, + ) + } + + item { ExtraPaddingBottom() } + } +} + +@Composable +private fun subTitle( + model: MediaItemModel +): String = when (model) { + is AudioItemModel -> model.artist + is AlbumItemModel, + is PlayListItemModel, + is ArtistItemModel -> stringResource(Res.string.track_count, model.trackCount.toString()) + + is GenreItemModel -> "" +} diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabcontent/TabContentState.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabcontent/TabContentState.kt new file mode 100644 index 00000000..121c17e7 --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabcontent/TabContentState.kt @@ -0,0 +1,143 @@ +package com.andannn.melodify.ui.components.tabcontent + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import com.andannn.melodify.core.data.Repository +import com.andannn.melodify.core.data.model.AudioItemModel +import com.andannn.melodify.core.data.model.CustomTab +import com.andannn.melodify.core.data.model.MediaItemModel +import com.andannn.melodify.ui.common.util.getUiRetainedScope +import com.andannn.melodify.ui.components.drawer.DrawerController +import com.andannn.melodify.ui.components.drawer.DrawerEvent +import com.andannn.melodify.ui.components.drawer.model.SheetModel +import com.andannn.melodify.ui.components.message.MessageController +import com.andannn.melodify.ui.components.message.dialog.Dialog +import com.andannn.melodify.ui.components.message.dialog.InteractionResult +import io.github.aakira.napier.Napier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import org.koin.mp.KoinPlatform.getKoin + +@Composable +fun rememberTabContentStateHolder( + selectedTab: CustomTab?, + repository: Repository = getKoin().get(), + scope: CoroutineScope = rememberCoroutineScope(), + drawerController: DrawerController = getUiRetainedScope()?.get() + ?: getKoin().get(), + messageController: MessageController = getUiRetainedScope()?.get() + ?: getKoin().get(), +) = remember( + selectedTab, + repository, + drawerController, + messageController +) { + TabContentStateHolder( + selectedTab = selectedTab, + repository = repository, + scope = scope, + messageController = messageController, + drawerController = drawerController, + ) +} + +private const val TAG = "TabContentState" + +class TabContentStateHolder( + private val selectedTab: CustomTab?, + private val repository: Repository, + private val scope: CoroutineScope, + private val messageController: MessageController, + private val drawerController: DrawerController, +) { + private val mediaControllerRepository = repository.mediaControllerRepository + private val playListRepository = repository.playListRepository + + @OptIn(ExperimentalCoroutinesApi::class) + private val _mediaContentFlow = flowOf(selectedTab) + .flatMapLatest { tab -> + if (tab == null) { + return@flatMapLatest flow { emit(emptyList()) } + } + with(tab) { + repository.contentFlow() + } + } + + private val _state = _mediaContentFlow.map { + TabContentState( + it + ) + } + + var state by mutableStateOf(TabContentState()) + private set + + init { + scope.launch { + _state.collect { + state = it + } + } + } + + fun playMusic(mediaItem: AudioItemModel) { + if (mediaItem.isValid()) { + val mediaItems = state.itemList + + mediaControllerRepository.playMediaList( + mediaItems.toList(), + mediaItems.indexOf(mediaItem) + ) + } else { + Napier.d(tag = TAG) { "invalid media item click $mediaItem" } + scope.launch { + val result = + messageController.showMessageDialogAndWaitResult(Dialog.ConfirmDeletePlaylist) + Napier.d(tag = TAG) { "ConfirmDeletePlaylist result: $result" } + if (result == InteractionResult.AlertDialog.ACCEPT) { + val playListId = (selectedTab as CustomTab.PlayListDetail).playListId + val mediaId = mediaItem.id.substringAfter(AudioItemModel.INVALID_ID_PREFIX) + + playListRepository.removeMusicFromPlayList(playListId.toLong(), listOf(mediaId)) + } + } + } + } + + fun onShowMusicItemOption(mediaItemModel: MediaItemModel) { + val currentTab = selectedTab + if (mediaItemModel is AudioItemModel && currentTab is CustomTab.PlayListDetail) { + drawerController.onEvent( + DrawerEvent.OnShowBottomDrawer( + SheetModel.AudioOptionInPlayListSheet( + playListId = currentTab.playListId, + mediaItemModel + ) + ) + ) + } else { + drawerController.onEvent( + DrawerEvent.OnShowBottomDrawer( + SheetModel.MediaOptionSheet.fromMediaModel( + item = mediaItemModel, + ) + ) + ) + } + } +} + +data class TabContentState( + val itemList: List = emptyList() +) \ No newline at end of file diff --git a/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomShrinkableTabSelector.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabselector/CustomShrinkableTabSelector.kt similarity index 93% rename from feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomShrinkableTabSelector.kt rename to ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabselector/CustomShrinkableTabSelector.kt index a28a8c78..dcd00449 100644 --- a/feature/customtab/src/commonMain/kotlin/com/andannn.melodify/feature/customtab/CustomShrinkableTabSelector.kt +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabselector/CustomShrinkableTabSelector.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.customtab +package com.andannn.melodify.ui.components.tabselector import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.ExperimentalFoundationApi @@ -28,17 +28,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.andannn.melodify.core.data.model.CustomTab -import com.andannn.melodify.feature.common.util.getCategoryResource -import melodify.feature.common.generated.resources.Res -import melodify.feature.common.generated.resources.audio_page_title +import com.andannn.melodify.ui.common.util.getCategoryResource +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.audio_page_title import org.jetbrains.compose.resources.stringResource @Composable @OptIn(ExperimentalFoundationApi::class) fun CustomTabSelector( - modifier: Modifier = Modifier, + modifier: Modifier = Modifier, + stateHolder: CustomTabSettingViewStateHolder = rememberCustomTabSettingViewStateHolder() ) { - val stateHolder = rememberCustomTabSettingViewStateHolder() val state by stateHolder.state.collectAsState() val expandState = remember { mutableStateOf>(emptyMap()) diff --git a/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabselector/CustomTabSettingViewStateHolder.kt b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabselector/CustomTabSettingViewStateHolder.kt new file mode 100644 index 00000000..d8bd1160 --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/andannn/melodify/ui/components/tabselector/CustomTabSettingViewStateHolder.kt @@ -0,0 +1,183 @@ +package com.andannn.melodify.ui.components.tabselector + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import com.andannn.melodify.core.data.model.CustomTab +import com.andannn.melodify.core.data.repository.DefaultCustomTabs +import com.andannn.melodify.core.data.repository.MediaContentRepository +import com.andannn.melodify.core.data.repository.PlayListRepository +import com.andannn.melodify.core.data.repository.UserPreferenceRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import melodify.ui.common.generated.resources.Res +import melodify.ui.common.generated.resources.album_page_title +import melodify.ui.common.generated.resources.artist_page_title +import melodify.ui.common.generated.resources.genre_title +import melodify.ui.common.generated.resources.playlist_page_title +import org.jetbrains.compose.resources.StringResource +import org.koin.mp.KoinPlatform.getKoin + +@Composable +fun rememberCustomTabSettingViewStateHolder( + playListRepository: PlayListRepository = getKoin().get(), + contentRepository: MediaContentRepository = getKoin().get(), + userPreferenceRepository: UserPreferenceRepository = getKoin().get(), + scope: CoroutineScope = rememberCoroutineScope(), +) = remember( + playListRepository, + contentRepository, + userPreferenceRepository, +) { + CustomTabSettingViewStateHolder( + playListRepository, + contentRepository, + userPreferenceRepository, + scope, + ) +} + +sealed interface UiEvent { + data class OnSelectedChange( + val tab: CustomTab, + val isSelected: Boolean, + ) : UiEvent + + data class OnUpdateTabs( + val newTabs: List, + ) : UiEvent + + data object OnResetClick : UiEvent +} + +private const val TAG = "CustomTabSettingViewState" + +class CustomTabSettingViewStateHolder( + private val playListRepository: PlayListRepository, + private val contentRepository: MediaContentRepository, + private val userPreferenceRepository: UserPreferenceRepository, + scope: CoroutineScope, +) : CoroutineScope by scope { + val state = + combine( + userPreferenceRepository.currentCustomTabsFlow, + contentRepository.getAllAlbumsFlow(), + contentRepository.getAllArtistFlow(), + contentRepository.getAllGenreFlow(), + playListRepository.getAllPlayListFlow(), + ) { tabs, albums, artists, genre, playlist -> + TabUiState( + currentTabs = tabs, + allAvailableTabSectors = + mutableListOf() + .apply { +// add( +// TabSector( +// Res.string.home, +// listOf( +// CustomTab.AllMusic, +// CustomTab.AllPlayList, +// CustomTab.AllAlbum, +// CustomTab.AllArtist, +// CustomTab.AllGenre, +// ) +// ) +// ) + + val albumTabs = + albums.map { + CustomTab.AlbumDetail(it.id, it.name) + } + add( + TabSector( + Res.string.album_page_title, + albumTabs, + ), + ) + + val playListTabs = + playlist.map { + CustomTab.PlayListDetail(it.id, it.name) + } + add( + TabSector( + Res.string.playlist_page_title, + playListTabs, + ), + ) + + val artistTabs = + artists.map { + CustomTab.ArtistDetail(it.id, it.name) + } + add( + TabSector( + Res.string.artist_page_title, + artistTabs, + ), + ) + + val genreTabs = + genre.map { + CustomTab.GenreDetail(it.id, it.name) + } + add( + TabSector( + Res.string.genre_title, + genreTabs, + ), + ) + }.toList(), + ) + }.stateIn(scope = scope, SharingStarted.WhileSubscribed(), TabUiState()) + + fun onEvent(event: UiEvent) { + val state = state.value + + when (event) { + is UiEvent.OnSelectedChange -> { + val (tab, selected) = event + launch { + if (selected) { + userPreferenceRepository.updateCurrentCustomTabs( + state.currentTabs + tab, + ) + } else { + userPreferenceRepository.updateCurrentCustomTabs( + state.currentTabs - tab, + ) + } + } + } + + is UiEvent.OnUpdateTabs -> { + launch { + userPreferenceRepository.updateCurrentCustomTabs( + event.newTabs, + ) + } + } + + UiEvent.OnResetClick -> { + launch { + userPreferenceRepository.updateCurrentCustomTabs( + DefaultCustomTabs.customTabs, + ) + } + } + } + } +} + +data class TabUiState( + val currentTabs: List = emptyList(), + val allAvailableTabSectors: List = emptyList(), +) + +data class TabSector( + val sectorTitle: StringResource, + val sectorContent: List, +) diff --git a/feature/player/src/desktopMain/kotlin/com/andannn/melodify/feature/player/PlayerSector.kt b/ui/components/src/desktopMain/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.desktop.kt similarity index 69% rename from feature/player/src/desktopMain/kotlin/com/andannn/melodify/feature/player/PlayerSector.kt rename to ui/components/src/desktopMain/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.desktop.kt index 5e31074a..25d7fc88 100644 --- a/feature/player/src/desktopMain/kotlin/com/andannn/melodify/feature/player/PlayerSector.kt +++ b/ui/components/src/desktopMain/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.desktop.kt @@ -1,4 +1,4 @@ -package com.andannn.melodify.feature.player +package com.andannn.melodify.ui.components.playcontrol import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -23,54 +23,47 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.andannn.melodify.core.data.model.AudioItemModel import com.andannn.melodify.core.data.model.PlayMode -import com.andannn.melodify.feature.common.component.CircleBorderImage -import com.andannn.melodify.feature.player.util.getIcon -import org.koin.compose.viewmodel.koinViewModel +import com.andannn.melodify.ui.common.util.getIcon +import com.andannn.melodify.ui.common.widgets.CircleBorderImage @Composable -fun PlayerSector( - playerStateViewModel: PlayerStateViewModel = koinViewModel(), - modifier: Modifier = Modifier, +actual fun Player( + modifier: Modifier, + stateHolder: PlayStateHolder ) { - val uiState = playerStateViewModel.playerUiStateFlow.collectAsState() - when (val state = uiState.value) { + when (val uiState = stateHolder.state) { is PlayerUiState.Active -> PlayStateBar( modifier = modifier, - coverUri = state.mediaItem.artWorkUri, - playMode = state.playMode, - isShuffle = state.isShuffle, - isPlaying = state.isPlaying, - isFavorite = state.isFavorite, - playListQueue = state.playListQueue, - activeMediaItem = state.mediaItem, - title = state.mediaItem.name, - artist = state.mediaItem.artist, - progress = state.progress, - duration = state.duration, - onEvent = playerStateViewModel::onEvent, + coverUri = uiState.mediaItem.artWorkUri, + playMode = uiState.playMode, + isShuffle = uiState.isShuffle, + isPlaying = uiState.isPlaying, + isFavorite = uiState.isFavorite, + title = uiState.mediaItem.name, + artist = uiState.mediaItem.artist, + progress = uiState.progress, + duration = uiState.duration, + onEvent = stateHolder::onEvent ) PlayerUiState.Inactive -> PlayStateBar( modifier = modifier, coverUri = "", - activeMediaItem = AudioItemModel.DEFAULT, + enabled = false, ) } } - @Composable -fun PlayStateBar( +private fun PlayStateBar( coverUri: String, - activeMediaItem: AudioItemModel, - playListQueue: List = emptyList(), + enabled: Boolean = true, modifier: Modifier = Modifier, playMode: PlayMode = PlayMode.REPEAT_ALL, isShuffle: Boolean = false, @@ -78,7 +71,7 @@ fun PlayStateBar( isFavorite: Boolean = false, title: String = "", artist: String = "", - progress: Float = 1f, + progress: Float = 0f, duration: Long = 0L, onEvent: (PlayerUiEvent) -> Unit = {}, ) { @@ -99,7 +92,7 @@ fun PlayStateBar( PlayControlBar( modifier = Modifier, - enabled = true, + enabled = enabled, isPlaying = isPlaying, playMode = playMode, isShuffle = isShuffle, @@ -109,20 +102,22 @@ fun PlayStateBar( Spacer(modifier = Modifier.weight(1f)) } - Slider( + + ProgressBar( modifier = Modifier.height(24.dp), - value = progress, - enabled = true, + enabled = enabled, + progress = progress, + duration = duration, onValueChange = { onEvent(PlayerUiEvent.OnProgressChange(it)) - }, + } ) } } } @Composable -internal fun PlayControlBar( +private fun PlayControlBar( modifier: Modifier = Modifier, isShuffle: Boolean, isPlaying: Boolean, @@ -205,18 +200,20 @@ private fun PlayInfoWithAlbumCover( title: String, artist: String ) { - Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { - CircleBorderImage( - modifier = Modifier - .fillMaxHeight() - .aspectRatio(1f) - .padding(6.dp), - model = coverUri - ) + if (coverUri.isNotEmpty()) { + CircleBorderImage( + modifier = Modifier + .fillMaxHeight() + .aspectRatio(1f), + model = coverUri + ) + + Spacer(modifier = Modifier.width(10.dp)) + } Column( modifier = Modifier, @@ -237,3 +234,40 @@ private fun PlayInfoWithAlbumCover( } } } + +@Composable +private fun ProgressBar( + modifier: Modifier = Modifier, + enabled: Boolean = true, + progress: Float = 0f, + duration: Long = 0L, + onValueChange: (Float) -> Unit = {}, +) { + Row( + modifier = modifier.padding(start = 8.dp, end = 8.dp, bottom = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.graphicsLayer { + alpha = if (enabled) 1f else 0.5f + }, + text = "0:00", + style = MaterialTheme.typography.bodySmall, + ) + Spacer(modifier = Modifier.width(8.dp)) + Slider( + modifier = modifier.weight(1f), + value = progress, + enabled = enabled, + onValueChange = onValueChange, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + modifier = Modifier.graphicsLayer { + alpha = if (enabled) 1f else 0.5f + }, + text = "0:00", + style = MaterialTheme.typography.bodySmall, + ) + } +} \ No newline at end of file diff --git a/ui/components/src/iosMain/kotlin/com/andannn/melodify/ui/components/playcontrol/PlayerView.ios.kt b/ui/components/src/iosMain/kotlin/com/andannn/melodify/ui/components/playcontrol/PlayerView.ios.kt new file mode 100644 index 00000000..2457f570 --- /dev/null +++ b/ui/components/src/iosMain/kotlin/com/andannn/melodify/ui/components/playcontrol/PlayerView.ios.kt @@ -0,0 +1,11 @@ +package com.andannn.melodify.ui.components.playcontrol + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.andannn.melodify.ui.components.playcontrol.ui.PlayerAreaView + +@Composable +actual fun Player( + modifier: Modifier, + stateHolder: PlayStateHolder +) = PlayerAreaView(modifier, stateHolder) \ No newline at end of file diff --git a/ui/components/src/main/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.android.kt b/ui/components/src/main/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.android.kt new file mode 100644 index 00000000..0ed2bc36 --- /dev/null +++ b/ui/components/src/main/kotlin/com/andannn/melodify/ui/components/playcontrol/Player.android.kt @@ -0,0 +1,11 @@ +package com.andannn.melodify.ui.components.playcontrol + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.andannn.melodify.ui.components.playcontrol.ui.PlayerAreaView + +@Composable +actual fun Player( + modifier: Modifier, + stateHolder: PlayStateHolder +) = PlayerAreaView(modifier, stateHolder) \ No newline at end of file