|
|
|
@ -72,86 +72,81 @@ class HomeView extends GetView<HomeController> { |
|
|
|
|
|
|
|
@override |
|
|
|
Widget build(BuildContext context) { |
|
|
|
// 预缓存图片 |
|
|
|
precacheImage( |
|
|
|
const AssetImage('assets/images/headphone_new3.png'), context); |
|
|
|
precacheImage( |
|
|
|
const AssetImage('assets/images/headphone_new4.png'), context); |
|
|
|
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark; |
|
|
|
return Scaffold( |
|
|
|
backgroundColor: isDarkMode ? Colors.black : const Color(0xFFF9FAFC), |
|
|
|
body: SafeArea( |
|
|
|
child: Padding( |
|
|
|
child: SingleChildScrollView( |
|
|
|
padding: EdgeInsets.all(16.w), |
|
|
|
child: Column( |
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
children: [ |
|
|
|
// 顶部标题栏 |
|
|
|
_buildAppBar(isDarkMode), |
|
|
|
// 可变高度的顶部图片区域 - 使用Expanded让它占用剩余空间 |
|
|
|
Expanded( |
|
|
|
child: Column( |
|
|
|
children: [ |
|
|
|
Obx(() => _buildAdaptiveEarbudsImage()), |
|
|
|
SizedBox(height: 20.h), |
|
|
|
// 设备状态卡片 |
|
|
|
IntroStepBuilder( |
|
|
|
group: 'home', |
|
|
|
order: 1, |
|
|
|
onWidgetLoad: () { |
|
|
|
controller.noviceGuideService.startHomeGuide(); |
|
|
|
}, |
|
|
|
getOverlayPosition: IntroOverlayBuilder.overlayPosition, |
|
|
|
overlayBuilder: (params) { |
|
|
|
return IntroOverlayBuilder.aiBuilder( |
|
|
|
params, |
|
|
|
'欢迎使用Voitrans!首先,让我们唤醒你的专属 AI—— 小言', |
|
|
|
onNext: () { |
|
|
|
controller.noviceGuideService.startWakeupListen(); |
|
|
|
}, |
|
|
|
); |
|
|
|
}, |
|
|
|
builder: (BuildContext context, GlobalKey key) => |
|
|
|
SizedBox( |
|
|
|
key: key, |
|
|
|
child: _buildAnimatedDeviceStatusCard(isDarkMode), |
|
|
|
), |
|
|
|
), |
|
|
|
SizedBox(height: 20.h), |
|
|
|
IntroStepBuilder( |
|
|
|
group: 'home', |
|
|
|
order: 2, |
|
|
|
getOverlayPosition: IntroOverlayBuilder.overlayPosition, |
|
|
|
overlayBuilder: (params) { |
|
|
|
return Obx( |
|
|
|
() => IntroOverlayBuilder.homeBuilder( |
|
|
|
params, |
|
|
|
controller.noviceGuideService.isWakeup.value, |
|
|
|
onNext: () { |
|
|
|
controller.noviceGuideService.closeWakeupListen(); |
|
|
|
}, |
|
|
|
), |
|
|
|
); |
|
|
|
|
|
|
|
// 顶部图片 |
|
|
|
_buildEarbudsImage(), |
|
|
|
|
|
|
|
SizedBox(height: 20.h), |
|
|
|
|
|
|
|
// 设备状态卡片 |
|
|
|
IntroStepBuilder( |
|
|
|
group: 'home', |
|
|
|
order: 1, |
|
|
|
onWidgetLoad: () { |
|
|
|
controller.noviceGuideService.startHomeGuide(); |
|
|
|
}, |
|
|
|
overlayBuilder: (params) { |
|
|
|
return IntroOverlayBuilder.aiBuilder( |
|
|
|
params, |
|
|
|
'欢迎使用Voitrans!首先,让我们唤醒你的专属 AI—— 小言', |
|
|
|
onNext: () { |
|
|
|
controller.noviceGuideService.startWakeupListen(); |
|
|
|
}, |
|
|
|
); |
|
|
|
}, |
|
|
|
builder: (BuildContext context, GlobalKey key) => SizedBox( |
|
|
|
key: key, |
|
|
|
child: _buildDeviceStatusCard(isDarkMode), |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
SizedBox(height: 20.h), |
|
|
|
|
|
|
|
IntroStepBuilder( |
|
|
|
group: 'home', |
|
|
|
order: 2, |
|
|
|
getOverlayPosition: ({ |
|
|
|
required Offset offset, |
|
|
|
required Size screenSize, |
|
|
|
required Size size, |
|
|
|
}) { |
|
|
|
return OverlayPosition( |
|
|
|
top: 200.h, |
|
|
|
width: screenSize.width, |
|
|
|
crossAxisAlignment: CrossAxisAlignment.end, |
|
|
|
); |
|
|
|
}, |
|
|
|
overlayBuilder: (params) { |
|
|
|
return Obx( |
|
|
|
() => IntroOverlayBuilder.homeBuilder( |
|
|
|
params, |
|
|
|
controller.noviceGuideService.isWakeup.value, |
|
|
|
onNext: () { |
|
|
|
controller.noviceGuideService.closeWakeupListen(); |
|
|
|
}, |
|
|
|
builder: (BuildContext context, GlobalKey key) => |
|
|
|
SizedBox( |
|
|
|
key: key, |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
); |
|
|
|
}, |
|
|
|
builder: (BuildContext context, GlobalKey key) => SizedBox( |
|
|
|
key: key, |
|
|
|
), |
|
|
|
), |
|
|
|
// 热门AI体验 - 固定在底部,基于内容的固定高度 |
|
|
|
Column( |
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
mainAxisSize: MainAxisSize.min, // 重要:让高度基于内容 |
|
|
|
children: [ |
|
|
|
_buildSectionTitle(isDarkMode), |
|
|
|
SizedBox(height: 16.h), |
|
|
|
// 给AI布局一个固定的高度,或者让它基于内容自适应 |
|
|
|
_buildPremiumAILayout(), |
|
|
|
], |
|
|
|
), |
|
|
|
|
|
|
|
// 热门AI体验 |
|
|
|
_buildSectionTitle(isDarkMode), |
|
|
|
SizedBox(height: 16.h), |
|
|
|
_buildPremiumAILayout(), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
@ -201,47 +196,70 @@ class HomeView extends GetView<HomeController> { |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 自适应高度的耳机图片 - 根据设备连接状态动态调整高度 |
|
|
|
// 在自适应图片组件中添加RepaintBoundary |
|
|
|
Widget _buildAdaptiveEarbudsImage() { |
|
|
|
// 耳机图片 |
|
|
|
Widget _buildEarbudsImage() { |
|
|
|
final bool isDarkMode = Get.isDarkMode; |
|
|
|
final double imageHeight = |
|
|
|
controller.isFullyConnectedRx.value ? 160.h : 220.h; |
|
|
|
|
|
|
|
return RepaintBoundary( |
|
|
|
// 添加重绘边界,优化动画性能 |
|
|
|
child: AnimatedContainer( |
|
|
|
duration: const Duration(milliseconds: 500), |
|
|
|
curve: Curves.easeInOut, |
|
|
|
height: imageHeight, |
|
|
|
child: PageView( |
|
|
|
children: [ |
|
|
|
Center( |
|
|
|
child: ClipRRect( |
|
|
|
borderRadius: BorderRadius.circular(16.r), |
|
|
|
child: Image.asset( |
|
|
|
isDarkMode |
|
|
|
? 'assets/images/headphone_new3.png' |
|
|
|
: 'assets/images/headphone_new4.png', |
|
|
|
height: imageHeight, |
|
|
|
fit: BoxFit.contain, |
|
|
|
), |
|
|
|
return SizedBox( |
|
|
|
height: 220.h, |
|
|
|
child: PageView( |
|
|
|
children: [ |
|
|
|
// Center( |
|
|
|
// child: ClipRRect( |
|
|
|
// // 添加 ClipRRect 来实现圆角 |
|
|
|
// borderRadius: BorderRadius.circular(16.r), // 使用与其他部件一致的圆角大小 |
|
|
|
// child: SizedBox( |
|
|
|
// width: double.infinity, |
|
|
|
// height: 200.h, |
|
|
|
// child: Image.asset( |
|
|
|
// isDarkMode |
|
|
|
// ? 'assets/images/robot_light.png' |
|
|
|
// : 'assets/images/robot_light.png', |
|
|
|
// fit: BoxFit.fitWidth, |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
Center( |
|
|
|
child: ClipRRect( |
|
|
|
// 添加 ClipRRect |
|
|
|
borderRadius: BorderRadius.circular(16.r), |
|
|
|
child: Image.asset( |
|
|
|
isDarkMode |
|
|
|
? 'assets/images/headphone_new3.png' |
|
|
|
: 'assets/images/headphone_new4.png', |
|
|
|
height: 220.h, |
|
|
|
fit: BoxFit.contain, |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
// Center( |
|
|
|
// child: ClipRRect( |
|
|
|
// // 添加 ClipRRect |
|
|
|
// borderRadius: BorderRadius.circular(16.r), |
|
|
|
// child: SizedBox( |
|
|
|
// width: double.infinity, |
|
|
|
// height: 200.h, |
|
|
|
// child: Image.asset( |
|
|
|
// isDarkMode |
|
|
|
// ? 'assets/images/glasses_light.png' |
|
|
|
// : 'assets/images/glasses_light.png', |
|
|
|
// fit: BoxFit.fitWidth, |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 带动画的设备状态卡片 - 实现平滑的高度变化过渡 |
|
|
|
Widget _buildAnimatedDeviceStatusCard(bool isDarkMode) { |
|
|
|
// 设备状态卡片 |
|
|
|
Widget _buildDeviceStatusCard(bool isDarkMode) { |
|
|
|
return Obx(() { |
|
|
|
// 使用连接状态作为key,强制在状态改变时重建widget |
|
|
|
final isConnected = controller.isFullyConnectedRx.value; |
|
|
|
|
|
|
|
return AnimatedContainer( |
|
|
|
duration: const Duration(milliseconds: 500), // 平滑过渡动画 |
|
|
|
curve: Curves.easeInOut, |
|
|
|
return Container( |
|
|
|
key: ValueKey('device_status_$isConnected'), |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: isDarkMode ? const Color(0xFF1A1A1A) : Colors.white, |
|
|
|
borderRadius: BorderRadius.circular(16.r), |
|
|
|
@ -255,58 +273,30 @@ class HomeView extends GetView<HomeController> { |
|
|
|
), |
|
|
|
padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 16.h), |
|
|
|
child: Column( |
|
|
|
mainAxisSize: MainAxisSize.min, // 重要:让容器高度自适应内容 |
|
|
|
children: [ |
|
|
|
// 状态标题行 |
|
|
|
_buildDeviceStatusHeader(isDarkMode), |
|
|
|
|
|
|
|
// 设备信息 - 使用 AnimatedSwitcher 实现内容切换动画 |
|
|
|
// 设备信息 |
|
|
|
SizedBox(height: 12.h), |
|
|
|
AnimatedSwitcher( |
|
|
|
duration: const Duration(milliseconds: 300), |
|
|
|
transitionBuilder: (Widget child, Animation<double> animation) { |
|
|
|
return FadeTransition( |
|
|
|
opacity: animation, |
|
|
|
child: SlideTransition( |
|
|
|
position: Tween<Offset>( |
|
|
|
begin: const Offset(0, 0.1), |
|
|
|
end: Offset.zero, |
|
|
|
).animate(animation), |
|
|
|
child: child, |
|
|
|
), |
|
|
|
); |
|
|
|
}, |
|
|
|
child: Container( |
|
|
|
key: ValueKey('device_info_$isConnected'), |
|
|
|
child: isConnected |
|
|
|
? _buildConnectedDeviceInfo(isDarkMode) |
|
|
|
: _buildDisconnectedInfo(isDarkMode), |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
// 连接按钮 - 使用 AnimatedSwitcher 实现按钮切换动画 |
|
|
|
Obx(() { |
|
|
|
// 移除连接失败和连接中的状态判断,直接根据是否完全连接来判断 |
|
|
|
// 使用响应式访问确保UI更新 |
|
|
|
return controller.isFullyConnectedRx.value |
|
|
|
? _buildConnectedDeviceInfo(isDarkMode) //已连接设备信息 |
|
|
|
: _buildDisconnectedInfo(isDarkMode); //未连接提示 |
|
|
|
}), |
|
|
|
|
|
|
|
// 连接按钮(仅在未连接时显示) |
|
|
|
SizedBox(height: 12.h), |
|
|
|
AnimatedSwitcher( |
|
|
|
duration: const Duration(milliseconds: 300), |
|
|
|
transitionBuilder: (Widget child, Animation<double> animation) { |
|
|
|
return FadeTransition( |
|
|
|
opacity: animation, |
|
|
|
child: SlideTransition( |
|
|
|
position: Tween<Offset>( |
|
|
|
begin: const Offset(0, 0.1), |
|
|
|
end: Offset.zero, |
|
|
|
).animate(animation), |
|
|
|
child: child, |
|
|
|
), |
|
|
|
); |
|
|
|
}, |
|
|
|
child: Container( |
|
|
|
key: ValueKey('device_button_$isConnected'), |
|
|
|
child: !isConnected |
|
|
|
? _buildConnectButton() |
|
|
|
: _buildDeviceButton(isDarkMode), |
|
|
|
), |
|
|
|
), |
|
|
|
Obx(() { |
|
|
|
// 使用响应式访问确保UI更新 |
|
|
|
if (!controller.isFullyConnectedRx.value) { |
|
|
|
return _buildConnectButton(); |
|
|
|
} else { |
|
|
|
return _buildDeviceButton(isDarkMode); |
|
|
|
} |
|
|
|
}), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
@ -740,15 +730,14 @@ class HomeView extends GetView<HomeController> { |
|
|
|
child: IntroStepBuilder( |
|
|
|
group: 'home', |
|
|
|
order: 3, |
|
|
|
getOverlayPosition: IntroOverlayBuilder.overlayPosition, |
|
|
|
onHighlightWidgetTap: () { |
|
|
|
Intro.of(Get.context!).dispose(); |
|
|
|
controller.exploreAIFeatures(); |
|
|
|
}, |
|
|
|
overlayBuilder: (params) { |
|
|
|
return IntroOverlayBuilder.aiBuilder( |
|
|
|
params, |
|
|
|
'太棒了!你已成功唤醒小言,接下来试试让它帮你做件事吧', |
|
|
|
onTap: () { |
|
|
|
params.onFinish(); |
|
|
|
controller.exploreAIFeatures(); |
|
|
|
}, |
|
|
|
); |
|
|
|
}, |
|
|
|
builder: (BuildContext context, GlobalKey key) => SizedBox( |
|
|
|
|