Merge pull request #217 from MeshEnvy/chrome/main

enh: Chrome compatibility
This commit is contained in:
zjs81 2026-03-01 20:02:29 -07:00 committed by GitHub
commit 6bd3c17cdf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 399 additions and 31 deletions

38
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: Deploy to Cloudflare Workers
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
# Match local development version which provides Dart 3.11.0
flutter-version: '3.41.2'
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Get dependencies
run: flutter pub get
- name: Build Web
run: bun run build
- name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy

3
.gitignore vendored
View file

@ -83,3 +83,6 @@ keystore.properties
# IDE
.vscode/launch.json
.vscode/settings.json
# Cloudflare Wrangler
.wrangler

View file

@ -717,17 +717,13 @@ class MeshCoreConnector extends ChangeNotifier {
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
_scanResults.clear();
for (var result in results) {
if (result.device.platformName.startsWith("MeshCore-") ||
result.advertisementData.advName.startsWith("MeshCore-") ||
result.advertisementData.advName.startsWith("Whisper-")) {
_scanResults.add(result);
}
}
_scanResults.addAll(results);
notifyListeners();
});
await FlutterBluePlus.startScan(
withKeywords: ["MeshCore-", "Whisper-"],
webOptionalServices: [Guid(MeshCoreUuids.service)],
timeout: timeout,
androidScanMode: AndroidScanMode.lowLatency,
);

View file

@ -1606,6 +1606,8 @@
"scanner_bluetoothOff": "Bluetooth е изключен.",
"scanner_enableBluetooth": "Активирайте Bluetooth",
"scanner_bluetoothOffMessage": "Моля, активирайте Bluetooth, за да сканирате за устройства.",
"scanner_chromeRequired": "Изисква се браузър Chrome",
"scanner_chromeRequiredMessage": "Това уеб приложение изисква Google Chrome или браузър, базиран на Chromium, за поддръжка на Bluetooth.",
"snrIndicator_lastSeen": "Последно видян",
"snrIndicator_nearByRepeaters": "Близки повтарящи се устройства",
"chat_ShowAllPaths": "Покажи всички пътища",

View file

@ -1635,6 +1635,8 @@
"pathTrace_clearTooltip": "Pfad löschen",
"map_pathTraceCancelled": "Pfadverfolgung abgebrochen.",
"scanner_bluetoothOffMessage": "Bitte aktivieren Sie Bluetooth, um nach Geräten zu suchen.",
"scanner_chromeRequired": "Chrome Browser erforderlich",
"scanner_chromeRequiredMessage": "Diese Webanwendung erfordert Google Chrome oder einen Chromium-basierten Browser für die Bluetooth-Unterstützung.",
"scanner_bluetoothOff": "Bluetooth ist deaktiviert.",
"scanner_enableBluetooth": "Bluetooth aktivieren",
"snrIndicator_lastSeen": "Zuletzt gesehen",

View file

@ -72,6 +72,8 @@
"scanner_scan": "Scan",
"scanner_bluetoothOff": "Bluetooth is off",
"scanner_bluetoothOffMessage": "Please turn on Bluetooth to scan for devices",
"scanner_chromeRequired": "Chrome Browser Required",
"scanner_chromeRequiredMessage": "This web application requires Google Chrome or a Chromium-based browser for Bluetooth support.",
"scanner_enableBluetooth": "Enable Bluetooth",
"device_quickSwitch": "Quick switch",
"device_meshcore": "MeshCore",

View file

@ -1632,6 +1632,8 @@
"map_removeLast": "Eliminar último",
"map_pathTraceCancelled": "Rastreo de ruta cancelado.",
"scanner_bluetoothOffMessage": "Por favor, active el Bluetooth para escanear dispositivos.",
"scanner_chromeRequired": "Navegador Chrome requerido",
"scanner_chromeRequiredMessage": "Esta aplicación web requiere Google Chrome o un navegador basado en Chromium para el soporte de Bluetooth.",
"scanner_bluetoothOff": "Bluetooth está desactivado.",
"scanner_enableBluetooth": "Habilitar Bluetooth",
"snrIndicator_nearByRepeaters": "Repetidores cercanos",

View file

