mirror of
https://github.com/Assasinnys/RemoteAudioControl.git
synced 2025-04-12 22:37:12 +03:00
Refactored compose ui and states
This commit is contained in:
parent
75baf06c46
commit
266e5eb360
@ -1,5 +0,0 @@
|
||||
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
||||
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun Float.classicRound(): Float = times(100).roundToInt().div(100f)
|
@ -1,14 +1,9 @@
|
||||
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.*
|
||||
import com.dmitryzenevich.remoteaudiocontrol.presentation.theme.RemoteAudioControlTheme
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -23,23 +18,3 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreen(viewModel: MainViewModel = viewModel()) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
LazyRow {
|
||||
items(
|
||||
items = uiState.volumes,
|
||||
key = { item -> item.pid }
|
||||
) { itemState ->
|
||||
VolumeItem(
|
||||
volumeItemState = itemState,
|
||||
onValueChanged = { newValue: Float ->
|
||||
Log.i("debug", "new value: $newValue")
|
||||
viewModel.onVolumeChanged(itemState, newValue.toInt())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun MainScreen(viewModel: MainViewModel = androidx.lifecycle.viewmodel.compose.viewModel()) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
LazyRow(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
items(
|
||||
items = uiState.volumes,
|
||||
key = { item -> item.pid }
|
||||
) { itemState ->
|
||||
VolumeItem(
|
||||
volumeItemState = itemState,
|
||||
onValueChanged = { newValue: Float ->
|
||||
viewModel.onVolumeChanged(itemState, newValue.toInt())
|
||||
},
|
||||
onMuteClick = { viewModel.onMuteClick(itemState) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
|
||||
data class MainScreenUiState(
|
||||
val volumes: SnapshotStateList<VolumeItemState> = mutableStateListOf(),
|
||||
val isError: Boolean = false
|
||||
)
|
||||
|
||||
data class VolumeItemState(
|
||||
val pid: Long,
|
||||
val name: String = "",
|
||||
val volume: MutableState<Int> = mutableStateOf(0),
|
||||
val isMuted: MutableState<Boolean> = mutableStateOf(false),
|
||||
val isActive: Boolean = false
|
||||
)
|
@ -1,11 +1,6 @@
|
||||
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.dmitryzenevich.remoteaudiocontrol.data.SocketRepositoryImpl
|
||||
@ -15,6 +10,8 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
const val NOT_BLOCKED = -1L
|
||||
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
private val socketRepositoryImpl: SocketRepositoryImpl
|
||||
@ -23,7 +20,9 @@ class MainViewModel @Inject constructor(
|
||||
private val _uiState = MutableStateFlow(MainScreenUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
private var commandJob: Job? = null
|
||||
@Volatile
|
||||
private var blockingPid: Long = NOT_BLOCKED
|
||||
private var blockingJob: Job? = null
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
@ -33,7 +32,6 @@ class MainViewModel @Inject constructor(
|
||||
.filterNotNull()
|
||||
.onEach {
|
||||
Log.i(javaClass.simpleName, "received: $it")
|
||||
// TODO: test it
|
||||
withContext(Dispatchers.Main) { proceedEvent(it) }
|
||||
}
|
||||
.catch { Log.e(javaClass.simpleName, "Fetch event error", it) }
|
||||
@ -49,11 +47,7 @@ class MainViewModel @Inject constructor(
|
||||
is NewSessionEvent -> addIfNotExist(event)
|
||||
is MuteStateChangedEvent -> {
|
||||
volumes.find { event.PID == it.pid }?.let { item ->
|
||||
val newItem = item.copy(isMuted = event.isMuted)
|
||||
volumes.set(
|
||||
index = volumes.indexOfFirst { it.pid == newItem.pid },
|
||||
element = newItem
|
||||
)
|
||||
item.isMuted.value = event.isMuted
|
||||
}
|
||||
}
|
||||
is SetNameEvent -> {
|
||||
@ -75,28 +69,40 @@ class MainViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
is VolumeChangedEvent -> {
|
||||
if (blockingPid == event.PID) return
|
||||
|
||||
volumes.find { event.PID == it.pid }?.let { item ->
|
||||
// val newItem = item.copy(volume = event.volume)
|
||||
// volumes.set(
|
||||
// index = volumes.indexOfFirst { it.pid == newItem.pid },
|
||||
// element = newItem
|
||||
// )
|
||||
item.volume.value = event.volume
|
||||
blockVolumeEvent(event.PID)
|
||||
}
|
||||
}
|
||||
UnknownEvent -> _uiState.value.isError // TODO: implement error state
|
||||
}
|
||||
}
|
||||
|
||||
private fun blockVolumeEvent(eventPid: Long) {
|
||||
blockingJob?.cancel()
|
||||
blockingJob = viewModelScope.launch {
|
||||
blockingPid = eventPid
|
||||
delay(200)
|
||||
blockingPid = NOT_BLOCKED
|
||||
}
|
||||
}
|
||||
|
||||
fun onVolumeChanged(volumeItemState: VolumeItemState, newVolume: Int) {
|
||||
Log.i(javaClass.simpleName, "old volume: ${volumeItemState.volume}, newVolume: $newVolume")
|
||||
volumeItemState.volume.value = newVolume
|
||||
sendCommand(SetVolumeCommand(volumeItemState.pid, newVolume))
|
||||
}
|
||||
|
||||
fun onMuteClick(volumeItemState: VolumeItemState) {
|
||||
Log.i(javaClass.simpleName, "new checked: ${!volumeItemState.isMuted.value}")
|
||||
volumeItemState.isMuted.value = !volumeItemState.isMuted.value
|
||||
sendCommand(MuteToggleCommand(volumeItemState.pid))
|
||||
}
|
||||
|
||||
private fun sendCommand(command: Command) {
|
||||
commandJob?.cancel()
|
||||
commandJob = viewModelScope.launch { socketRepositoryImpl.sendCommand(command) }
|
||||
viewModelScope.launch { socketRepositoryImpl.sendCommand(command) }
|
||||
}
|
||||
|
||||
private fun addIfNotExist(event: NewSessionEvent) {
|
||||
@ -104,22 +110,6 @@ class MainViewModel @Inject constructor(
|
||||
_uiState.value.volumes.add(event.toVolumeItemState())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MainScreenUiState(
|
||||
val volumes: SnapshotStateList<VolumeItemState> = mutableStateListOf(),
|
||||
val isError: Boolean = false
|
||||
)
|
||||
|
||||
data class VolumeItemState(
|
||||
val pid: Long,
|
||||
val name: String = "",
|
||||
val volume: MutableState<Int> = mutableStateOf(0),
|
||||
val isMuted: Boolean = false,
|
||||
val isActive: Boolean = false
|
||||
)
|
||||
|
||||
fun NewSessionEvent.toVolumeItemState() = VolumeItemState(pid = PID)
|
||||
|
||||
//fun VolumeChangedEvent.toVolumeItemState() = VolumeItemState(pid = PID, volume = volume)
|
@ -1,50 +1,66 @@
|
||||
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.dmitryzenevich.remoteaudiocontrol.R
|
||||
|
||||
@Composable
|
||||
fun VolumeItem(
|
||||
volumeItemState: VolumeItemState,
|
||||
onValueChanged: (Float) -> Unit
|
||||
onValueChanged: (Float) -> Unit,
|
||||
onMuteClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.wrapContentSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
.fillMaxHeight()
|
||||
.padding(8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Text(
|
||||
text = "${volumeItemState.name}\nVolume: ${volumeItemState.volume.value}"
|
||||
)
|
||||
Text(text = volumeItemState.name)
|
||||
Text(text = volumeItemState.volume.value.toString())
|
||||
VerticalSlider(
|
||||
value = volumeItemState.volume.value.toFloat(),
|
||||
onValueChanged = onValueChanged
|
||||
onValueChanged = onValueChanged,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
)
|
||||
IconButton(onClick = onMuteClick) {
|
||||
if (volumeItemState.isMuted.value)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_volume_off),
|
||||
contentDescription = "Muted"
|
||||
)
|
||||
else
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_volume_up),
|
||||
contentDescription = "Resumed"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VerticalSlider(
|
||||
value: Float,
|
||||
onValueChanged: (Float) -> Unit
|
||||
onValueChanged: (Float) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Slider(
|
||||
value = value,
|
||||
valueRange = 0f..100f,
|
||||
onValueChange = onValueChanged,
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.graphicsLayer {
|
||||
rotationZ = 270f
|
||||
transformOrigin = TransformOrigin(0f, 0f)
|
||||
@ -62,6 +78,6 @@ fun VerticalSlider(
|
||||
placeable.place(-placeable.width, 0)
|
||||
}
|
||||
}
|
||||
.padding(horizontal = 24.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
}
|
5
app/src/main/res/drawable/ic_volume_off.xml
Normal file
5
app/src/main/res/drawable/ic_volume_off.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_volume_up.xml
Normal file
5
app/src/main/res/drawable/ic_volume_up.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
|
||||
</vector>
|
Loading…
x
Reference in New Issue
Block a user