working breadcrumb, scrollable drawer list, etc.

This commit is contained in:
Gary Wang 2021-11-15 00:21:59 +08:00
parent 8edcb91242
commit d0800429c5
6 changed files with 153 additions and 69 deletions

View File

@ -24,13 +24,13 @@ class MainViewModel : ViewModel() {
val servers: LiveData<List<Server>> = MutableLiveData<List<Server>>(
listOf(
Server(
url = "http://localhost/api.php",
url = "https://localhost/api.php",
name = "PCM",
baseFolderName = "pcm",
),
Server(
url = "http://localhost/api.php",
name = "PCM2",
url = "https://localhost/pcm.cgi",
name = "Chris",
baseFolderName = "pcm2",
)
)
@ -42,13 +42,16 @@ class MainViewModel : ViewModel() {
_currentServer.postValue(srv)
}
private val _currentPath: MutableLiveData<String> = MutableLiveData("")
val currentPath: LiveData<String> = _currentPath
private val _currentFolder: MutableLiveData<Folder> = MutableLiveData(null)
val currentFolder: LiveData<Folder> = _currentFolder
fun setCurrentFolder(folder: Folder) {
_currentFolder.postValue(folder)
}
private val _drawerFolders: MutableLiveData<List<Folder>> = MutableLiveData(
listOf(
Folder("Test/"),
Folder("Folder/")
Folder("Folder/"),
)
)
val drawerFolders: LiveData<List<Folder>> = _drawerFolders
@ -86,16 +89,21 @@ class MainViewModel : ViewModel() {
.post(formBody)
.build()
httpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
// TODO: non-200 response will go to here too.
return@use
try {
httpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
// TODO: non-200 response will go to here too.
return@use
}
val pair = parseFromJsonString(response.body!!.string())
_drawerFolders.postValue(pair.first)
setCurrentServer(srv)
Log.d("vvv", drawerFolders.value.toString())
}
val pair = parseFromJsonString(response.body!!.string())
_drawerFolders.postValue(pair.first)
setCurrentServer(srv)
Log.d("vvv", drawerFolders.value.toString())
} catch (e: java.io.IOException) {
e.printStackTrace()
}
// Thread.sleep(5_000)
}
}
@ -124,9 +132,11 @@ class MainViewModel : ViewModel() {
val pair = parseFromJsonString(response.body!!.string())
_folders.postValue(pair.first)
_songs.postValue(pair.second)
setCurrentFolder(folder)
Log.d("vvv", folders.value.toString())
}
// Thread.sleep(5_000)
}
}
}

View File

@ -4,4 +4,8 @@ data class Server (
val url: String,
val name: String,
val baseFolderName: String,
)
) {
fun displayName(): String {
return name
}
}

View File

