Skip to content

Commit

Permalink
Merge pull request #42 from albertms10/refactor/improve-booking-card-…
Browse files Browse the repository at this point in the history
…interaction-ux

feat(booking_preview_panel): implement new preview overlay
  • Loading branch information
albertms10 committed Dec 24, 2021
2 parents 9c046d4 + 050ba29 commit 030ce67
Show file tree
Hide file tree
Showing 11 changed files with 936 additions and 413 deletions.
40 changes: 40 additions & 0 deletions lib/src/model/booking/booking.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:cabin_booking/utils/date_time_extension.dart';
import 'package:cabin_booking/utils/time_of_day_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';

import '../date/date_range.dart';
Expand Down Expand Up @@ -155,3 +156,42 @@ class Booking extends Item {
}

enum BookingStatus { pending, confirmed, cancelled }

extension LocalizedBookingStatus on BookingStatus {
String localized(AppLocalizations appLocalizations) {
switch (this) {
case BookingStatus.pending:
return appLocalizations.pending;
case BookingStatus.confirmed:
return appLocalizations.confirmed;
case BookingStatus.cancelled:
return appLocalizations.cancelled;
}
}
}

extension LayoutBookingStatus on BookingStatus {
Color color(ThemeData theme) {
switch (this) {
case BookingStatus.pending:
return theme.hintColor;
case BookingStatus.confirmed:
return theme.brightness == Brightness.light
? Colors.greenAccent[700]!
: Colors.greenAccent;
case BookingStatus.cancelled:
return Colors.redAccent;
}
}

IconData get icon {
switch (this) {
case BookingStatus.pending:
return Icons.help_outline;
case BookingStatus.confirmed:
return Icons.check;
case BookingStatus.cancelled:
return Icons.clear;
}
}
}
213 changes: 114 additions & 99 deletions lib/widgets/booking/booking_card.dart
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import 'package:cabin_booking/constants.dart';
import 'package:cabin_booking/model.dart';
import 'package:cabin_booking/widgets/booking/booking_popup_menu.dart';
import 'package:cabin_booking/widgets/booking/booking_status_button.dart';
import 'package:cabin_booking/widgets/booking/booking_preview_panel_overlay.dart';
import 'package:cabin_booking/widgets/layout/scrollable_time_table.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:timer_builder/timer_builder.dart';

class BookingCard extends StatelessWidget {
final Cabin cabin;
final Booking booking;
final ShowPreviewOverlayCallback? showPreviewPanel;
final SetPreventTimeTableScroll? setPreventTimeTableScroll;

const BookingCard({
Key? key,
required this.cabin,
required this.booking,
this.showPreviewPanel,
this.setPreventTimeTableScroll,
}) : super(key: key);

double get height => booking.duration.inMinutes * kBookingHeightRatio - 16.0;

bool get isRecurring => RecurringBooking.isRecurringBooking(booking);

@override
Widget build(BuildContext context) {
final isBeforeNow = booking.endDateTime.isBefore(DateTime.now());

return TimerBuilder.periodic(
const Duration(minutes: 1),
builder: (context) {
final isBeforeNow = booking.endDateTime.isBefore(DateTime.now());

return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: height, end: height),
duration: const Duration(milliseconds: 300),
Expand All @@ -45,22 +46,10 @@ class BookingCard extends StatelessWidget {
width: 1.5,
),
),
color: Colors.transparent,
child: Container(
height: value,
padding: const EdgeInsets.only(
top: 8.0,
right: 4.0,
left: 10.0,
),
decoration: (!booking.isLocked)
height: height,
decoration: booking.isLocked
? BoxDecoration(
color: Theme.of(context)
.cardColor
.withOpacity(isBeforeNow ? 0.41 : 1.0),
borderRadius: BorderRadius.circular(10.0),
)
: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
gradient: const LinearGradient(
begin: Alignment.topLeft,
Expand All @@ -74,11 +63,19 @@ class BookingCard extends StatelessWidget {
],
tileMode: TileMode.repeated,
),
)
: BoxDecoration(
color: Theme.of(context)
.cardColor
.withOpacity(isBeforeNow ? 0.41 : 1.0),
borderRadius: BorderRadius.circular(10.0),
),
child: _BookingCardInfo(
child: _BookingCardInteractive(
cabin: cabin,
booking: booking,
isRecurring: isRecurring,
height: value,
showPreviewPanel: showPreviewPanel,
setPreventTimeTableScroll: setPreventTimeTableScroll,
),
),
);
Expand All @@ -89,6 +86,61 @@ class BookingCard extends StatelessWidget {
}
}

