Browse Source

refactor(home): 简化主页布局

weicu
lxm 1 year ago
parent
commit
c6a3acc818
  1. 5
      lib/modules/agent/views/agent_view.dart
  2. 287
      lib/modules/home/views/home_view.dart

5
lib/modules/agent/views/agent_view.dart

@ -405,8 +405,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
SizedBox(height: 12.h), SizedBox(height: 12.h),
], ],
); );
} else if (controller.isTextInputMode.value && } else {
!controller.hasTextInput.value) {
// 文本输入模式下,且输入框为空 // 文本输入模式下,且输入框为空
return Column( return Column(
children: [ children: [
@ -449,8 +448,6 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
SizedBox(height: 12.h), SizedBox(height: 12.h),
], ],
); );
} else {
return const SizedBox.shrink();
} }
}, },
), ),

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

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

Loading…
Cancel
Save