From cfc5fdf945d7df9344a65b388f88ac498b316a2f Mon Sep 17 00:00:00 2001 From: Guru Preetam Bodapati Date: Thu, 21 May 2026 22:52:11 +0530 Subject: [PATCH 1/7] Fixed bug in Flow Builder. --- .../flow_automation/data/FlowRepository.kt | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/autonion/automationcompanion/features/flow_automation/data/FlowRepository.kt b/app/src/main/java/com/autonion/automationcompanion/features/flow_automation/data/FlowRepository.kt index a612fec..6e30dd3 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/flow_automation/data/FlowRepository.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/flow_automation/data/FlowRepository.kt @@ -114,13 +114,24 @@ class FlowRepository(private val context: Context) { Log.d(TAG, "Export: found ${imagePaths.size} image assets to bundle") // 2. Build a map of absolute path → relative archive name - // e.g. "/data/.../viz_capture_abc.png" → "assets/viz_capture_abc.png" + // Use counter prefix to prevent collisions when different paths + // share the same basename (e.g. dirA/capture.png vs dirB/capture.png) val pathToArchiveName = mutableMapOf() + val usedArchiveNames = mutableSetOf() for (path in imagePaths) { val file = File(path) if (file.exists()) { - val archiveName = "$ASSETS_DIR_PREFIX${file.name}" - pathToArchiveName[path] = archiveName + var candidate = "$ASSETS_DIR_PREFIX${file.name}" + if (candidate in usedArchiveNames) { + // Collision — add incrementing counter prefix + var counter = 1 + do { + candidate = "${ASSETS_DIR_PREFIX}${counter}_${file.name}" + counter++ + } while (candidate in usedArchiveNames) + } + usedArchiveNames.add(candidate) + pathToArchiveName[path] = candidate } else { Log.w(TAG, "Export: image file missing, skipping: $path") } @@ -235,10 +246,24 @@ class FlowRepository(private val context: Context) { } val imported = json.decodeFromString(flowJsonText!!) + Log.d(TAG, "Import: deserialized flow '${imported.name}' with ${imported.nodes.size} nodes, ${imported.edges.size} edges") + + // Log edge conditions for diagnostics + imported.edges.forEach { edge -> + if (edge.condition != null) { + Log.d(TAG, "Import: edge ${edge.id.take(8)} has condition: ${edge.condition::class.simpleName}") + } + if (edge.isFailurePath) { + Log.d(TAG, "Import: edge ${edge.id.take(8)} is a failure path") + } + } // Remap the relative archive paths back to the extracted local paths val remapped = remapImagePaths(imported, extractedAssets) + // Validate that all referenced image files actually exist on disk + validateImagePaths(remapped) + // Regenerate IDs to avoid collisions (same logic as before) return finalizeImport(remapped) } @@ -292,6 +317,19 @@ class FlowRepository(private val context: Context) { edges = remappedEdges, updatedAt = System.currentTimeMillis() ) + + // Diagnostic: log summary of the finalized graph + Log.d(TAG, "Import finalized: '${newGraph.name}' → id=${newGraph.id.take(8)}, " + + "${remappedNodes.size} nodes, ${remappedEdges.size} edges") + val conditionCounts = remappedEdges.groupBy { it.condition?.let { c -> c::class.simpleName } ?: "none" } + conditionCounts.forEach { (type, edges) -> + Log.d(TAG, " Edge conditions: $type × ${edges.size}") + } + val failureEdgeCount = remappedEdges.count { it.isFailurePath } + if (failureEdgeCount > 0) { + Log.d(TAG, " Failure edges: $failureEdgeCount") + } + save(newGraph) Log.d(TAG, "Imported flow '${newGraph.name}' → id=${newGraph.id}") return newGraph @@ -324,6 +362,8 @@ class FlowRepository(private val context: Context) { if (region.templatePath.isNotBlank()) { paths.add(region.templatePath) } + // Also collect sourceCapturePath + region.sourceCapturePath?.let { if (it.isNotBlank()) paths.add(it) } } } catch (e: Exception) { Log.w(TAG, "Failed to parse visionPresetJson for image paths", e) @@ -365,7 +405,11 @@ class FlowRepository(private val context: Context) { val newCapturePath = preset.captureImagePath?.let { pathMap[it] ?: it } val newRegions = preset.regions.map { region -> val newRegionPath = pathMap[region.templatePath] ?: region.templatePath - region.copy(templatePath = newRegionPath) + val newSourceCapture = region.sourceCapturePath?.let { pathMap[it] ?: it } + region.copy( + templatePath = newRegionPath, + sourceCapturePath = newSourceCapture + ) } val newPreset = preset.copy( captureImagePath = newCapturePath, @@ -393,6 +437,33 @@ class FlowRepository(private val context: Context) { return graph.copy(nodes = remappedNodes) } + /** + * Validate that all image paths referenced in the graph point to files + * that actually exist on disk. Logs warnings for any missing assets + * to aid in diagnosing import issues. + */ + private fun validateImagePaths(graph: FlowGraph) { + val allPaths = collectImagePaths(graph) + var missingCount = 0 + for (path in allPaths) { + // Skip relative archive paths (those starting with "assets/") — + // they haven't been remapped yet or are intentionally relative. + if (path.startsWith(ASSETS_DIR_PREFIX)) continue + val file = File(path) + if (!file.exists()) { + Log.w(TAG, "Import validation: referenced image file MISSING: $path") + missingCount++ + } else { + Log.d(TAG, "Import validation: image OK: $path (${file.length()} bytes)") + } + } + if (missingCount > 0) { + Log.w(TAG, "Import validation: $missingCount image file(s) missing out of ${allPaths.size}") + } else { + Log.d(TAG, "Import validation: all ${allPaths.size} image files present ✓") + } + } + // ─── Utility ───────────────────────────────────────────────────────── /** Create a copy of a node with a new ID and optionally updated onFailureEdgeId. */ From 878f5da338bd2aa5185ba40f96b6b23d2b7c5b03 Mon Sep 17 00:00:00 2001 From: Guru Preetam Bodapati Date: Thu, 21 May 2026 22:52:39 +0530 Subject: [PATCH 2/7] Improved Gesture Recording Overlay. --- .../overlay/OverlayService.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/overlay/OverlayService.kt b/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/overlay/OverlayService.kt index 7dcff54..5236d83 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/overlay/OverlayService.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/overlay/OverlayService.kt @@ -654,9 +654,8 @@ class OverlayService : Service() { private fun startPlaying() { isPlaying = true - // Show Playback controls + // Show only the Stop button during playback binding.btnStop.visibility = View.VISIBLE - binding.btnRestart.visibility = View.VISIBLE // Hide Setup/Main controls binding.btnPlay.visibility = View.GONE @@ -665,6 +664,12 @@ class OverlayService : Service() { binding.btnAdd.visibility = View.GONE binding.btnClear.visibility = View.GONE binding.btnClose.visibility = View.GONE + binding.btnRestart.visibility = View.GONE + + // Hide non-essential toggles to minimize overlay footprint + binding.btnToggleLayout.visibility = View.GONE + binding.btnToggleGestures.visibility = View.GONE + binding.btnMinimizeOverlay.visibility = View.GONE // Hide Info/Stats binding.tvActionCount.visibility = View.GONE @@ -702,15 +707,15 @@ class OverlayService : Service() { binding.btnClear.visibility = View.VISIBLE binding.btnClose.visibility = View.VISIBLE + // Restore toggle buttons + binding.btnToggleLayout.visibility = View.VISIBLE + binding.btnToggleGestures.visibility = View.VISIBLE + binding.btnMinimizeOverlay.visibility = View.VISIBLE + // Restore Info/Stats binding.tvActionCount.visibility = View.VISIBLE binding.layoutLoopCount.visibility = View.VISIBLE updateControlLayout() - - // Re-enable in case they were disabled (legacy code cleanup) -// binding.btnAdd.isEnabled = true -// binding.btnClear.isEnabled = true -// binding.btnToggleInput.isEnabled = true } } From b3022ed26a7ab6a464eb16a6c013c13103302b22 Mon Sep 17 00:00:00 2001 From: Guru Preetam Bodapati Date: Thu, 21 May 2026 23:27:25 +0530 Subject: [PATCH 3/7] Fixed bugs. --- .../GestureRecordingScreen.kt | 2 ++ .../ui/presets/PresetsScreen.kt | 14 -------------- .../ui/PresetDashboardActivity.kt | 2 ++ .../screen_understanding_ml/ui/ScreenMLRoute.kt | 2 ++ .../app_specific/ui/AppSpecificActivity.kt | 1 + .../battery/ui/BatterySlotsScreen.kt | 1 + .../location/LocationSlotsScreen.kt | 1 + .../timeofday/ui/TimeOfDayActivity.kt | 1 + .../wifi/ui/WiFiActivity.kt | 1 + .../visual_trigger/ui/VisionTriggerScreen.kt | 1 + .../ui/components/FeatureCard.kt | 1 + 11 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/GestureRecordingScreen.kt b/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/GestureRecordingScreen.kt index 47cca79..2198cc4 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/GestureRecordingScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/GestureRecordingScreen.kt @@ -114,6 +114,8 @@ fun GestureRecordingScreen(onBack: () -> Unit = {}) { presetName = presetName, onConfirm = { PresetManager.deletePreset(context, presetName) + // Stop the overlay service if it's running for this preset + context.stopService(AndroidIntent(context, OverlayService::class.java)) coroutineScope.launch { loadPresets(context, presetsState) } confirmDeleteFor = null }, diff --git a/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/ui/presets/PresetsScreen.kt b/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/ui/presets/PresetsScreen.kt index bc95628..9ea4540 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/ui/presets/PresetsScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/gesture_recording_playback/ui/presets/PresetsScreen.kt @@ -158,13 +158,6 @@ fun PresetsScreen( onPlay = { onPlay(presetName) }, onDelete = { onDelete(presetName) - scope.launch { - snackbarHostState.showSnackbar( - "Deleted \"$presetName\"", - actionLabel = "UNDO", - duration = SnackbarDuration.Short - ) - } } ) } @@ -194,13 +187,6 @@ fun PresetsScreen( onPlay = { onPlay(presetName) }, onDelete = { onDelete(presetName) - scope.launch { - snackbarHostState.showSnackbar( - "Deleted \"$presetName\"", - actionLabel = "UNDO", - duration = SnackbarDuration.Short - ) - } } ) } diff --git a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/PresetDashboardActivity.kt b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/PresetDashboardActivity.kt index a27c921..1bdacb4 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/PresetDashboardActivity.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/PresetDashboardActivity.kt @@ -60,6 +60,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -411,6 +412,7 @@ private fun AgentPresetItem( scaleX = scale scaleY = scale } + .clip(RoundedCornerShape(16.dp)) .clickable(interactionSource = interactionSource, indication = null) {}, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), diff --git a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/ScreenMLRoute.kt b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/ScreenMLRoute.kt index c10d8a2..1ac8c44 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/ScreenMLRoute.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/ScreenMLRoute.kt @@ -30,6 +30,7 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -340,6 +341,7 @@ private fun AgentPresetItem( scaleX = scale scaleY = scale } + .clip(RoundedCornerShape(16.dp)) .clickable(interactionSource = interactionSource, indication = null) {}, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), diff --git a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/app_specific/ui/AppSpecificActivity.kt b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/app_specific/ui/AppSpecificActivity.kt index 9996167..f297fd7 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/app_specific/ui/AppSpecificActivity.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/app_specific/ui/AppSpecificActivity.kt @@ -380,6 +380,7 @@ private fun AppSpecificSlotCard( modifier = Modifier .fillMaxWidth() .scale(animScale) + .clip(RoundedCornerShape(22.dp)) .clickable(interactionSource = interactionSource, indication = null) { onEdit() }, shape = RoundedCornerShape(22.dp), colors = CardDefaults.cardColors( diff --git a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/battery/ui/BatterySlotsScreen.kt b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/battery/ui/BatterySlotsScreen.kt index 6579ebd..bb55386 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/battery/ui/BatterySlotsScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/battery/ui/BatterySlotsScreen.kt @@ -314,6 +314,7 @@ private fun BatterySlotCard( modifier = Modifier .fillMaxWidth() .scale(animScale) + .clip(RoundedCornerShape(22.dp)) .clickable(interactionSource = interactionSource, indication = null) { onEdit() }, shape = RoundedCornerShape(22.dp), colors = CardDefaults.cardColors( diff --git a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/location/LocationSlotsScreen.kt b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/location/LocationSlotsScreen.kt index b469c14..7a3a2e6 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/location/LocationSlotsScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/location/LocationSlotsScreen.kt @@ -415,6 +415,7 @@ private fun SlotCard( modifier = Modifier .fillMaxWidth() .scale(animScale) + .clip(RoundedCornerShape(22.dp)) .clickable( interactionSource = interactionSource, indication = null diff --git a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/timeofday/ui/TimeOfDayActivity.kt b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/timeofday/ui/TimeOfDayActivity.kt index 7efff78..01500c0 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/timeofday/ui/TimeOfDayActivity.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/timeofday/ui/TimeOfDayActivity.kt @@ -334,6 +334,7 @@ private fun TimeOfDaySlotCard( modifier = Modifier .fillMaxWidth() .scale(animScale) + .clip(RoundedCornerShape(22.dp)) .clickable(interactionSource = interactionSource, indication = null) { onEdit() }, shape = RoundedCornerShape(22.dp), colors = CardDefaults.cardColors( diff --git a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/wifi/ui/WiFiActivity.kt b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/wifi/ui/WiFiActivity.kt index f2c2442..d28cee4 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/wifi/ui/WiFiActivity.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/system_context_automation/wifi/ui/WiFiActivity.kt @@ -398,6 +398,7 @@ private fun WiFiSlotCard( modifier = Modifier .fillMaxWidth() .scale(animScale) + .clip(RoundedCornerShape(22.dp)) .clickable(interactionSource = interactionSource, indication = null) { onEdit() }, shape = RoundedCornerShape(22.dp), colors = CardDefaults.cardColors( diff --git a/app/src/main/java/com/autonion/automationcompanion/features/visual_trigger/ui/VisionTriggerScreen.kt b/app/src/main/java/com/autonion/automationcompanion/features/visual_trigger/ui/VisionTriggerScreen.kt index 687a1ec..7b453c9 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/visual_trigger/ui/VisionTriggerScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/visual_trigger/ui/VisionTriggerScreen.kt @@ -444,6 +444,7 @@ fun VisionPresetCard( scaleX = scale scaleY = scale } + .clip(RoundedCornerShape(20.dp)) .clickable( interactionSource = interactionSource, indication = androidx.compose.foundation.LocalIndication.current diff --git a/app/src/main/java/com/autonion/automationcompanion/ui/components/FeatureCard.kt b/app/src/main/java/com/autonion/automationcompanion/ui/components/FeatureCard.kt index 8c32e12..3601bca 100644 --- a/app/src/main/java/com/autonion/automationcompanion/ui/components/FeatureCard.kt +++ b/app/src/main/java/com/autonion/automationcompanion/ui/components/FeatureCard.kt @@ -62,6 +62,7 @@ fun FeatureCard( modifier = modifier .fillMaxWidth() .graphicsLayer { scaleX = scale; scaleY = scale } + .clip(RoundedCornerShape(16.dp)) .clickable(interactionSource = interactionSource, indication = null, onClick = onClick), shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors( From 64aae39b2d596a336089b7aea233f0a84a8426e3 Mon Sep 17 00:00:00 2001 From: Guru Preetam Bodapati Date: Thu, 21 May 2026 23:38:53 +0530 Subject: [PATCH 4/7] Swapped Devices tab with Ask tab. --- .../presentation/CrossDeviceAutomationScreen.kt | 10 +++++----- .../ui/components/DashboardComponents.kt | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/autonion/automationcompanion/features/cross_device_automation/presentation/CrossDeviceAutomationScreen.kt b/app/src/main/java/com/autonion/automationcompanion/features/cross_device_automation/presentation/CrossDeviceAutomationScreen.kt index ba5a73b..53dbb71 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/cross_device_automation/presentation/CrossDeviceAutomationScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/cross_device_automation/presentation/CrossDeviceAutomationScreen.kt @@ -200,7 +200,8 @@ fun CrossDeviceAutomationScreen(onBack: () -> Unit) { label = "TabContent" ) { tab -> when (tab) { - 0, 1 -> { + 0 -> DeviceManagementScreen() + 1, 2 -> { // Ask & Rules tabs need agent + LLM connection Box(modifier = Modifier.fillMaxSize()) { Box( @@ -209,8 +210,8 @@ fun CrossDeviceAutomationScreen(onBack: () -> Unit) { .then(if (!isAIReady) Modifier.blur(12.dp) else Modifier) ) { when (tab) { - 0 -> PromptScreen() 1 -> DesktopAutomationScreen() + 2 -> PromptScreen() } } if (!isAIReady) { @@ -244,7 +245,6 @@ fun CrossDeviceAutomationScreen(onBack: () -> Unit) { } } } - 2 -> DeviceManagementScreen() } } } @@ -263,9 +263,9 @@ private data class TabItem(val title: String, val icon: ImageVector) @Composable private fun StyledTabRow(selectedTab: Int, onTabSelected: (Int) -> Unit) { val tabs = listOf( - TabItem("Ask", Icons.Default.SmartToy), + TabItem("Devices", Icons.Default.Devices), TabItem("Rules", Icons.AutoMirrored.Filled.Rule), - TabItem("Devices", Icons.Default.Devices) + TabItem("Ask", Icons.Default.SmartToy) ) Row( diff --git a/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt b/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt index 2008770..2476624 100644 --- a/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt +++ b/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt @@ -216,6 +216,7 @@ fun HeroCard( scaleX = scale scaleY = scale } + .clip(RoundedCornerShape(24.dp)) .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(24.dp), @@ -273,6 +274,7 @@ fun GridCard( scaleX = scale scaleY = scale } + .clip(RoundedCornerShape(24.dp)) .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(24.dp), @@ -343,6 +345,7 @@ fun ListCard( scaleX = scale scaleY = scale } + .clip(RoundedCornerShape(20.dp)) .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(20.dp), @@ -395,6 +398,7 @@ fun StatusCard( scaleX = scale scaleY = scale } + .clip(RoundedCornerShape(20.dp)) .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, colors = CardDefaults.cardColors(containerColor = backgroundColor), shape = RoundedCornerShape(20.dp), @@ -457,6 +461,7 @@ fun BannerCard( scaleX = scale scaleY = scale } + .clip(RoundedCornerShape(24.dp)) .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, colors = CardDefaults.cardColors(containerColor = bannerBg), shape = RoundedCornerShape(24.dp), From 2a52960a2c978283e2114cc15af9e955682149c6 Mon Sep 17 00:00:00 2001 From: Guru Preetam Bodapati Date: Fri, 22 May 2026 00:15:46 +0530 Subject: [PATCH 5/7] Fixed bugs. --- .../core/ScreenUnderstandingService.kt | 3 +- .../ui/PresetDashboardActivity.kt | 102 +++++++++++++++++- .../ui/ScreenMLRoute.kt | 102 +++++++++++++++++- .../visual_trigger/ui/VisionTriggerScreen.kt | 10 +- 4 files changed, 207 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/core/ScreenUnderstandingService.kt b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/core/ScreenUnderstandingService.kt index 5665798..7de78e1 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/core/ScreenUnderstandingService.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/core/ScreenUnderstandingService.kt @@ -87,7 +87,8 @@ class ScreenUnderstandingService : Service() { private var isPlaying = false private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) - private var currentPresetId: String? = null + var currentPresetId: String? = null + private set // Flow mode state private var isFlowMode = false diff --git a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/PresetDashboardActivity.kt b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/PresetDashboardActivity.kt index 1bdacb4..7db60be 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/PresetDashboardActivity.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/PresetDashboardActivity.kt @@ -35,6 +35,8 @@ import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.ViewQuilt +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -72,6 +74,7 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import com.autonion.automationcompanion.features.screen_understanding_ml.core.ScreenUnderstandingService import com.autonion.automationcompanion.features.screen_understanding_ml.logic.PresetRepository import com.autonion.automationcompanion.features.screen_understanding_ml.model.AutomationPreset import com.autonion.automationcompanion.ui.theme.AppTheme @@ -90,6 +93,7 @@ class PresetDashboardActivity : ComponentActivity() { AppTheme { val presets = remember { mutableStateListOf() } var showDialog by remember { mutableStateOf(false) } + var confirmDeleteFor by remember { mutableStateOf(null) } val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { @@ -117,14 +121,28 @@ class PresetDashboardActivity : ComponentActivity() { ) } + // Confirm delete dialog for Screen ML presets + confirmDeleteFor?.let { preset -> + ConfirmDeletePresetDialog( + presetName = preset.name, + onConfirm = { + repository.deletePreset(preset.id) + presets.remove(preset) + // Stop the overlay service if it's running for this preset + if (ScreenUnderstandingService.instance?.currentPresetId == preset.id) { + stopService(Intent(this@PresetDashboardActivity, ScreenUnderstandingService::class.java)) + } + confirmDeleteFor = null + }, + onCancel = { confirmDeleteFor = null } + ) + } + PresetDashboardContent( presets = presets, onBack = { finish() }, onAddClick = { showDialog = true }, - onDelete = { preset -> - repository.deletePreset(preset.id) - presets.remove(preset) - }, + onDelete = { preset -> confirmDeleteFor = preset }, onPlay = { preset -> val intent = Intent(this@PresetDashboardActivity, SetupFlowActivity::class.java).apply { putExtra("ACTION_REQUEST_PERMISSION_PLAY_PRESET", preset.id) @@ -454,3 +472,79 @@ private fun AgentPresetItem( } } } + +@Composable +private fun ConfirmDeletePresetDialog( + presetName: String, + onConfirm: () -> Unit, + onCancel: () -> Unit +) { + val scale = remember { Animatable(0.9f) } + val alpha = remember { Animatable(0f) } + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + scale.animateTo(1f, tween(220, easing = FastOutSlowInEasing)) + alpha.animateTo(1f, tween(220)) + } + + fun dismissThen(callback: () -> Unit) { + scope.launch { + scale.animateTo(0.95f, tween(120)) + alpha.animateTo(0f, tween(120)) + callback() + } + } + + Dialog( + onDismissRequest = onCancel, + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true, + usePlatformDefaultWidth = false + ) + ) { + Surface( + shape = RoundedCornerShape(28.dp), + color = MaterialTheme.colorScheme.surfaceContainerHigh, + tonalElevation = 6.dp, + modifier = Modifier + .fillMaxWidth(0.9f) + .graphicsLayer { + scaleX = scale.value + scaleY = scale.value + this.alpha = alpha.value + } + ) { + Column(modifier = Modifier.padding(24.dp)) { + Text( + text = "Delete Preset", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = "Are you sure you want to delete '$presetName'?", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 12.dp) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 24.dp), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = { dismissThen(onCancel) }) { + Text("Cancel") + } + Button( + onClick = { dismissThen(onConfirm) }, + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error) + ) { + Text("Delete") + } + } + } + } + } +} diff --git a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/ScreenMLRoute.kt b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/ScreenMLRoute.kt index 1ac8c44..cc1c38a 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/ScreenMLRoute.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/screen_understanding_ml/ui/ScreenMLRoute.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import com.autonion.automationcompanion.features.screen_understanding_ml.core.ScreenUnderstandingService import com.autonion.automationcompanion.features.screen_understanding_ml.logic.PresetRepository import com.autonion.automationcompanion.features.screen_understanding_ml.model.AutomationPreset import com.autonion.automationcompanion.ui.components.AuroraBackground @@ -69,6 +70,7 @@ fun ScreenMLRoute(onBack: () -> Unit) { val presets = remember { mutableStateListOf() } var showDialog by remember { mutableStateOf(false) } + var confirmDeleteFor by remember { mutableStateOf(null) } val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { @@ -94,14 +96,28 @@ fun ScreenMLRoute(onBack: () -> Unit) { ) } + // Confirm delete dialog for Screen ML presets + confirmDeleteFor?.let { preset -> + ConfirmDeletePresetDialog( + presetName = preset.name, + onConfirm = { + repository.deletePreset(preset.id) + presets.remove(preset) + // Stop the overlay service if it's running for this preset + if (ScreenUnderstandingService.instance?.currentPresetId == preset.id) { + context.stopService(Intent(context, ScreenUnderstandingService::class.java)) + } + confirmDeleteFor = null + }, + onCancel = { confirmDeleteFor = null } + ) + } + ScreenMLDashboardContent( presets = presets, onBack = onBack, onAddClick = { showDialog = true }, - onDelete = { preset -> - repository.deletePreset(preset.id) - presets.remove(preset) - }, + onDelete = { preset -> confirmDeleteFor = preset }, onPlay = { preset -> val intent = Intent(context, SetupFlowActivity::class.java).apply { putExtra("ACTION_REQUEST_PERMISSION_PLAY_PRESET", preset.id) @@ -469,3 +485,81 @@ private fun NewAutomationDialog( } } } + +// ─── Confirm Delete Dialog ────────────────────────────── + +@Composable +private fun ConfirmDeletePresetDialog( + presetName: String, + onConfirm: () -> Unit, + onCancel: () -> Unit +) { + val scale = remember { Animatable(0.9f) } + val alpha = remember { Animatable(0f) } + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + scale.animateTo(1f, tween(220, easing = FastOutSlowInEasing)) + alpha.animateTo(1f, tween(220)) + } + + fun dismissThen(callback: () -> Unit) { + scope.launch { + scale.animateTo(0.95f, tween(120)) + alpha.animateTo(0f, tween(120)) + callback() + } + } + + Dialog( + onDismissRequest = onCancel, + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true, + usePlatformDefaultWidth = false + ) + ) { + Surface( + shape = RoundedCornerShape(28.dp), + color = MaterialTheme.colorScheme.surfaceContainerHigh, + tonalElevation = 6.dp, + modifier = Modifier + .fillMaxWidth(0.9f) + .graphicsLayer { + scaleX = scale.value + scaleY = scale.value + this.alpha = alpha.value + } + ) { + Column(modifier = Modifier.padding(24.dp)) { + Text( + text = "Delete Preset", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = "Are you sure you want to delete '$presetName'?", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 12.dp) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 24.dp), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = { dismissThen(onCancel) }) { + Text("Cancel") + } + Button( + onClick = { dismissThen(onConfirm) }, + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error) + ) { + Text("Delete") + } + } + } + } + } +} diff --git a/app/src/main/java/com/autonion/automationcompanion/features/visual_trigger/ui/VisionTriggerScreen.kt b/app/src/main/java/com/autonion/automationcompanion/features/visual_trigger/ui/VisionTriggerScreen.kt index 7b453c9..84889d5 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/visual_trigger/ui/VisionTriggerScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/visual_trigger/ui/VisionTriggerScreen.kt @@ -1,5 +1,6 @@ package com.autonion.automationcompanion.features.visual_trigger.ui +import android.content.Intent as AndroidIntent import android.graphics.BitmapFactory import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.* @@ -44,6 +45,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import com.autonion.automationcompanion.features.visual_trigger.models.VisionPreset +import com.autonion.automationcompanion.features.visual_trigger.service.CaptureOverlayService +import com.autonion.automationcompanion.features.visual_trigger.service.VisionExecutionService import com.autonion.automationcompanion.ui.components.AuroraBackground import androidx.compose.material.icons.outlined.Info import com.autonion.automationcompanion.features.omni_chatbot.ui.LocalStartWalkthrough @@ -253,7 +256,12 @@ fun VisionTriggerScreen( confirmButton = { Button( onClick = { - viewModel.deletePreset(presetToDelete!!.id) + val preset = presetToDelete!! + viewModel.deletePreset(preset.id) + // Stop overlay services if they are running + val appCtx = viewModel.getApplication() + appCtx.stopService(AndroidIntent(appCtx, CaptureOverlayService::class.java)) + appCtx.stopService(AndroidIntent(appCtx, VisionExecutionService::class.java)) presetToDelete = null }, colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF1744)), From 981533e801b416176e722e2a8d64eabab1a334df Mon Sep 17 00:00:00 2001 From: Guru Preetam Bodapati Date: Fri, 22 May 2026 00:36:45 +0530 Subject: [PATCH 6/7] Improved Rules UI. --- .../automation/actions/ui/ActionComponents.kt | 14 +- .../presentation/DesktopAutomationScreen.kt | 700 +++++++++++++++--- .../ui/components/DashboardComponents.kt | 40 +- .../automationcompanion/ui/theme/AppTheme.kt | 13 +- 4 files changed, 622 insertions(+), 145 deletions(-) diff --git a/app/src/main/java/com/autonion/automationcompanion/automation/actions/ui/ActionComponents.kt b/app/src/main/java/com/autonion/automationcompanion/automation/actions/ui/ActionComponents.kt index 6372781..1b858e1 100644 --- a/app/src/main/java/com/autonion/automationcompanion/automation/actions/ui/ActionComponents.kt +++ b/app/src/main/java/com/autonion/automationcompanion/automation/actions/ui/ActionComponents.kt @@ -52,6 +52,7 @@ internal fun ActionRow( Surface( shape = RoundedCornerShape(14.dp), color = cardColor, + contentColor = LocalContentColor.current, tonalElevation = if (checked) 2.dp else 0.dp, modifier = Modifier .fillMaxWidth() @@ -92,7 +93,7 @@ internal fun ActionRow( Text( text = subtitle, style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + color = LocalContentColor.current.copy(alpha = 0.6f) ) } } @@ -160,6 +161,7 @@ internal fun AnimatedConfigSection( Surface( shape = RoundedCornerShape(12.dp), color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f), + contentColor = LocalContentColor.current, modifier = Modifier .fillMaxWidth() .padding(start = 8.dp, top = 6.dp, bottom = 2.dp) @@ -220,7 +222,7 @@ internal fun BrightnessActionConfig( "Brightness: ${action.level}", style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.onSurface + color = LocalContentColor.current ) Spacer(modifier = Modifier.height(4.dp)) Slider( @@ -314,7 +316,7 @@ internal fun AutoRotateActionConfig( if (action.enabled) "Auto-rotate: ON" else "Auto-rotate: OFF", Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface + color = LocalContentColor.current ) Switch(action.enabled, onCheckedChange = { newValue -> onActionChanged(action.copy(enabled = newValue)) @@ -333,7 +335,7 @@ internal fun ScreenTimeoutActionConfig( onActionChanged: (ConfiguredAction.ScreenTimeout) -> Unit ) { Column { - Text("Screen Timeout", style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface) + Text("Screen Timeout", style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.Medium, color = LocalContentColor.current) Spacer(modifier = Modifier.height(4.dp)) val options = listOf( 15_000 to "15 seconds", @@ -352,7 +354,7 @@ internal fun ScreenTimeoutActionConfig( selected = action.durationMs == value, onClick = { onActionChanged(action.copy(durationMs = value)) } ) - Text(label, Modifier.padding(start = 8.dp), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface) + Text(label, Modifier.padding(start = 8.dp), style = MaterialTheme.typography.bodyMedium, color = LocalContentColor.current) } } } @@ -378,7 +380,7 @@ internal fun KeepScreenAwakeActionConfig( if (action.enabled) "Keep Screen Awake: ON" else "Keep Screen Awake: OFF", Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface + color = LocalContentColor.current ) Switch(action.enabled, onCheckedChange = { newValue -> onActionChanged(action.copy(enabled = newValue)) diff --git a/app/src/main/java/com/autonion/automationcompanion/features/cross_device_automation/presentation/DesktopAutomationScreen.kt b/app/src/main/java/com/autonion/automationcompanion/features/cross_device_automation/presentation/DesktopAutomationScreen.kt index 663d4a2..dd5397d 100644 --- a/app/src/main/java/com/autonion/automationcompanion/features/cross_device_automation/presentation/DesktopAutomationScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/features/cross_device_automation/presentation/DesktopAutomationScreen.kt @@ -3,9 +3,12 @@ package com.autonion.automationcompanion.features.cross_device_automation.presen import androidx.compose.animation.* import androidx.compose.animation.core.* import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add @@ -15,6 +18,8 @@ import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.MeetingRoom import androidx.compose.material.icons.filled.People import androidx.compose.material.icons.filled.Work +import androidx.compose.material.icons.rounded.* +import androidx.compose.material.icons.automirrored.rounded.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -30,6 +35,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel import androidx.activity.compose.rememberLauncherForActivityResult @@ -39,7 +45,9 @@ import com.autonion.automationcompanion.automation.actions.models.ConfiguredActi import com.autonion.automationcompanion.automation.actions.ui.ActionPicker import com.autonion.automationcompanion.features.cross_device_automation.CrossDeviceAutomationManager import com.autonion.automationcompanion.features.cross_device_automation.domain.AutomationRule +import com.autonion.automationcompanion.features.cross_device_automation.domain.RuleAction import android.content.Intent +import com.autonion.automationcompanion.ui.theme.AppTheme // ─── Colors ─────────────────────────────────────────────────── private val CardGlass = Color(0xFF1A1D2E).copy(alpha = 0.55f) @@ -130,38 +138,154 @@ fun DesktopAutomationScreen() { private fun EmptyRulesState() { val infiniteTransition = rememberInfiniteTransition(label = "pulse") val pulseScale by infiniteTransition.animateFloat( - initialValue = 0.9f, - targetValue = 1.1f, + initialValue = 0.95f, + targetValue = 1.05f, animationSpec = infiniteRepeatable( - animation = tween(2000, easing = FastOutSlowInEasing), + animation = tween(2200, easing = FastOutSlowInEasing), repeatMode = RepeatMode.Reverse ), label = "emptyPulse" ) - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Icon( - Icons.Default.AutoFixHigh, - contentDescription = null, + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Glassmorphic Info Banner Card + Box( modifier = Modifier - .size(64.dp) - .scale(pulseScale), - tint = AccentPurple.copy(alpha = 0.5f) - ) - Spacer(Modifier.height(16.dp)) - Text( - "Create your first rule", - color = Color.White.copy(alpha = 0.6f), - fontSize = 16.sp, - fontWeight = FontWeight.Medium - ) - Spacer(Modifier.height(4.dp)) + .fillMaxWidth() + .clip(RoundedCornerShape(20.dp)) + .background( + Brush.verticalGradient( + listOf(CardGlass.copy(alpha = 0.7f), CardGlass.copy(alpha = 0.35f)) + ) + ) + .background(CardBorder, RoundedCornerShape(20.dp)) + .padding(20.dp), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + // Glowy computer-to-phone icon + Box( + modifier = Modifier + .size(68.dp) + .clip(RoundedCornerShape(16.dp)) + .background(AccentPurple.copy(alpha = 0.12f)), + contentAlignment = Alignment.Center + ) { + Icon( + Icons.Default.AutoFixHigh, + contentDescription = null, + modifier = Modifier + .size(36.dp) + .scale(pulseScale), + tint = AccentPurple + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Desktop-to-Mobile Rules", + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Automate your phone based on what you browse on your PC! When a connected computer visits meetings, social, or work sites, your phone triggers designated settings instantly.", + color = Color.White.copy(alpha = 0.7f), + fontSize = 13.sp, + lineHeight = 18.sp, + textAlign = TextAlign.Center + ) + } + } + + Spacer(modifier = Modifier.height(24.dp)) + + // Simple visual how-to guide steps Text( - "e.g. \"Meeting → Enable DND\"", - color = Color.White.copy(alpha = 0.35f), - fontSize = 13.sp, - textAlign = TextAlign.Center + text = "HOW IT WORKS", + color = Color.White.copy(alpha = 0.4f), + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.sp ) + + Spacer(modifier = Modifier.height(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + HowItWorksStep( + number = "1", + title = "Browse PC", + subtitle = "Open a website category or URL on your connected PC.", + modifier = Modifier.weight(1f) + ) + HowItWorksStep( + number = "2", + title = "Trigger Sync", + subtitle = "PC Agent sends sync event to your phone.", + modifier = Modifier.weight(1f) + ) + HowItWorksStep( + number = "3", + title = "Automate", + subtitle = "Your phone mutes, opens apps, changes display, etc.", + modifier = Modifier.weight(1f) + ) + } + } +} + +@Composable +private fun HowItWorksStep(number: String, title: String, subtitle: String, modifier: Modifier = Modifier) { + Box( + modifier = modifier + .clip(RoundedCornerShape(12.dp)) + .background(CardGlass.copy(alpha = 0.35f)) + .background(CardBorder, RoundedCornerShape(12.dp)) + .padding(10.dp) + ) { + Column { + Box( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(AccentPurple.copy(alpha = 0.2f)), + contentAlignment = Alignment.Center + ) { + Text( + text = number, + color = AccentPurple, + fontSize = 11.sp, + fontWeight = FontWeight.Bold + ) + } + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = title, + color = Color.White, + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = subtitle, + color = Color.White.copy(alpha = 0.5f), + fontSize = 10.sp, + lineHeight = 13.sp + ) + } } } @@ -184,17 +308,28 @@ private fun StaggeredRuleItem(rule: AutomationRule, index: Int, onDelete: () -> @Composable private fun RuleGlassCard(rule: AutomationRule, onDelete: () -> Unit) { val categoryInfo = getCategoryInfo(rule) + val friendlyTriggerText = when { + categoryInfo.description.contains("meeting", ignoreCase = true) -> + "When browsing Meeting sites on PC (Zoom, Teams, Meet...)" + categoryInfo.description.contains("social", ignoreCase = true) -> + "When browsing Social Media on PC (YouTube, Twitter, Reddit...)" + categoryInfo.description.contains("work", ignoreCase = true) -> + "When browsing Work Portals on PC (GitHub, Slack, Jira...)" + categoryInfo.description.contains("URL:", ignoreCase = true) -> + "When visiting website containing: ${categoryInfo.description.removePrefix("URL: ")}" + else -> "When trigger matches: ${categoryInfo.description}" + } Box( modifier = Modifier .fillMaxWidth() - .clip(RoundedCornerShape(16.dp)) + .clip(RoundedCornerShape(20.dp)) .background( Brush.verticalGradient( - listOf(CardGlass, CardGlass.copy(alpha = 0.4f)) + listOf(CardGlass, CardGlass.copy(alpha = 0.45f)) ) ) - .background(CardBorder, RoundedCornerShape(16.dp)) + .background(CardBorder, RoundedCornerShape(20.dp)) ) { Row( modifier = Modifier @@ -205,8 +340,8 @@ private fun RuleGlassCard(rule: AutomationRule, onDelete: () -> Unit) { // Category icon Box( modifier = Modifier - .size(44.dp) - .clip(RoundedCornerShape(12.dp)) + .size(46.dp) + .clip(RoundedCornerShape(14.dp)) .background(categoryInfo.color.copy(alpha = 0.15f)), contentAlignment = Alignment.Center ) { @@ -224,31 +359,53 @@ private fun RuleGlassCard(rule: AutomationRule, onDelete: () -> Unit) { Text( rule.name, color = Color.White, - fontWeight = FontWeight.SemiBold, - fontSize = 15.sp, + fontWeight = FontWeight.Bold, + fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.Ellipsis ) - Spacer(Modifier.height(2.dp)) + Spacer(Modifier.height(4.dp)) Text( - categoryInfo.description, - color = categoryInfo.color.copy(alpha = 0.8f), + friendlyTriggerText, + color = Color.White.copy(alpha = 0.7f), fontSize = 12.sp, - maxLines = 1, + lineHeight = 15.sp, + maxLines = 2, overflow = TextOverflow.Ellipsis ) - Text( - "${rule.actions.size} action${if (rule.actions.size != 1) "s" else ""}", - color = Color.White.copy(alpha = 0.4f), - fontSize = 11.sp - ) + + Spacer(Modifier.height(10.dp)) + + // Horizontal Flow of action badges + if (rule.actions.isNotEmpty()) { + androidx.compose.foundation.layout.FlowRow( + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.fillMaxWidth() + ) { + rule.actions.forEach { action -> + ActionBadge(action) + } + } + } else { + Text( + "No actions configured", + color = Color.White.copy(alpha = 0.35f), + fontSize = 11.sp + ) + } } - IconButton(onClick = onDelete) { + IconButton( + onClick = onDelete, + modifier = Modifier + .align(Alignment.Top) + .padding(top = 2.dp) + ) { Icon( Icons.Default.Delete, contentDescription = "Delete", - tint = Color(0xFFFF6B6B).copy(alpha = 0.7f), + tint = Color(0xFFFF6B6B).copy(alpha = 0.85f), modifier = Modifier.size(20.dp) ) } @@ -256,6 +413,53 @@ private fun RuleGlassCard(rule: AutomationRule, onDelete: () -> Unit) { } } +@Composable +private fun ActionBadge(action: RuleAction) { + val info = when (action.type) { + "set_volume" -> Pair(Icons.AutoMirrored.Rounded.VolumeUp, "Set Volume") + "enable_dnd" -> Pair(Icons.Rounded.DoNotDisturb, "Enable DND") + "send_notification" -> Pair(Icons.Rounded.Notifications, "Notify") + "set_brightness" -> Pair(Icons.Rounded.Brightness6, "Brightness") + "set_auto_rotate" -> Pair(Icons.Rounded.ScreenRotation, "Auto-Rotate") + "set_screen_timeout" -> { + val durationMs = action.parameters["duration_ms"]?.toIntOrNull() ?: 0 + if (durationMs == Int.MAX_VALUE) { + Pair(Icons.Rounded.Visibility, "Keep Awake") + } else { + Pair(Icons.Rounded.Timer, "Screen Timeout") + } + } + "send_sms" -> Pair(Icons.AutoMirrored.Rounded.Message, "Send SMS") + "launch_app" -> Pair(Icons.Rounded.Apps, "Launch App") + "set_battery_saver" -> Pair(Icons.Rounded.BatterySaver, "Battery Saver") + else -> Pair(Icons.Rounded.AutoFixHigh, action.type) + } + + Row( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(Color.White.copy(alpha = 0.05f)) + .padding(horizontal = 8.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + imageVector = info.first, + contentDescription = null, + tint = AccentPurple, + modifier = Modifier.size(12.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = info.second, + color = Color.White.copy(alpha = 0.7f), + fontSize = 10.sp, + fontWeight = FontWeight.Medium + ) + } +} + + private data class CategoryInfo(val icon: ImageVector, val color: Color, val description: String) private fun getCategoryInfo(rule: AutomationRule): CategoryInfo { @@ -273,7 +477,7 @@ private fun getCategoryInfo(rule: AutomationRule): CategoryInfo { } // ═══════════════════════════════════════════════════════════════ -// CREATE RULE DIALOG (Kept mostly unchanged for function) +// CREATE RULE DIALOG (Premium overhaul) // ═══════════════════════════════════════════════════════════════ @Composable @@ -298,72 +502,178 @@ fun CreateDesktopRuleDialog( if (packageName != null && pendingAppActionIndex >= 0 && pendingAppActionIndex < actions.size) { val currentAction = actions[pendingAppActionIndex] if (currentAction is ConfiguredAction.AppAction) { - val updatedAction = currentAction.copy(packageName = packageName) - val newActions = actions.toMutableList() - newActions[pendingAppActionIndex] = updatedAction - actions = newActions + val updatedAction = currentAction.copy(packageName = packageName) + val newActions = actions.toMutableList() + newActions[pendingAppActionIndex] = updatedAction + actions = newActions } } } pendingAppActionIndex = -1 } - AlertDialog( + Dialog( onDismissRequest = onDismiss, - properties = DialogProperties(usePlatformDefaultWidth = false), - modifier = Modifier - .fillMaxWidth(0.95f) - .height(600.dp), - title = { Text(if (step == 1) "When..." else "Then...") }, - text = { - Column(modifier = Modifier.fillMaxSize()) { - if (step == 1) { - OutlinedTextField( - value = name, - onValueChange = { name = it }, - label = { Text("Rule Name (e.g. Work Mode)") }, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(16.dp)) - - Text("Trigger Condition", style = MaterialTheme.typography.labelLarge) - Spacer(modifier = Modifier.height(8.dp)) - - val categories = listOf("Meeting", "Social", "Work", "Custom URL") - var expanded by remember { mutableStateOf(false) } - - Box(modifier = Modifier.fillMaxWidth()) { - OutlinedButton( - onClick = { expanded = true }, - modifier = Modifier.fillMaxWidth() - ) { - Text(selectedCategory) - } - DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { - categories.forEach { cat -> - DropdownMenuItem( - text = { Text(cat) }, - onClick = { - selectedCategory = cat - expanded = false - } - ) - } - } + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + Box( + modifier = Modifier + .fillMaxWidth(0.92f) + .height(600.dp) + .clip(RoundedCornerShape(24.dp)) + .background(Color(0xFF1A1D2E)) // Solid opaque background + .background(CardBorder, RoundedCornerShape(24.dp)) + .padding(20.dp) + ) { + AppTheme(darkTheme = true) { // Force dark theme for this dialog to match the custom colors + CompositionLocalProvider(LocalContentColor provides Color.White) { + Column(modifier = Modifier.fillMaxSize()) { + // Header + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column { + Text( + text = "New Sync Rule", + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = if (step == 1) "Define trigger condition" else "Choose actions to trigger", + color = Color.White.copy(alpha = 0.5f), + fontSize = 12.sp + ) } - - if (selectedCategory == "Custom URL") { - Spacer(modifier = Modifier.height(8.dp)) - OutlinedTextField( - value = customUrl, - onValueChange = { customUrl = it }, - label = { Text("URL Contains (e.g. youtube.com)") }, - modifier = Modifier.fillMaxWidth() + + IconButton(onClick = onDismiss) { + Icon( + Icons.Rounded.Close, + contentDescription = "Close", + tint = Color.White.copy(alpha = 0.5f), + modifier = Modifier.size(20.dp) ) } - } else { - Box(modifier = Modifier.weight(1f)) { - androidx.compose.foundation.lazy.LazyColumn { + } + + Spacer(modifier = Modifier.height(10.dp)) + + // Progress indicator + StepIndicator(step = step) + + Spacer(modifier = Modifier.height(16.dp)) + + // Form content area + Box(modifier = Modifier.weight(1f)) { + if (step == 1) { + Column(modifier = Modifier.fillMaxSize()) { + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text("Rule Name") }, + placeholder = { Text("e.g. DND Meeting Mode") }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = AccentPurple, + unfocusedBorderColor = CardBorder, + focusedLabelColor = AccentPurple, + unfocusedLabelColor = Color.White.copy(alpha = 0.4f), + focusedTextColor = Color.White, + unfocusedTextColor = Color.White, + cursorColor = AccentPurple + ) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + "Trigger Condition", + color = Color.White.copy(alpha = 0.8f), + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(8.dp)) + + // 2x2 Grid of Category Card selectors + Row(modifier = Modifier.fillMaxWidth()) { + CategoryCard( + category = "Meeting", + label = "Meetings", + description = "Zoom, Teams, Meet...", + icon = Icons.Default.MeetingRoom, + color = MeetingColor, + isSelected = selectedCategory == "Meeting", + onClick = { selectedCategory = "Meeting" }, + modifier = Modifier.weight(1f) + ) + Spacer(Modifier.width(8.dp)) + CategoryCard( + category = "Social", + label = "Social Media", + description = "YouTube, Reddit, Twitter...", + icon = Icons.Default.People, + color = SocialColor, + isSelected = selectedCategory == "Social", + onClick = { selectedCategory = "Social" }, + modifier = Modifier.weight(1f) + ) + } + Spacer(Modifier.height(8.dp)) + Row(modifier = Modifier.fillMaxWidth()) { + CategoryCard( + category = "Work", + label = "Work Portals", + description = "Slack, GitHub, Jira...", + icon = Icons.Default.Work, + color = WorkColor, + isSelected = selectedCategory == "Work", + onClick = { selectedCategory = "Work" }, + modifier = Modifier.weight(1f) + ) + Spacer(Modifier.width(8.dp)) + CategoryCard( + category = "Custom URL", + label = "Custom URL", + description = "Target specific domains", + icon = Icons.Default.Language, + color = CustomColor, + isSelected = selectedCategory == "Custom URL", + onClick = { selectedCategory = "Custom URL" }, + modifier = Modifier.weight(1f) + ) + } + + if (selectedCategory == "Custom URL") { + Spacer(modifier = Modifier.height(12.dp)) + OutlinedTextField( + value = customUrl, + onValueChange = { customUrl = it }, + label = { Text("URL Contains") }, + placeholder = { Text("e.g. docs.google.com") }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = AccentPurple, + unfocusedBorderColor = CardBorder, + focusedLabelColor = AccentPurple, + unfocusedLabelColor = Color.White.copy(alpha = 0.4f), + focusedTextColor = Color.White, + unfocusedTextColor = Color.White, + cursorColor = AccentPurple + ) + ) + } + } + } else { + // Step 2 content + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxSize() + ) { item { ActionPicker( configuredActions = actions, @@ -380,28 +690,186 @@ fun CreateDesktopRuleDialog( } } } - } - }, - confirmButton = { - Button( - onClick = { + + Spacer(modifier = Modifier.height(16.dp)) + + // Bottom Buttons Row + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { if (step == 1) { - if (name.isNotBlank()) step = 2 + TextButton( + onClick = onDismiss, + colors = ButtonDefaults.textButtonColors(contentColor = Color.White.copy(alpha = 0.6f)) + ) { + Text("Cancel") + } + + Button( + onClick = { step = 2 }, + enabled = name.isNotBlank(), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = AccentPurple, + contentColor = Color.White, + disabledContainerColor = AccentPurple.copy(alpha = 0.3f), + disabledContentColor = Color.White.copy(alpha = 0.3f) + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Text("Next", fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.width(4.dp)) + Icon( + Icons.AutoMirrored.Rounded.ArrowForward, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + } + } } else { - onCreate(name, selectedCategory, customUrl, actions) + TextButton( + onClick = { step = 1 }, + colors = ButtonDefaults.textButtonColors(contentColor = Color.White.copy(alpha = 0.6f)) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text("Back") + } + } + + Button( + onClick = { + val categoryStr = if (selectedCategory == "Custom URL") "Custom URL" else selectedCategory + val urlValue = if (selectedCategory == "Custom URL") customUrl else null + onCreate(name, categoryStr, urlValue, actions) + }, + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = AccentPurple, + contentColor = Color.White + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + Icons.Rounded.Check, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text("Create Rule", fontWeight = FontWeight.Bold) + } + } } - }, - enabled = (step == 1 && name.isNotBlank()) || (step == 2 && actions.isNotEmpty()) - ) { - Text(if (step == 1) "Next" else "Create Rule") + } } - }, - dismissButton = { - TextButton(onClick = { - if (step == 2) step = 1 else onDismiss() - }) { - Text(if (step == 2) "Back" else "Cancel") + } + } +} +} +} + +@Composable +private fun StepIndicator(step: Int) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + val activeColor = AccentPurple + val inactiveColor = Color.White.copy(alpha = 0.1f) + + Box( + modifier = Modifier + .weight(1f) + .height(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(if (step >= 1) activeColor else inactiveColor) + ) + Box( + modifier = Modifier + .weight(1f) + .height(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(if (step >= 2) activeColor else inactiveColor) + ) + } +} + +@Composable +private fun CategoryCard( + category: String, + label: String, + description: String, + icon: ImageVector, + color: Color, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val borderWidth = if (isSelected) 1.5.dp else 1.dp + val borderColor = if (isSelected) color else Color.White.copy(alpha = 0.08f) + val bgColor = if (isSelected) color.copy(alpha = 0.15f) else CardGlass + + Box( + modifier = modifier + .clip(RoundedCornerShape(14.dp)) + .background(bgColor) + .clickable(onClick = onClick) + .border(borderWidth, borderColor, RoundedCornerShape(14.dp)) + .padding(12.dp) + ) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Box( + modifier = Modifier + .size(32.dp) + .clip(RoundedCornerShape(8.dp)) + .background(color.copy(alpha = 0.2f)), + contentAlignment = Alignment.Center + ) { + Icon( + icon, + contentDescription = null, + tint = color, + modifier = Modifier.size(16.dp) + ) + } + + Text( + text = label, + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 13.sp + ) } + + Text( + text = description, + color = Color.White.copy(alpha = 0.6f), + fontSize = 10.sp, + lineHeight = 13.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) } - ) + } } diff --git a/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt b/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt index 2476624..3800b27 100644 --- a/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt +++ b/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt @@ -209,19 +209,19 @@ fun HeroCard( val scale by animateFloatAsState(targetValue = if (isPressed) 0.95f else 1f, label = "scale") Card( + onClick = onClick, modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp) .graphicsLayer { scaleX = scale scaleY = scale - } - .clip(RoundedCornerShape(24.dp)) - .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, + }, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(24.dp), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), - border = if (isDark) BorderStroke(1.dp, Color.White.copy(alpha = 0.12f)) else null + border = if (isDark) BorderStroke(1.dp, Color.White.copy(alpha = 0.12f)) else null, + interactionSource = interactionSource ) { Column(modifier = Modifier.padding(20.dp)) { Row( @@ -268,18 +268,18 @@ fun GridCard( val scale by animateFloatAsState(targetValue = if (isPressed) 0.95f else 1f, label = "scale") Card( + onClick = onClick, modifier = modifier .height(190.dp) // Fixed height ensures uniform rows in grid layouts .graphicsLayer { scaleX = scale scaleY = scale - } - .clip(RoundedCornerShape(24.dp)) - .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, + }, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(24.dp), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), - border = if (isDark) BorderStroke(1.dp, Color.White.copy(alpha = 0.12f)) else null + border = if (isDark) BorderStroke(1.dp, Color.White.copy(alpha = 0.12f)) else null, + interactionSource = interactionSource ) { Column( modifier = Modifier.padding(16.dp) @@ -338,19 +338,19 @@ fun ListCard( val scale by animateFloatAsState(targetValue = if (isPressed) 0.95f else 1f, label = "scale") Card( + onClick = onClick, modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp, vertical = 6.dp) .graphicsLayer { scaleX = scale scaleY = scale - } - .clip(RoundedCornerShape(20.dp)) - .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, + }, colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(20.dp), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), - border = if (isDark) BorderStroke(1.dp, Color.White.copy(alpha = 0.12f)) else null + border = if (isDark) BorderStroke(1.dp, Color.White.copy(alpha = 0.12f)) else null, + interactionSource = interactionSource ) { Row( modifier = Modifier.padding(16.dp), @@ -392,17 +392,17 @@ fun StatusCard( val resolvedTitleColor = titleColor ?: MaterialTheme.colorScheme.onSurface Card( + onClick = onClick, modifier = modifier .height(110.dp) .graphicsLayer { scaleX = scale scaleY = scale - } - .clip(RoundedCornerShape(20.dp)) - .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, + }, colors = CardDefaults.cardColors(containerColor = backgroundColor), shape = RoundedCornerShape(20.dp), - border = if (isSystemInDarkTheme()) BorderStroke(1.dp, iconContainerColor.copy(alpha = 0.3f)) else BorderStroke(1.dp, iconContainerColor.copy(alpha = 0.1f)) + border = if (isSystemInDarkTheme()) BorderStroke(1.dp, iconContainerColor.copy(alpha = 0.3f)) else BorderStroke(1.dp, iconContainerColor.copy(alpha = 0.1f)), + interactionSource = interactionSource ) { Column( modifier = Modifier.padding(16.dp), @@ -454,18 +454,18 @@ fun BannerCard( val scale by animateFloatAsState(targetValue = if (isPressed) 0.95f else 1f, label = "scale") Card( + onClick = onClick, modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp) .graphicsLayer { scaleX = scale scaleY = scale - } - .clip(RoundedCornerShape(24.dp)) - .clickable(interactionSource = interactionSource, indication = LocalIndication.current) { onClick() }, + }, colors = CardDefaults.cardColors(containerColor = bannerBg), shape = RoundedCornerShape(24.dp), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + interactionSource = interactionSource ) { Row( modifier = Modifier.padding(20.dp), diff --git a/app/src/main/java/com/autonion/automationcompanion/ui/theme/AppTheme.kt b/app/src/main/java/com/autonion/automationcompanion/ui/theme/AppTheme.kt index 836c300..d2b1559 100644 --- a/app/src/main/java/com/autonion/automationcompanion/ui/theme/AppTheme.kt +++ b/app/src/main/java/com/autonion/automationcompanion/ui/theme/AppTheme.kt @@ -31,9 +31,16 @@ fun AppTheme( val view = LocalView.current if (!view.isInEditMode) { SideEffect { - val window = (view.context as Activity).window - window.statusBarColor = colorScheme.primary.toArgb() - WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + var context = view.context + while (context is android.content.ContextWrapper) { + if (context is Activity) break + context = context.baseContext + } + val window = (context as? Activity)?.window + if (window != null) { + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } } } From a738368f300553ae2e82ced5f0acdfcc82c05a9c Mon Sep 17 00:00:00 2001 From: Guru Preetam Bodapati Date: Fri, 22 May 2026 01:03:29 +0530 Subject: [PATCH 7/7] Added Privacy Policy link in footer. --- app/build.gradle.kts | 4 +- .../automationcompanion/ui/HomeScreen.kt | 20 ++++++++- .../ui/components/DashboardComponents.kt | 43 ++++++++++++++++--- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index af63d4d..424fb74 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,8 @@ android { applicationId = "com.autonion.automationcompanion" minSdk = 24 targetSdk = 36 - versionCode = 5 - versionName = "1.0.6" + versionCode = 6 + versionName = "1.0.7" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/autonion/automationcompanion/ui/HomeScreen.kt b/app/src/main/java/com/autonion/automationcompanion/ui/HomeScreen.kt index f40fd3e..0ec79e1 100644 --- a/app/src/main/java/com/autonion/automationcompanion/ui/HomeScreen.kt +++ b/app/src/main/java/com/autonion/automationcompanion/ui/HomeScreen.kt @@ -572,7 +572,7 @@ private fun HomeFooter() { verticalAlignment = Alignment.CenterVertically ) { Text( - text = "v1.0.6", + text = "v1.0.7", style = MaterialTheme.typography.bodySmall.copy( color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), fontSize = 12.sp @@ -596,6 +596,24 @@ private fun HomeFooter() { uriHandler.openUri("https://github.com/Autonion") } ) + Text( + text = " · ", + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), + fontSize = 12.sp + ) + ) + Text( + text = "Privacy Policy", + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f), + fontSize = 12.sp, + textDecoration = TextDecoration.Underline + ), + modifier = Modifier.clickable { + uriHandler.openUri("https://autonion.github.io/autonion-policies/") + } + ) } } diff --git a/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt b/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt index 3800b27..960efc3 100644 --- a/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt +++ b/app/src/main/java/com/autonion/automationcompanion/ui/components/DashboardComponents.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.autonion.automationcompanion.ui.theme.DarkBannerBackground @@ -629,14 +630,42 @@ fun TabletBrandingPanel( Spacer(modifier = Modifier.height(16.dp)) - // Version + GitHub - Text( - text = "v1.0.4 · github.com/Autonion", - style = MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), - fontSize = 12.sp + // Version + GitHub + Privacy Policy + val uriHandler = androidx.compose.ui.platform.LocalUriHandler.current + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "v1.0.6 · ", + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), + fontSize = 12.sp + ) ) - ) + Text( + text = "GitHub", + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f), + fontSize = 12.sp, + textDecoration = TextDecoration.Underline + ), + modifier = Modifier.clickable { uriHandler.openUri("https://github.com/Autonion") } + ) + Text( + text = " · ", + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), + fontSize = 12.sp + ) + ) + Text( + text = "Privacy Policy", + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f), + fontSize = 12.sp, + textDecoration = TextDecoration.Underline + ), + modifier = Modifier.clickable { uriHandler.openUri("https://autonion.github.io/autonion-policies/") } + ) + } } } }