Browse Source

上传移动的界面优化

newdev_shunjiawei
liwei1dao 5 months ago
parent
commit
b21fbb1f83
  1. 14
      ios/Runner.xcodeproj/project.pbxproj
  2. 2
      lib/core/translations/language/en_us.dart
  3. 2
      lib/core/translations/language/zh_cn.dart
  4. 8
      lib/data/models/appconfig.dart
  5. 26
      lib/data/services/ble_manager.dart
  6. 4
      lib/modules/explore/views/explore_view.dart
  7. 4
      lib/modules/home/views/home_view.dart
  8. 25
      lib/modules/mobile_elf/controllers/mobile_elf_controller.dart
  9. 39
      lib/modules/mobile_elf/views/mobile_elf_view.dart

14
ios/Runner.xcodeproj/project.pbxproj

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 60; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -251,7 +251,7 @@
); );
mainGroup = 97C146E51CF9000F007C117D; mainGroup = 97C146E51CF9000F007C117D;
packageReferences = ( packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
); );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */; productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -346,14 +346,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@ -367,14 +363,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@ -789,7 +781,7 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */ /* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference; isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
}; };

2
lib/core/translations/language/en_us.dart

@ -1733,6 +1733,8 @@ const Map<String, String> enUS = {
"deviceNotAuth": "Device not authenticated", "deviceNotAuth": "Device not authenticated",
"microphoneName": "Microphone", "microphoneName": "Microphone",
"mobileElfMicPermission": "Mobile Elf needs microphone permission for voice chat", "mobileElfMicPermission": "Mobile Elf needs microphone permission for voice chat",
"deviceNotConnected": "Device not connected",
"mobileElfNeedDevice": "Please connect your Mobile device to use AI features",
"micPermissionSettings": "Please enable microphone permission in Settings to use voice features", "micPermissionSettings": "Please enable microphone permission in Settings to use voice features",
"weekdayMon": "Monday", "weekdayMon": "Monday",
"weekdayTue": "Tuesday", "weekdayTue": "Tuesday",

2
lib/core/translations/language/zh_cn.dart

@ -1572,6 +1572,8 @@ const Map<String, String> zhCN = {
"deviceNotAuth": "设备未认证", "deviceNotAuth": "设备未认证",
"microphoneName": "麦克风", "microphoneName": "麦克风",
"mobileElfMicPermission": "移动精灵需要麦克风权限才能进行语音对话", "mobileElfMicPermission": "移动精灵需要麦克风权限才能进行语音对话",
"deviceNotConnected": "设备未连接",
"mobileElfNeedDevice": "请连接移动设备后使用 AI 功能",
"micPermissionSettings": "请在设置中开启麦克风权限以使用语音功能", "micPermissionSettings": "请在设置中开启麦克风权限以使用语音功能",
"weekdayMon": "星期一", "weekdayMon": "星期一",
"weekdayTue": "星期二", "weekdayTue": "星期二",

8
lib/data/models/appconfig.dart

@ -1,13 +1,15 @@
import 'dart:convert'; import 'dart:convert';
import 'package:get/get.dart';
import '../../../data/models/appconfig_model.dart'; import '../../../data/models/appconfig_model.dart';
class AppConfig { class AppConfig {
// 中文注释:是否隐藏"AI全能助手"模块。 // 是否隐藏"AI全能助手"模块(响应式)。
// true => 首页/发现页切换为精简布局,新手引导跳过 AI 引导步骤 // true => 首页/发现页切换为精简布局,新手引导跳过 AI 引导步骤
// false => 保持原有布局 // false => 保持原有布局
// TODO: 后续接入 BLE PID 判断逻辑,B017 设备连接时自动置 true // 数据来源:连接的 BLE 设备对应 DBProduct.hideaiassistant,由 BleManager 同步。
static bool hideAiAssistant = false; static final RxBool hideAiAssistant = false.obs;
static late UserGetAppConfigResp config; static late UserGetAppConfigResp config;

26
lib/data/services/ble_manager.dart

@ -212,6 +212,7 @@ class BleManager extends GetxService {
final _isCall = false.obs; final _isCall = false.obs;
final _isConnected = false.obs; final _isConnected = false.obs;
bool get isConnected => _isConnected.value; bool get isConnected => _isConnected.value;
RxBool get isConnectedRx => _isConnected;
bool get isCall => _isCall.value; bool get isCall => _isCall.value;
// 设备信息缓存 // 设备信息缓存
final deviceInfo = Rx<DeviceInfo?>(null); // 使用 Rx 包装 final deviceInfo = Rx<DeviceInfo?>(null); // 使用 Rx 包装
@ -260,6 +261,31 @@ class BleManager extends GetxService {
// 设置BLE事件监听 // 设置BLE事件监听
_setupBleListener(); _setupBleListener();
// 同步 AI 助手隐藏开关:deviceInfo 拿到名字后按 DBProduct 配置写入;
// 断连时 deviceInfo 会被置 null,统一在断连分支复位。
ever<DeviceInfo?>(deviceInfo, _syncHideAiAssistant);
}
/// 按当前连接设备同步 AppConfig.hideAiAssistant
void _syncHideAiAssistant(DeviceInfo? info) {
if (info == null || info.name.isEmpty) {
if (AppConfig.hideAiAssistant.value) {
AppConfig.hideAiAssistant.value = false;
}
return;
}
try {
final product = AppConfig.getproduct(info.name);
final hide = product?.hideaiassistant ?? false;
if (AppConfig.hideAiAssistant.value != hide) {
AppConfig.hideAiAssistant.value = hide;
Logger.i('BleManager',
'hideAiAssistant 同步: device=${info.name} hide=$hide');
}
} catch (e) {
Logger.e('BleManager', 'hideAiAssistant 同步失败: $e');
}
} }
/// 释放资源 /// 释放资源

4
lib/modules/explore/views/explore_view.dart

@ -66,9 +66,9 @@ class ExploreView extends GetView<ExploreController> {
// AI功能列表 // AI功能列表
SizedBox(height: 24.h), SizedBox(height: 24.h),
AppConfig.hideAiAssistant Obx(() => AppConfig.hideAiAssistant.value
? _buildSimplifiedAILayout(isDarkMode) ? _buildSimplifiedAILayout(isDarkMode)
: _buildCustomAILayout(isDarkMode), : _buildCustomAILayout(isDarkMode)),
], ],
), ),
), ),

4
lib/modules/home/views/home_view.dart

@ -145,9 +145,9 @@ class HomeView extends GetView<HomeController> {
// 热门AI体验 // 热门AI体验
_buildSectionTitle(isDarkMode), _buildSectionTitle(isDarkMode),
SizedBox(height: 16.h), SizedBox(height: 16.h),
AppConfig.hideAiAssistant Obx(() => AppConfig.hideAiAssistant.value
? _buildSimplifiedAILayout() ? _buildSimplifiedAILayout()
: _buildPremiumAILayout(), : _buildPremiumAILayout()),
], ],
), ),
), ),

25
lib/modules/mobile_elf/controllers/mobile_elf_controller.dart

@ -45,6 +45,10 @@ class MobileElfController extends GetxController {
final credentialFailed = false.obs; final credentialFailed = false.obs;
final credentialFailedReason = ''.obs; final credentialFailedReason = ''.obs;
// 设备连接状态(直接转发 BleManager,UI 用 Obx 监听)
RxBool get deviceConnectedRx => BleManager.to.isConnectedRx;
bool get isDeviceConnected => BleManager.to.isConnected;
// ── 内部 ─────────────────────────────────────────────────────────────────── // ── 内部 ───────────────────────────────────────────────────────────────────
StreamSubscription<AgentServiceEvent>? _eventSub; StreamSubscription<AgentServiceEvent>? _eventSub;
Timer? _callTimer; Timer? _callTimer;
@ -256,6 +260,10 @@ class MobileElfController extends GetxController {
Future<void> startCall() async { Future<void> startCall() async {
if (inCall.value || _callStarting) return; if (inCall.value || _callStarting) return;
if (!isDeviceConnected) {
_promptDeviceRequired();
return;
}
_callStarting = true; _callStarting = true;
_pttAborted = false; _pttAborted = false;
_checkHeartbeat(); _checkHeartbeat();
@ -314,6 +322,19 @@ class MobileElfController extends GetxController {
await AgentService.stopConversation(); await AgentService.stopConversation();
} }
/// 未连接设备时统一的拦截提示(startCall / startPtt 共用)
void _promptDeviceRequired() {
Get.snackbar(
'deviceNotConnected'.tr,
'mobileElfNeedDevice'.tr,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: const Color(0xCC1F2937),
colorText: const Color(0xFFFFFFFF),
margin: const EdgeInsets.all(16),
duration: const Duration(seconds: 2),
);
}
/// 超过1小时未上报心跳则补报一次 /// 超过1小时未上报心跳则补报一次
void _checkHeartbeat() { void _checkHeartbeat() {
final last = _storage.read<int>('sts_heartbeat_time') ?? 0; final last = _storage.read<int>('sts_heartbeat_time') ?? 0;
@ -324,6 +345,10 @@ class MobileElfController extends GetxController {
Future<void> startPtt() async { Future<void> startPtt() async {
if (pttRecording.value || _pttStarting) return; if (pttRecording.value || _pttStarting) return;
if (!isDeviceConnected) {
_promptDeviceRequired();
return;
}
_pttStarting = true; _pttStarting = true;
_pttAborted = false; _pttAborted = false;
_checkHeartbeat(); _checkHeartbeat();

39
lib/modules/mobile_elf/views/mobile_elf_view.dart

@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; // 用于 DateFormat import 'package:intl/intl.dart'; // 用于 DateFormat
import '../../../data/services/ble_manager.dart';
import '../controllers/mobile_elf_controller.dart'; import '../controllers/mobile_elf_controller.dart';
class MobileElfView extends GetView<MobileElfController> { class MobileElfView extends GetView<MobileElfController> {
@ -144,6 +145,18 @@ class MobileElfView extends GetView<MobileElfController> {
/// 右上角状态胶囊 + 通话计时 /// 右上角状态胶囊 + 通话计时
Widget _buildStatusPill() { Widget _buildStatusPill() {
// 未连接设备优先展示(用户更直观的可恢复状态)
if (!BleManager.to.isConnectedRx.value) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 3.h),
decoration: BoxDecoration(color: const Color(0xFFFEF2F2), borderRadius: BorderRadius.circular(20.r)),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.bluetooth_disabled_rounded, size: 11.sp, color: _red),
SizedBox(width: 4.w),
Text('deviceNotConnected'.tr, style: TextStyle(fontSize: 11.sp, color: _red, fontWeight: FontWeight.w500)),
]),
);
}
if (controller.credentialFailed.value) { if (controller.credentialFailed.value) {
return Container( return Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 3.h), padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 3.h),
@ -483,6 +496,7 @@ class MobileElfView extends GetView<MobileElfController> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (!BleManager.to.isConnectedRx.value) _buildDeviceRequiredBar(isDark),
if (showStatusBar) _buildStatusBar(s, inCall, isDark), if (showStatusBar) _buildStatusBar(s, inCall, isDark),
if (inCall) if (inCall)
_buildInCallInputBar(isDark) _buildInCallInputBar(isDark)
@ -495,6 +509,28 @@ class MobileElfView extends GetView<MobileElfController> {
}); });
} }
// ── 未连接设备警示横幅 ────────────────────────────────────────────────────
Widget _buildDeviceRequiredBar(bool isDark) {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
color: const Color(0xFFFEF2F2),
child: Row(
children: [
Icon(Icons.bluetooth_disabled_rounded, size: 16.sp, color: _red),
SizedBox(width: 8.w),
Expanded(
child: Text(
'mobileElfNeedDevice'.tr,
style: TextStyle(fontSize: 13.sp, color: _red, fontWeight: FontWeight.w500),
),
),
],
),
);
}
// ── 状态提示栏 ──────────────────────────────────────────────────────────── // ── 状态提示栏 ────────────────────────────────────────────────────────────
Widget _buildStatusBar(MobileElfStatus s, bool inCall, bool isDark) { Widget _buildStatusBar(MobileElfStatus s, bool inCall, bool isDark) {
@ -633,7 +669,8 @@ class MobileElfView extends GetView<MobileElfController> {
final s = controller.status.value; final s = controller.status.value;
final ptt = controller.pttRecording.value; final ptt = controller.pttRecording.value;
final locked = controller.credentialFailed.value; final notConnected = !BleManager.to.isConnectedRx.value;
final locked = controller.credentialFailed.value || notConnected;
final disabled = locked || (!ptt && s != MobileElfStatus.idle && s != MobileElfStatus.error); final disabled = locked || (!ptt && s != MobileElfStatus.idle && s != MobileElfStatus.error);
final pttClickable = !locked && s == MobileElfStatus.speaking; final pttClickable = !locked && s == MobileElfStatus.speaking;
final sideDisabled = locked || ptt || (disabled && !pttClickable); final sideDisabled = locked || ptt || (disabled && !pttClickable);

Loading…
Cancel
Save