feat: add bluetooth service and viewmodel
This commit is contained in:
parent
a7184ece91
commit
421b1719be
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
Text(text = "Camera Screen", modifier = modifier)
|
Text(text = "Camera Screen", modifier = modifier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RemoteScreen(modifier: Modifier = Modifier) {
|
fun RemoteScreen(modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
Text(text = "Remote Screen", modifier = modifier)
|
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) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
Text(text = "Settings Screen", modifier = modifier)
|
Text(text = "Settings Screen", modifier = modifier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package com.example.tvcontroller
|
package com.example.tvcontroller
|
||||||
|
|
||||||
enum class Screens {
|
enum class Screen {
|
||||||
Camera,
|
Camera,
|
||||||
Remote,
|
Remote,
|
||||||
Settings
|
Settings
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.example.tvcontroller.ui
|
||||||
|
|
||||||
|
import com.example.tvcontroller.Screen
|
||||||
|
|
||||||
|
data class AppUiState(
|
||||||
|
val currentScreen: Screen = Screen.Camera,
|
||||||
|
)
|
||||||
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user