diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..9661ac7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 6864d8f..f03097f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation 'com.google.android.material:material:1.4.0' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material:material:$compose_version" + implementation "androidx.compose.material:material-icons-extended:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation "androidx.navigation:navigation-compose:$nav_compose_version" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b7fea51..564250e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + > = MutableLiveData>( + listOf( + Server( + url = Uri.parse("https://localhost/api.php"), + name = "PCM", + baseFolderName = "pcm", + ), + Server( + url = Uri.parse("https://localhost/api.php"), + name = "PCM2", + baseFolderName = "pcm2", + ) + ) + ) + + val currentPath: LiveData = MutableLiveData("") + + val drawerFolders: LiveData> = MutableLiveData( + listOf( + Folder("Test/"), + Folder("Folder/") + ) + ) + + val folders: LiveData> = MutableLiveData( + listOf( + Folder("Test/Converted-Modules/") + ) + ) + + val songs: LiveData> = MutableLiveData( + listOf( + Song( + filePath = "Test/Rick Astley - Never Gonna Give You Up.mp3", + url = Uri.parse("http://localhost/rickroll.mp3") + ), + Song( + filePath = "Test/哇 好东西一堆.xm", + url = Uri.parse("http://localhost/rickroll.mp3") + ) + ) + ) + + fun fetchServer(srv: Server) { + // + } + + fun fetchFolder(folder: Folder) { + // + } +} \ No newline at end of file diff --git a/app/src/main/java/net/blumia/pcmdroid/model/Server.kt b/app/src/main/java/net/blumia/pcmdroid/model/Server.kt index 1896f43..569970b 100644 --- a/app/src/main/java/net/blumia/pcmdroid/model/Server.kt +++ b/app/src/main/java/net/blumia/pcmdroid/model/Server.kt @@ -6,4 +6,4 @@ data class Server ( val url: Uri, val name: String, val baseFolderName: String, -) \ No newline at end of file +) diff --git a/app/src/main/java/net/blumia/pcmdroid/model/Song.kt b/app/src/main/java/net/blumia/pcmdroid/model/Song.kt new file mode 100644 index 0000000..868eee0 --- /dev/null +++ b/app/src/main/java/net/blumia/pcmdroid/model/Song.kt @@ -0,0 +1,23 @@ +package net.blumia.pcmdroid.model + +import android.net.Uri + +data class Song ( + val filePath: String, // with file name + val modifiedTime: Int = 0, + val fileSize: Int = 0, + val additionalInfo: Boolean = false, // if sidecar meta-info json file exist. + val url: Uri, +) { + fun displayName(): String { + return Uri.parse(filePath).lastPathSegment ?: "???" + } +} + +data class Folder ( + val folderPath: String, +) { + fun displayName(): String { + return Uri.parse(folderPath).lastPathSegment ?: "???" + } +} \ No newline at end of file diff --git a/app/src/main/java/net/blumia/pcmdroid/repository/ServerRepository.kt b/app/src/main/java/net/blumia/pcmdroid/repository/ServerRepository.kt new file mode 100644 index 0000000..abe395e --- /dev/null +++ b/app/src/main/java/net/blumia/pcmdroid/repository/ServerRepository.kt @@ -0,0 +1,4 @@ +package net.blumia.pcmdroid.repository + +class ServerRepository { +} \ No newline at end of file diff --git a/app/src/main/java/net/blumia/pcmdroid/ui/NavGraph.kt b/app/src/main/java/net/blumia/pcmdroid/ui/NavGraph.kt index f6ac492..38b710c 100644 --- a/app/src/main/java/net/blumia/pcmdroid/ui/NavGraph.kt +++ b/app/src/main/java/net/blumia/pcmdroid/ui/NavGraph.kt @@ -1,11 +1,11 @@ package net.blumia.pcmdroid.ui import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import net.blumia.pcmdroid.MainViewModel import net.blumia.pcmdroid.ui.screen.addserver.AddServerScreen import net.blumia.pcmdroid.ui.screen.main.MainPlayer import net.blumia.pcmdroid.ui.screen.settings.SettingsScreen @@ -18,6 +18,7 @@ object MainDestinations { @Composable fun NavGraph( + viewModel: MainViewModel, navController: NavHostController = rememberNavController(), startDestination: String = MainDestinations.MAIN_ROUTE, ) { @@ -27,6 +28,7 @@ fun NavGraph( ) { composable(MainDestinations.MAIN_ROUTE) { MainPlayer( + viewModel = viewModel, addServerActionTriggered = { navController.navigate(MainDestinations.ADD_SERVER_ROUTE) }, diff --git a/app/src/main/java/net/blumia/pcmdroid/ui/screen/main/MainPlayer.kt b/app/src/main/java/net/blumia/pcmdroid/ui/screen/main/MainPlayer.kt index 99cc6a0..0a72826 100644 --- a/app/src/main/java/net/blumia/pcmdroid/ui/screen/main/MainPlayer.kt +++ b/app/src/main/java/net/blumia/pcmdroid/ui/screen/main/MainPlayer.kt @@ -1,50 +1,72 @@ package net.blumia.pcmdroid.ui.screen.main +import android.net.Uri import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* +import androidx.compose.material.icons.rounded.FeaturedPlayList import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material.icons.rounded.PlayCircle +import androidx.compose.material.icons.rounded.PlayCircleOutline import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.VerticalAlignmentLine import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch +import net.blumia.pcmdroid.MainViewModel import net.blumia.pcmdroid.R +import net.blumia.pcmdroid.model.Folder +import net.blumia.pcmdroid.model.Server +import net.blumia.pcmdroid.model.Song @Preview(showBackground = true) @OptIn(ExperimentalMaterialApi::class) @Composable fun NowPlaying() { - Row( + Column( modifier = Modifier - .fillMaxWidth() - .height(56.dp), - verticalAlignment = Alignment.CenterVertically, + .fillMaxWidth(), ) { - Image( - painter = painterResource(id = R.drawable.ic_launcher_background), - contentDescription = "Album cover", + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + progress = 0f, ) - ListItem( - text = { - Text( - text = "Not playing at all.", - ) - }, - trailing = { - IconButton(onClick = { /*TODO*/ }) { - Icon(imageVector = Icons.Rounded.PlayArrow, contentDescription = "Menu") + Row( + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(id = R.drawable.ic_launcher_background), + contentDescription = "Album cover", + ) + + ListItem( + text = { + Text( + text = "Not playing at all.", + ) + }, + trailing = { + IconButton(onClick = { /*TODO*/ }) { + Icon(imageVector = Icons.Rounded.PlayCircleOutline, contentDescription = "Menu") + } } - } - ) - - + ) + } } } @@ -52,32 +74,34 @@ fun NowPlaying() { @OptIn(ExperimentalMaterialApi::class) @Composable fun DrawerContent( + viewModel: MainViewModel = MainViewModel(), addServerActionTriggered: () -> Unit = {}, settingsActionTriggered: () -> Unit = {}, ) { Row(modifier = Modifier.fillMaxSize()) { - var selectedItem by remember { mutableStateOf(null) } - val items = listOf() - val icons = listOf() + var selectedItem by remember { mutableStateOf(0) } + val servers = viewModel.servers.value NavigationRail { - items.forEachIndexed { index, item -> + servers?.forEachIndexed { index, server -> NavigationRailItem( icon = { Icon( - icons[index], item + Icons.Filled.LibraryMusic, server.name ) }, label = { - Text(item) + Text(server.name) }, selected = selectedItem == index, - onClick = { /*TODO*/ } + onClick = { + viewModel.fetchServer(server) + } ) } NavigationRailItem( icon = { Icon(Icons.Filled.AddCircle, null) }, - label = { Text("Add") }, + label = { Text(stringResource(id = R.string.add)) }, selected = false, onClick = addServerActionTriggered, ) @@ -86,7 +110,7 @@ fun DrawerContent( NavigationRailItem( icon = { Icon(Icons.Filled.Settings, null) }, - label = { Text("Settings") }, + label = { Text(stringResource(id = R.string.settings)) }, selected = false, onClick = settingsActionTriggered, ) @@ -108,13 +132,30 @@ fun DrawerContent( ) Divider() + + val drawerFolders = viewModel.drawerFolders.value + drawerFolders?.forEach { folder -> + ListItem( + modifier = Modifier.clickable{ + viewModel.fetchFolder(folder) + }, + text = { + Text( + text = folder.displayName(), + style = MaterialTheme.typography.subtitle1 + ) + } + ) + } } } } @Preview(showBackground = true) +@OptIn(ExperimentalMaterialApi::class) @Composable fun MainPlayer( + viewModel: MainViewModel = MainViewModel(), addServerActionTriggered: () -> Unit = {}, settingsActionTriggered: () -> Unit = {}, ) { @@ -124,6 +165,7 @@ fun MainPlayer( drawerState = drawerState, drawerContent = { DrawerContent( + viewModel = viewModel, addServerActionTriggered = addServerActionTriggered, settingsActionTriggered = settingsActionTriggered, ) @@ -132,7 +174,7 @@ fun MainPlayer( Scaffold( topBar = { TopAppBar( - title = { Text("Private Cloud Music") }, + title = { Text(stringResource(id = R.string.app_name)) }, navigationIcon = { IconButton(onClick = { drawerScope.launch { @@ -154,7 +196,68 @@ fun MainPlayer( modifier = Modifier.fillMaxSize() ) { Column(modifier = Modifier.weight(1f, fill = true)) { - // + + val iconLeftPadding = 16.dp + val iconVerticalPadding = 12.dp + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + .padding( + horizontal = iconLeftPadding, + vertical = iconVerticalPadding, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + val pathSeg = listOf("Deep", "Dark", "Folder") + pathSeg.forEach { seg -> + val isLastSeg = seg == pathSeg.last() + + Text( + text = seg, + style = MaterialTheme.typography.body1, + color = if (isLastSeg) MaterialTheme.colors.primary else MaterialTheme.colors.onSurface + ) + + if (!isLastSeg) { + Icon( + modifier = Modifier.padding(horizontal = 5.dp), + imageVector = Icons.Filled.NavigateNext, + contentDescription = null, + ) + } + } + } + + viewModel.folders.value?.forEach { folder -> + ListItem( + modifier = Modifier.clickable { }, + icon = { + Icon( + imageVector = Icons.Filled.FolderOpen, + contentDescription = null, + ) + }, + text = { + Text(folder.displayName()) + } + ) + } + + viewModel.songs.value?.forEach { song -> + ListItem( + modifier = Modifier.clickable { }, + icon = { + Icon( + imageVector = Icons.Filled.MusicNote, + contentDescription = null + ) + }, + text = { + Text(song.displayName()) + } + ) + } } NowPlaying() } diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..3a438ee --- /dev/null +++ b/app/src/main/res/values-zh/strings.xml @@ -0,0 +1,5 @@ + + 私有云音乐 + 设置 + 添加 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8912f9..9efca95 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ Private Cloud Music + Settings + Add \ No newline at end of file