fix livedata not updated, playlist not scrollable

This commit is contained in:
Gary Wang 2021-11-14 19:28:46 +08:00
parent 4312d1fdea
commit 8edcb91242
6 changed files with 175 additions and 34 deletions

View File

@ -52,6 +52,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$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"
@ -59,6 +60,9 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
implementation 'androidx.activity:activity-compose:1.4.0'
implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation("com.squareup.okhttp3:okhttp:4.9.0")
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View File

@ -1,63 +1,132 @@
package net.blumia.pcmdroid
import android.net.Uri
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.blumia.pcmdroid.model.Folder
import net.blumia.pcmdroid.model.Server
import net.blumia.pcmdroid.model.Song
import net.blumia.pcmdroid.model.parseFromJsonString
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
class MainViewModel : ViewModel() {
private val httpClient: OkHttpClient = OkHttpClient()
val servers: LiveData<List<Server>> = MutableLiveData<List<Server>>(
listOf(
Server(
url = Uri.parse("https://localhost/api.php"),
url = "http://localhost/api.php",
name = "PCM",
baseFolderName = "pcm",
),
Server(
url = Uri.parse("https://localhost/api.php"),
url = "http://localhost/api.php",
name = "PCM2",
baseFolderName = "pcm2",
)
)
)
val currentPath: LiveData<String> = MutableLiveData("")
private val _currentServer: MutableLiveData<Server> = MutableLiveData(null)
val currentServer: LiveData<Server> = _currentServer;
fun setCurrentServer(srv: Server) {
_currentServer.postValue(srv)
}
val drawerFolders: LiveData<List<Folder>> = MutableLiveData(
private val _currentPath: MutableLiveData<String> = MutableLiveData("")
val currentPath: LiveData<String> = _currentPath
private val _drawerFolders: MutableLiveData<List<Folder>> = MutableLiveData(
listOf(
Folder("Test/"),
Folder("Folder/")
)
)
val drawerFolders: LiveData<List<Folder>> = _drawerFolders
val folders: LiveData<List<Folder>> = MutableLiveData(
private val _folders: MutableLiveData<List<Folder>> = MutableLiveData(
listOf(
Folder("Test/Converted-Modules/")
)
)
val folders: LiveData<List<Folder>> = _folders
val songs: LiveData<List<Song>> = MutableLiveData(
private val _songs: MutableLiveData<List<Song>> = MutableLiveData(
listOf(
Song(
filePath = "Test/Rick Astley - Never Gonna Give You Up.mp3",
url = Uri.parse("http://localhost/rickroll.mp3")
url = "http://localhost/rickroll.mp3"
),
Song(
filePath = "Test/哇 好东西一堆.xm",
url = Uri.parse("http://localhost/rickroll.mp3")
url = "http://localhost/rickroll.mp3"
)
)
)
val songs: LiveData<List<Song>> = _songs
fun fetchServer(srv: Server) {
//
viewModelScope.launch(context = Dispatchers.IO) {
val formBody = FormBody.Builder()
.add("do", "getfilelist")
.build()
val request = Request.Builder()
.url(srv.url)
.post(formBody)
.build()
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())
}
// Thread.sleep(5_000)
}
}
fun fetchFolder(folder: Folder) {
//
if (currentServer.value == null) return
val srv = currentServer.value!!
viewModelScope.launch(context = Dispatchers.IO) {
val formBody = FormBody.Builder()
.add("do", "getfilelist")
.add("folder", folder.folderPath)
.build()
val request = Request.Builder()
.url(srv.url)
.post(formBody)
.build()
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())
_folders.postValue(pair.first)
_songs.postValue(pair.second)
Log.d("vvv", folders.value.toString())
}
// Thread.sleep(5_000)
}
}
}

View File

@ -1,9 +1,7 @@
package net.blumia.pcmdroid.model
import android.net.Uri
data class Server (
val url: Uri,
val url: String,
val name: String,
val baseFolderName: String,
)

View File

