diff --git a/lib/core/config/app_config.dart b/lib/core/config/app_config.dart index 45d26d5..90e2e10 100644 --- a/lib/core/config/app_config.dart +++ b/lib/core/config/app_config.dart @@ -1,14 +1,24 @@ import 'dart:io' show Platform; +import 'package:flutter/foundation.dart' show kIsWeb; + /// App 設定:API 位址可透過 --dart-define=API_BASE=... 覆寫。 /// 未覆寫時依平台給「模擬器連本機後端」的預設值: /// Android 模擬器是 10.0.2.2,iOS/macOS 模擬器是 127.0.0.1(10.0.2.2 只有 Android 模擬器認得)。 +/// Web 是瀏覽器直接連,用 localhost。 /// 真機一律請帶 --dart-define=API_BASE=http://<電腦區網 IP>:8080。 class AppConfig { static const _apiBaseOverride = String.fromEnvironment('API_BASE'); static String get apiBase { if (_apiBaseOverride.isNotEmpty) return _apiBaseOverride; + + // kIsWeb 是編譯期常數,這一行讓下面的 dart:io 分支在 web build 被整段 + // 消除。少了它,沒帶 --dart-define=API_BASE 在瀏覽器開啟就會噴 + // 「Unsupported operation: Platform._operatingSystem」——那個訊息完全 + // 看不出跟 API 位址有關,排查成本很高。 + if (kIsWeb) return 'http://localhost:8080'; + return (Platform.isIOS || Platform.isMacOS) ? 'http://127.0.0.1:8080' : 'http://10.0.2.2:8080'; diff --git a/lib/core/platform/web_unsupported.dart b/lib/core/platform/web_unsupported.dart new file mode 100644 index 0000000..a25045f --- /dev/null +++ b/lib/core/platform/web_unsupported.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +/// 司機端不支援 web,這裡負責「講清楚」而不是讓它在半路炸掉。 +/// +/// 司機端依賴背景定位、系統定位權限對話框與 `dart:io` 的 `Platform`, +/// 這些在瀏覽器都沒有對應實作。沒有這道攔截的話,`flutter run -d chrome` +/// (預設進入點就是司機端)會噴 +/// 「Unsupported operation: Platform._operatingSystem」——訊息完全看不出 +/// 是「平台選錯了」,排查成本很高。 +void runDriverWebUnsupportedApp() { + runApp(const _WebUnsupportedApp()); +} + +class _WebUnsupportedApp extends StatelessWidget { + const _WebUnsupportedApp(); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Fleet 司機', + debugShowCheckedModeBanner: false, + home: Scaffold( + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 460), + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '司機端不支援瀏覽器', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 12), + const Text( + '司機端需要背景定位與系統定位權限,瀏覽器沒有對應能力。' + '請改跑 Android 或 iOS。', + ), + const SizedBox(height: 20), + const Text('瀏覽器只支援乘客端:'), + const SizedBox(height: 6), + SelectableText( + 'flutter run -d chrome -t lib/main_customer.dart', + style: TextStyle( + fontFamily: 'monospace', + backgroundColor: Colors.black.withValues(alpha: 0.06), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 27bd037..d66bca6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,20 @@ +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; +import 'package:line_fleet_app/core/platform/web_unsupported.dart'; import 'package:line_fleet_app/core/push/firebase_push_service.dart'; import 'package:line_fleet_app/driver/app.dart'; /// 預設入口:司機端 Future main() async { WidgetsFlutterBinding.ensureInitialized(); + + // 司機端在瀏覽器不可能運作,先攔下來給明確訊息,別讓它在 dart:io 半路炸。 + if (kIsWeb) { + runDriverWebUnsupportedApp(); + return; + } + final push = await createDriverPushService(); runApp(DriverApp(pushService: push)); } diff --git a/lib/main_driver.dart b/lib/main_driver.dart index 234ce8c..51f5b74 100644 --- a/lib/main_driver.dart +++ b/lib/main_driver.dart @@ -1,10 +1,19 @@ +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; +import 'package:line_fleet_app/core/platform/web_unsupported.dart'; import 'package:line_fleet_app/core/push/firebase_push_service.dart'; import 'package:line_fleet_app/driver/app.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + + // 司機端在瀏覽器不可能運作,先攔下來給明確訊息,別讓它在 dart:io 半路炸。 + if (kIsWeb) { + runDriverWebUnsupportedApp(); + return; + } + final push = await createDriverPushService(); runApp(DriverApp(pushService: push)); } diff --git a/test/core/config/app_config_web_test.dart b/test/core/config/app_config_web_test.dart new file mode 100644 index 0000000..48013ae --- /dev/null +++ b/test/core/config/app_config_web_test.dart @@ -0,0 +1,31 @@ +@TestOn('browser') +library; + +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter_test/flutter_test.dart'; +import 'package:line_fleet_app/core/config/app_config.dart'; + +/// 這支測試必須在瀏覽器裡跑才有意義: +/// +/// ```bash +/// flutter test --platform chrome test/core/config/app_config_web_test.dart +/// ``` +/// +/// 一般的 `flutter test` 跑在 Dart VM 上,`kIsWeb` 恆為 false, +/// 把 AppConfig 裡的 kIsWeb 防線整條拔掉也不會紅——那種測試等於沒有。 +/// `@TestOn('browser')` 讓 VM 執行時直接跳過,不會偽裝成通過。 +void main() { + test('確認真的跑在 web 上,否則本檔的斷言沒有意義', () { + expect(kIsWeb, isTrue); + }); + + test('沒帶 --dart-define=API_BASE 時,apiBase 不會碰到 dart:io', () { + // 防線被拔掉的話,這裡會噴 + // 「Unsupported operation: Platform._operatingSystem」。 + expect(AppConfig.apiBase, 'http://localhost:8080'); + }); + + test('wsBase 由 apiBase 推導,同樣不得碰到 dart:io', () { + expect(AppConfig.wsBase, 'ws://localhost:8080'); + }); +}