1import 'package:flutter/material.dart';
2import 'widget_styles.dart';
3import 'floor_selector_view_config.dart';
13 final int sublocationId;
14 LevelInfo({required this.levelId, required this.sublocationId});
18typedef FloorSelectedCallback =
void Function(
int sublocationId, String levelId);
40 State<FloorSelectorView>
createState() => FloorSelectorViewState();
43class FloorSelectorViewState
extends State<FloorSelectorView> {
44 final ScrollController _scrollController = ScrollController();
46 List<LevelInfo> _floors = [];
47 int _selectedFloorIndex = -1;
49 final ValueNotifier<bool> _showTopNotifier = ValueNotifier(
false);
50 final ValueNotifier<bool> _showBottomNotifier = ValueNotifier(
false);
55 _scrollController.addListener(_updateScrollButtonsVisibility);
60 _scrollController.removeListener(_updateScrollButtonsVisibility);
61 _scrollController.dispose();
62 _showTopNotifier.dispose();
63 _showBottomNotifier.dispose();
67 void _updateScrollButtonsVisibility() {
68 if (!_scrollController.hasClients) {
69 _showTopNotifier.value =
false;
70 _showBottomNotifier.value =
false;
74 final bool tooManyFloors = _floors.length > kMaxVisibleFloors;
75 final double offset = _scrollController.offset;
76 final double maxScroll = _scrollController.position.maxScrollExtent;
78 final bool showTop = tooManyFloors && offset > 1.0;
79 final bool showBottom = tooManyFloors && (maxScroll - offset) > 1.0;
81 if (_showTopNotifier.value != showTop) {
82 _showTopNotifier.value = showTop;
84 if (_showBottomNotifier.value != showBottom) {
85 _showBottomNotifier.value = showBottom;
90 void setFloors(List<LevelInfo> floors) {
91 final newFloors = floors.isNotEmpty ? floors : <LevelInfo>[];
94 if (_selectedFloorIndex < 0 || _selectedFloorIndex >= _floors.length) {
95 _selectedFloorIndex = _floors.isNotEmpty ? 0 : -1;
99 _updateScrollButtonsVisibility();
101 if (_selectedFloorIndex >= 0 && _scrollController.hasClients) {
102 WidgetsBinding.instance.addPostFrameCallback((_) {
103 final target = _selectedFloorIndex * kFloorRowHeight;
104 final max = _scrollController.position.maxScrollExtent;
105 _scrollController.jumpTo(target.clamp(0.0, max));
111 void setSublocationId(
int newSublocationId) {
112 final int newIndex = _floors.indexWhere((floor) => floor.sublocationId == newSublocationId);
114 if (newIndex == -1) {
118 final bool wasAlreadySelected = _selectedFloorIndex == newIndex;
120 if (!wasAlreadySelected) {
122 _selectedFloorIndex = newIndex;
128 _updateScrollButtonsVisibility();
130 if (_scrollController.hasClients) {
131 final bool needAnimation = _floors.length > kMaxVisibleFloors;
133 final double targetOffset = newIndex * kFloorRowHeight;
134 final double maxScroll = _scrollController.position.maxScrollExtent;
135 final double clampedOffset = targetOffset.clamp(0.0, maxScroll);
138 _scrollController.animateTo(
140 duration: kScrollAnimationDuration,
141 curve: kScrollAnimationCurve,
144 _scrollController.jumpTo(clampedOffset);
148 final selectedLevel = _floors[newIndex];
149 widget.onFloorSelected?.call(selectedLevel.sublocationId, selectedLevel.levelId);
153 final target = (_scrollController.offset - 4 * kFloorRowHeight)
154 .clamp(0.0, _scrollController.position.maxScrollExtent);
155 _scrollController.animateTo(
157 duration: kScrollAnimationDuration,
158 curve: kScrollAnimationCurve,
163 final target = (_scrollController.offset + 4 * kFloorRowHeight)
164 .clamp(0.0, _scrollController.position.maxScrollExtent);
165 _scrollController.animateTo(
167 duration: kScrollAnimationDuration,
168 curve: kScrollAnimationCurve,
172 String _display(String
id) => id.length <= 5 ? id :
'${id.substring(0, 5)}...';
175 if (_floors.isEmpty || _floors.length <= 1)
return 0.0;
176 return _floors.length >= kMaxVisibleFloors
177 ? kFloorSelectorMaxHeight - 1
178 : _floors.length * kFloorRowHeight - 1;
182 Widget build(BuildContext context) {
183 if (_height <= 0)
return const SizedBox.shrink();
185 final safePadding = MediaQuery.of(context).padding;
186 final padding = widget.config.padding ?? EdgeInsets.only(
187 left: kStandardLeftPadding + safePadding.left,
188 top: kFloorSelectorTopPadding + safePadding.top,
192 alignment: Alignment.topLeft,
196 width: kStandardButtonWidth,
198 decoration: BoxDecoration(
200 borderRadius: kStandardBorderRadius,
201 boxShadow: kStandardShadows,
204 clipBehavior: Clip.none,
207 borderRadius: kStandardBorderRadius,
208 child: ListView.builder(
209 controller: _scrollController,
210 physics: _floors.length > kMaxVisibleFloors
211 ?
const AlwaysScrollableScrollPhysics()
212 :
const NeverScrollableScrollPhysics(),
213 itemCount: _floors.length,
214 itemExtent: kFloorRowHeight,
215 itemBuilder: (context, i) {
216 final level = _floors[i];
217 final selected = i == _selectedFloorIndex;
218 final accentColor = widget.config.accentColor ?? kBaseBlueColor;
219 final textColor = widget.config.textColor ?? kBaseBlackColor;
221 color: selected ? accentColor : Colors.white,
224 if (i == _selectedFloorIndex) return;
225 setState(() => _selectedFloorIndex = i);
226 widget.onFloorSelected?.call(level.sublocationId, level.levelId);
228 final target = i * kFloorRowHeight;
229 final max = _scrollController.position.maxScrollExtent;
230 _scrollController.jumpTo(target.clamp(0.0, max));
234 _display(level.levelId),
236 color: selected ? Colors.white : textColor,
237 fontSize: kFloorSelectorFontSize,
247 ValueListenableBuilder<bool>(
248 valueListenable: _showTopNotifier,
249 builder: (_, showTop, __) {
255 child: _scrollButton(
'▲', _scrollUp, kVerticalTopBorderRadius),
257 : const SizedBox.shrink();
261 ValueListenableBuilder<bool>(
262 valueListenable: _showBottomNotifier,
263 builder: (_, showBottom, __) {
269 child: _scrollButton(
'▼', _scrollDown, kVerticalBottomBorderRadius),
271 : const SizedBox.shrink();
281 Widget _scrollButton(String icon, VoidCallback onTap, BorderRadius radius) {
283 height: kFloorRowHeight,
284 width: kStandardButtonWidth,
285 decoration: BoxDecoration(
286 color: kButtonBackgroundColor,
287 borderRadius: radius,
290 color: Colors.transparent,
292 borderRadius: radius,
297 style:
const TextStyle(
298 color: kBaseBlackColor,
299 fontSize: kScrollButtonFontSize,
300 fontWeight: kScrollButtonFontWeight,