@ -1,13 +1,16 @@
package net.blumia.pcmdroid.model
import android.net.Uri
import org.json.JSONException
import org.json.JSONObject
import java.net.URLDecoder
data class Song (
val filePath: String, // with file name
val modifiedTime: Int = 0,
val modifiedTime: Long = 0,
val fileSize: Int = 0,
val additionalInfo: Boolean = false, // if sidecar meta-info json file exist.
val url: Uri,
val url: String,
) {
fun displayName(): String {
return Uri.parse(filePath).lastPathSegment ?: "???"
@ -18,6 +21,41 @@ data class Folder (
val folderPath: String,
) {
fun displayName(): String {
return Uri.parse(folderPath).lastPathSegment ?: "???"
return URLDecoder.decode(Uri.parse(folderPath).lastPathSegment ?: "???", "UTF-8")
}
}
fun parseFromJsonString(jsonString: String): Pair<List<Folder>, List<Song>> {
val folders = mutableListOf<Folder>()
val songs = mutableListOf<Song>()
try {
val jsonObj = JSONObject(jsonString)
val result = jsonObj.getJSONObject("result")
val data = result.getJSONObject("data")
val subFolders = data.getJSONArray("subFolderList")
val musicList = data.getJSONArray("musicList")
for (i in 0 until subFolders.length()) {
val rawFolderName = subFolders.getString(i)
folders.add(Folder(rawFolderName))
}
for (i in 0 until musicList.length()) {
val fileObject = musicList.getJSONObject(i)
val rawFileName = fileObject.getString("fileName")
val fileName = URLDecoder.decode(rawFileName, "UTF-8")
val modifiedTime = fileObject.getLong("modifiedTime")
val fileSize = fileObject.getLong("fileSize")
songs.add(
Song(
rawFileName,
modifiedTime = modifiedTime,
url = rawFileName
)
)
}
} catch (e: JSONException) {
e.printStackTrace()
}
return Pair(folders, songs)
}

View File

@ -1,11 +1,16 @@
package net.blumia.pcmdroid.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import net.blumia.pcmdroid.MainViewModel
import net.blumia.pcmdroid.model.Folder
import net.blumia.pcmdroid.model.Server
import net.blumia.pcmdroid.model.Song
import net.blumia.pcmdroid.ui.screen.addserver.AddServerScreen
import net.blumia.pcmdroid.ui.screen.main.MainPlayer
import net.blumia.pcmdroid.ui.screen.settings.SettingsScreen
@ -27,11 +32,26 @@ fun NavGraph(
startDestination = startDestination
) {
composable(MainDestinations.MAIN_ROUTE) {
val servers: List<Server> by viewModel.servers.observeAsState(initial = listOf())
val drawerFolders: List<Folder> by viewModel.drawerFolders.observeAsState(initial = listOf())
val folders: List<Folder> by viewModel.folders.observeAsState(listOf())
val songs: List<Song> by viewModel.songs.observeAsState(listOf())
MainPlayer(
viewModel = viewModel,
servers = servers,
drawerFolders = drawerFolders,
folders = folders,
songs = songs,
addServerActionTriggered = {
navController.navigate(MainDestinations.ADD_SERVER_ROUTE)
},
onServerItemIconClicked = { server ->
viewModel.fetchServer(server)
},
onDrawerFolderItemClicked = { folder ->
viewModel.fetchFolder(folder)
},
settingsActionTriggered = {
navController.navigate(MainDestinations.SETTINGS_ROUTE)
}

View File

@ -1,12 +1,9 @@
package net.blumia.pcmdroid.ui.screen.main
import android.net.Uri
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
@ -15,6 +12,9 @@ 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.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
@ -24,7 +24,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import net.blumia.pcmdroid.MainViewModel
import net.blumia.pcmdroid.R
import net.blumia.pcmdroid.model.Folder
import net.blumia.pcmdroid.model.Server
@ -74,15 +73,17 @@ fun NowPlaying() {
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun DrawerContent(
viewModel: MainViewModel = MainViewModel(),
servers: List<Server> = listOf(),
drawerFolders: List<Folder> = listOf(),
addServerActionTriggered: () -> Unit = {},
onServerItemIconClicked: (Server) -> Unit = {},
onDrawerFolderItemClicked: (Folder) -> Unit = {},
settingsActionTriggered: () -> Unit = {},
) {
Row(modifier = Modifier.fillMaxSize()) {
var selectedItem by remember { mutableStateOf<Int?>(0) }
val servers = viewModel.servers.value
NavigationRail {
servers?.forEachIndexed { index, server ->
servers.forEachIndexed { index, server ->
NavigationRailItem(
icon = {
Icon(
@ -94,7 +95,7 @@ fun DrawerContent(
},
selected = selectedItem == index,
onClick = {
viewModel.fetchServer(server)
onServerItemIconClicked(server)
}
)
}
@ -133,11 +134,10 @@ fun DrawerContent(
Divider()
val drawerFolders = viewModel.drawerFolders.value
drawerFolders?.forEach { folder ->
drawerFolders.forEach { folder ->
ListItem(
modifier = Modifier.clickable{
viewModel.fetchFolder(folder)
onDrawerFolderItemClicked(folder)
},
text = {
Text(
@ -155,8 +155,13 @@ fun DrawerContent(
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MainPlayer(
viewModel: MainViewModel = MainViewModel(),
servers: List<Server> = listOf(),
drawerFolders: List<Folder> = listOf(),
folders: List<Folder> = listOf(),
songs: List<Song> = listOf(),
addServerActionTriggered: () -> Unit = {},
onServerItemIconClicked: (Server) -> Unit = {},
onDrawerFolderItemClicked: (Folder) -> Unit = {},
settingsActionTriggered: () -> Unit = {},
) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
@ -165,8 +170,11 @@ fun MainPlayer(
drawerState = drawerState,
drawerContent = {
DrawerContent(
viewModel = viewModel,
servers = servers,
drawerFolders = drawerFolders,
addServerActionTriggered = addServerActionTriggered,
onServerItemIconClicked = onServerItemIconClicked,
onDrawerFolderItemClicked = onDrawerFolderItemClicked,
settingsActionTriggered = settingsActionTriggered,
)
}
@ -195,7 +203,11 @@ fun MainPlayer(
Column(
modifier = Modifier.fillMaxSize()
) {
Column(modifier = Modifier.weight(1f, fill = true)) {
Column(
modifier = Modifier
.weight(1f, fill = true)
.verticalScroll(rememberScrollState())
) {
val iconLeftPadding = 16.dp
val iconVerticalPadding = 12.dp
@ -229,7 +241,7 @@ fun MainPlayer(
}
}
viewModel.folders.value?.forEach { folder ->
folders.forEach { folder ->
ListItem(
modifier = Modifier.clickable { },
icon = {
@ -244,7 +256,7 @@ fun MainPlayer(
)
}
viewModel.songs.value?.forEach { song ->
songs.forEach { song ->
ListItem(
modifier = Modifier.clickable { },
icon = {