@ -1604,6 +1604,8 @@
"map_removeLast": "Supprimer le dernier",
"map_runTrace": "Exécuter la traçage de chemin",
"scanner_bluetoothOffMessage": "Veuillez activer le Bluetooth pour rechercher des appareils.",
"scanner_chromeRequired": "Navigateur Chrome requis",
"scanner_chromeRequiredMessage": "Cette application web nécessite Google Chrome ou un navigateur basé sur Chromium pour le support Bluetooth.",
"scanner_bluetoothOff": "Le Bluetooth est désactivé.",
"scanner_enableBluetooth": "Activer le Bluetooth",
"snrIndicator_lastSeen": "Dernière fois vu",

View file

@ -1605,6 +1605,8 @@
"map_tapToAdd": "Tocca i nodi per aggiungerli al percorso.",
"scanner_bluetoothOff": "Il Bluetooth è disattivato.",
"scanner_bluetoothOffMessage": "Si prega di attivare il Bluetooth per effettuare la scansione dei dispositivi.",
"scanner_chromeRequired": "Browser Chrome richiesto",
"scanner_chromeRequiredMessage": "Questa applicazione web richiede Google Chrome o un browser basato su Chromium per il supporto Bluetooth.",
"scanner_enableBluetooth": "Abilita il Bluetooth",
"snrIndicator_nearByRepeaters": "Ripetitori vicini",
"snrIndicator_lastSeen": "Ultimo accesso",

View file

