working breadcrumb, scrollable drawer list, etc.
This commit is contained in:
parent
8edcb91242
commit
d0800429c5
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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 = {
|
||||||
|
@ -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"""))
|
||||||
|
}
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user