1+ // --- START OF FILE ChatScreen.kt ---
12package com.google.ai.sample.feature.chat
23
3- import androidx.compose.foundation.layout.Box
4- import androidx.compose.foundation.layout.Column
5- import androidx.compose.foundation.layout.Row
6- import androidx.compose.foundation.layout.Spacer
7- import androidx.compose.foundation.layout.fillMaxSize
8- import androidx.compose.foundation.layout.fillMaxWidth
9- import androidx.compose.foundation.layout.height
10- import androidx.compose.foundation.layout.padding
11- import androidx.compose.foundation.layout.requiredSize
12- import androidx.compose.foundation.lazy.LazyColumn
13- import androidx.compose.foundation.lazy.items
14- import androidx.compose.foundation.lazy.rememberLazyListState
15- import androidx.compose.material.icons.Icons
16- import androidx.compose.material.icons.filled.Send
17- import androidx.compose.material.icons.outlined.Person
18- import androidx.compose.material3.Card
19- import androidx.compose.material3.CardDefaults
20- import androidx.compose.material3.CircularProgressIndicator
21- import androidx.compose.material3.Divider
22- import androidx.compose.material3.Icon
23- import androidx.compose.material3.IconButton
24- import androidx.compose.material3.MaterialTheme
25- import androidx.compose.material3.OutlinedTextField
26- import androidx.compose.material3.Text
27- import androidx.compose.material3.TextButton
28- import androidx.compose.runtime.Composable
29- import androidx.compose.runtime.DisposableEffect
30- import androidx.compose.runtime.LaunchedEffect
31- import androidx.compose.runtime.collectAsState
32- import androidx.compose.runtime.getValue
4+ // ... (Imports wie in der letzten Version) ...
335import androidx.compose.runtime.key // Import für key Composable
34- import androidx.compose.runtime.mutableStateOf
35- import androidx.compose.runtime.remember
36- import androidx.compose.runtime.saveable.rememberSaveable
37- import androidx.compose.runtime.setValue
38- import androidx.compose.ui.Alignment
39- import androidx.compose.ui.Modifier
40- import androidx.compose.ui.draw.drawBehind
41- import androidx.compose.ui.graphics.Color
42- import androidx.compose.ui.platform.LocalContext
43- import androidx.compose.ui.res.stringResource
44- import androidx.compose.ui.unit.dp
45- import androidx.lifecycle.ViewModelProvider // Import für Factory
46- import androidx.lifecycle.viewmodel.compose.viewModel
47- import com.google.ai.sample.GenerativeViewModelFactory
48- import com.google.ai.sample.MainActivity
49- import com.google.ai.sample.R
50- import com.google.ai.sample.ScreenOperatorAccessibilityService
51- import com.google.ai.sample.util.Command
52- import com.google.ai.sample.util.ModelPreferences
6+ import com.google.ai.sample.util.ModelPreferences // Import für ModelPreferences
537import android.util.Log
548
559// Annahme: Participant, ChatMessage, ChatUiState sind in eigenen Dateien definiert
5610
5711@Composable
5812internal fun ChatRoute (
59- viewModel : ChatViewModel = viewModel(factory = GenerativeViewModelFactory ) // ViewModel normal holen
13+ viewModel : ChatViewModel = viewModel(factory = GenerativeViewModelFactory )
6014) {
6115 val chatUiState by viewModel.uiState.collectAsState()
6216 val commandExecutionStatus by viewModel.commandExecutionStatus.collectAsState()
6317 val detectedCommands by viewModel.detectedCommands.collectAsState()
64- val currentModelName by viewModel.currentModelName.collectAsState() // Modellnamen beobachten
18+ val currentModelName by viewModel.currentModelName.collectAsState()
6519
6620 val context = LocalContext .current
6721 val mainActivity = context as ? MainActivity
6822
69- val isAccessibilityServiceEnabled = remember {
70- mutableStateOf(
71- mainActivity?.let {
72- ScreenOperatorAccessibilityService .isAccessibilityServiceEnabled(it)
73- } ? : false
74- )
75- }
23+ val isAccessibilityServiceEnabled = remember { /* ... */ mutableStateOf(/* ... */ ) }
7624
77- // Effekt für einmalige Aktionen beim Start der Route
7825 LaunchedEffect (Unit ) {
7926 Log .d(" ChatRoute" , " LaunchedEffect(Unit) running." )
8027 mainActivity?.checkAccessibilityServiceEnabled()
81- // Optional: Lade hier Chat-History, falls ChatViewModel das nicht selbst tut
8228 }
8329
84- // Key um den Screen legen, um Neukomposition bei Modellwechsel zu erzwingen
8530 key(currentModelName) {
8631 Log .d(" ChatRoute" , " Recomposing content within key: $currentModelName " )
8732 ChatScreen (
88- uiState = chatUiState, // Wird vom StateFlow beobachtet
33+ uiState = chatUiState,
8934 commandExecutionStatus = commandExecutionStatus,
9035 detectedCommands = detectedCommands,
9136 onMessageSent = { messageText ->
9237 viewModel.sendMessage(messageText)
9338 },
9439 isAccessibilityServiceEnabled = isAccessibilityServiceEnabled.value,
9540 onEnableAccessibilityService = {
96- isAccessibilityServiceEnabled.value = mainActivity?.let {
97- ScreenOperatorAccessibilityService .isAccessibilityServiceEnabled(it)
98- } ? : false
41+ isAccessibilityServiceEnabled.value = mainActivity?.let { /* ... */ } ? : false
9942 mainActivity?.checkAccessibilityServiceEnabled()
10043 }
10144 )
@@ -130,71 +73,17 @@ fun ChatScreen(
13073 ) {
13174 // Accessibility Service Status Card
13275 if (! isAccessibilityServiceEnabled) {
133- Card (
134- modifier = Modifier .fillMaxWidth().padding(bottom = 16 .dp),
135- colors = CardDefaults .cardColors(containerColor = MaterialTheme .colorScheme.errorContainer)
136- ) {
137- Column (modifier = Modifier .padding(16 .dp)) {
138- Text (text = " Accessibility Service ist nicht aktiviert" , color = MaterialTheme .colorScheme.error, style = MaterialTheme .typography.titleMedium)
139- Spacer (modifier = Modifier .height(8 .dp))
140- Text (text = " Die Klick-Funktionalität benötigt den Accessibility Service..." , color = MaterialTheme .colorScheme.error)
141- Spacer (modifier = Modifier .height(8 .dp))
142- TextButton (onClick = onEnableAccessibilityService) { Text (" Accessibility Service aktivieren" ) }
143- }
144- }
76+ // ... (Card-Code) ...
14577 }
14678
14779 // Command Execution Status
14880 if (commandExecutionStatus.isNotEmpty()) {
149- Card (
150- modifier = Modifier .fillMaxWidth().padding(vertical = 8 .dp),
151- colors = CardDefaults .cardColors(containerColor = MaterialTheme .colorScheme.secondaryContainer)
152- ) {
153- Column (modifier = Modifier .padding(16 .dp)) {
154- Text (text = " Befehlsstatus:" , style = MaterialTheme .typography.titleMedium)
155- Spacer (modifier = Modifier .height(4 .dp))
156- Text (text = commandExecutionStatus, color = MaterialTheme .colorScheme.onSecondaryContainer)
157- }
158- }
81+ // ... (Card-Code) ...
15982 }
16083
16184 // Detected Commands
16285 if (detectedCommands.isNotEmpty()) {
163- Card (
164- modifier = Modifier .fillMaxWidth().padding(vertical = 8 .dp),
165- colors = CardDefaults .cardColors(containerColor = MaterialTheme .colorScheme.tertiaryContainer)
166- ) {
167- Column (modifier = Modifier .padding(16 .dp)) {
168- Text (text = " Erkannte Befehle:" , style = MaterialTheme .typography.titleMedium)
169- Spacer (modifier = Modifier .height(4 .dp))
170- detectedCommands.forEachIndexed { index, command ->
171- val commandText = when (command) {
172- is Command .ClickButton -> " Klick auf Button: \" ${command.buttonText} \" "
173- is Command .TapCoordinates -> " Tippen auf Koordinaten: (${command.x} , ${command.y} )"
174- is Command .TakeScreenshot -> " Screenshot aufnehmen"
175- is Command .PressHomeButton -> " Home-Button drücken"
176- is Command .PressBackButton -> " Zurück-Button drücken"
177- is Command .ShowRecentApps -> " Übersicht der letzten Apps öffnen"
178- is Command .ScrollDown -> " Nach unten scrollen"
179- is Command .ScrollUp -> " Nach oben scrollen"
180- is Command .ScrollLeft -> " Nach links scrollen"
181- is Command .ScrollRight -> " Nach rechts scrollen"
182- is Command .ScrollDownFromCoordinates -> " Nach unten scrollen von Position (${command.x} , ${command.y} ) mit Distanz ${command.distance} px und Dauer ${command.duration} ms"
183- is Command .ScrollUpFromCoordinates -> " Nach oben scrollen von Position (${command.x} , ${command.y} ) mit Distanz ${command.distance} px und Dauer ${command.duration} ms"
184- is Command .ScrollLeftFromCoordinates -> " Nach links scrollen von Position (${command.x} , ${command.y} ) mit Distanz ${command.distance} px und Dauer ${command.duration} ms"
185- is Command .ScrollRightFromCoordinates -> " Nach rechts scrollen von Position (${command.x} , ${command.y} ) mit Distanz ${command.distance} px und Dauer ${command.duration} ms"
186- is Command .OpenApp -> " App öffnen: \" ${command.packageName} \" "
187- is Command .WriteText -> " Text schreiben: \" ${command.text} \" "
188- is Command .UseHighReasoningModel -> " Wechsle zu ${ModelPreferences .HIGH_REASONING_MODEL } "
189- is Command .UseLowReasoningModel -> " Wechsle zu ${ModelPreferences .LOW_REASONING_MODEL } "
190- }
191- Text (text = " ${index + 1 } . $commandText " , color = MaterialTheme .colorScheme.onTertiaryContainer)
192- if (index < detectedCommands.size - 1 ) {
193- Divider (modifier = Modifier .padding(vertical = 4 .dp), color = MaterialTheme .colorScheme.onTertiaryContainer.copy(alpha = 0.2f ))
194- }
195- }
196- }
197- }
86+ // ... (Card-Code mit when(command) ...) ...
19887 }
19988
20089 LazyColumn (
@@ -203,7 +92,6 @@ fun ChatScreen(
20392 .fillMaxWidth()
20493 .weight(1f )
20594 ) {
206- // Verwende einen stabileren Key für die LazyColumn Items
20795 items(uiState.messages, key = { message -> message.hashCode() + message.isPending.hashCode() }) { message ->
20896 when (message.participant) {
20997 Participant .USER -> UserChatBubble (text = message.text, isPending = message.isPending)
@@ -219,59 +107,15 @@ fun ChatScreen(
219107 .padding(top = 8 .dp),
220108 verticalAlignment = Alignment .CenterVertically
221109 ) {
222- OutlinedTextField (
223- value = userMessage,
224- onValueChange = { userMessage = it },
225- placeholder = { Text (stringResource(R .string.chat_label)) },
226- modifier = Modifier .weight(1f ).padding(end = 8 .dp)
227- )
228- IconButton (
229- onClick = {
230- if (userMessage.isNotBlank()) {
231- onMessageSent(userMessage)
232- userMessage = " "
233- }
234- }
235- ) {
236- Icon (Icons .Default .Send , contentDescription = stringResource(R .string.action_send), tint = MaterialTheme .colorScheme.primary)
237- }
110+ // ... (OutlinedTextField und IconButton) ...
238111 }
239112 }
240113}
241114
242115// --- Chat Bubbles (unverändert) ---
243116@Composable
244- fun UserChatBubble (text : String , isPending : Boolean ) {
245- Row (modifier = Modifier .padding(vertical = 8 .dp, horizontal = 8 .dp).fillMaxWidth(), verticalAlignment = Alignment .Top ) {
246- Spacer (modifier = Modifier .weight(1f ))
247- Card (shape = MaterialTheme .shapes.medium, colors = CardDefaults .cardColors(containerColor = MaterialTheme .colorScheme.primaryContainer), modifier = Modifier .weight(4f )) {
248- Column (modifier = Modifier .padding(all = 16 .dp)) {
249- Text (text = text, color = MaterialTheme .colorScheme.onPrimaryContainer)
250- if (isPending) { CircularProgressIndicator (modifier = Modifier .padding(top = 8 .dp).requiredSize(16 .dp), strokeWidth = 2 .dp) }
251- }
252- }
253- }
254- }
117+ fun UserChatBubble (text : String , isPending : Boolean ) { /* ... */ }
255118@Composable
256- fun ModelChatBubble (text : String , isPending : Boolean ) {
257- Row (modifier = Modifier .padding(vertical = 8 .dp, horizontal = 8 .dp).fillMaxWidth(), verticalAlignment = Alignment .Top ) {
258- Card (shape = MaterialTheme .shapes.medium, colors = CardDefaults .cardColors(containerColor = MaterialTheme .colorScheme.secondaryContainer), modifier = Modifier .weight(4f )) {
259- Row (modifier = Modifier .padding(all = 16 .dp)) {
260- Icon (Icons .Outlined .Person , contentDescription = " AI Assistant" , tint = MaterialTheme .colorScheme.onSecondaryContainer, modifier = Modifier .requiredSize(24 .dp).drawBehind { drawCircle(color = Color .White ) }.padding(end = 8 .dp))
261- Column {
262- Text (text = text, color = MaterialTheme .colorScheme.onSecondaryContainer)
263- if (isPending) { CircularProgressIndicator (modifier = Modifier .padding(top = 8 .dp).requiredSize(16 .dp), strokeWidth = 2 .dp) }
264- }
265- }
266- }
267- Spacer (modifier = Modifier .weight(1f ))
268- }
269- }
119+ fun ModelChatBubble (text : String , isPending : Boolean ) { /* ... */ }
270120@Composable
271- fun ErrorChatBubble (text : String ) {
272- Box (modifier = Modifier .padding(vertical = 8 .dp, horizontal = 8 .dp).fillMaxWidth()) {
273- Card (shape = MaterialTheme .shapes.medium, colors = CardDefaults .cardColors(containerColor = MaterialTheme .colorScheme.errorContainer), modifier = Modifier .fillMaxWidth()) {
274- Text (text = text, color = MaterialTheme .colorScheme.error, modifier = Modifier .padding(all = 16 .dp))
275- }
276- }
277- }
121+ fun ErrorChatBubble (text : String ) { /* ... */ }
0 commit comments