diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index afd1626..773e182 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -30,6 +30,7 @@ import '../storage/message_store.dart'; import '../storage/unread_store.dart'; import '../utils/app_logger.dart'; import '../utils/battery_utils.dart'; +import '../utils/platform_info.dart'; import 'meshcore_protocol.dart'; class MeshCoreUuids { @@ -693,8 +694,7 @@ class MeshCoreConnector extends ChangeNotifier { await _scanSubscription?.cancel(); // On iOS/macOS, wait for Bluetooth to be powered on before scanning - if (defaultTargetPlatform == TargetPlatform.iOS || - defaultTargetPlatform == TargetPlatform.macOS) { + if (PlatformInfo.isIOS || PlatformInfo.isMacOS) { // Wait for adapter state to be powered on final adapterState = await FlutterBluePlus.adapterState.first; if (adapterState != BluetoothAdapterState.on) { diff --git a/lib/screens/scanner_screen.dart b/lib/screens/scanner_screen.dart index af9d75e..b5dedc1 100644 --- a/lib/screens/scanner_screen.dart +++ b/lib/screens/scanner_screen.dart @@ -1,7 +1,6 @@ import 'dart:async'; -import 'dart:io' show Platform; - import 'package:flutter/material.dart'; +import '../utils/platform_info.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:provider/provider.dart'; @@ -265,7 +264,7 @@ class _ScannerScreenState extends State { ], ), ), - if (Platform.isAndroid) + if (PlatformInfo.isAndroid) TextButton( onPressed: () => FlutterBluePlus.turnOn(), child: Text(context.l10n.scanner_enableBluetooth), diff --git a/lib/services/background_service.dart b/lib/services/background_service.dart index 0edd393..6202b3b 100644 --- a/lib/services/background_service.dart +++ b/lib/services/background_service.dart @@ -1,12 +1,11 @@ -import 'dart:io'; - +import '../utils/platform_info.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; class BackgroundService { bool _initialized = false; Future initialize() async { - if (!Platform.isAndroid || _initialized) return; + if (!PlatformInfo.isAndroid || _initialized) return; FlutterForegroundTask.init( androidNotificationOptions: AndroidNotificationOptions( channelId: 'meshcore_background', @@ -29,7 +28,7 @@ class BackgroundService { } Future start() async { - if (!Platform.isAndroid) return; + if (!PlatformInfo.isAndroid) return; if (!_initialized) { await initialize(); } @@ -43,7 +42,7 @@ class BackgroundService { } Future stop() async { - if (!Platform.isAndroid) return; + if (!PlatformInfo.isAndroid) return; final running = await FlutterForegroundTask.isRunningService; if (!running) return; await FlutterForegroundTask.stopService(); diff --git a/lib/utils/gpx_export.dart b/lib/utils/gpx_export.dart index 595479c..b0165bd 100644 --- a/lib/utils/gpx_export.dart +++ b/lib/utils/gpx_export.dart @@ -4,6 +4,7 @@ import 'package:meshcore_open/connector/meshcore_connector.dart'; import 'package:meshcore_open/connector/meshcore_protocol.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:io'; +import '../utils/platform_info.dart'; import 'package:share_plus/share_plus.dart'; @@ -109,6 +110,10 @@ class GpxExport { String shareText, String subject, ) async { + if (PlatformInfo.isWeb) { + debugPrint("GPX export is not supported on Web."); + return gpxExportNotAvailable; + } if (_contacts.isEmpty) { debugPrint("No repeaters to export – nothing to share."); return gpxExportNoContacts; diff --git a/lib/utils/platform_info.dart b/lib/utils/platform_info.dart new file mode 100644 index 0000000..399bf13 --- /dev/null +++ b/lib/utils/platform_info.dart @@ -0,0 +1,32 @@ +import 'package:flutter/foundation.dart'; +import 'dart:io' show Platform; + +/// Utility class to safely check the current platform across web and native. +/// +/// Using `Platform` from `dart:io` directly on Web causes a crash. +/// This class handles the `kIsWeb` check first to avoid those crashes. +class PlatformInfo { + /// Whether the app is running in a web browser. + static bool get isWeb => kIsWeb; + + /// Whether the app is running on Android. + static bool get isAndroid => !kIsWeb && Platform.isAndroid; + + /// Whether the app is running on iOS. + static bool get isIOS => !kIsWeb && Platform.isIOS; + + /// Whether the app is running on macOS. + static bool get isMacOS => !kIsWeb && Platform.isMacOS; + + /// Whether the app is running on Windows. + static bool get isWindows => !kIsWeb && Platform.isWindows; + + /// Whether the app is running on Linux. + static bool get isLinux => !kIsWeb && Platform.isLinux; + + /// Whether the app is running on a mobile platform (Android or iOS). + static bool get isMobile => isAndroid || isIOS; + + /// Whether the app is running on a desktop platform (macOS, Windows, or Linux). + static bool get isDesktop => isMacOS || isWindows || isLinux; +} diff --git a/lib/widgets/repeater_login_dialog.dart b/lib/widgets/repeater_login_dialog.dart index b550cc2..ec0af66 100644 --- a/lib/widgets/repeater_login_dialog.dart +++ b/lib/widgets/repeater_login_dialog.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import '../utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; @@ -326,8 +327,7 @@ class _RepeaterLoginDialogState extends State { }, onSubmitted: (_) => _handleLogin(), autofocus: - !(defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS) && + !PlatformInfo.isMobile && _passwordController.text.isEmpty, ), const SizedBox(height: 12), diff --git a/lib/widgets/room_login_dialog.dart b/lib/widgets/room_login_dialog.dart index cba7bec..7324f44 100644 --- a/lib/widgets/room_login_dialog.dart +++ b/lib/widgets/room_login_dialog.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import '../utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; @@ -274,8 +275,7 @@ class _RoomLoginDialogState extends State { ), onSubmitted: (_) => _handleLogin(), autofocus: - !(defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS) && + !PlatformInfo.isMobile && _passwordController.text.isEmpty, ), const SizedBox(height: 12),