228 lines
6.9 KiB
Dart
228 lines
6.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:lba/res/colors.dart';
|
|
import 'package:lba/widgets/button.dart';
|
|
import 'dart:math' as math;
|
|
|
|
class TimeSelectionBottomSheet extends StatefulWidget {
|
|
const TimeSelectionBottomSheet({super.key});
|
|
|
|
@override
|
|
State<TimeSelectionBottomSheet> createState() =>
|
|
_TimeSelectionBottomSheetState();
|
|
}
|
|
|
|
class _TimeSelectionBottomSheetState extends State<TimeSelectionBottomSheet> {
|
|
final List<String> _timeSlots = [];
|
|
String? _selectedTimeSlot;
|
|
late ScrollController _scrollController;
|
|
|
|
static const double _itemHeight = 50.0;
|
|
static const int _visibleItems = 3;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_generateTimeSlots();
|
|
|
|
final initialIndex = _timeSlots.length > 1 ? 1 : 0;
|
|
if (_timeSlots.isNotEmpty) {
|
|
_selectedTimeSlot = _timeSlots[initialIndex];
|
|
}
|
|
|
|
_scrollController =
|
|
ScrollController(initialScrollOffset: initialIndex * _itemHeight);
|
|
_scrollController.addListener(_onScroll);
|
|
}
|
|
|
|
void _onScroll() {
|
|
setState(() {});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_scrollController.removeListener(_onScroll);
|
|
_scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _generateTimeSlots() {
|
|
final now = DateTime.now();
|
|
final endTime = DateTime(now.year, now.month, now.day, 22, 0);
|
|
DateTime startTime = DateTime(
|
|
now.year,
|
|
now.month,
|
|
now.day,
|
|
now.hour,
|
|
now.minute > 30 ? 60 : 30,
|
|
);
|
|
|
|
if (startTime.isAfter(endTime)) return;
|
|
|
|
_timeSlots.add('');
|
|
while (startTime.isBefore(endTime)) {
|
|
final slotEnd = startTime.add(const Duration(minutes: 30));
|
|
final formattedStart = DateFormat('HH:mm').format(startTime);
|
|
final formattedEnd = DateFormat('HH:mm').format(slotEnd);
|
|
_timeSlots.add('$formattedStart - $formattedEnd');
|
|
startTime = slotEnd;
|
|
}
|
|
_timeSlots.add('');
|
|
}
|
|
|
|
void _onScrollEnd() {
|
|
final currentOffset = _scrollController.offset;
|
|
final targetIndex = (currentOffset / _itemHeight).round();
|
|
final targetOffset = targetIndex * _itemHeight;
|
|
|
|
if ((targetOffset - currentOffset).abs() > 0.1) {
|
|
_scrollController.animateTo(
|
|
targetOffset,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeOut,
|
|
);
|
|
}
|
|
|
|
if (mounted && _timeSlots.isNotEmpty) {
|
|
final finalIndex = targetIndex.clamp(0, _timeSlots.length - 1);
|
|
final newSelection = _timeSlots[finalIndex];
|
|
|
|
if (_selectedTimeSlot != newSelection && newSelection.isNotEmpty) {
|
|
setState(() {
|
|
_selectedTimeSlot = newSelection;
|
|
});
|
|
HapticFeedback.lightImpact();
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 4,
|
|
margin: const EdgeInsets.symmetric(vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[300],
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text('Select a time', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 8),
|
|
Divider(color: Colors.grey.shade300),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Philadelphia Honey Pecan Cream Cheese Spread, 7.5 oz Tub available from Now - 10:00 PM Today',
|
|
style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
|
|
),
|
|
const SizedBox(height: 16),
|
|
_buildTimePicker(),
|
|
const SizedBox(height: 24),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 50,
|
|
child: Button(
|
|
text: 'Confirm',
|
|
onPressed: () {
|
|
if (_selectedTimeSlot != null && _selectedTimeSlot!.isNotEmpty) {
|
|
Navigator.pop(context, _selectedTimeSlot);
|
|
}
|
|
},
|
|
color: LightAppColors.offerTimer,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTimePicker() {
|
|
if (_timeSlots.length <= 2) {
|
|
return const Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(20.0),
|
|
child: Text('No available time slots for today.'),
|
|
),
|
|
);
|
|
}
|
|
|
|
final listHeight = _itemHeight * _visibleItems;
|
|
|
|
return SizedBox(
|
|
height: listHeight,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Positioned(
|
|
top: _itemHeight,
|
|
bottom: _itemHeight,
|
|
left: 0,
|
|
right: 0,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: LightAppColors.fillOrder,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
),
|
|
|
|
NotificationListener<ScrollNotification>(
|
|
onNotification: (notification) {
|
|
if (notification is ScrollEndNotification) {
|
|
_onScrollEnd();
|
|
}
|
|
return true;
|
|
},
|
|
child: ListView.builder(
|
|
controller: _scrollController,
|
|
padding: EdgeInsets.symmetric(vertical: (listHeight - _itemHeight) / 2),
|
|
itemCount: _timeSlots.length,
|
|
itemExtent: _itemHeight,
|
|
itemBuilder: (context, index) {
|
|
final centerOffset = _scrollController.offset;
|
|
final itemOffset = index * _itemHeight;
|
|
final distance = (itemOffset - centerOffset).abs();
|
|
|
|
final isSelected = distance < _itemHeight / 2;
|
|
final scale = math.max(1.0 - (distance / listHeight) * 0.7, 0.8);
|
|
final opacity = math.max(1.0 - (distance / listHeight) * 1.2, 0.3);
|
|
|
|
return Center(
|
|
child: Transform.scale(
|
|
scale: scale,
|
|
child: Text(
|
|
_timeSlots[index],
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
color: isSelected ? Colors.black : Colors.grey.shade600.withOpacity(opacity),
|
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |