working breadcrumb, scrollable drawer list, etc.
This commit is contained in:
		@ -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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -4,4 +4,8 @@ data class Server (
 | 
			
		||||
    val url: String,
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val baseFolderName: String,
 | 
			
		||||
)
 | 
			
		||||
) {
 | 
			
		||||
    fun displayName(): String {
 | 
			
		||||
        return name
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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 = {
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
@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,
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user