mirror of
https://github.com/Assasinnys/RemoteAudioControl.git
synced 2025-04-13 06:47:13 +03:00
pre-build
This commit is contained in:
parent
0500756be8
commit
75baf06c46
@ -68,4 +68,6 @@ dependencies {
|
|||||||
|
|
||||||
implementation "com.squareup.moshi:moshi:1.14.0"
|
implementation "com.squareup.moshi:moshi:1.14.0"
|
||||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.14.0"
|
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.14.0"
|
||||||
|
|
||||||
|
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
|
||||||
}
|
}
|
@ -1,105 +1,45 @@
|
|||||||
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.Slider
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.*
|
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.unit.Constraints
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.dmitryzenevich.remoteaudiocontrol.presentation.theme.RemoteAudioControlTheme
|
import com.dmitryzenevich.remoteaudiocontrol.presentation.theme.RemoteAudioControlTheme
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
private val viewModel by viewModels<MainViewModel>()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
RemoteAudioControlTheme {
|
RemoteAudioControlTheme {
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
MainScreen()
|
||||||
LazyRow {
|
|
||||||
items(uiState.volumes) { itemState ->
|
|
||||||
VolumeItem(itemState, viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VolumeItem(volumeItemState: VolumeItemState, viewModel: MainViewModel) {
|
fun MainScreen(viewModel: MainViewModel = viewModel()) {
|
||||||
var volume by remember { mutableStateOf(volumeItemState.volume) }
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
volume = volumeItemState.volume
|
|
||||||
val onValueChanged = { newValue: Float ->
|
|
||||||
Log.i("debug", "new value: $newValue")
|
|
||||||
viewModel.onVolumeChanged(volumeItemState, newValue.toInt())
|
|
||||||
volume = newValue.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
LazyRow {
|
||||||
modifier = Modifier
|
items(
|
||||||
.padding(8.dp)
|
items = uiState.volumes,
|
||||||
.wrapContentSize(),
|
key = { item -> item.pid }
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
) { itemState ->
|
||||||
) {
|
VolumeItem(
|
||||||
Text(
|
volumeItemState = itemState,
|
||||||
text = "${volumeItemState.name}\nVolume: ${volumeItemState.volume}"
|
onValueChanged = { newValue: Float ->
|
||||||
)
|
Log.i("debug", "new value: $newValue")
|
||||||
VerticalSlider(
|
viewModel.onVolumeChanged(itemState, newValue.toInt())
|
||||||
value = /*volumeItemState.volume.toFloat()*/volume.toFloat(),
|
|
||||||
onValueChanged = onValueChanged
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun VerticalSlider(
|
|
||||||
value: Float,
|
|
||||||
onValueChanged: (Float) -> Unit
|
|
||||||
) {
|
|
||||||
Slider(
|
|
||||||
value = value,
|
|
||||||
valueRange = 0f..100f,
|
|
||||||
steps = 99,
|
|
||||||
onValueChange = onValueChanged,
|
|
||||||
modifier = Modifier
|
|
||||||
.graphicsLayer {
|
|
||||||
rotationZ = 270f
|
|
||||||
transformOrigin = TransformOrigin(0f, 0f)
|
|
||||||
}
|
|
||||||
.layout { measurable, constraints ->
|
|
||||||
val placeable = measurable.measure(
|
|
||||||
Constraints(
|
|
||||||
minWidth = constraints.minHeight,
|
|
||||||
maxWidth = constraints.maxHeight,
|
|
||||||
minHeight = constraints.minWidth,
|
|
||||||
maxHeight = constraints.maxHeight,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
layout(placeable.height, placeable.width) {
|
|
||||||
placeable.place(-placeable.width, 0)
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
.padding(horizontal = 24.dp)
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
package com.dmitryzenevich.remoteaudiocontrol.presentation
|
||||||
|
|
||||||
import android.util.Log
|
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.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.dmitryzenevich.remoteaudiocontrol.data.SocketRepositoryImpl
|
import com.dmitryzenevich.remoteaudiocontrol.data.SocketRepositoryImpl
|
||||||
@ -9,7 +14,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MainViewModel @Inject constructor(
|
class MainViewModel @Inject constructor(
|
||||||
@ -23,7 +27,7 @@ class MainViewModel @Inject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val isReady = socketRepositoryImpl.openSocketConnection("10.0.2.2", 54683)
|
val isReady = socketRepositoryImpl.openSocketConnection(/*"10.0.2.2"*/"192.168.100.9", 54683)
|
||||||
if (isReady) {
|
if (isReady) {
|
||||||
socketRepositoryImpl.bindSocketInput()
|
socketRepositoryImpl.bindSocketInput()
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
@ -39,23 +43,18 @@ class MainViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun proceedEvent(event: Event) {
|
private fun proceedEvent(event: Event) {
|
||||||
val volumes = _uiState.value.volumes.toMutableList()
|
val volumes = _uiState.value.volumes
|
||||||
|
|
||||||
when(event) {
|
when(event) {
|
||||||
is NewSessionEvent -> {
|
is NewSessionEvent -> addIfNotExist(event)
|
||||||
// if (_uiState.value.volumes.find { it.pid == event.PID } == null) {
|
|
||||||
// _uiState.value = MainScreenUiState(volumes = _uiState.value.volumes + event.toVolumeItemState())
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
is MuteStateChangedEvent -> {
|
is MuteStateChangedEvent -> {
|
||||||
volumes.find { event.PID == it.pid }?.let { item ->
|
volumes.find { event.PID == it.pid }?.let { item ->
|
||||||
val newItem = item.copy(isMuted = event.isMuted)
|
val newItem = item.copy(isMuted = event.isMuted)
|
||||||
volumes.set(
|
volumes.set(
|
||||||
index = volumes.indexOfFirst { it.pid == newItem.pid },
|
index = volumes.indexOfFirst { it.pid == newItem.pid },
|
||||||
element = newItem
|
element = newItem
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_uiState.value = _uiState.value.copy(volumes = volumes)
|
|
||||||
}
|
}
|
||||||
is SetNameEvent -> {
|
is SetNameEvent -> {
|
||||||
volumes.find { event.PID == it.pid }?.let { item ->
|
volumes.find { event.PID == it.pid }?.let { item ->
|
||||||
@ -65,7 +64,6 @@ class MainViewModel @Inject constructor(
|
|||||||
element = newItem
|
element = newItem
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_uiState.value = _uiState.value.copy(volumes = volumes)
|
|
||||||
}
|
}
|
||||||
is StateChangedEvent -> {
|
is StateChangedEvent -> {
|
||||||
volumes.find { event.PID == it.pid }?.let { item ->
|
volumes.find { event.PID == it.pid }?.let { item ->
|
||||||
@ -75,40 +73,24 @@ class MainViewModel @Inject constructor(
|
|||||||
element = newItem
|
element = newItem
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_uiState.value = _uiState.value.copy(volumes = volumes)
|
|
||||||
}
|
}
|
||||||
is VolumeChangedEvent -> {
|
is VolumeChangedEvent -> {
|
||||||
addIfNotExist(event)
|
volumes.find { event.PID == it.pid }?.let { item ->
|
||||||
val v = _uiState.value.volumes.toMutableList()
|
// val newItem = item.copy(volume = event.volume)
|
||||||
|
// volumes.set(
|
||||||
v.find { event.PID == it.pid }?.let { item ->
|
// index = volumes.indexOfFirst { it.pid == newItem.pid },
|
||||||
val newItem = item.copy(volume = event.volume)
|
// element = newItem
|
||||||
v.set(
|
// )
|
||||||
index = v.indexOfFirst { it.pid == newItem.pid },
|
item.volume.value = event.volume
|
||||||
element = newItem
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
_uiState.value = _uiState.value.copy(volumes = v)
|
|
||||||
}
|
}
|
||||||
UnknownEvent -> _uiState.value = _uiState.value.copy(isError = true)
|
UnknownEvent -> _uiState.value.isError // TODO: implement error state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onVolumeChanged(volumeItemState: VolumeItemState, newVolume: Int) {
|
fun onVolumeChanged(volumeItemState: VolumeItemState, newVolume: Int) {
|
||||||
Log.i(javaClass.simpleName, "old volume: ${volumeItemState.volume}, newVolume: $newVolume")
|
Log.i(javaClass.simpleName, "old volume: ${volumeItemState.volume}, newVolume: $newVolume")
|
||||||
val increment = newVolume.minus(volumeItemState.volume)
|
volumeItemState.volume.value = newVolume
|
||||||
Log.i(javaClass.simpleName, "increment: $increment")
|
|
||||||
|
|
||||||
// val volumes = _uiState.value.volumes.toMutableList()
|
|
||||||
// volumes.find { volumeItemState.pid == it.pid }?.let { item ->
|
|
||||||
// val newItem = item.copy(volume = newVolume)
|
|
||||||
// volumes.set(
|
|
||||||
// index = volumes.indexOfFirst { it.pid == newItem.pid },
|
|
||||||
// element = newItem
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// _uiState.value = _uiState.value.copy(volumes = volumes)
|
|
||||||
|
|
||||||
sendCommand(SetVolumeCommand(volumeItemState.pid, newVolume))
|
sendCommand(SetVolumeCommand(volumeItemState.pid, newVolume))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,27 +99,27 @@ class MainViewModel @Inject constructor(
|
|||||||
commandJob = viewModelScope.launch { socketRepositoryImpl.sendCommand(command) }
|
commandJob = viewModelScope.launch { socketRepositoryImpl.sendCommand(command) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addIfNotExist(event: VolumeChangedEvent) {
|
private fun addIfNotExist(event: NewSessionEvent) {
|
||||||
if (_uiState.value.volumes.find { it.pid == event.PID } == null) {
|
if (_uiState.value.volumes.find { it.pid == event.PID } == null) {
|
||||||
_uiState.value = MainScreenUiState(volumes = _uiState.value.volumes + event.toVolumeItemState())
|
_uiState.value.volumes.add(event.toVolumeItemState())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MainScreenUiState(
|
class MainScreenUiState(
|
||||||
val volumes: List<VolumeItemState> = emptyList(),
|
val volumes: SnapshotStateList<VolumeItemState> = mutableStateListOf(),
|
||||||
val isError: Boolean = false
|
val isError: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
data class VolumeItemState(
|
data class VolumeItemState(
|
||||||
val pid: Long,
|
val pid: Long,
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val volume: Int = 0,
|
val volume: MutableState<Int> = mutableStateOf(0),
|
||||||
val isMuted: Boolean = false,
|
val isMuted: Boolean = false,
|
||||||
val isActive: Boolean = false
|
val isActive: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
fun NewSessionEvent.toVolumeItemState() = VolumeItemState(pid = PID)
|
fun NewSessionEvent.toVolumeItemState() = VolumeItemState(pid = PID)
|
||||||
|
|
||||||
fun VolumeChangedEvent.toVolumeItemState() = VolumeItemState(pid = PID, volume = volume)
|
//fun VolumeChangedEvent.toVolumeItemState() = VolumeItemState(pid = PID, volume = volume)
|
@ -0,0 +1,67 @@
|
|||||||
|
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.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.unit.Constraints
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VolumeItem(
|
||||||
|
volumeItemState: VolumeItemState,
|
||||||
|
onValueChanged: (Float) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.wrapContentSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${volumeItemState.name}\nVolume: ${volumeItemState.volume.value}"
|
||||||
|
)
|
||||||
|
VerticalSlider(
|
||||||
|
value = volumeItemState.volume.value.toFloat(),
|
||||||
|
onValueChanged = onValueChanged
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VerticalSlider(
|
||||||
|
value: Float,
|
||||||
|
onValueChanged: (Float) -> Unit
|
||||||
|
) {
|
||||||
|
Slider(
|
||||||
|
value = value,
|
||||||
|
valueRange = 0f..100f,
|
||||||
|
onValueChange = onValueChanged,
|
||||||
|
modifier = Modifier
|
||||||
|
.graphicsLayer {
|
||||||
|
rotationZ = 270f
|
||||||
|
transformOrigin = TransformOrigin(0f, 0f)
|
||||||
|
}
|
||||||
|
.layout { measurable, constraints ->
|
||||||
|
val placeable = measurable.measure(
|
||||||
|
Constraints(
|
||||||
|
minWidth = constraints.minHeight,
|
||||||
|
maxWidth = constraints.maxHeight,
|
||||||
|
minHeight = constraints.minWidth,
|
||||||
|
maxHeight = constraints.maxHeight,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
layout(placeable.height, placeable.width) {
|
||||||
|
placeable.place(-placeable.width, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user