@ -1,6 +1,8 @@
package net.blumia.pcmdroid.model
import android.net.Uri
import android.util.Log
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.net.URLDecoder
@ -34,7 +36,7 @@ fun parseFromJsonString(jsonString: String): Pair<List<Folder>, List<Song>> {
val result = jsonObj.getJSONObject("result")
val data = result.getJSONObject("data")
val subFolders = data.getJSONArray("subFolderList")
val musicList = data.getJSONArray("musicList")
val musicList = if (data.has("musicList")) data.getJSONArray("musicList") else JSONArray()
for (i in 0 until subFolders.length()) {
val rawFolderName = subFolders.getString(i)
folders.add(Folder(rawFolderName))
@ -55,6 +57,7 @@ fun parseFromJsonString(jsonString: String): Pair<List<Folder>, List<Song>> {
}
} catch (e: JSONException) {
e.printStackTrace()
Log.d("vvv", jsonString)
}
return Pair(folders, songs)

View File

@ -33,14 +33,18 @@ fun NavGraph(
) {
composable(MainDestinations.MAIN_ROUTE) {
val curServer: Server? by viewModel.currentServer.observeAsState(initial = null)
val servers: List<Server> by viewModel.servers.observeAsState(initial = listOf())
val drawerFolders: List<Folder> by viewModel.drawerFolders.observeAsState(initial = listOf())
val curFolder: Folder? by viewModel.currentFolder.observeAsState(initial = null)
val folders: List<Folder> by viewModel.folders.observeAsState(listOf())
val songs: List<Song> by viewModel.songs.observeAsState(listOf())
MainPlayer(
currentServer = curServer,
servers = servers,
drawerFolders = drawerFolders,
currentFolder = curFolder,
folders = folders,
songs = songs,
addServerActionTriggered = {
@ -49,7 +53,7 @@ fun NavGraph(
onServerItemIconClicked = { server ->
viewModel.fetchServer(server)
},
onDrawerFolderItemClicked = { folder ->
onFolderItemClicked = { folder ->
viewModel.fetchFolder(folder)
},
settingsActionTriggered = {

View File

@ -0,0 +1,74 @@
package net.blumia.pcmdroid.ui.screen.main
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.NavigateNext
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.blumia.pcmdroid.model.Folder
@Composable
fun Breadcrumb(
folder: Folder? = null,
onFolderClicked: (Folder) -> Unit = {}
) {
val iconLeftPadding = 16.dp
val iconVerticalPadding = 12.dp
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.padding(
horizontal = iconLeftPadding,
vertical = iconVerticalPadding,
),
verticalAlignment = Alignment.CenterVertically,
) {
val folderPath = folder?.folderPath ?: ""
val pathSeg = Uri.parse(folderPath).pathSegments
pathSeg.forEachIndexed { idx, seg ->
val isLastSeg = seg == pathSeg.last()
Text(
modifier = Modifier.clickable {
val segList = pathSeg.take(idx + 1)
val clickedFolder = Folder(
folderPath = segList.joinToString("/")
)
onFolderClicked(clickedFolder)
},
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,
)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun BreadcrumbPreview() {
Breadcrumb(Folder("""Deep/Dark/Folder"""))
}

View File

@ -73,6 +73,7 @@ fun NowPlaying() {
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun DrawerContent(
currentServer: Server? = null,
servers: List<Server> = listOf(),
drawerFolders: List<Folder> = listOf(),
addServerActionTriggered: () -> Unit = {},
@ -80,8 +81,9 @@ fun DrawerContent(
onDrawerFolderItemClicked: (Folder) -> Unit = {},
settingsActionTriggered: () -> Unit = {},
) {
val currentServerApiUrl = currentServer?.url
Row(modifier = Modifier.fillMaxSize()) {
var selectedItem by remember { mutableStateOf<Int?>(0) }
NavigationRail {
servers.forEachIndexed { index, server ->
NavigationRailItem(
@ -91,9 +93,9 @@ fun DrawerContent(
)
},
label = {
Text(server.name)
Text(server.displayName())
},
selected = selectedItem == index,
selected = server.url == currentServerApiUrl,
onClick = {
onServerItemIconClicked(server)
}
@ -117,11 +119,14 @@ fun DrawerContent(
)
}
Column(modifier = Modifier.weight(1f)) {
Column(
modifier = Modifier
.weight(1f)
) {
ListItem(
text = {
Text(
text = "No server selected",
text = currentServer?.displayName() ?: "No server selected",
style = MaterialTheme.typography.subtitle1
)
},
@ -134,18 +139,24 @@ fun DrawerContent(
Divider()
drawerFolders.forEach { folder ->
ListItem(
modifier = Modifier.clickable{
onDrawerFolderItemClicked(folder)
},
text = {
Text(
text = folder.displayName(),
style = MaterialTheme.typography.subtitle1
)
}
)
Column(
modifier = Modifier
.weight(1f, fill = true)
.verticalScroll(rememberScrollState())
) {
drawerFolders.forEach { folder ->
ListItem(
modifier = Modifier.clickable{
onDrawerFolderItemClicked(folder)
},
text = {
Text(
text = folder.displayName(),
style = MaterialTheme.typography.subtitle1
)
}
)
}
}
}
}
@ -155,13 +166,15 @@ fun DrawerContent(
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MainPlayer(
currentServer: Server? = null,
servers: List<Server> = listOf(),
drawerFolders: List<Folder> = listOf(),
currentFolder: Folder? = null,
folders: List<Folder> = listOf(),
songs: List<Song> = listOf(),
addServerActionTriggered: () -> Unit = {},
onServerItemIconClicked: (Server) -> Unit = {},
onDrawerFolderItemClicked: (Folder) -> Unit = {},
onFolderItemClicked: (Folder) -> Unit = {},
settingsActionTriggered: () -> Unit = {},
) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
@ -170,11 +183,12 @@ fun MainPlayer(
drawerState = drawerState,
drawerContent = {
DrawerContent(
currentServer = currentServer,
servers = servers,
drawerFolders = drawerFolders,
addServerActionTriggered = addServerActionTriggered,
onServerItemIconClicked = onServerItemIconClicked,
onDrawerFolderItemClicked = onDrawerFolderItemClicked,
onDrawerFolderItemClicked = onFolderItemClicked,
settingsActionTriggered = settingsActionTriggered,
)
}
@ -209,41 +223,16 @@ fun MainPlayer(
.verticalScroll(rememberScrollState())
) {
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,
)
}
}
}
Breadcrumb(
folder = currentFolder,
onFolderClicked = onFolderItemClicked,
)
folders.forEach { folder ->
ListItem(
modifier = Modifier.clickable { },
modifier = Modifier.clickable {
onFolderItemClicked(folder)
},
icon = {
Icon(
imageVector = Icons.Filled.FolderOpen,