basic ui mock-up

This commit is contained in:
Gary Wang 2021-11-13 19:13:47 +08:00
parent b9caa79e48
commit 4312d1fdea
12 changed files with 249 additions and 47 deletions

6
.idea/vcs.xml Normal file
View 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>

View File

@ -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'

View File

@ -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"

View File

@ -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")
}
}

View 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) {
//
}
}

View File

@ -6,4 +6,4 @@ data class Server (
val url: Uri,
val name: String,
val baseFolderName: String,
)
)

View 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 ?: "???"
}
}

View File

@ -0,0 +1,4 @@
package net.blumia.pcmdroid.repository
class ServerRepository {
}

View File

@ -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)
},

View File

@ -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<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()
}

View File

@ -0,0 +1,5 @@
<resources>
<string name="app_name">私有云音乐</string>
<string name="settings">设置</string>
<string name="add">添加</string>
</resources>

View File

@ -1,3 +1,5 @@
<resources>
<string name="app_name">Private Cloud Music</string>
<string name="settings">Settings</string>
<string name="add">Add</string>
</resources>