From d11910b4651f03425feb556b5b883dd39074b796 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sat, 17 Jan 2026 14:27:40 +0100 Subject: [PATCH 1/2] Show a user-friendly view when ADB could not be found - Improve scrcpy not found infoBar --- assets/i18n/en.json | 16 ++++-- lib/presentation/devices/devices_screen.dart | 13 +++-- .../devices/widget/adb_not_found_widget.dart | 57 +++++++++++++++++++ lib/presentation/home/home_screen.dart | 3 +- 4 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 lib/presentation/devices/widget/adb_not_found_widget.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 58e3963..a92461f 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -27,8 +27,8 @@ "error": { "scrcpy": { "notFound": { - "title": "Scrcpy not found", - "message": "Scrcpy not found in path. Please make sure if it's installed. If already installed, please configure the path in settings." + "title": "Scrcpy could not be found", + "message": "Please make sure if it's installed. If already installed, please configure the path in settings." }, "failedToStart": "Failed to start scrcpy", "unexpectedStop": { @@ -40,7 +40,7 @@ } } }, - "goToSettings": "Go to settings", + "openSettings": "Open settings", "profile": { "default": "Default", "manage": "Manage profiles", @@ -83,6 +83,12 @@ "serial": "Serial", "actions": "Actions" }, + "adbNotFound": { + "title": "ADB could not be found", + "hint": "What is ADB?", + "description": "Please make sure if it's installed. If already installed, please configure the path in ", + "settings": "settings" + }, "copy": "Copy", "stop": "Stop", "toNetwork": "To Network", @@ -100,14 +106,14 @@ "adbExecutable": { "title": "adb executable", "dialogTitle": "adb scrcpy executable", - "description": "Explicitly set adb executable path.\nSet this in case the executable is not added to $PATH or, the tool can't find it.", + "description": "Explicitly set adb executable path.\nSet this in case the executable is not added to $PATH, or the app can't find it.", "invalidPath": "Invalid path provided", "check": "Check" }, "scrcpyExecutable": { "title": "scrcpy executable", "dialogTitle": "Select scrcpy executable", - "description": "Explicitly set scrcpy executable path.\nSet this in case the executable is not added to $PATH or, the tool can't find it.", + "description": "Explicitly set scrcpy executable path.\nSet this in case the executable is not added to $PATH, or the app can't find it.", "invalidPath": "Invalid path provided", "check": "Check" }, diff --git a/lib/presentation/devices/devices_screen.dart b/lib/presentation/devices/devices_screen.dart index 927d579..d3d223c 100644 --- a/lib/presentation/devices/devices_screen.dart +++ b/lib/presentation/devices/devices_screen.dart @@ -1,9 +1,11 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:scrcpy_buddy/application/extension/adb_error_extension.dart'; +import 'package:scrcpy_buddy/application/model/adb/adb_error.dart'; import 'package:scrcpy_buddy/application/scrcpy_bloc/scrcpy_bloc.dart'; import 'package:scrcpy_buddy/presentation/devices/bloc/devices_bloc.dart'; import 'package:scrcpy_buddy/presentation/devices/device_row.dart'; +import 'package:scrcpy_buddy/presentation/devices/widget/adb_not_found_widget.dart'; import 'package:scrcpy_buddy/presentation/devices/widget/devices_header.dart'; import 'package:scrcpy_buddy/presentation/extension/context_extension.dart'; import 'package:scrcpy_buddy/presentation/extension/translation_extension.dart'; @@ -54,11 +56,14 @@ class _DevicesScreenState extends AppModuleState { const Center(child: ProgressBar()), ], if (devicesState is DevicesUpdateError) ...[ - Center( - child: Text( - devicesState.adbError?.message ?? context.translatedText(key: 'common.somethingWentWrong'), + if (devicesState.adbError is AdbNotFoundError) + AdbNotFoundWidget() + else + Center( + child: Text( + devicesState.adbError?.message ?? context.translatedText(key: 'common.somethingWentWrong'), + ), ), - ), ], if (devicesState is DevicesUpdateSuccess) ...[ if (devicesState.devices.isEmpty) ...[ diff --git a/lib/presentation/devices/widget/adb_not_found_widget.dart b/lib/presentation/devices/widget/adb_not_found_widget.dart new file mode 100644 index 0000000..c80f51c --- /dev/null +++ b/lib/presentation/devices/widget/adb_not_found_widget.dart @@ -0,0 +1,57 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:go_router/go_router.dart'; +import 'package:scrcpy_buddy/presentation/extension/context_extension.dart'; +import 'package:scrcpy_buddy/presentation/scrcpy_config/widgets/link_span.dart'; +import 'package:scrcpy_buddy/presentation/widgets/app_widgets.dart'; +import 'package:scrcpy_buddy/routes.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class AdbNotFoundWidget extends AppStatelessWidget { + const AdbNotFoundWidget({super.key}); + + @override + String get module => 'devices.adbNotFound'; + + void _openAdbDocs() => _launchUrlString("https://developer.android.com/tools/adb"); + + void _launchUrlString(String url) async { + if (await canLaunchUrlString(url)) { + launchUrlString(url); + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: .center, + children: [ + WindowsIcon(WindowsIcons.warning), + const SizedBox(width: 4), + Text(translatedText(context, key: 'title'), style: context.typography.title), + ], + ), + const SizedBox(height: 8), + RichText( + textAlign: .center, + text: TextSpan( + style: context.typography.body, + children: [ + TextSpan(text: translatedText(context, key: 'description')), + LinkSpan( + text: translatedText(context, key: 'settings'), + onTap: () => context.go(AppRoute.settings), + ), + TextSpan(text: '\n'), + LinkSpan( + text: translatedText(context, key: 'hint'), + onTap: _openAdbDocs, + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/home/home_screen.dart b/lib/presentation/home/home_screen.dart index 01740b1..3d12640 100644 --- a/lib/presentation/home/home_screen.dart +++ b/lib/presentation/home/home_screen.dart @@ -258,8 +258,9 @@ class _HomeScreenState extends AppModuleState with WindowListener, T title: translatedText(key: 'error.scrcpy.notFound.title'), content: translatedText(key: 'error.scrcpy.notFound.message'), severity: InfoBarSeverity.error, + isLong: true, action: HyperlinkButton( - child: Text(translatedText(key: 'goToSettings')), + child: Text(translatedText(key: 'openSettings')), onPressed: () => router.push(AppRoute.settings), ), ); From 212cd8252c0627519c762a59df2a4488c959ca95 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sat, 17 Jan 2026 14:28:15 +0100 Subject: [PATCH 2/2] Fix devices getting de-selected upon refresh --- lib/presentation/devices/bloc/devices_bloc.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/devices/bloc/devices_bloc.dart b/lib/presentation/devices/bloc/devices_bloc.dart index af4d89d..22d1aee 100644 --- a/lib/presentation/devices/bloc/devices_bloc.dart +++ b/lib/presentation/devices/bloc/devices_bloc.dart @@ -61,13 +61,13 @@ class DevicesBloc extends Bloc { .where((device) => device.status == AdbDeviceStatus.device) .map((d) => d.serial) .toSet(); - final newSelectedDeviceSerials = _selectedDeviceSerials.where((serial) => currentSerials.contains(serial)); + final newSelectedDeviceSerials = _selectedDeviceSerials.intersection(currentSerials); _selectedDeviceSerials.clear(); _selectedDeviceSerials.addAll(newSelectedDeviceSerials); _emitSuccess(emit); }, ); - } on ProcessException catch (e) { + } on ProcessException catch (_) { emit( DevicesUpdateError( devices: _devices,