@ -388,6 +388,18 @@ abstract class AppLocalizations {
/// **'Please turn on Bluetooth to scan for devices'**
String get scanner_bluetoothOffMessage;
/// No description provided for @scanner_chromeRequired.
///
/// In en, this message translates to:
/// **'Chrome Browser Required'**
String get scanner_chromeRequired;
/// No description provided for @scanner_chromeRequiredMessage.
///
/// In en, this message translates to:
/// **'This web application requires Google Chrome or a Chromium-based browser for Bluetooth support.'**
String get scanner_chromeRequiredMessage;
/// No description provided for @scanner_enableBluetooth.
///
/// In en, this message translates to:

View file

@ -150,6 +150,13 @@ class AppLocalizationsBg extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Моля, активирайте Bluetooth, за да сканирате за устройства.';
@override
String get scanner_chromeRequired => 'Изисква се браузър Chrome';
@override
String get scanner_chromeRequiredMessage =>
'Това уеб приложение изисква Google Chrome или браузър, базиран на Chromium, за поддръжка на Bluetooth.';
@override
String get scanner_enableBluetooth => 'Активирайте Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsDe extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Bitte aktivieren Sie Bluetooth, um nach Geräten zu suchen.';
@override
String get scanner_chromeRequired => 'Chrome Browser erforderlich';
@override
String get scanner_chromeRequiredMessage =>
'Diese Webanwendung erfordert Google Chrome oder einen Chromium-basierten Browser für die Bluetooth-Unterstützung.';
@override
String get scanner_enableBluetooth => 'Bluetooth aktivieren';

View file

@ -149,6 +149,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Please turn on Bluetooth to scan for devices';
@override
String get scanner_chromeRequired => 'Chrome Browser Required';
@override
String get scanner_chromeRequiredMessage =>
'This web application requires Google Chrome or a Chromium-based browser for Bluetooth support.';
@override
String get scanner_enableBluetooth => 'Enable Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsEs extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Por favor, active el Bluetooth para escanear dispositivos.';
@override
String get scanner_chromeRequired => 'Navegador Chrome requerido';
@override
String get scanner_chromeRequiredMessage =>
'Esta aplicación web requiere Google Chrome o un navegador basado en Chromium para el soporte de Bluetooth.';
@override
String get scanner_enableBluetooth => 'Habilitar Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Veuillez activer le Bluetooth pour rechercher des appareils.';
@override
String get scanner_chromeRequired => 'Navigateur Chrome requis';
@override
String get scanner_chromeRequiredMessage =>
'Cette application web nécessite Google Chrome ou un navigateur basé sur Chromium pour le support Bluetooth.';
@override
String get scanner_enableBluetooth => 'Activer le Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsIt extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Si prega di attivare il Bluetooth per effettuare la scansione dei dispositivi.';
@override
String get scanner_chromeRequired => 'Browser Chrome richiesto';
@override
String get scanner_chromeRequiredMessage =>
'Questa applicazione web richiede Google Chrome o un browser basato su Chromium per il supporto Bluetooth.';
@override
String get scanner_enableBluetooth => 'Abilita il Bluetooth';

View file

@ -149,6 +149,13 @@ class AppLocalizationsNl extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Zorg ervoor dat Bluetooth is ingeschakeld om naar apparaten te zoeken.';
@override
String get scanner_chromeRequired => 'Chrome-browser vereist';
@override
String get scanner_chromeRequiredMessage =>
'Deze webapplicatie vereist Google Chrome of een op Chromium gebaseerde browser voor Bluetooth-ondersteuning.';
@override
String get scanner_enableBluetooth => 'Activeer Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsPl extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Prosimy włączyć Bluetooth, aby przeskanować urządzenia.';
@override
String get scanner_chromeRequired => 'Wymagana przeglądarka Chrome';
@override
String get scanner_chromeRequiredMessage =>
'Ta aplikacja internetowa wymaga przeglądarki Google Chrome lub opartej na Chromium do obsługi Bluetooth.';
@override
String get scanner_enableBluetooth => 'Włącz Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsPt extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Por favor, ative o Bluetooth para escanear por dispositivos.';
@override
String get scanner_chromeRequired => 'Navegador Chrome necessário';
@override
String get scanner_chromeRequiredMessage =>
'Esta aplicação web requer o Google Chrome ou um navegador baseado no Chromium para suporte de Bluetooth.';
@override
String get scanner_enableBluetooth => 'Ative o Bluetooth';

View file

@ -149,6 +149,13 @@ class AppLocalizationsRu extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Пожалуйста, включите Bluetooth, чтобы найти устройства.';
@override
String get scanner_chromeRequired => 'Требуется браузер Chrome';
@override
String get scanner_chromeRequiredMessage =>
'Для поддержки Bluetooth в этом веб-приложении требуется Google Chrome или браузер на базе Chromium.';
@override
String get scanner_enableBluetooth => 'Включите Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsSk extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Prosím, zapnite Bluetooth, aby ste mohli skenovať pre zariadenia.';
@override
String get scanner_chromeRequired => 'Vyžaduje sa prehliadač Chrome';
@override
String get scanner_chromeRequiredMessage =>
'Táto webová aplikácia vyžaduje Google Chrome alebo prehliadač založený na Chromium pre podporu Bluetooth.';
@override
String get scanner_enableBluetooth => 'Povolte Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsSl extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Prosimo, vklopite Bluetooth, da lahko poiščete naprave.';
@override
String get scanner_chromeRequired => 'Zahtevan brskalnik Chrome';
@override
String get scanner_chromeRequiredMessage =>
'Ta spletna aplikacija za podporo Bluetooth zahteva Google Chrome ali brskalnik na osnovi Chromiuma.';
@override
String get scanner_enableBluetooth => 'Omogočite Bluetooth';

View file

@ -149,6 +149,13 @@ class AppLocalizationsSv extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Vänligen aktivera Bluetooth för att söka efter enheter.';
@override
String get scanner_chromeRequired => 'Chrome-webbläsare krävs';
@override
String get scanner_chromeRequiredMessage =>
'Denna webbapplikation kräver Google Chrome oder en Chromium-baserader webbläsare för Bluetooth-stöd.';
@override
String get scanner_enableBluetooth => 'Aktivera Bluetooth';

View file

@ -150,6 +150,13 @@ class AppLocalizationsUk extends AppLocalizations {
String get scanner_bluetoothOffMessage =>
'Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.';
@override
String get scanner_chromeRequired => 'Потрібен браузер Chrome';
@override
String get scanner_chromeRequiredMessage =>
'Для підтримки Bluetooth у цьому веб-додатку потрібен Google Chrome або браузер на базі Chromium.';
@override
String get scanner_enableBluetooth => 'Увімкніть Bluetooth';

View file

@ -148,6 +148,13 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get scanner_bluetoothOffMessage => '请开启蓝牙以搜索设备';
@override
String get scanner_chromeRequired => '需要 Chrome 浏览器';
@override
String get scanner_chromeRequiredMessage =>
'此 Web 应用程序需要 Google Chrome 或基于 Chromium 的浏览器以支持蓝牙。';
@override
String get scanner_enableBluetooth => '启用蓝牙';

View file

@ -1605,6 +1605,8 @@
"map_runTrace": "Padeshulp traceren",
"scanner_enableBluetooth": "Activeer Bluetooth",
"scanner_bluetoothOffMessage": "Zorg ervoor dat Bluetooth is ingeschakeld om naar apparaten te zoeken.",
"scanner_chromeRequired": "Chrome-browser vereist",
"scanner_chromeRequiredMessage": "Deze webapplicatie vereist Google Chrome of een op Chromium gebaseerde browser voor Bluetooth-ondersteuning.",
"scanner_bluetoothOff": "Bluetooth is uitgeschakeld",
"snrIndicator_lastSeen": "Laatst gezien",
"snrIndicator_nearByRepeaters": "Nabije herhalingseenheden",

View file

@ -1604,6 +1604,8 @@
"map_removeLast": "Usuń ostatni",
"map_tapToAdd": "Kliknij na węzły, aby dodać je do ścieżki.",
"scanner_bluetoothOffMessage": "Prosimy włączyć Bluetooth, aby przeskanować urządzenia.",
"scanner_chromeRequired": "Wymagana przeglądarka Chrome",
"scanner_chromeRequiredMessage": "Ta aplikacja internetowa wymaga przeglądarki Google Chrome lub opartej na Chromium do obsługi Bluetooth.",
"scanner_bluetoothOff": "Bluetooth jest wyłączony",
"scanner_enableBluetooth": "Włącz Bluetooth",
"snrIndicator_lastSeen": "Ostatnio widziany",

View file

@ -1606,6 +1606,8 @@
"scanner_enableBluetooth": "Ative o Bluetooth",
"scanner_bluetoothOff": "Bluetooth está desativado",
"scanner_bluetoothOffMessage": "Por favor, ative o Bluetooth para escanear por dispositivos.",
"scanner_chromeRequired": "Navegador Chrome necessário",
"scanner_chromeRequiredMessage": "Esta aplicação web requer o Google Chrome ou um navegador baseado no Chromium para suporte de Bluetooth.",
"snrIndicator_nearByRepeaters": "Repetidores Próximos",
"snrIndicator_lastSeen": "Visto pela última vez",
"chat_ShowAllPaths": "Mostrar todos os caminhos",

View file

@ -846,6 +846,8 @@
"scanner_enableBluetooth": "Включите Bluetooth",
"scanner_bluetoothOff": "Bluetooth выключен",
"scanner_bluetoothOffMessage": "Пожалуйста, включите Bluetooth, чтобы найти устройства.",
"scanner_chromeRequired": "Требуется браузер Chrome",
"scanner_chromeRequiredMessage": "Для поддержки Bluetooth в этом веб-приложении требуется Google Chrome или браузер на базе Chromium.",
"snrIndicator_nearByRepeaters": "Ближайшие ретрансляторы",
"snrIndicator_lastSeen": "Последний раз видели",
"chat_ShowAllPaths": "Показать все пути",

View file

@ -1604,6 +1604,8 @@
"map_runTrace": "Spustiť trasovaním cesty",
"map_pathTraceCancelled": "Zrušenie stopáže cesty bolo zrušené.",
"scanner_bluetoothOffMessage": "Prosím, zapnite Bluetooth, aby ste mohli skenovať pre zariadenia.",
"scanner_chromeRequired": "Vyžaduje sa prehliadač Chrome",
"scanner_chromeRequiredMessage": "Táto webová aplikácia vyžaduje Google Chrome alebo prehliadač založený na Chromium pre podporu Bluetooth.",
"scanner_bluetoothOff": "Bluetooth je vypnutý",
"scanner_enableBluetooth": "Povolte Bluetooth",
"snrIndicator_lastSeen": "Naposledy videný",

View file

@ -1605,6 +1605,8 @@
"map_pathTraceCancelled": "Spremljanje poti je prekinjeno.",
"scanner_enableBluetooth": "Omogočite Bluetooth",
"scanner_bluetoothOffMessage": "Prosimo, vklopite Bluetooth, da lahko poiščete naprave.",
"scanner_chromeRequired": "Zahtevan brskalnik Chrome",
"scanner_chromeRequiredMessage": "Ta spletna aplikacija za podporo Bluetooth zahteva Google Chrome ali brskalnik na osnovi Chromiuma.",
"scanner_bluetoothOff": "Bluetooth je izklopljen",
"snrIndicator_lastSeen": "Zadnjič videno",
"snrIndicator_nearByRepeaters": "Bližnji ponovitelji",

View file

@ -1605,6 +1605,8 @@
"map_removeLast": "Ta bort sista",
"scanner_enableBluetooth": "Aktivera Bluetooth",
"scanner_bluetoothOffMessage": "Vänligen aktivera Bluetooth för att söka efter enheter.",
"scanner_chromeRequired": "Chrome-webbläsare krävs",
"scanner_chromeRequiredMessage": "Denna webbapplikation kräver Google Chrome oder en Chromium-baserader webbläsare för Bluetooth-stöd.",
"scanner_bluetoothOff": "Bluetooth är avstängt",
"snrIndicator_lastSeen": "Senast sedd",
"snrIndicator_nearByRepeaters": "Närliggande uppreparstationer",

View file

@ -1605,6 +1605,8 @@
"map_pathTraceCancelled": "Відмінується трасування шляху",
"scanner_enableBluetooth": "Увімкніть Bluetooth",
"scanner_bluetoothOffMessage": "Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.",
"scanner_chromeRequired": "Потрібен браузер Chrome",
"scanner_chromeRequiredMessage": "Для підтримки Bluetooth у цьому веб-додатку потрібен Google Chrome або браузер на базі Chromium.",
"scanner_bluetoothOff": "Bluetooth вимкнено",
"snrIndicator_lastSeen": "Останній раз бачили",
"snrIndicator_nearByRepeaters": "Ближні ретранслятори",

View file

@ -1609,6 +1609,8 @@
"map_removeLast": "移除最后一个",
"map_runTrace": "运行路径追踪",
"scanner_bluetoothOffMessage": "请开启蓝牙以搜索设备",
"scanner_chromeRequired": "需要 Chrome 浏览器",
"scanner_chromeRequiredMessage": "此 Web 应用程序需要 Google Chrome 或基于 Chromium 的浏览器以支持蓝牙。",
"scanner_bluetoothOff": "蓝牙已关闭",
"scanner_enableBluetooth": "启用蓝牙",
"snrIndicator_lastSeen": "最近访问",

View file

@ -4,6 +4,9 @@ import 'package:flutter/foundation.dart';
import 'l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'screens/chrome_required_screen.dart';
import 'utils/platform_info.dart';
import 'connector/meshcore_connector.dart';
import 'screens/scanner_screen.dart';
import 'services/storage_service.dart';
@ -187,7 +190,9 @@ class MeshCoreApp extends StatelessWidget {
NotificationService().setLocale(locale);
return child ?? const SizedBox.shrink();
},
home: const ScannerScreen(),
home: (PlatformInfo.isWeb && !PlatformInfo.isChrome)
? const ChromeRequiredScreen()
: const ScannerScreen(),
);
},
),

