Browse Source
目录、类名、文案键、路由、测试全部改成 EaiCar。刻意保留:原生插件 device_smartcar、 持久化厂商键 'smartcar'、模块 id 'smartcarChat'、GetStorage 两个键(改了会丢老装机的编排/白名单)。 广播名准入白名单本来就只认 EaiCar,旧名 Smartcar 在测试里仍作为被拒绝的样本保留。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>main
76 changed files with 724 additions and 720 deletions
@ -0,0 +1,130 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
|
|||
import '../../routes/app_routes.dart'; |
|||
import '../emai/views/emai_view.dart'; |
|||
import '../module_descriptor.dart'; |
|||
import 'bindings/eaicar_chat_binding.dart'; |
|||
|
|||
/// EaiCar 车载助手(EaiCar 车载香薰的随身对话 agent) |
|||
/// |
|||
/// 与 EMAI 共用同一套对话 UI 和控制器,差别只有一个:连的是百炼里**另一个应用** |
|||
/// (见 [kEaiCarBailianAppId])。所以这里直接复用 [EmaiView],靠 `controllerTag` |
|||
/// 取到 [EaiCarChatBinding] 装配的那份带专属 app_id 的实例。 |
|||
/// |
|||
/// ⚠️ **没有声明 `requiredCapabilities`**:这个 agent 手机自己就能聊,不必连着香薰 |
|||
/// (产品定位是"随时开聊")。设备连上时它会被自动铺到 AI 页首位,那是 |
|||
/// `AgentModules.defaultsByCategory[carAroma]` 的事,与本字段无关。 |
|||
/// |
|||
/// ⚠️ **对话记录与 EMAI 共用 `EmaiChatStore`**:设备按键唤醒的轮次由 |
|||
/// `DeviceAiSessionService` 写进那个 store,页面不开也能收;这里若另起一个 store, |
|||
/// 设备发起的对话就不会出现在本页上。要拆分历史得连 `DeviceAiSessionService` |
|||
/// 一起改,本期不做。 |
|||
final AgentModuleDescriptor eaicarModule = AgentModuleDescriptor( |
|||
id: 'smartcarChat', |
|||
title: 'agentEaiCar', |
|||
subtitle: 'agentEaiCarDesc', |
|||
// 卡片图标只支持 IconData(module_card 里是 Icon(module.icon) 套渐变块), |
|||
// 用不了产品图 EaiCar_icon.png。要用图得给 AgentModule 加字段并改 module_card, |
|||
// 那是耳机/支架共用的卡片组件,本期不动。 |
|||
icon: Icons.directions_car, |
|||
// 与「AI车载香薰」品类卡同色系(device_category.dart 的 carAroma) |
|||
gradient: const [Color(0xFFFFB74D), Color(0xFFE65100)], |
|||
entryRoute: Routes.eaicarChat, |
|||
// 设备按键唤醒时接的就是这个应用(不是 EMAI)——见 AgentModuleDescriptor.bailianAppId |
|||
bailianAppId: kEaiCarBailianAppId, |
|||
pages: [ |
|||
GetPage( |
|||
name: Routes.eaicarChat, |
|||
page: () => const EmaiView( |
|||
controllerTag: kEaiCarTag, |
|||
titleKey: 'agentEaiCar', |
|||
), |
|||
binding: EaiCarChatBinding(), |
|||
transition: Transition.rightToLeft, |
|||
transitionDuration: const Duration(milliseconds: 250), |
|||
), |
|||
], |
|||
// 模块自带文案:不用再去改 41 个语言文件。 |
|||
// 缺某个语种时 GetX 回退到全局表,全局也没有就显示 key 本身, |
|||
// 所以至少要保证 zh_CN / en_US 有(module_registry_test 守着这两个)。 |
|||
translations: const { |
|||
'zh_CN': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': '您的车载助手', |
|||
'eaicarWelcome': '欢迎回来', |
|||
'eaicarA2dpTitle': '需要连接蓝牙音频', |
|||
'eaicarA2dpHint': 'AI 回复和音乐要经典蓝牙才能从设备里放出来。请到「设置 → 蓝牙」里配对 @name。', |
|||
}, |
|||
'zh_TW': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': '您的車載助手', |
|||
'eaicarWelcome': '歡迎回來', |
|||
'eaicarA2dpTitle': '需要連接藍牙音訊', |
|||
'eaicarA2dpHint': 'AI 回覆和音樂要經典藍牙才能從裝置裡播出來。請到「設定 → 藍牙」配對 @name。', |
|||
}, |
|||
'zh_HK': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': '您的車載助手', |
|||
'eaicarWelcome': '歡迎回來', |
|||
'eaicarA2dpTitle': '需要連接藍牙音訊', |
|||
'eaicarA2dpHint': 'AI 回覆和音樂要經典藍牙先可以喺裝置度播出嚟。請去「設定 → 藍牙」配對 @name。', |
|||
}, |
|||
'en_US': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': 'Your in-car assistant', |
|||
'eaicarWelcome': 'Welcome back', |
|||
'eaicarA2dpTitle': 'Bluetooth audio not connected', |
|||
'eaicarA2dpHint': 'Pair @name under Settings → Bluetooth so replies and music play from the device.', |
|||
}, |
|||
'ja_JP': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': '車載アシスタント', |
|||
'eaicarWelcome': 'おかえりなさい', |
|||
'eaicarA2dpTitle': 'Bluetoothオーディオ未接続', |
|||
'eaicarA2dpHint': '「設定 → Bluetooth」で @name をペアリングすると、応答と音楽が本体から再生されます。', |
|||
}, |
|||
'ko_KR': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': '차량용 어시스턴트', |
|||
'eaicarWelcome': '다시 오신 것을 환영합니다', |
|||
'eaicarA2dpTitle': '블루투스 오디오 미연결', |
|||
'eaicarA2dpHint': '설정 → 블루투스에서 @name 을(를) 페어링하면 응답과 음악이 기기에서 재생됩니다。', |
|||
}, |
|||
'de_DE': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': 'Ihr Auto-Assistent', |
|||
'eaicarWelcome': 'Willkommen zurück', |
|||
'eaicarA2dpTitle': 'Bluetooth-Audio nicht verbunden', |
|||
'eaicarA2dpHint': 'Koppeln Sie @name unter Einstellungen → Bluetooth, damit Antworten und Musik über das Gerät laufen.', |
|||
}, |
|||
'fr_FR': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': 'Votre assistant automobile', |
|||
'eaicarWelcome': 'Bon retour', |
|||
'eaicarA2dpTitle': 'Audio Bluetooth non connecté', |
|||
'eaicarA2dpHint': 'Associez @name dans Réglages → Bluetooth pour que les réponses et la musique sortent de l\'appareil.', |
|||
}, |
|||
'es_ES': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': 'Tu asistente para el coche', |
|||
'eaicarWelcome': 'Bienvenido de nuevo', |
|||
'eaicarA2dpTitle': 'Audio Bluetooth no conectado', |
|||
'eaicarA2dpHint': 'Empareja @name en Ajustes → Bluetooth para que las respuestas y la música suenen en el dispositivo.', |
|||
}, |
|||
'pt_PT': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': 'O seu assistente para o carro', |
|||
'eaicarWelcome': 'Bem-vindo de volta', |
|||
'eaicarA2dpTitle': 'Áudio Bluetooth não ligado', |
|||
'eaicarA2dpHint': 'Emparelhe @name em Definições → Bluetooth para que as respostas e a música saiam do dispositivo.', |
|||
}, |
|||
'ru_RU': { |
|||
'agentEaiCar': 'EaiCar', |
|||
'agentEaiCarDesc': 'Ваш автомобильный помощник', |
|||
'eaicarWelcome': 'С возвращением', |
|||
'eaicarA2dpTitle': 'Bluetooth-аудио не подключено', |
|||
'eaicarA2dpHint': 'Подключите @name в «Настройки → Bluetooth», чтобы ответы и музыка звучали из устройства.', |
|||
}, |
|||
}, |
|||
); |
|||
@ -1,130 +0,0 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
|
|||
import '../../routes/app_routes.dart'; |
|||
import '../emai/views/emai_view.dart'; |
|||
import '../module_descriptor.dart'; |
|||
import 'bindings/smartcar_chat_binding.dart'; |
|||
|
|||
/// Smartcar 车载助手(Smartcar 车载香薰的随身对话 agent) |
|||
/// |
|||
/// 与 EMAI 共用同一套对话 UI 和控制器,差别只有一个:连的是百炼里**另一个应用** |
|||
/// (见 [kSmartcarBailianAppId])。所以这里直接复用 [EmaiView],靠 `controllerTag` |
|||
/// 取到 [SmartcarChatBinding] 装配的那份带专属 app_id 的实例。 |
|||
/// |
|||
/// ⚠️ **没有声明 `requiredCapabilities`**:这个 agent 手机自己就能聊,不必连着香薰 |
|||
/// (产品定位是"随时开聊")。设备连上时它会被自动铺到 AI 页首位,那是 |
|||
/// `AgentModules.defaultsByCategory[carAroma]` 的事,与本字段无关。 |
|||
/// |
|||
/// ⚠️ **对话记录与 EMAI 共用 `EmaiChatStore`**:设备按键唤醒的轮次由 |
|||
/// `DeviceAiSessionService` 写进那个 store,页面不开也能收;这里若另起一个 store, |
|||
/// 设备发起的对话就不会出现在本页上。要拆分历史得连 `DeviceAiSessionService` |
|||
/// 一起改,本期不做。 |
|||
final AgentModuleDescriptor smartcarModule = AgentModuleDescriptor( |
|||
id: 'smartcarChat', |
|||
title: 'agentSmartcar', |
|||
subtitle: 'agentSmartcarDesc', |
|||
// 卡片图标只支持 IconData(module_card 里是 Icon(module.icon) 套渐变块), |
|||
// 用不了产品图 Smartcar_icon.png。要用图得给 AgentModule 加字段并改 module_card, |
|||
// 那是耳机/支架共用的卡片组件,本期不动。 |
|||
icon: Icons.directions_car, |
|||
// 与「AI车载香薰」品类卡同色系(device_category.dart 的 carAroma) |
|||
gradient: const [Color(0xFFFFB74D), Color(0xFFE65100)], |
|||
entryRoute: Routes.smartcarChat, |
|||
// 设备按键唤醒时接的就是这个应用(不是 EMAI)——见 AgentModuleDescriptor.bailianAppId |
|||
bailianAppId: kSmartcarBailianAppId, |
|||
pages: [ |
|||
GetPage( |
|||
name: Routes.smartcarChat, |
|||
page: () => const EmaiView( |
|||
controllerTag: kSmartcarTag, |
|||
titleKey: 'agentSmartcar', |
|||
), |
|||
binding: SmartcarChatBinding(), |
|||
transition: Transition.rightToLeft, |
|||
transitionDuration: const Duration(milliseconds: 250), |
|||
), |
|||
], |
|||
// 模块自带文案:不用再去改 41 个语言文件。 |
|||
// 缺某个语种时 GetX 回退到全局表,全局也没有就显示 key 本身, |
|||
// 所以至少要保证 zh_CN / en_US 有(module_registry_test 守着这两个)。 |
|||
translations: const { |
|||
'zh_CN': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': '您的车载助手', |
|||
'smartcarWelcome': '欢迎回来', |
|||
'smartcarA2dpTitle': '需要连接蓝牙音频', |
|||
'smartcarA2dpHint': 'AI 回复和音乐要经典蓝牙才能从设备里放出来。请到「设置 → 蓝牙」里配对 @name。', |
|||
}, |
|||
'zh_TW': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': '您的車載助手', |
|||
'smartcarWelcome': '歡迎回來', |
|||
'smartcarA2dpTitle': '需要連接藍牙音訊', |
|||
'smartcarA2dpHint': 'AI 回覆和音樂要經典藍牙才能從裝置裡播出來。請到「設定 → 藍牙」配對 @name。', |
|||
}, |
|||
'zh_HK': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': '您的車載助手', |
|||
'smartcarWelcome': '歡迎回來', |
|||
'smartcarA2dpTitle': '需要連接藍牙音訊', |
|||
'smartcarA2dpHint': 'AI 回覆和音樂要經典藍牙先可以喺裝置度播出嚟。請去「設定 → 藍牙」配對 @name。', |
|||
}, |
|||
'en_US': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': 'Your in-car assistant', |
|||
'smartcarWelcome': 'Welcome back', |
|||
'smartcarA2dpTitle': 'Bluetooth audio not connected', |
|||
'smartcarA2dpHint': 'Pair @name under Settings → Bluetooth so replies and music play from the device.', |
|||
}, |
|||
'ja_JP': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': '車載アシスタント', |
|||
'smartcarWelcome': 'おかえりなさい', |
|||
'smartcarA2dpTitle': 'Bluetoothオーディオ未接続', |
|||
'smartcarA2dpHint': '「設定 → Bluetooth」で @name をペアリングすると、応答と音楽が本体から再生されます。', |
|||
}, |
|||
'ko_KR': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': '차량용 어시스턴트', |
|||
'smartcarWelcome': '다시 오신 것을 환영합니다', |
|||
'smartcarA2dpTitle': '블루투스 오디오 미연결', |
|||
'smartcarA2dpHint': '설정 → 블루투스에서 @name 을(를) 페어링하면 응답과 음악이 기기에서 재생됩니다。', |
|||
}, |
|||
'de_DE': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': 'Ihr Auto-Assistent', |
|||
'smartcarWelcome': 'Willkommen zurück', |
|||
'smartcarA2dpTitle': 'Bluetooth-Audio nicht verbunden', |
|||
'smartcarA2dpHint': 'Koppeln Sie @name unter Einstellungen → Bluetooth, damit Antworten und Musik über das Gerät laufen.', |
|||
}, |
|||
'fr_FR': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': 'Votre assistant automobile', |
|||
'smartcarWelcome': 'Bon retour', |
|||
'smartcarA2dpTitle': 'Audio Bluetooth non connecté', |
|||
'smartcarA2dpHint': 'Associez @name dans Réglages → Bluetooth pour que les réponses et la musique sortent de l\'appareil.', |
|||
}, |
|||
'es_ES': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': 'Tu asistente para el coche', |
|||
'smartcarWelcome': 'Bienvenido de nuevo', |
|||
'smartcarA2dpTitle': 'Audio Bluetooth no conectado', |
|||
'smartcarA2dpHint': 'Empareja @name en Ajustes → Bluetooth para que las respuestas y la música suenen en el dispositivo.', |
|||
}, |
|||
'pt_PT': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': 'O seu assistente para o carro', |
|||
'smartcarWelcome': 'Bem-vindo de volta', |
|||
'smartcarA2dpTitle': 'Áudio Bluetooth não ligado', |
|||
'smartcarA2dpHint': 'Emparelhe @name em Definições → Bluetooth para que as respostas e a música saiam do dispositivo.', |
|||
}, |
|||
'ru_RU': { |
|||
'agentSmartcar': 'EaiCar', |
|||
'agentSmartcarDesc': 'Ваш автомобильный помощник', |
|||
'smartcarWelcome': 'С возвращением', |
|||
'smartcarA2dpTitle': 'Bluetooth-аудио не подключено', |
|||
'smartcarA2dpHint': 'Подключите @name в «Настройки → Bluetooth», чтобы ответы и музыка звучали из устройства.', |
|||
}, |
|||
}, |
|||
); |
|||
@ -0,0 +1,30 @@ |
|||
import 'package:eaimar/devices/eaicar/eaicar_link.dart'; |
|||
import 'package:flutter_test/flutter_test.dart'; |
|||
|
|||
void main() { |
|||
group('isAllowedEaiCarName —— 车载香薰广播名准入', () { |
|||
test('规定的名字放行', () { |
|||
expect(isAllowedEaiCarName('EaiCar'), isTrue); |
|||
expect(isAllowedEaiCarName(' eaicar '), isTrue); |
|||
expect(isAllowedEaiCarName('EAICAR-3F2A'), isTrue); |
|||
}); |
|||
|
|||
// ⚠️ 旧广播名不再接纳。厂商 ID(0x8505) 也不再单独放行——它只说明 |
|||
// "这是云衍方案的设备",说明不了"这是我们的正式产品"。 |
|||
test('旧名 Smartcar 一律拒绝', () { |
|||
expect(isAllowedEaiCarName('Smartcar'), isFalse); |
|||
expect(isAllowedEaiCarName('smartcar'), isFalse); |
|||
expect(isAllowedEaiCarName('SmartcarX'), isFalse); |
|||
}); |
|||
|
|||
test('空名字拒绝', () { |
|||
expect(isAllowedEaiCarName(''), isFalse); |
|||
expect(isAllowedEaiCarName(' '), isFalse); |
|||
}); |
|||
|
|||
test('别家设备拒绝', () { |
|||
expect(isAllowedEaiCarName('EaiRec'), isFalse); |
|||
expect(isAllowedEaiCarName('Echo-one'), isFalse); |
|||
}); |
|||
}); |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
import 'package:eaimar/devices/smartcar/smartcar_link.dart'; |
|||
import 'package:flutter_test/flutter_test.dart'; |
|||
|
|||
void main() { |
|||
group('isAllowedSmartcarName —— 车载香薰广播名准入', () { |
|||
test('规定的名字放行', () { |
|||
expect(isAllowedSmartcarName('EaiCar'), isTrue); |
|||
expect(isAllowedSmartcarName(' eaicar '), isTrue); |
|||
expect(isAllowedSmartcarName('EAICAR-3F2A'), isTrue); |
|||
}); |
|||
|
|||
// ⚠️ 旧广播名不再接纳。厂商 ID(0x8505) 也不再单独放行——它只说明 |
|||
// "这是云衍方案的设备",说明不了"这是我们的正式产品"。 |
|||
test('旧名 Smartcar 一律拒绝', () { |
|||
expect(isAllowedSmartcarName('Smartcar'), isFalse); |
|||
expect(isAllowedSmartcarName('smartcar'), isFalse); |
|||
expect(isAllowedSmartcarName('SmartcarX'), isFalse); |
|||
}); |
|||
|
|||
test('空名字拒绝', () { |
|||
expect(isAllowedSmartcarName(''), isFalse); |
|||
expect(isAllowedSmartcarName(' '), isFalse); |
|||
}); |
|||
|
|||
test('别家设备拒绝', () { |
|||
expect(isAllowedSmartcarName('EaiRec'), isFalse); |
|||
expect(isAllowedSmartcarName('Echo-one'), isFalse); |
|||
}); |
|||
}); |
|||
} |
|||
Loading…
Reference in new issue