diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 58f3d60..682240c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -60,6 +60,7 @@ dependencies { implementation(libs.androidx.camera.mlkit.vision) implementation(libs.androidx.camera.extensions) implementation(libs.material3) + implementation(libs.opencsv) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/com/example/tvcontroller/MainActivity.kt b/app/src/main/java/com/example/tvcontroller/MainActivity.kt index ff3770a..57c1a29 100644 --- a/app/src/main/java/com/example/tvcontroller/MainActivity.kt +++ b/app/src/main/java/com/example/tvcontroller/MainActivity.kt @@ -5,11 +5,8 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem @@ -24,7 +21,6 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.example.tvcontroller.ui.theme.TVControllerTheme import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.res.painterResource import androidx.core.app.ActivityCompat import com.example.tvcontroller.services.BluetoothService @@ -32,6 +28,7 @@ import com.example.tvcontroller.services.CameraService import com.example.tvcontroller.services.ControllerService import com.example.tvcontroller.services.DeviceService import com.example.tvcontroller.ui.views.CameraView +import com.example.tvcontroller.ui.views.RemoteView import com.example.tvcontroller.ui.views.SettingsView @@ -48,7 +45,7 @@ class MainActivity : ComponentActivity() { bluetoothService = BluetoothService(this.applicationContext) deviceService = DeviceService(this.applicationContext) cameraService = CameraService(this.applicationContext) - controllerService = ControllerService(bluetoothService) + controllerService = ControllerService(this.applicationContext, bluetoothService) checkPermissions() enableEdgeToEdge() setContent { @@ -125,19 +122,20 @@ fun TvControllerApp( }) { innerPadding -> NavHost( navController = navController, - startDestination = Screen.Settings.name, + startDestination = Screen.Remote.name, modifier = Modifier.padding(innerPadding) ) { composable(route = Screen.Camera.name) { CameraView() } composable(route = Screen.Remote.name) { - RemoteScreen() + RemoteView( + controllerService = controllerService + ) } composable(route = Screen.Settings.name) { SettingsView( deviceService = deviceService, - controllerService = controllerService, bluetoothService = bluetoothService ) } @@ -145,17 +143,3 @@ fun TvControllerApp( } } -@Composable -fun RemoteScreen(modifier: Modifier = Modifier) { - Column( - modifier = modifier - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text(text = "Remote Screen", modifier = modifier) - Button(onClick = { Log.i(TAG, "RemoteScreen: Button clicked") }) { - Text(text = "Button") - } - } -} diff --git a/app/src/main/java/com/example/tvcontroller/SettingsViewModel.kt b/app/src/main/java/com/example/tvcontroller/SettingsViewModel.kt index 67c88ec..888fb1b 100644 --- a/app/src/main/java/com/example/tvcontroller/SettingsViewModel.kt +++ b/app/src/main/java/com/example/tvcontroller/SettingsViewModel.kt @@ -1,7 +1,6 @@ package com.example.tvcontroller import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel @@ -9,14 +8,12 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.example.tvcontroller.data.BluetoothDevice import com.example.tvcontroller.services.BluetoothService -import com.example.tvcontroller.services.ControllerService import com.example.tvcontroller.services.DeviceService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class SettingsViewModel( private val deviceService: DeviceService, - controllerService: ControllerService, private val bluetoothService: BluetoothService ) : ViewModel() { var serverAddress by mutableStateOf(deviceService.getServerAddress()) @@ -103,12 +100,11 @@ class SettingsViewModel( fun provideFactory( deviceService: DeviceService, - controllerService: ControllerService, bluetoothService: BluetoothService ) = object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return SettingsViewModel(deviceService, controllerService, bluetoothService) as T + return SettingsViewModel(deviceService, bluetoothService) as T } } } diff --git a/app/src/main/java/com/example/tvcontroller/data/RemoteCommand.kt b/app/src/main/java/com/example/tvcontroller/data/RemoteCommand.kt new file mode 100644 index 0000000..709e2db --- /dev/null +++ b/app/src/main/java/com/example/tvcontroller/data/RemoteCommand.kt @@ -0,0 +1,9 @@ +package com.example.tvcontroller.data + +class RemoteCommand { + var functionName: String? = null + var protocol: String? = null + var device: String? = null + var subdevice: String? = null + var function: String? = null +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tvcontroller/services/BluetoothService.kt b/app/src/main/java/com/example/tvcontroller/services/BluetoothService.kt index f41bff3..b7757be 100644 --- a/app/src/main/java/com/example/tvcontroller/services/BluetoothService.kt +++ b/app/src/main/java/com/example/tvcontroller/services/BluetoothService.kt @@ -152,6 +152,8 @@ class BluetoothService(private val context: Context) { } } catch (e: IOException) { Log.e(TAG, "Error reading from socket: $e") + } catch (e: Exception) { + Log.e(TAG, "Error receiving data: $e") } } } diff --git a/app/src/main/java/com/example/tvcontroller/services/ControllerService.kt b/app/src/main/java/com/example/tvcontroller/services/ControllerService.kt index 4880587..27dc16f 100644 --- a/app/src/main/java/com/example/tvcontroller/services/ControllerService.kt +++ b/app/src/main/java/com/example/tvcontroller/services/ControllerService.kt @@ -1,39 +1,102 @@ package com.example.tvcontroller.services -import android.bluetooth.BluetoothAdapter +import android.content.Context +import android.util.Log +import com.example.tvcontroller.R +import com.example.tvcontroller.data.RemoteCommand +import com.opencsv.CSVReader +import org.json.JSONObject -class ControllerService(bluetoothService: BluetoothService) { - private var connectionStateChangedListener: MutableList<(Int) -> Unit> = mutableListOf() - var connectionState = BLUETOOTH_DISABLED - private set +class ControllerService( + private val context: Context, + private val bluetoothService: BluetoothService +) { + private val samsungCommands = mutableMapOf() init { - handleBluetoothStateChanged(bluetoothService.state) - bluetoothService.onBluetoothStateChanged { - handleBluetoothStateChanged(it) - } - } - - fun onConnectionStateChanged(callback: (Int) -> Unit) { - connectionStateChangedListener.add(callback) - } - - private fun setConnectionState(state: Int) { - connectionState = state - connectionStateChangedListener.forEach { it(state) } - } - - private fun handleBluetoothStateChanged(state: String) { - when (state) { - BluetoothService.STATE_OFF -> setConnectionState(BLUETOOTH_DISABLED) - else -> setConnectionState(NOT_CONNECTED) + loadCommands() + } + + fun sendCommand(command: String) { + if (samsungCommands[command] == null) return + Log.i("ControllerService", "Sending command: $command") + val jsonString = remoteCommandToJsonString(samsungCommands[command]!!) + sendData(jsonString) + } + + fun remoteCommandToJsonString(command: RemoteCommand): String { + var commandObject = JSONObject() + commandObject.put("protocol", command.protocol) + commandObject.put("address", command.device) + commandObject.put("command", command.function) + return commandObject.toString() + } + + fun sendData(data: String) { + bluetoothService.sendData(data) + } + + fun loadCommands() { + Log.i("ControllerService", "Loading commands"); + var inputStream = context.resources.openRawResource(R.raw.samsung) + var csvReader = CSVReader(inputStream.reader()) + csvReader.forEach { nextLine -> + if (nextLine.size < 5) return@forEach + if (nextLine[0] == "functionname") return@forEach + var remoteCommand = RemoteCommand() + remoteCommand.functionName = nextLine[0] + remoteCommand.protocol = "samsung" + remoteCommand.device = nextLine[2] + remoteCommand.subdevice = nextLine[3] + remoteCommand.function = nextLine[4] + samsungCommands[remoteCommand.functionName!!] = remoteCommand } + Log.i("ControllerService", "Commands loaded: ${samsungCommands.size}") } companion object { - const val BLUETOOTH_DISABLED = 0 - const val NOT_CONNECTED = 1 - const val CONNECTED = 2 + const val POWER = "POWER" + const val CURSOR_UP = "CURSOR UP" + const val CURSOR_DOWN = "CURSOR DOWN" + const val CURSOR_LEFT = "CURSOR LEFT" + const val CURSOR_RIGHT = "CURSOR RIGHT" + const val ENTER = "ENTER" + const val ONE = "1" + const val TWO = "2" + const val THREE = "3" + const val FOUR = "4" + const val FIVE = "5" + const val SIX = "6" + const val SEVEN = "7" + const val EIGHT = "8" + const val NINE = "9" + const val ZERO = "0" + const val CHANNEL_UP = "CHANNEL +" + const val CHANNEL_DOWN = "CHANNEL -" + const val VOLUME_UP = "VOLUME +" + const val VOLUME_DOWN = "VOLUME -" + const val MUTE = "MUTE" + const val GUIDE = "GUIDE" + const val INFO = "INFO" + const val AD_SUBTITLE = "AD/SUBT" + const val E_MANUAL = "E-MANUAL" + const val TOOLS = "TOOLS" + const val RETURN = "RETURN" + const val MENU = "MENU" + const val SMART_HUB = "SMART HUB" + const val EXIT = "EXIT" + const val CHANNEL_LIST = "CH LIST" + const val HOME = "HOME" + const val SETTINGS = "SETTINGS" + const val RED = "RED" + const val GREEN = "GREEN" + const val YELLOW = "YELLOW" + const val BLUE = "BLUE" + const val INPUT_SOURCE = "INPUT SOURCE" + const val REWIND = "REWIND" + const val PLAY = "PLAY" + const val PAUSE = "PAUSE" + const val FORWARD = "FORWARD" } } \ No newline at end of file diff --git a/app/src/main/java/com/example/tvcontroller/ui/components/RemoteButton.kt b/app/src/main/java/com/example/tvcontroller/ui/components/RemoteButton.kt new file mode 100644 index 0000000..1072d14 --- /dev/null +++ b/app/src/main/java/com/example/tvcontroller/ui/components/RemoteButton.kt @@ -0,0 +1,44 @@ +package com.example.tvcontroller.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun RemoteButton( + modifier: Modifier = Modifier, + text: String? = "", + onClick: () -> Unit, + icon: Painter? = null, + width: Dp = 64.dp, +) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .width(width) + .clip(RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant) + .clickable(onClick = onClick) + .then(modifier) + ) { + if (!text.isNullOrBlank()) Text(text = text) + if (icon != null) { + Icon( + icon, + contentDescription = "Settings" + ) + } + } +} diff --git a/app/src/main/java/com/example/tvcontroller/ui/components/RemoteButtonPlaceholder.kt b/app/src/main/java/com/example/tvcontroller/ui/components/RemoteButtonPlaceholder.kt new file mode 100644 index 0000000..a399844 --- /dev/null +++ b/app/src/main/java/com/example/tvcontroller/ui/components/RemoteButtonPlaceholder.kt @@ -0,0 +1,18 @@ +package com.example.tvcontroller.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun RemoteButtonPlaceholder( + modifier: Modifier = Modifier, +) { + Box( + modifier = Modifier + .width(64.dp) + .then(modifier) + ) {} +} diff --git a/app/src/main/java/com/example/tvcontroller/ui/views/RemoteView.kt b/app/src/main/java/com/example/tvcontroller/ui/views/RemoteView.kt new file mode 100644 index 0000000..a59cacf --- /dev/null +++ b/app/src/main/java/com/example/tvcontroller/ui/views/RemoteView.kt @@ -0,0 +1,269 @@ +package com.example.tvcontroller.ui.views + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.tvcontroller.R +import com.example.tvcontroller.services.ControllerService +import com.example.tvcontroller.ui.components.RemoteButton +import com.example.tvcontroller.ui.components.RemoteButtonPlaceholder + +@Composable +fun RemoteView(modifier: Modifier = Modifier, controllerService: ControllerService) { + val viewModel = viewModel( + factory = RemoteViewModel.provideFactory( + controllerService + ) + ) + + Column( + modifier = modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.POWER) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_power_settings_new_24) + ) + RemoteButtonPlaceholder() + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.INPUT_SOURCE) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_login_24) + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.ONE) }, + modifier = Modifier.aspectRatio(1.5f), + text = "1" + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.TWO) }, + modifier = Modifier.aspectRatio(1.5f), + text = "2" + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.THREE) }, + modifier = Modifier.aspectRatio(1.5f), + text = "3" + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.FOUR) }, + modifier = Modifier.aspectRatio(1.5f), + text = "4" + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.FIVE) }, + modifier = Modifier.aspectRatio(1.5f), + text = "5" + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.SIX) }, + modifier = Modifier.aspectRatio(1.5f), + text = "6" + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.SEVEN) }, + modifier = Modifier.aspectRatio(1.5f), + text = "7" + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.EIGHT) }, + modifier = Modifier.aspectRatio(1.5f), + text = "8" + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.NINE) }, + modifier = Modifier.aspectRatio(1.5f), + text = "9" + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.VOLUME_UP) }, + modifier = Modifier.aspectRatio(1.5f), + icon=painterResource(R.drawable.baseline_volume_up_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.ZERO) }, + modifier = Modifier.aspectRatio(1.5f), + text = "0" + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.CHANNEL_UP) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_keyboard_arrow_up_24) + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.VOLUME_DOWN) }, + modifier = Modifier.aspectRatio(1.5f), + icon=painterResource(R.drawable.baseline_volume_down_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.MUTE) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_volume_off_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.CHANNEL_DOWN) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_keyboard_arrow_down_24) + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.MENU) }, + modifier = Modifier.aspectRatio(1.5f), + text = "MENU" + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.HOME) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_home_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.SETTINGS) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_settings_24) + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.CHANNEL_LIST) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_list_alt_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.CURSOR_UP) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_arrow_upward_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.INFO) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_info_24) + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.CURSOR_LEFT) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_arrow_back_24) + ) + RemoteButton( + text = "OK", + onClick = { viewModel.sendCommand(ControllerService.ENTER) }, + modifier = Modifier.aspectRatio(1.5f), + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.CURSOR_RIGHT) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_arrow_forward_24) + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.RETURN) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_keyboard_return_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.CURSOR_DOWN) }, + modifier = Modifier.aspectRatio(1.5f), + icon = painterResource(id = R.drawable.baseline_arrow_downward_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.EXIT) }, + modifier = Modifier.aspectRatio(1.5f), + text = "EXIT" + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + val width = 46.dp + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.RED) }, + modifier = Modifier.aspectRatio(1.5f).background(color = Color.Red).clip( + RoundedCornerShape(12.dp) + ), + width = width, + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.GREEN) }, + modifier = Modifier.aspectRatio(1.5f).background(color = Color.Green).clip( + RoundedCornerShape(12.dp) + ), + width = width, + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.YELLOW) }, + modifier = Modifier.aspectRatio(1.5f).background(color = Color.Yellow).clip( + RoundedCornerShape(12.dp) + ), + width = width, + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.BLUE) }, + modifier = Modifier.aspectRatio(1.5f).background(color = Color.Blue).clip( + RoundedCornerShape(12.dp) + ), + width = width, + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + val width = 46.dp + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.REWIND) }, + modifier = Modifier.aspectRatio(1.5f), + width = width, + icon = painterResource(id = R.drawable.baseline_fast_rewind_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.PLAY) }, + modifier = Modifier.aspectRatio(1.5f), + width = width, + icon = painterResource(id = R.drawable.baseline_play_arrow_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.PAUSE) }, + modifier = Modifier.aspectRatio(1.5f), + width = width, + icon = painterResource(id = R.drawable.baseline_pause_24) + ) + RemoteButton( + onClick = { viewModel.sendCommand(ControllerService.FORWARD) }, + modifier = Modifier.aspectRatio(1.5f), + width = width, + icon = painterResource(id = R.drawable.baseline_fast_forward_24) + ) + } + } + } +} diff --git a/app/src/main/java/com/example/tvcontroller/ui/views/RemoteViewModel.kt b/app/src/main/java/com/example/tvcontroller/ui/views/RemoteViewModel.kt new file mode 100644 index 0000000..deb94be --- /dev/null +++ b/app/src/main/java/com/example/tvcontroller/ui/views/RemoteViewModel.kt @@ -0,0 +1,32 @@ +package com.example.tvcontroller.ui.views + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.example.tvcontroller.services.BluetoothService +import com.example.tvcontroller.services.ControllerService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.json.JSONObject + +class RemoteViewModel( + private val controllerService: ControllerService +) : ViewModel() { + + fun sendCommand(command: String) { + viewModelScope.launch(Dispatchers.IO) { + controllerService.sendCommand(command) + } + } + + companion object { + fun provideFactory( + controllerService: ControllerService + ) = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return RemoteViewModel(controllerService) as T + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt b/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt index db6591e..4df031f 100644 --- a/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt +++ b/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt @@ -42,12 +42,11 @@ import com.example.tvcontroller.services.DeviceService @Composable fun SettingsView( deviceService: DeviceService, - controllerService: ControllerService, bluetoothService: BluetoothService ) { val viewModel = viewModel( factory = SettingsViewModel.provideFactory( - deviceService, controllerService, bluetoothService + deviceService, bluetoothService ) ) val navController = rememberNavController() diff --git a/app/src/main/res/drawable/baseline_arrow_downward_24.xml b/app/src/main/res/drawable/baseline_arrow_downward_24.xml new file mode 100644 index 0000000..f540e4e --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_downward_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_arrow_forward_24.xml b/app/src/main/res/drawable/baseline_arrow_forward_24.xml new file mode 100644 index 0000000..77e4f24 --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_forward_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_arrow_upward_24.xml b/app/src/main/res/drawable/baseline_arrow_upward_24.xml new file mode 100644 index 0000000..9696eb6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_upward_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_fast_forward_24.xml b/app/src/main/res/drawable/baseline_fast_forward_24.xml new file mode 100644 index 0000000..a19b235 --- /dev/null +++ b/app/src/main/res/drawable/baseline_fast_forward_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_fast_rewind_24.xml b/app/src/main/res/drawable/baseline_fast_rewind_24.xml new file mode 100644 index 0000000..0313dac --- /dev/null +++ b/app/src/main/res/drawable/baseline_fast_rewind_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_home_24.xml b/app/src/main/res/drawable/baseline_home_24.xml new file mode 100644 index 0000000..20cb4d6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_home_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_info_24.xml b/app/src/main/res/drawable/baseline_info_24.xml new file mode 100644 index 0000000..ef3a1fe --- /dev/null +++ b/app/src/main/res/drawable/baseline_info_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml new file mode 100644 index 0000000..1a69b23 --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml new file mode 100644 index 0000000..a0f7d8c --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_return_24.xml b/app/src/main/res/drawable/baseline_keyboard_return_24.xml new file mode 100644 index 0000000..c2c25b7 --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_return_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_list_alt_24.xml b/app/src/main/res/drawable/baseline_list_alt_24.xml new file mode 100644 index 0000000..93cb69e --- /dev/null +++ b/app/src/main/res/drawable/baseline_list_alt_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_login_24.xml b/app/src/main/res/drawable/baseline_login_24.xml new file mode 100644 index 0000000..d99fc66 --- /dev/null +++ b/app/src/main/res/drawable/baseline_login_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_pause_24.xml b/app/src/main/res/drawable/baseline_pause_24.xml new file mode 100644 index 0000000..ae853f2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_pause_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_play_arrow_24.xml b/app/src/main/res/drawable/baseline_play_arrow_24.xml new file mode 100644 index 0000000..b176182 --- /dev/null +++ b/app/src/main/res/drawable/baseline_play_arrow_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_power_settings_new_24.xml b/app/src/main/res/drawable/baseline_power_settings_new_24.xml new file mode 100644 index 0000000..6cc717e --- /dev/null +++ b/app/src/main/res/drawable/baseline_power_settings_new_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_volume_down_24.xml b/app/src/main/res/drawable/baseline_volume_down_24.xml new file mode 100644 index 0000000..bebf09e --- /dev/null +++ b/app/src/main/res/drawable/baseline_volume_down_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_volume_off_24.xml b/app/src/main/res/drawable/baseline_volume_off_24.xml new file mode 100644 index 0000000..17567d1 --- /dev/null +++ b/app/src/main/res/drawable/baseline_volume_off_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_volume_up_24.xml b/app/src/main/res/drawable/baseline_volume_up_24.xml new file mode 100644 index 0000000..a5df024 --- /dev/null +++ b/app/src/main/res/drawable/baseline_volume_up_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/raw/samsung.csv b/app/src/main/res/raw/samsung.csv new file mode 100644 index 0000000..767f603 --- /dev/null +++ b/app/src/main/res/raw/samsung.csv @@ -0,0 +1,39 @@ +functionname,protocol,device,subdevice,function +INPUT SOURCE,NECx2,7,7,1 +POWER,NECx2,7,7,2 +1,NECx2,7,7,4 +2,NECx2,7,7,5 +3,NECx2,7,7,6 +VOLUME +,NECx2,7,7,7 +4,NECx2,7,7,8 +5,NECx2,7,7,9 +6,NECx2,7,7,10 +VOLUME -,NECx2,7,7,11 +7,NECx2,7,7,12 +8,NECx2,7,7,13 +9,NECx2,7,7,14 +MUTE,NECx2,7,7,15 +CHANNEL -,NECx2,7,7,16 +0,NECx2,7,7,17 +CHANNEL +,NECx2,7,7,18 +LAST,NECx2,7,7,19 +MENU,NECx2,7,7,26 +INFO,NECx2,7,7,31 +AD/SUBT,NECx2,7,7,37 +EXIT,NECx2,7,7,45 +E-MANUAL,NECx2,7,7,63 +TOOLS,NECx2,7,7,75 +GUIDE,NECx2,7,7,79 +RETURN,NECx2,7,7,88 +CURSOR UP,NECx2,7,7,96 +CURSOR DOWN,NECx2,7,7,97 +CURSOR RIGHT,NECx2,7,7,98 +CURSOR LEFT,NECx2,7,7,101 +ENTER,NECx2,7,7,104 +CH LIST,NECx2,7,7,107 +SMART HUB,NECx2,7,7,121 +3D,NECx2,7,7,159 +HDMI2,NECx2,7,7,190 +HDMI3,NECx2,7,7,194 +HDMI4,NECx2,7,7,197 +HDMI1,NECx2,7,7,233 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 349d121..05418c1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ activityCompose = "1.8.0" composeBom = "2024.04.01" material3 = "1.4.0-alpha10" navigationCompose = "2.8.4" +opencsv = "4.6" [libraries] androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" } @@ -39,6 +40,7 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } +opencsv = { module = "com.opencsv:opencsv", version.ref = "opencsv" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }