feat: add bluetooth service and viewmodel

This commit is contained in:
Fritz Heiden 2024-12-11 16:48:53 +01:00
parent a7184ece91
commit 421b1719be
6 changed files with 109 additions and 19 deletions

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application <application
android:allowBackup="true" android:allowBackup="true"

View File

@ -0,0 +1,29 @@
package com.example.tvcontroller
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.app.Activity
import androidx.activity.result.ActivityResultLauncher
import androidx.core.app.ComponentActivity
import androidx.core.content.ContextCompat.getSystemService
object BluetoothService {
private lateinit var bluetoothManager: BluetoothManager;
private var bluetoothAdapter: BluetoothAdapter? = null;
fun init(context: Context) {
bluetoothManager = getSystemService(context, BluetoothManager::class.java)!!
bluetoothAdapter = bluetoothManager.adapter
if (bluetoothAdapter == null) {
throw Exception("Bluetooth not supported on this device")
}
}
fun isEnabled(): Boolean {
return bluetoothAdapter?.isEnabled?: false
}
}

View File

@ -1,20 +1,23 @@
package com.example.tvcontroller package com.example.tvcontroller
import android.content.ContentValues.TAG
import android.os.Bundle import android.os.Bundle
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.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material3.Button
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
@ -23,12 +26,16 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.tvcontroller.ui.theme.TVControllerTheme import com.example.tvcontroller.ui.theme.TVControllerTheme
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.tvcontroller.ui.AppViewModel
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
BluetoothService.init(this)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
TVControllerTheme { TVControllerTheme {
@ -40,17 +47,19 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun TvControllerApp( fun TvControllerApp(
navController: NavHostController = rememberNavController() navController: NavHostController = rememberNavController(),
appViewModel: AppViewModel = viewModel()
) { ) {
val appUiState by appViewModel.uiState.collectAsState();
val backStackEntry by navController.currentBackStackEntryAsState() val backStackEntry by navController.currentBackStackEntryAsState()
val currentScreen = Screens.valueOf(backStackEntry?.destination?.route ?: Screens.Camera.name) val currentScreen = Screen.valueOf(backStackEntry?.destination?.route ?: Screen.Camera.name)
val baselineCamera24 = painterResource(R.drawable.baseline_camera_24) val baselineCamera24 = painterResource(R.drawable.baseline_camera_24)
val baselineRemote24 = painterResource(R.drawable.baseline_settings_remote_24) val baselineRemote24 = painterResource(R.drawable.baseline_settings_remote_24)
val baselineSettings24 = painterResource(R.drawable.baseline_settings_24) val baselineSettings24 = painterResource(R.drawable.baseline_settings_24)
Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = { Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = {
NavigationBar { NavigationBar {
NavigationBarItem( NavigationBarItem(
onClick = { navController.navigate(Screens.Camera.name) }, onClick = { navController.navigate(Screen.Camera.name) },
icon = { icon = {
Icon( Icon(
baselineCamera24, baselineCamera24,
@ -58,10 +67,10 @@ fun TvControllerApp(
) )
}, },
label = { Text("Camera") }, label = { Text("Camera") },
selected = currentScreen == Screens.Camera selected = currentScreen == Screen.Camera
) )
NavigationBarItem( NavigationBarItem(
onClick = { navController.navigate(Screens.Remote.name) }, onClick = { navController.navigate(Screen.Remote.name) },
icon = { icon = {
Icon( Icon(
baselineRemote24, baselineRemote24,
@ -69,10 +78,10 @@ fun TvControllerApp(
) )
}, },
label = { Text("Remote") }, label = { Text("Remote") },
selected = currentScreen == Screens.Remote selected = currentScreen == Screen.Remote
) )
NavigationBarItem( NavigationBarItem(
onClick = { navController.navigate(Screens.Settings.name) }, onClick = { navController.navigate(Screen.Settings.name) },
icon = { icon = {
Icon( Icon(
baselineSettings24, baselineSettings24,
@ -80,23 +89,23 @@ fun TvControllerApp(
) )
}, },
label = { Text("Settings") }, label = { Text("Settings") },
selected = currentScreen == Screens.Settings selected = currentScreen == Screen.Settings
) )
} }
}) { innerPadding -> }) { innerPadding ->
Column { Column {
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = Screens.Camera.name, startDestination = Screen.Camera.name,
modifier = Modifier.padding(innerPadding) modifier = Modifier.padding(innerPadding)
) { ) {
composable(route = Screens.Camera.name) { composable(route = Screen.Camera.name) {
CameraScreen() CameraScreen()
} }
composable(route = Screens.Remote.name) { composable(route = Screen.Remote.name) {
RemoteScreen() RemoteScreen()
} }
composable(route = Screens.Settings.name) { composable(route = Screen.Settings.name) {
SettingsScreen() SettingsScreen()
} }
} }
@ -106,15 +115,39 @@ fun TvControllerApp(
@Composable @Composable
fun CameraScreen(modifier: Modifier = Modifier) { fun CameraScreen(modifier: Modifier = Modifier) {
Text(text = "Camera Screen", modifier = modifier) Column(
modifier = modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Camera Screen", modifier = modifier)
}
} }
@Composable @Composable
fun RemoteScreen(modifier: Modifier = Modifier) { fun RemoteScreen(modifier: Modifier = Modifier) {
Text(text = "Remote Screen", 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")
}
}
} }
@Composable @Composable
fun SettingsScreen(modifier: Modifier = Modifier) { fun SettingsScreen(modifier: Modifier = Modifier) {
Text(text = "Settings Screen", modifier = modifier) Column(
modifier = modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Settings Screen", modifier = modifier)
}
} }

View File

@ -1,6 +1,6 @@
package com.example.tvcontroller package com.example.tvcontroller
enum class Screens { enum class Screen {
Camera, Camera,
Remote, Remote,
Settings Settings

View File

@ -0,0 +1,7 @@
package com.example.tvcontroller.ui
import com.example.tvcontroller.Screen
data class AppUiState(
val currentScreen: Screen = Screen.Camera,
)

View File

@ -0,0 +1,19 @@
package com.example.tvcontroller.ui
import androidx.lifecycle.ViewModel
import com.example.tvcontroller.Screen
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class AppViewModel : ViewModel() {
private val _uiState = MutableStateFlow(AppUiState())
val uiState: StateFlow<AppUiState> = _uiState.asStateFlow()
fun updateScreen(screen: Screen) {
if (uiState.value.currentScreen == screen) return
val newUiState = uiState.value.copy(currentScreen = screen)
_uiState.update { newUiState }
}
}