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

View File

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

View File

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

View File

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