Merge branch 'bottombar' of https://github.com/DDRBoxman/MPD
This commit is contained in:
commit
7a40ac52a8
|
@ -61,6 +61,7 @@ dependencies {
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
|
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
|
||||||
|
implementation("androidx.navigation:navigation-compose:2.7.6")
|
||||||
|
|
||||||
implementation("com.github.alorma:compose-settings-ui-m3:1.0.3")
|
implementation("com.github.alorma:compose-settings-ui-m3:1.0.3")
|
||||||
implementation("com.github.alorma:compose-settings-storage-preferences:1.0.3")
|
implementation("com.github.alorma:compose-settings-storage-preferences:1.0.3")
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
android:theme="@style/Theme.MPD"
|
android:theme="@style/Theme.MPD"
|
||||||
android:name=".MPDApplication">
|
android:name=".MPDApplication">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.SettingsActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
|
@ -27,7 +27,6 @@ import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.MediaSession;
|
import androidx.media3.session.MediaSession;
|
||||||
|
|
||||||
import org.musicpd.data.LoggingRepository;
|
import org.musicpd.data.LoggingRepository;
|
||||||
import org.musicpd.ui.SettingsActivity;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -208,7 +207,7 @@ public class Main extends Service implements Runnable {
|
||||||
}
|
}
|
||||||
}, filter);
|
}, filter);
|
||||||
|
|
||||||
final Intent mainIntent = new Intent(this, SettingsActivity.class);
|
final Intent mainIntent = new Intent(this, MainActivity.class);
|
||||||
mainIntent.setAction("android.intent.action.MAIN");
|
mainIntent.setAction("android.intent.action.MAIN");
|
||||||
mainIntent.addCategory("android.intent.category.LAUNCHER");
|
mainIntent.addCategory("android.intent.category.LAUNCHER");
|
||||||
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.musicpd
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import org.musicpd.ui.MPDApp
|
||||||
|
import org.musicpd.ui.SettingsViewModel
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
setContent {
|
||||||
|
MaterialTheme {
|
||||||
|
MPDApp(settingsViewModel = settingsViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connectClient() {
|
||||||
|
val client = Main.Client(this, object : Main.Client.Callback {
|
||||||
|
override fun onStopped() {
|
||||||
|
settingsViewModel.updateStatus("", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStarted() {
|
||||||
|
settingsViewModel.updateStatus("MPD Service Started", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(error: String) {
|
||||||
|
settingsViewModel.removeClient()
|
||||||
|
settingsViewModel.updateStatus(error, false)
|
||||||
|
connectClient()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
settingsViewModel.setClient(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
//mFirstRun = false
|
||||||
|
connectClient()
|
||||||
|
super.onStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
settingsViewModel.removeClient()
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.musicpd.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LogView(messages: State<List<String>>) {
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxSize()) {
|
||||||
|
LazyColumn(
|
||||||
|
Modifier.padding(4.dp),
|
||||||
|
state
|
||||||
|
) {
|
||||||
|
items(messages.value) { message ->
|
||||||
|
Text(text = message, fontFamily = FontFamily.Monospace)
|
||||||
|
}
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
state.scrollToItem(messages.value.count(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package org.musicpd.ui
|
||||||
|
|
||||||
|
import MPDSettings
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Circle
|
||||||
|
import androidx.compose.material.icons.filled.Home
|
||||||
|
import androidx.compose.material.icons.filled.List
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.NavigationBar
|
||||||
|
import androidx.compose.material3.NavigationBarItem
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
|
||||||
|
enum class Screen {
|
||||||
|
HOME,
|
||||||
|
LOGS,
|
||||||
|
SETTINGS,
|
||||||
|
}
|
||||||
|
sealed class NavigationItem(val route: String, val label: String, val icon: ImageVector) {
|
||||||
|
data object Home : NavigationItem(
|
||||||
|
Screen.HOME.name,
|
||||||
|
"Home",
|
||||||
|
Icons.Default.Home
|
||||||
|
)
|
||||||
|
data object Logs : NavigationItem(
|
||||||
|
Screen.LOGS.name,
|
||||||
|
"Logs",
|
||||||
|
Icons.Default.List)
|
||||||
|
data object Settings : NavigationItem(
|
||||||
|
Screen.SETTINGS.name,
|
||||||
|
"Settings",
|
||||||
|
Icons.Default.Settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MPDApp(
|
||||||
|
navController: NavHostController = rememberNavController(),
|
||||||
|
settingsViewModel: SettingsViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
BottomNavigationBar(navController)
|
||||||
|
},
|
||||||
|
) { innerPadding ->
|
||||||
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = NavigationItem.Home.route,
|
||||||
|
modifier = Modifier.padding(innerPadding)
|
||||||
|
) {
|
||||||
|
composable(NavigationItem.Home.route) {
|
||||||
|
StatusScreen(settingsViewModel)
|
||||||
|
}
|
||||||
|
composable(NavigationItem.Logs.route) {
|
||||||
|
LogView(settingsViewModel.getLogs().collectAsStateWithLifecycle())
|
||||||
|
}
|
||||||
|
composable(NavigationItem.Settings.route) {
|
||||||
|
MPDSettings(settingsViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BottomNavigationBar(navController: NavController) {
|
||||||
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
|
|
||||||
|
val items = listOf(
|
||||||
|
NavigationItem.Home,
|
||||||
|
NavigationItem.Logs,
|
||||||
|
NavigationItem.Settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
NavigationBar {
|
||||||
|
items.forEach { item ->
|
||||||
|
NavigationBarItem(
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = item.icon,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { Text (item.label) },
|
||||||
|
onClick = {
|
||||||
|
navController.navigate(item.route) {
|
||||||
|
popUpTo(navController.graph.startDestinationId)
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selected = currentRoute == item.route,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,210 +0,0 @@
|
||||||
package org.musicpd.ui
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Circle
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
|
||||||
import com.google.accompanist.permissions.isGranted
|
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
|
||||||
import com.google.accompanist.permissions.shouldShowRationale
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.musicpd.Main
|
|
||||||
import org.musicpd.R
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class SettingsActivity : ComponentActivity() {
|
|
||||||
|
|
||||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
setContent {
|
|
||||||
MaterialTheme {
|
|
||||||
SettingsContainer(settingsViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun connectClient() {
|
|
||||||
val client = Main.Client(this, object : Main.Client.Callback {
|
|
||||||
override fun onStopped() {
|
|
||||||
settingsViewModel.updateStatus("", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStarted() {
|
|
||||||
settingsViewModel.updateStatus("MPD Service Started", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(error: String) {
|
|
||||||
settingsViewModel.removeClient()
|
|
||||||
settingsViewModel.updateStatus(error, false)
|
|
||||||
connectClient()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
settingsViewModel.setClient(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
//mFirstRun = false
|
|
||||||
connectClient()
|
|
||||||
super.onStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
settingsViewModel.removeClient()
|
|
||||||
super.onStop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
|
||||||
@Composable
|
|
||||||
fun SettingsContainer(settingsViewModel: SettingsViewModel = viewModel()) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val storagePermissionState = rememberPermissionState(
|
|
||||||
android.Manifest.permission.READ_EXTERNAL_STORAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
if (storagePermissionState.status.shouldShowRationale) {
|
|
||||||
Column(Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center
|
|
||||||
) {
|
|
||||||
Text(stringResource(id = R.string.external_files_permission_request))
|
|
||||||
Button(onClick = { }) {
|
|
||||||
Text("Request permission")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Column {
|
|
||||||
NetworkAddress()
|
|
||||||
ServerStatus(settingsViewModel)
|
|
||||||
if (!storagePermissionState.status.isGranted) {
|
|
||||||
OutlinedButton(onClick = { storagePermissionState.launchPermissionRequest() }, Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.fillMaxWidth()) {
|
|
||||||
Text("Request external storage permission", color = MaterialTheme.colorScheme.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SettingsOptions(
|
|
||||||
onBootChanged = { newValue ->
|
|
||||||
if (newValue) {
|
|
||||||
settingsViewModel.startMPD(context)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onWakeLockChanged = { newValue ->
|
|
||||||
settingsViewModel.setWakelockEnabled(newValue)
|
|
||||||
},
|
|
||||||
onHeadphonesChanged = { newValue ->
|
|
||||||
settingsViewModel.setPauseOnHeadphonesDisconnect(newValue)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
LogView(settingsViewModel.getLogs().collectAsStateWithLifecycle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ServerStatus(settingsViewModel: SettingsViewModel) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val statusUiState by settingsViewModel.statusUIState.collectAsState()
|
|
||||||
|
|
||||||
Column {
|
|
||||||
Row(
|
|
||||||
Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
|
||||||
Row {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Circle,
|
|
||||||
contentDescription = "",
|
|
||||||
tint = if (statusUiState.running) Color(0xFFB8F397) else Color(0xFFFFDAD6)
|
|
||||||
)
|
|
||||||
Text(text = if (statusUiState.running) "Running" else "Stopped")
|
|
||||||
}
|
|
||||||
Button(onClick = {
|
|
||||||
if (statusUiState.running)
|
|
||||||
settingsViewModel.stopMPD()
|
|
||||||
else
|
|
||||||
settingsViewModel.startMPD(context)
|
|
||||||
}) {
|
|
||||||
Text(text = if (statusUiState.running) "Stop MPD" else "Start MPD")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
|
||||||
Text(text = statusUiState.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LogView(messages: State<List<String>>) {
|
|
||||||
val state = rememberLazyListState()
|
|
||||||
|
|
||||||
LazyColumn(
|
|
||||||
Modifier.padding(4.dp),
|
|
||||||
state
|
|
||||||
) {
|
|
||||||
items(messages.value) { message ->
|
|
||||||
Text(text = message, fontFamily = FontFamily.Monospace)
|
|
||||||
}
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
state.scrollToItem(messages.value.count(), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
|
||||||
@Composable
|
|
||||||
fun SettingsPreview() {
|
|
||||||
MaterialTheme {
|
|
||||||
SettingsContainer()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
package org.musicpd.ui
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.BatteryAlert
|
import androidx.compose.material.icons.filled.BatteryAlert
|
||||||
import androidx.compose.material.icons.filled.Headphones
|
import androidx.compose.material.icons.filled.Headphones
|
||||||
|
@ -7,11 +7,35 @@ import androidx.compose.material.icons.filled.PowerSettingsNew
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.alorma.compose.settings.storage.preferences.rememberPreferenceBooleanSettingState
|
import com.alorma.compose.settings.storage.preferences.rememberPreferenceBooleanSettingState
|
||||||
import com.alorma.compose.settings.ui.SettingsSwitch
|
import com.alorma.compose.settings.ui.SettingsSwitch
|
||||||
import org.musicpd.Preferences
|
import org.musicpd.Preferences
|
||||||
import org.musicpd.R
|
import org.musicpd.R
|
||||||
|
import org.musicpd.ui.SettingsViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MPDSettings(settingsViewModel: SettingsViewModel) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxSize()) {
|
||||||
|
SettingsOptions(
|
||||||
|
onBootChanged = { newValue ->
|
||||||
|
if (newValue) {
|
||||||
|
settingsViewModel.startMPD(context)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onWakeLockChanged = { newValue ->
|
||||||
|
settingsViewModel.setWakelockEnabled(newValue)
|
||||||
|
},
|
||||||
|
onHeadphonesChanged = { newValue ->
|
||||||
|
settingsViewModel.setPauseOnHeadphonesDisconnect(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsOptions(
|
fun SettingsOptions(
|
|
@ -0,0 +1,119 @@
|
||||||
|
package org.musicpd.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Circle
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
import com.google.accompanist.permissions.isGranted
|
||||||
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
|
import org.musicpd.R
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
|
@Composable
|
||||||
|
fun StatusScreen(settingsViewModel: SettingsViewModel) {
|
||||||
|
val storagePermissionState = rememberPermissionState(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
if (storagePermissionState.status.shouldShowRationale) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(stringResource(id = R.string.external_files_permission_request))
|
||||||
|
Button(onClick = { }) {
|
||||||
|
Text("Request permission")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
NetworkAddress()
|
||||||
|
ServerStatus(settingsViewModel)
|
||||||
|
if (!storagePermissionState.status.isGranted) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { storagePermissionState.launchPermissionRequest() }, Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Request external storage permission",
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ServerStatus(settingsViewModel: SettingsViewModel) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val statusUiState by settingsViewModel.statusUIState.collectAsState()
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Circle,
|
||||||
|
contentDescription = "",
|
||||||
|
tint = if (statusUiState.running) Color(0xFFB8F397) else Color(0xFFFFDAD6)
|
||||||
|
)
|
||||||
|
Text(text = if (statusUiState.running) "Running" else "Stopped")
|
||||||
|
}
|
||||||
|
Button(onClick = {
|
||||||
|
if (statusUiState.running)
|
||||||
|
settingsViewModel.stopMPD()
|
||||||
|
else
|
||||||
|
settingsViewModel.startMPD(context)
|
||||||
|
}) {
|
||||||
|
Text(text = if (statusUiState.running) "Stop MPD" else "Start MPD")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
Text(text = statusUiState.statusMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue