You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

203 lines
7.2 KiB

import 'package:echomeet_device_sdk/echomeet_device_sdk.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../../core/theme/app_palette.dart';
import '../../../data/services/background_launch_consent.dart';
import '../../../data/services/holder_device_service.dart';
import '../../../routes/app_routes.dart';
import '../../main_tab/controllers/main_tab_controller.dart';
/// 蓝讯方案设备(AI手机支架)的扫描 / 连接页。
///
/// 走的是从 echomeet 拉过来的 [EchoMeetDeviceSdk],与杰理耳机的扫描页
/// (DevicesView)是两条独立链路,互不干扰。
class HolderScanView extends StatefulWidget {
const HolderScanView({super.key});
@override
State<HolderScanView> createState() => _HolderScanViewState();
}
class _HolderScanViewState extends State<HolderScanView> {
late final HolderDeviceService _service;
@override
void initState() {
super.initState();
_service = Get.isRegistered<HolderDeviceService>()
? Get.find<HolderDeviceService>()
: Get.put(HolderDeviceService());
// 进页面自动开扫
WidgetsBinding.instance.addPostFrameCallback((_) => _service.startScan());
}
@override
void dispose() {
_service.stopScan();
super.dispose();
}
bool _connecting = false;
Future<void> _connect(EchoMeetScannedDevice d) async {
if (_connecting) return;
// Android 上 SDK 的 connect() 本身就是向系统注册后台扫连(设备靠近就唤醒
// 本应用),属于自启动行为,必须先向用户明示并取得同意。
if (!await BackgroundLaunchConsent.ensure(userInitiated: true)) {
Get.snackbar(
'autoConnectRequired'.tr,
BackgroundLaunchConsent.declined
? 'autoConnectReenableHint'.tr
: 'autoConnectRequiredDesc'.tr,
snackPosition: SnackPosition.TOP);
return;
}
setState(() => _connecting = true);
// 用户主动点了连接:允许再申请一次系统蓝牙权限
_service.resetPermissionDenied();
// connect() 只是注册后台扫连,真正连上要等 connectionState
final registered = await _service.connect(d);
final ok = registered && await _service.waitConnected();
if (!mounted) return;
setState(() => _connecting = false);
if (ok) {
Get.snackbar('连接成功', '${d.name} 已连接',
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 2));
Get.offAllNamed(Routes.home);
// 回到主框架后切到「设备」页,展示刚连上的设备
WidgetsBinding.instance.addPostFrameCallback((_) {
if (Get.isRegistered<MainTabController>()) {
MainTabController.to.changeTab(MainTabController.tabDevice);
}
});
} else {
Get.snackbar('连接失败', 'holderRetryHint'.tr,
snackPosition: SnackPosition.TOP);
}
}
@override
Widget build(BuildContext context) {
final bool isDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: AppPalette.background(isDark),
appBar: AppBar(
backgroundColor: AppPalette.background(isDark),
elevation: 0,
title: Text('holderAddTitle'.tr),
actions: [
Obx(() => _service.isScanning.value
? Padding(
padding: EdgeInsets.only(right: 16.w),
child: Center(
child: SizedBox(
width: 18.w,
height: 18.w,
child: const CircularProgressIndicator(strokeWidth: 2),
),
),
)
: IconButton(
icon: const Icon(Icons.refresh),
onPressed: _service.startScan,
)),
],
),
body: _connecting
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('holderConnecting'.tr),
],
),
)
: Obx(() {
final list = _service.scanned;
if (list.isEmpty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.bluetooth_searching,
size: 56.sp, color: AppPalette.brand300),
SizedBox(height: 12.h),
Text(
_service.isScanning.value ? 'holderSearching'.tr : 'holderNotFound'.tr,
style: TextStyle(
fontSize: 15.sp, color: AppPalette.textSecondary(isDark)),
),
SizedBox(height: 6.h),
Text(
'holderPairHint'.tr,
style: TextStyle(
fontSize: 12.sp, color: AppPalette.textHint(isDark)),
),
],
),
);
}
return ListView.separated(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
itemCount: list.length,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (context, i) {
final d = list[i];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _connect(d),
child: Container(
padding: EdgeInsets.all(14.w),
decoration: BoxDecoration(
color: AppPalette.surface(isDark),
borderRadius: BorderRadius.circular(14.r),
border: Border.all(color: AppPalette.border(isDark)),
),
child: Row(
children: [
Icon(Icons.stay_current_portrait,
size: 26.sp, color: AppPalette.accent),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
d.displayName,
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w600,
color: AppPalette.textPrimary(isDark),
),
),
SizedBox(height: 2.h),
Text(
d.address,
style: TextStyle(
fontSize: 11.sp,
color: AppPalette.textHint(isDark),
),
),
],
),
),
Text('连接',
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
color: AppPalette.accent)),
],
),
),
);
},
);
}),
);
}
}