View file

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import '../l10n/l10n.dart';
class ChromeRequiredScreen extends StatelessWidget {
const ChromeRequiredScreen({super.key});
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
return Scaffold(
body: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 32),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [const Color(0xFF1A1A1A), const Color(0xFF0D0D0D)]
: [const Color(0xFFF5F7FA), const Color(0xFFE4E7EB)],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.browser_not_supported_rounded,
size: 80,
color: Colors.orange,
),
),
const SizedBox(height: 32),
Text(
l10n.scanner_chromeRequired,
textAlign: TextAlign.center,
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
const SizedBox(height: 16),
Text(
l10n.scanner_chromeRequiredMessage,
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge?.copyWith(
color: isDark ? Colors.white70 : Colors.black54,
height: 1.5,
),
),
const SizedBox(height: 48),
// We can't really "fix" it for them other than telling them to use Chrome
// but we can provide a nice visual.
Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: Colors.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(30),
border: Border.all(color: Colors.blue.withValues(alpha: 0.3)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.info_outline, size: 20, color: Colors.blue),
const SizedBox(width: 12),
Text(
"Web Bluetooth requires a Chromium browser",
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
),
);
}
}

View file

@ -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';
@ -46,17 +45,22 @@ class _ScannerScreenState extends State<ScannerScreen> {
connector.addListener(_connectionListener);
_bluetoothStateSubscription = FlutterBluePlus.adapterState.listen((state) {
if (mounted) {
setState(() {
_bluetoothState = state;
});
// Cancel scan if Bluetooth turns off while scanning
if (state != BluetoothAdapterState.on) {
unawaited(connector.stopScan());
_bluetoothStateSubscription = FlutterBluePlus.adapterState.listen(
(state) {
if (mounted) {
setState(() {
_bluetoothState = state;
});
// Cancel scan if Bluetooth turns off while scanning
if (state != BluetoothAdapterState.on) {
unawaited(connector.stopScan());
}
}
}
});
},
onError: (Object e) {
debugPrint("Scanner adapterState stream error: $e");
},
);
}
@override
@ -108,7 +112,11 @@ class _ScannerScreenState extends State<ScannerScreen> {
if (isScanning) {
connector.stopScan();
} else {
connector.startScan();
unawaited(
connector.startScan().catchError((e) {
debugPrint("Scanner screen startScan error: $e");
}),
);
}
},
icon: isScanning
@ -265,7 +273,7 @@ class _ScannerScreenState extends State<ScannerScreen> {
],
),
),
if (Platform.isAndroid)
if (PlatformInfo.isAndroid)
TextButton(
onPressed: () => FlutterBluePlus.turnOn(),
child: Text(context.l10n.scanner_enableBluetooth),

View file

@ -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<void> 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<void> start() async {
if (!Platform.isAndroid) return;
if (!PlatformInfo.isAndroid) return;
if (!_initialized) {
await initialize();
}
@ -43,7 +42,7 @@ class BackgroundService {
}
Future<void> stop() async {
if (!Platform.isAndroid) return;
if (!PlatformInfo.isAndroid) return;
final running = await FlutterForegroundTask.isRunningService;
if (!running) return;
await FlutterForegroundTask.stopService();

View file

@ -0,0 +1,2 @@
export 'browser_detection_stub.dart'
if (dart.library.js_interop) 'browser_detection_web.dart';

View file

@ -0,0 +1,3 @@
class BrowserDetection {
static bool get isChrome => false;
}

View file

@ -0,0 +1,9 @@
import 'package:web/web.dart' as web;
class BrowserDetection {
static bool get isChrome {
final userAgent = web.window.navigator.userAgent.toLowerCase();
final isChrome = userAgent.contains('chrome');
return isChrome;
}
}

View file

@ -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;

View file

@ -0,0 +1,36 @@
import 'package:flutter/foundation.dart';
import 'dart:io' show Platform;
import 'browser_detection.dart';
/// 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 in the Chrome browser (only relevant if [isWeb] is true).
static bool get isChrome => isWeb && BrowserDetection.isChrome;
/// 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;
}

View file

@ -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<RepeaterLoginDialog> {
},
onSubmitted: (_) => _handleLogin(),
autofocus:
!(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS) &&
!PlatformInfo.isMobile &&
_passwordController.text.isEmpty,
),
const SizedBox(height: 12),

View file

@ -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<RoomLoginDialog> {
),
onSubmitted: (_) => _handleLogin(),
autofocus:
!(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS) &&
!PlatformInfo.isMobile &&
_passwordController.text.isEmpty,
),
const SizedBox(height: 12),

7
package.json Normal file
View file

@ -0,0 +1,7 @@
{
"name": "meshcore-open",
"scripts": {
"build": "dart run build_pipe:build",
"deploy": "bun x wrangler deploy"
}
}

View file

@ -60,6 +60,7 @@ dependencies:
gpx: ^2.3.0
path_provider: ^2.1.5
share_plus: ^12.0.1
build_pipe: ^0.3.1
material_symbols_icons: ^4.2906.0
web: ^1.1.1
flutter_svg: ^2.0.10+1
@ -128,3 +129,16 @@ flutter_launcher_icons:
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
build_pipe:
workflows:
default:
clean_flutter: true # Optional: Cleans old build artifacts before running
generate_log: true # Optional: Outputs a build log file for debugging
platforms:
web:
build:
build_command: flutter build web --release --pwa-strategy=none
# Strongly recommended: disables the default service worker which often causes more cache headaches
add_version_query_param: true
# This is the key flag! It appends ?v=<your pubspec version> to bootstrap/JS files

7
wrangler.toml Normal file
View file

@ -0,0 +1,7 @@
#:schema node_modules/wrangler/config-schema.json
name = "meshcore"
compatibility_date = "2025-10-08"
[assets]
directory = "build/web"