Skip to content

Commit 3b0ff6e

Browse files
committed
feat: 添加设备尺寸检测和滑动面板功能
- 新增DeviceSizeHelper工具类用于检测小尺寸设备 - 实现SlidePanel组件提供滑动面板功能 - 在点名和抽奖页面适配小尺寸设备布局 - 优化控制面板在小屏幕设备上的显示方式 - 添加滑动面板按钮和交互逻辑
1 parent aea68c8 commit 3b0ff6e

8 files changed

Lines changed: 615 additions & 143 deletions

File tree

changelogs/0.0.9.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## [0.0.9] - 2026-03-21
2+
3+
### Added
4+
- 在点名和抽奖页面适配极小尺寸设备(如智能手表)
5+
6+
### Changed
7+
- None
8+
9+
### Fixed
10+
- 部分界面元素的显示

lib/screens/home_screen.dart

Lines changed: 126 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
33
import '../widgets/control_panel.dart';
44
import '../widgets/name_display.dart';
55
import '../widgets/nav_rail.dart';
6+
import '../widgets/slide_panel.dart';
7+
import '../utils/device_size_helper.dart';
68
import 'history_screen.dart';
79
import 'lottery_screen.dart';
810
import 'settings_screen.dart';
@@ -25,6 +27,13 @@ class _HomeScreenState extends State<HomeScreen> {
2527

2628
int _selectedIndex = 0;
2729

30+
void _openSlidePanel(Widget panelContent) {
31+
SlidePanelOverlay.show(
32+
context: context,
33+
child: panelContent,
34+
);
35+
}
36+
2837
@override
2938
Widget build(BuildContext context) {
3039
return LayoutBuilder(
@@ -37,13 +46,18 @@ class _HomeScreenState extends State<HomeScreen> {
3746
shortestSide <= _kPhoneMaxShortestSide;
3847
final bool isWideScreen = constraints.maxWidth > 800 || isLandscapePhone;
3948
final bool isRailVisible = constraints.maxWidth >= _kRailMinWidth;
49+
final bool isSmallDevice = DeviceSizeHelper.isSmallDevice(constraints);
4050

4151
return Scaffold(
4252
bottomNavigationBar: !isRailVisible
4353
? NavigationBar(
4454
selectedIndex: _selectedIndex,
45-
onDestinationSelected: (index) =>
46-
setState(() => _selectedIndex = index),
55+
onDestinationSelected: (index) {
56+
if (SlidePanelOverlay.isShowing) {
57+
SlidePanelOverlay.hide();
58+
}
59+
setState(() => _selectedIndex = index);
60+
},
4761
destinations: const [
4862
NavigationDestination(
4963
icon: Icon(Icons.people_outline),
@@ -70,91 +84,27 @@ class _HomeScreenState extends State<HomeScreen> {
7084
NavRail(
7185
selectedIndex: _selectedIndex,
7286
onDestinationSelected: (int index) {
87+
if (SlidePanelOverlay.isShowing) {
88+
SlidePanelOverlay.hide();
89+
}
7390
setState(() {
7491
_selectedIndex = index;
7592
});
7693
},
7794
),
7895
if (isRailVisible) const VerticalDivider(thickness: 1, width: 1),
79-
Expanded(child: _buildBody(isWideScreen, constraints)),
96+
Expanded(child: _buildBody(isWideScreen, isSmallDevice, constraints)),
8097
],
8198
),
8299
);
83100
},
84101
);
85102
}
86103

87-
Widget _buildBody(bool isWideScreen, BoxConstraints viewportConstraints) {
104+
Widget _buildBody(bool isWideScreen, bool isSmallDevice, BoxConstraints viewportConstraints) {
88105
switch (_selectedIndex) {
89106
case 0:
90-
if (isWideScreen) {
91-
final normalPanelAvailableHeight =
92-
viewportConstraints.maxHeight - (_kPanelGap * 2);
93-
const rightPanelReservedWidth = _kPanelWidth + _kPanelGap;
94-
95-
return Container(
96-
color: Theme.of(context).colorScheme.surfaceContainer,
97-
child: Stack(
98-
children: [
99-
const Positioned(
100-
left: 0,
101-
right: rightPanelReservedWidth,
102-
top: 0,
103-
bottom: 0,
104-
child: Padding(
105-
padding: EdgeInsets.all(24),
106-
child: NameDisplay(isWideScreen: true),
107-
),
108-
),
109-
Positioned(
110-
right: _kPanelGap,
111-
bottom: _kPanelGap,
112-
child: SizedBox(
113-
width: _kPanelWidth,
114-
child: ControlPanel(
115-
layoutMode: ControlPanelLayoutMode.autoFit,
116-
availableHeight: normalPanelAvailableHeight,
117-
),
118-
),
119-
),
120-
],
121-
),
122-
);
123-
} else {
124-
return Container(
125-
color: Theme.of(context).colorScheme.surfaceContainer,
126-
child: Column(
127-
children: [
128-
const Expanded(
129-
child: Padding(
130-
padding: EdgeInsets.all(16),
131-
child: NameDisplay(isWideScreen: false),
132-
),
133-
),
134-
Container(
135-
padding: const EdgeInsets.symmetric(
136-
horizontal: 8,
137-
vertical: 6,
138-
),
139-
decoration: BoxDecoration(
140-
color: Theme.of(context).cardColor,
141-
boxShadow: [
142-
BoxShadow(
143-
color: Colors.black.withValues(alpha: 0.1),
144-
blurRadius: 4,
145-
offset: const Offset(0, -2),
146-
),
147-
],
148-
),
149-
child: const SizedBox(
150-
height: _kNarrowPanelHeight,
151-
child: ControlPanel(),
152-
),
153-
),
154-
],
155-
),
156-
);
157-
}
107+
return _buildRollCallScreen(isWideScreen, isSmallDevice, viewportConstraints);
158108
case 1:
159109
return const LotteryScreen();
160110
case 2:
@@ -165,4 +115,108 @@ class _HomeScreenState extends State<HomeScreen> {
165115
return const SizedBox.shrink();
166116
}
167117
}
118+
119+
Widget _buildRollCallScreen(bool isWideScreen, bool isSmallDevice, BoxConstraints viewportConstraints) {
120+
if (isSmallDevice) {
121+
return Stack(
122+
children: [
123+
Container(
124+
color: Theme.of(context).colorScheme.surfaceContainer,
125+
child: const Padding(
126+
padding: EdgeInsets.all(16),
127+
child: NameDisplay(isWideScreen: false),
128+
),
129+
),
130+
Positioned(
131+
right: 8,
132+
bottom: 8,
133+
child: FloatingActionButton.small(
134+
heroTag: 'rollcall_panel_fab',
135+
onPressed: () {
136+
_openSlidePanel(
137+
const ControlPanel(
138+
layoutMode: ControlPanelLayoutMode.compact,
139+
fillHeight: false,
140+
),
141+
);
142+
},
143+
backgroundColor: Theme.of(context).colorScheme.primary,
144+
foregroundColor: Colors.white,
145+
child: const Icon(Icons.tune, size: 20),
146+
),
147+
),
148+
],
149+
);
150+
}
151+
152+
if (isWideScreen) {
153+
final normalPanelAvailableHeight =
154+
viewportConstraints.maxHeight - (_kPanelGap * 2);
155+
const rightPanelReservedWidth = _kPanelWidth + _kPanelGap;
156+
157+
return Container(
158+
color: Theme.of(context).colorScheme.surfaceContainer,
159+
child: Stack(
160+
children: [
161+
const Positioned(
162+
left: 0,
163+
right: rightPanelReservedWidth,
164+
top: 0,
165+
bottom: 0,
166+
child: Padding(
167+
padding: EdgeInsets.all(24),
168+
child: NameDisplay(isWideScreen: true),
169+
),
170+
),
171+
Positioned(
172+
right: _kPanelGap,
173+
bottom: _kPanelGap,
174+
child: SizedBox(
175+
width: _kPanelWidth,
176+
child: ControlPanel(
177+
layoutMode: ControlPanelLayoutMode.autoFit,
178+
availableHeight: normalPanelAvailableHeight,
179+
),
180+
),
181+
),
182+
],
183+
),
184+
);
185+
} else {
186+
return Container(
187+
color: Theme.of(context).colorScheme.surfaceContainer,
188+
child: SafeArea(
189+
child: Column(
190+
children: [
191+
const Expanded(
192+
child: Padding(
193+
padding: EdgeInsets.all(16),
194+
child: NameDisplay(isWideScreen: false),
195+
),
196+
),
197+
Container(
198+
decoration: BoxDecoration(
199+
color: Theme.of(context).cardColor,
200+
boxShadow: [
201+
BoxShadow(
202+
color: Colors.black.withValues(alpha: 0.1),
203+
blurRadius: 4,
204+
offset: const Offset(0, -2),
205+
),
206+
],
207+
),
208+
constraints: BoxConstraints(
209+
maxHeight: _kNarrowPanelHeight,
210+
),
211+
child: const Padding(
212+
padding: EdgeInsets.all(8),
213+
child: ControlPanel(),
214+
),
215+
),
216+
],
217+
),
218+
),
219+
);
220+
}
221+
}
168222
}

0 commit comments

Comments
 (0)