class _BookingCardInteractive extends StatefulWidget {
final Cabin cabin;
final Booking booking;
final double height;
final ShowPreviewOverlayCallback? showPreviewPanel;
final SetPreventTimeTableScroll? setPreventTimeTableScroll;

const _BookingCardInteractive({
Key? key,
required this.cabin,
required this.booking,
required this.height,
this.showPreviewPanel,
this.setPreventTimeTableScroll,
}) : super(key: key);

bool get isRecurring => RecurringBooking.isRecurringBooking(booking);

@override
_BookingCardInteractiveState createState() => _BookingCardInteractiveState();
}

class _BookingCardInteractiveState extends State<_BookingCardInteractive> {
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: InkWell(
customBorder: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
onTap: () {
final renderBox = context.findRenderObject() as RenderBox?;
if (renderBox == null) return;

widget.showPreviewPanel?.call(
widget.cabin,
widget.booking,
renderBox,
widget.setPreventTimeTableScroll,
);
},
child: Padding(
padding: const EdgeInsets.only(top: 8.0, right: 4.0, left: 10.0),
child: _BookingCardInfo(
cabin: widget.cabin,
booking: widget.booking,
isRecurring: widget.isRecurring,
),
),
),
);
}
}

class _BookingCardInfo extends StatelessWidget {
final Cabin cabin;
final Booking booking;
Expand All @@ -108,87 +160,50 @@ class _BookingCardInfo extends StatelessWidget {

return LayoutBuilder(
builder: (context, constraints) {
return Stack(
alignment: Alignment.topRight,
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (isRecurring)
Tooltip(
message:
'${booking.recurringNumber}/${booking.recurringTotalTimes}',
child: Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Icon(
Icons.repeat,
color: theme.hintColor,
size: 16.0,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 28.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Text(
!booking.isLocked
? booking.description!
: '${booking.description} '
'(${appLocalizations.locked.toLowerCase()})',
style: TextStyle(
fontSize: constraints.maxHeight > 20.0
? 14.0
: constraints.maxHeight * 0.5,
),
overflow: TextOverflow.ellipsis,
maxLines: 4,
),
if (constraints.maxHeight > 30.0)
Text(
booking.timeRange,
style: theme.textTheme.caption?.copyWith(
fontSize: constraints.maxHeight > 40.0
? 14.0
: constraints.maxHeight * 0.4,
),
),
],
),
if (isRecurring)
Tooltip(
message:
'${booking.recurringNumber}/${booking.recurringTotalTimes}',
child: Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Icon(
Icons.repeat,
color: theme.hintColor,
size: 16.0,
),
),
],
),
SizedBox(
height: double.infinity,
child: SizedBox(
width: 48,
child: Wrap(
direction: Axis.vertical,
alignment: WrapAlignment.spaceBetween,
spacing: -8.0,
runAlignment: WrapAlignment.center,
runSpacing: -8.0,
crossAxisAlignment: WrapCrossAlignment.center,
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 28.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
BookingPopupMenu(
cabin: cabin,
booking: booking,
Text(
!booking.isLocked
? booking.description!
: '${booking.description} '
'(${appLocalizations.locked.toLowerCase()})',
style: TextStyle(
fontSize: constraints.maxHeight > 20.0
? 14.0
: constraints.maxHeight * 0.5,
),
overflow: TextOverflow.ellipsis,
maxLines: 4,
),
if (!booking.isLocked)
BookingStatusButton(
status: booking.status,
onPressed: () {
Provider.of<CabinManager>(context, listen: false)
.modifyBookingStatusById(
cabin.id,
booking.id,
BookingStatus.values[(booking.status.index + 1) %
BookingStatus.values.length],
);
},
if (constraints.maxHeight > 30.0)
Text(
booking.timeRange,
style: theme.textTheme.caption?.copyWith(
fontSize: constraints.maxHeight > 40.0
? 14.0
: constraints.maxHeight * 0.4,
),
),
],
),
Expand Down
Loading

0 comments on commit 030ce67

Please sign in to comment.