From 283c59d382286fc48eb900a6a9d4f9481e2058bd Mon Sep 17 00:00:00 2001 From: Rien Maertens Date: Sun, 25 Jul 2021 00:46:12 +0200 Subject: [PATCH] Create basic UI for devices --- .../me/vanpetegem/accentor/devices/Device.kt | 8 +- .../accentor/devices/DeviceManager.kt | 6 +- .../accentor/ui/devices/DevicesView.kt | 85 ++++++++++++------- .../accentor/ui/devices/DevicesViewModel.kt | 9 +- .../accentor/ui/main/MainActivity.kt | 2 +- .../main/res/drawable/ic_smartphone_sound.xml | 8 ++ app/src/main/res/values/strings.xml | 3 + 7 files changed, 82 insertions(+), 39 deletions(-) create mode 100644 app/src/main/res/drawable/ic_smartphone_sound.xml diff --git a/app/src/main/java/me/vanpetegem/accentor/devices/Device.kt b/app/src/main/java/me/vanpetegem/accentor/devices/Device.kt index 245a5640..5f33c9b3 100644 --- a/app/src/main/java/me/vanpetegem/accentor/devices/Device.kt +++ b/app/src/main/java/me/vanpetegem/accentor/devices/Device.kt @@ -8,7 +8,7 @@ sealed class Device( ) { val friendlyName: String = clingDevice.details.friendlyName - val firstCharacter: String = String(intArrayOf(friendlyName.codePointAt(0)), 0, 1) + val displayString: String = clingDevice.displayString val type: String = clingDevice.type.displayString val imageURL: String? = clingDevice @@ -17,6 +17,10 @@ sealed class Device( ?.let { clingDevice.normalizeURI(it.uri).toString() } + class Ready(clingDevice: RemoteDevice): Device(clingDevice) {} + + class Failed(clingDevice: RemoteDevice, val exception: Exception?): Device(clingDevice) {} + class Discovered(clingDevice: RemoteDevice): Device(clingDevice) { fun failed(exception: Exception?): Failed { return Failed(clingDevice, exception) @@ -26,8 +30,6 @@ sealed class Device( return Ready(clingDevice) } } - class Failed(clingDevice: RemoteDevice, val exception: Exception?): Device(clingDevice) {} - class Ready(clingDevice: RemoteDevice): Device(clingDevice) {} } diff --git a/app/src/main/java/me/vanpetegem/accentor/devices/DeviceManager.kt b/app/src/main/java/me/vanpetegem/accentor/devices/DeviceManager.kt index 5887dd7d..4d0162ae 100644 --- a/app/src/main/java/me/vanpetegem/accentor/devices/DeviceManager.kt +++ b/app/src/main/java/me/vanpetegem/accentor/devices/DeviceManager.kt @@ -5,11 +5,8 @@ import android.content.ComponentName import android.content.ServiceConnection import android.os.IBinder import android.util.Log -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import dagger.Reusable import org.fourthline.cling.android.AndroidUpnpService import org.fourthline.cling.model.message.header.ServiceTypeHeader import org.fourthline.cling.model.meta.RemoteDevice @@ -17,7 +14,6 @@ import org.fourthline.cling.model.types.ServiceType import org.fourthline.cling.model.types.UDN import org.fourthline.cling.registry.DefaultRegistryListener import org.fourthline.cling.registry.Registry -import java.lang.Exception import javax.inject.Inject import javax.inject.Singleton @@ -25,6 +21,8 @@ import javax.inject.Singleton class DeviceManager @Inject constructor() { val devices = MutableLiveData>(emptyMap()) + val selectedDevice = MutableLiveData(null) + val connection = DeviceServiceConnection() private lateinit var upnp: AndroidUpnpService diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/devices/DevicesView.kt b/app/src/main/java/me/vanpetegem/accentor/ui/devices/DevicesView.kt index f54e681c..1da0a8ee 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/devices/DevicesView.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/devices/DevicesView.kt @@ -1,60 +1,85 @@ package me.vanpetegem.accentor.ui.devices +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Card import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import coil.compose.rememberImagePainter import me.vanpetegem.accentor.R import me.vanpetegem.accentor.devices.Device -import me.vanpetegem.accentor.ui.util.FastScrollableGrid @Composable fun Devices(devicesViewModel: DevicesViewModel = hiltViewModel()) { val devices: List? by devicesViewModel.devices().observeAsState() - FastScrollableGrid(devices ?: emptyList(), { it.firstCharacter }) { DeviceCard(it) } + DeviceList(devices ?: emptyList()) } @Composable -fun DeviceCard(device: Device) { +fun DeviceList(devices: List) { + Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { + DeviceCard( + name = stringResource(R.string.local_device), + icon = R.drawable.ic_smartphone_sound, + iconDescription = R.string.local_device_description + ) + Spacer(Modifier.size(8.dp)) + Text( + stringResource(R.string.devices_available), + modifier = Modifier.padding(8.dp), + style = MaterialTheme.typography.h5 + ) + devices.forEach { device -> + DeviceCard( + name = device.friendlyName, + icon = R.drawable.ic_menu_devices + ) + } + } +} + +@Composable +fun DeviceCard( + name: String, + @StringRes + iconDescription: Int = R.string.device_image, + @DrawableRes + icon: Int = R.drawable.ic_menu_devices, + onClick: () -> Unit = {}) { Card( - modifier = Modifier.padding(8.dp), + modifier = Modifier.padding(8.dp).fillMaxWidth().clickable(onClick = onClick) ) { - Column { + Row( + modifier = Modifier.padding(8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { Image( - painter = if (device.imageURL != null) { - rememberImagePainter(device.imageURL) { - placeholder(R.drawable.ic_artist) - } - } else { - painterResource(R.drawable.ic_artist) - }, - contentDescription = stringResource(R.string.device_image), - modifier = Modifier.fillMaxWidth().aspectRatio(1f), - contentScale = ContentScale.Crop, - ) - Text( - device.friendlyName, - maxLines = 1, - modifier = Modifier.padding(4.dp), - style = MaterialTheme.typography.subtitle1, - ) - Text( - device.type + painter = painterResource(icon), + contentDescription = stringResource(iconDescription), + modifier = Modifier.requiredSize(48.dp) ) + Column() { + Text( + name, + maxLines = 1, + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.subtitle1 + ) + } } } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/devices/DevicesViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/devices/DevicesViewModel.kt index 98b3bd3a..c4419c5f 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/devices/DevicesViewModel.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/devices/DevicesViewModel.kt @@ -16,6 +16,13 @@ class DevicesViewModel @Inject constructor( ) : AndroidViewModel(application) { fun devices(): LiveData> = map(deviceManager.devices) { devices -> - devices.values.sortedWith(compareBy({ it.friendlyName }, { it.firstCharacter.uppercase() })) + devices.values.sortedWith(compareBy( + { when(it) { + is Device.Ready -> 1 + is Device.Failed -> 2 + is Device.Discovered -> 3 + }}, + { it.friendlyName }) + ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt b/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt index 9e6c1ea9..2b2c8b8a 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt @@ -130,7 +130,7 @@ fun Content(mainViewModel: MainViewModel = viewModel()) { } PlayerOverlay(navController) { - NavHost(navController = navController, startDestination = "home") { + NavHost(navController = navController, startDestination = "devices") { composable("home") { Base(navController, mainViewModel) { Home(navController) } } composable("artists") { Base(navController, mainViewModel) { ArtistGrid(navController) } } composable("artists/{artistId}", arguments = listOf(navArgument("artistId") { type = NavType.IntType })) { entry -> diff --git a/app/src/main/res/drawable/ic_smartphone_sound.xml b/app/src/main/res/drawable/ic_smartphone_sound.xml new file mode 100644 index 00000000..2967f542 --- /dev/null +++ b/app/src/main/res/drawable/ic_smartphone_sound.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44213fde..7e207250 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -65,4 +65,7 @@ Go to album Go to %s DeviceView + Play on this device + Sound coming from your device + Stream to devices