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