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 'com.google.android.material:material:1.4.0'
implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$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.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.navigation:navigation-compose:$nav_compose_version" implementation "androidx.navigation:navigation-compose:$nav_compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' 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" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.blumia.pcmdroid"> package="net.blumia.pcmdroid">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@ -3,6 +3,7 @@ package net.blumia.pcmdroid
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
@ -13,28 +14,18 @@ import net.blumia.pcmdroid.ui.screen.main.MainPlayer
import net.blumia.pcmdroid.ui.theme.PrivateCloudMusicTheme import net.blumia.pcmdroid.ui.theme.PrivateCloudMusicTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val model: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
PrivateCloudMusicTheme { PrivateCloudMusicTheme {
// A surface container using the 'background' color from the theme // A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) { 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

@ -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 package net.blumia.pcmdroid.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import net.blumia.pcmdroid.MainViewModel
import net.blumia.pcmdroid.ui.screen.addserver.AddServerScreen import net.blumia.pcmdroid.ui.screen.addserver.AddServerScreen
import net.blumia.pcmdroid.ui.screen.main.MainPlayer import net.blumia.pcmdroid.ui.screen.main.MainPlayer
import net.blumia.pcmdroid.ui.screen.settings.SettingsScreen import net.blumia.pcmdroid.ui.screen.settings.SettingsScreen
@ -18,6 +18,7 @@ object MainDestinations {
@Composable @Composable
fun NavGraph( fun NavGraph(
viewModel: MainViewModel,
navController: NavHostController = rememberNavController(), navController: NavHostController = rememberNavController(),
startDestination: String = MainDestinations.MAIN_ROUTE, startDestination: String = MainDestinations.MAIN_ROUTE,
) { ) {
@ -27,6 +28,7 @@ fun NavGraph(
) { ) {
composable(MainDestinations.MAIN_ROUTE) { composable(MainDestinations.MAIN_ROUTE) {
MainPlayer( MainPlayer(
viewModel = viewModel,
addServerActionTriggered = { addServerActionTriggered = {
navController.navigate(MainDestinations.ADD_SERVER_ROUTE) navController.navigate(MainDestinations.ADD_SERVER_ROUTE)
}, },

View File

@ -1,50 +1,72 @@
package net.blumia.pcmdroid.ui.screen.main package net.blumia.pcmdroid.ui.screen.main
import android.net.Uri
import androidx.compose.foundation.Image 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.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* 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.PlayArrow
import androidx.compose.material.icons.rounded.PlayCircle
import androidx.compose.material.icons.rounded.PlayCircleOutline
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.VerticalAlignmentLine
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.blumia.pcmdroid.MainViewModel
import net.blumia.pcmdroid.R 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) @Preview(showBackground = true)
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun NowPlaying() { fun NowPlaying() {
Row( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth(),
.height(56.dp),
verticalAlignment = Alignment.CenterVertically,
) { ) {
Image( LinearProgressIndicator(
painter = painterResource(id = R.drawable.ic_launcher_background), modifier = Modifier.fillMaxWidth(),
contentDescription = "Album cover", progress = 0f,
) )
ListItem( Row(
text = { modifier = Modifier
Text( .fillMaxWidth()
text = "Not playing at all.", .height(56.dp),
) verticalAlignment = Alignment.CenterVertically,
}, ) {
trailing = { Image(
IconButton(onClick = { /*TODO*/ }) { painter = painterResource(id = R.drawable.ic_launcher_background),
Icon(imageVector = Icons.Rounded.PlayArrow, contentDescription = "Menu") 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) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun DrawerContent( fun DrawerContent(
viewModel: MainViewModel = MainViewModel(),
addServerActionTriggered: () -> Unit = {}, addServerActionTriggered: () -> Unit = {},
settingsActionTriggered: () -> Unit = {}, settingsActionTriggered: () -> Unit = {},
) { ) {
Row(modifier = Modifier.fillMaxSize()) { Row(modifier = Modifier.fillMaxSize()) {
var selectedItem by remember { mutableStateOf<Int?>(null) } var selectedItem by remember { mutableStateOf<Int?>(0) }
val items = listOf<String>() val servers = viewModel.servers.value
val icons = listOf<ImageVector>()
NavigationRail { NavigationRail {
items.forEachIndexed { index, item -> servers?.forEachIndexed { index, server ->
NavigationRailItem( NavigationRailItem(
icon = { icon = {
Icon( Icon(
icons[index], item Icons.Filled.LibraryMusic, server.name
) )
}, },
label = { label = {
Text(item) Text(server.name)
}, },
selected = selectedItem == index, selected = selectedItem == index,
onClick = { /*TODO*/ } onClick = {
viewModel.fetchServer(server)
}
) )
} }
NavigationRailItem( NavigationRailItem(
icon = { Icon(Icons.Filled.AddCircle, null) }, icon = { Icon(Icons.Filled.AddCircle, null) },
label = { Text("Add") }, label = { Text(stringResource(id = R.string.add)) },
selected = false, selected = false,
onClick = addServerActionTriggered, onClick = addServerActionTriggered,
) )
@ -86,7 +110,7 @@ fun DrawerContent(
NavigationRailItem( NavigationRailItem(
icon = { Icon(Icons.Filled.Settings, null) }, icon = { Icon(Icons.Filled.Settings, null) },
label = { Text("Settings") }, label = { Text(stringResource(id = R.string.settings)) },
selected = false, selected = false,
onClick = settingsActionTriggered, onClick = settingsActionTriggered,
) )
@ -108,13 +132,30 @@ fun DrawerContent(
) )
Divider() 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) @Preview(showBackground = true)
@OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun MainPlayer( fun MainPlayer(
viewModel: MainViewModel = MainViewModel(),
addServerActionTriggered: () -> Unit = {}, addServerActionTriggered: () -> Unit = {},
settingsActionTriggered: () -> Unit = {}, settingsActionTriggered: () -> Unit = {},
) { ) {
@ -124,6 +165,7 @@ fun MainPlayer(
drawerState = drawerState, drawerState = drawerState,
drawerContent = { drawerContent = {
DrawerContent( DrawerContent(
viewModel = viewModel,
addServerActionTriggered = addServerActionTriggered, addServerActionTriggered = addServerActionTriggered,
settingsActionTriggered = settingsActionTriggered, settingsActionTriggered = settingsActionTriggered,
) )
@ -132,7 +174,7 @@ fun MainPlayer(
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("Private Cloud Music") }, title = { Text(stringResource(id = R.string.app_name)) },
navigationIcon = { navigationIcon = {
IconButton(onClick = { IconButton(onClick = {
drawerScope.launch { drawerScope.launch {
@ -154,7 +196,68 @@ fun MainPlayer(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Column(modifier = Modifier.weight(1f, fill = true)) { 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() 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> <resources>
<string name="app_name">Private Cloud Music</string> <string name="app_name">Private Cloud Music</string>
<string name="settings">Settings</string>
<string name="add">Add</string>
</resources> </resources>