basic ui mock-up
This commit is contained in:
parent
b9caa79e48
commit
4312d1fdea
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -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'
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.blumia.pcmdroid">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.blumia.pcmdroid
|
|||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
|
@ -13,28 +14,18 @@ import net.blumia.pcmdroid.ui.screen.main.MainPlayer
|
|||
import net.blumia.pcmdroid.ui.theme.PrivateCloudMusicTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private val model: MainViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
PrivateCloudMusicTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(color = MaterialTheme.colors.background) {
|
||||
NavGraph()
|
||||
NavGraph(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Greeting(name: String) {
|
||||
Text(text = "Hello $name!")
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun DefaultPreview() {
|
||||
PrivateCloudMusicTheme {
|
||||
Greeting("Android")
|
||||
}
|
||||
}
|
63
app/src/main/java/net/blumia/pcmdroid/MainViewModel.kt
Normal file
63
app/src/main/java/net/blumia/pcmdroid/MainViewModel.kt
Normal file
|
@ -0,0 +1,63 @@
|
|||
package net.blumia.pcmdroid
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import net.blumia.pcmdroid.model.Folder
|
||||
import net.blumia.pcmdroid.model.Server
|
||||
import net.blumia.pcmdroid.model.Song
|
||||
|
||||
class MainViewModel : ViewModel() {
|
||||
|
||||
val servers: LiveData<List<Server>> = MutableLiveData<List<Server>>(
|
||||
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<String> = MutableLiveData("")
|
||||
|
||||
val drawerFolders: LiveData<List<Folder>> = MutableLiveData(
|
||||
listOf(
|
||||
Folder("Test/"),
|
||||
Folder("Folder/")
|
||||
)
|
||||
)
|
||||
|
||||
val folders: LiveData<List<Folder>> = MutableLiveData(
|
||||
listOf(
|
||||
Folder("Test/Converted-Modules/")
|
||||
)
|
||||
)
|
||||
|
||||
val songs: LiveData<List<Song>> = 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) {
|
||||
//
|
||||
}
|
||||
}
|
23
app/src/main/java/net/blumia/pcmdroid/model/Song.kt
Normal file
23
app/src/main/java/net/blumia/pcmdroid/model/Song.kt
Normal file
|
@ -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 ?: "???"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package net.blumia.pcmdroid.repository
|
||||
|
||||
class ServerRepository {
|
||||
}
|
|
@ -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)
|
||||
},
|
||||
|
|
|
@ -1,25 +1,48 @@
|
|||
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() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
progress = 0f,
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -39,12 +62,11 @@ fun NowPlaying() {
|
|||
},
|
||||
trailing = {
|
||||
IconButton(onClick = { /*TODO*/ }) {
|
||||
Icon(imageVector = Icons.Rounded.PlayArrow, contentDescription = "Menu")
|
||||
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<Int?>(null) }
|
||||
val items = listOf<String>()
|
||||
val icons = listOf<ImageVector>()
|
||||
var selectedItem by remember { mutableStateOf<Int?>(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()
|
||||
}
|
||||
|
|
5
app/src/main/res/values-zh/strings.xml
Normal file
5
app/src/main/res/values-zh/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<resources>
|
||||
<string name="app_name">私有云音乐</string>
|
||||
<string name="settings">设置</string>
|
||||
<string name="add">添加</string>
|
||||
</resources>
|
|
@ -1,3 +1,5 @@
|
|||
<resources>
|
||||
<string name="app_name">Private Cloud Music</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="add">Add</string>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user