Skip to content

Commit

Permalink
Added exhibition bottom sheet
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcinusX committed May 28, 2019
1 parent 359de5c commit aa25f6d
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 72 deletions.
313 changes: 265 additions & 48 deletions lib/exhibition_bottom_sheet.dart
@@ -1,65 +1,278 @@
import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter/material.dart';

class ExhibitionBottomSheet extends StatelessWidget {
const double minHeight = 120;
const double iconStartSize = 44;
const double iconEndSize = 120;
const double iconStartMarginTop = 36;
const double iconEndMarginTop = 80;
const double iconsVerticalSpacing = 24;
const double iconsHorizontalSpacing = 16;

class ExhibitionBottomSheet extends StatefulWidget {
@override
_ExhibitionBottomSheetState createState() => _ExhibitionBottomSheetState();
}

class _ExhibitionBottomSheetState extends State<ExhibitionBottomSheet>
with SingleTickerProviderStateMixin {
AnimationController _controller;

double get maxHeight => MediaQuery.of(context).size.height;

double get headerTopMargin =>
lerp(20, 20 + MediaQuery.of(context).padding.top);

double get headerFontSize => lerp(14, 24);

double get itemBorderRadius => lerp(8, 24);

double get iconLeftBorderRadius => itemBorderRadius;

double get iconRightBorderRadius => lerp(8, 0);

double get iconSize => lerp(iconStartSize, iconEndSize);

double iconTopMargin(int index) =>
lerp(iconStartMarginTop,
iconEndMarginTop + index * (iconsVerticalSpacing + iconEndSize)) +
headerTopMargin;

double iconLeftMargin(int index) =>
lerp(index * (iconsHorizontalSpacing + iconStartSize), 0);

@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 600),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

double lerp(double min, double max) =>
lerpDouble(min, max, _controller.value);

@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(
left: 32,
right: 32,
bottom: MediaQuery.of(context).padding.bottom / 2,
),
decoration: BoxDecoration(
color: Color(0xFF162A49),
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
),
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 20),
Text(
'Booked Exhibition',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Positioned(
height: lerp(minHeight, maxHeight),
left: 0,
right: 0,
bottom: 0,
child: GestureDetector(
onTap: _toggle,
onVerticalDragUpdate: _handleDragUpdate,
onVerticalDragEnd: _handleDragEnd,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 32),
decoration: const BoxDecoration(
color: Color(0xFF162A49),
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
),
child: Stack(
children: <Widget>[
MenuButton(),
SheetHeader(
fontSize: headerFontSize,
topMargin: headerTopMargin,
),
for (Event event in events) _buildFullItem(event),
for (Event event in events) _buildIcon(event),
],
),
),
),
SizedBox(height: 20),
Row(
children: <Widget>[
ExhibitionSmallCard(assetName: 'steve-johnson.jpeg'),
SizedBox(width: 16),
ExhibitionSmallCard(assetName: 'efe-kurnaz.jpg'),
SizedBox(width: 16),
ExhibitionSmallCard(assetName: 'rodion-kutsaev.jpeg'),
Spacer(),
MenuButton(),
],
);
},
);
}

Widget _buildIcon(Event event) {
int index = events.indexOf(event);
return Positioned(
height: iconSize,
width: iconSize,
top: iconTopMargin(index),
left: iconLeftMargin(index),
child: ClipRRect(
borderRadius: BorderRadius.horizontal(
left: Radius.circular(iconLeftBorderRadius),
right: Radius.circular(iconRightBorderRadius),
),
child: Image.asset(
'assets/${event.assetName}',
fit: BoxFit.cover,
alignment: Alignment(lerp(1, 0), 0),
),
),
);
}

Widget _buildFullItem(Event event) {
int index = events.indexOf(event);
return ExpandedEventItem(
topMargin: iconTopMargin(index),
leftMargin: iconLeftMargin(index),
height: iconSize,
isVisible: _controller.status == AnimationStatus.completed,
borderRadius: itemBorderRadius,
title: event.title,
date: event.date,
);
}

void _toggle() {
final bool isOpen = _controller.status == AnimationStatus.completed;
_controller.fling(velocity: isOpen ? -2 : 2);
}

void _handleDragUpdate(DragUpdateDetails details) {
_controller.value -= details.primaryDelta / maxHeight;
}

void _handleDragEnd(DragEndDetails details) {
if (_controller.isAnimating ||
_controller.status == AnimationStatus.completed) return;

final double flingVelocity =
details.velocity.pixelsPerSecond.dy / maxHeight;
if (flingVelocity < 0.0)
_controller.fling(velocity: math.max(2.0, -flingVelocity));
else if (flingVelocity > 0.0)
_controller.fling(velocity: math.min(-2.0, -flingVelocity));
else
_controller.fling(velocity: _controller.value < 0.5 ? -2.0 : 2.0);
}
}

class ExpandedEventItem extends StatelessWidget {
final double topMargin;
final double leftMargin;
final double height;
final bool isVisible;
final double borderRadius;
final String title;
final String date;

const ExpandedEventItem(
{Key key,
this.topMargin,
this.height,
this.isVisible,
this.borderRadius,
this.title,
this.date,
this.leftMargin})
: super(key: key);

@override
Widget build(BuildContext context) {
return Positioned(
top: topMargin,
left: leftMargin,
right: 0,
height: height,
child: AnimatedOpacity(
opacity: isVisible ? 1 : 0,
duration: Duration(milliseconds: 200),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
color: Colors.white,
),
SizedBox(height: 16),
],
padding: EdgeInsets.only(left: height).add(EdgeInsets.all(8)),
child: _buildContent(),
),
),
);
}

Widget _buildContent() {
return Column(
children: <Widget>[
Text(title, style: TextStyle(fontSize: 16)),
SizedBox(height: 8),
Row(
children: <Widget>[
Text(
'1 ticket',
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 12,
color: Colors.grey.shade600,
),
),
SizedBox(width: 8),
Text(
date,
style: TextStyle(
fontWeight: FontWeight.w300,
fontSize: 12,
color: Colors.grey,
),
),
],
),
Spacer(),
Row(
children: <Widget>[
Icon(Icons.place, color: Colors.grey.shade400, size: 16),
Text(
'Science Park 10 25A',
style: TextStyle(color: Colors.grey.shade400, fontSize: 13),
)
],
)
],
);
}
}

class ExhibitionSmallCard extends StatelessWidget {
final List<Event> events = [
Event('steve-johnson.jpeg', 'Shenzhen GLOBAL DESIGN AWARD 2018', '4.20-30'),
Event('efe-kurnaz.jpg', 'Shenzhen GLOBAL DESIGN AWARD 2018', '4.20-30'),
Event('rodion-kutsaev.jpeg', 'Dawan District Guangdong Hong Kong', '4.28-31'),
];

class Event {
final String assetName;
final String title;
final String date;

const ExhibitionSmallCard({Key key, @required this.assetName})
Event(this.assetName, this.title, this.date);
}

class SheetHeader extends StatelessWidget {
final double fontSize;
final double topMargin;

const SheetHeader(
{Key key, @required this.fontSize, @required this.topMargin})
: super(key: key);

@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.asset(
'assets/$assetName',
width: 44,
height: 44,
fit: BoxFit.cover,
return Positioned(
top: topMargin,
child: Text(
'Booked Exhibition',
style: TextStyle(
color: Colors.white,
fontSize: fontSize,
fontWeight: FontWeight.w500,
),
),
);
}
Expand All @@ -68,10 +281,14 @@ class ExhibitionSmallCard extends StatelessWidget {
class MenuButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Icon(
Icons.menu,
color: Colors.white,
size: 28,
return Positioned(
right: 0,
bottom: 24,
child: Icon(
Icons.menu,
color: Colors.white,
size: 28,
),
);
}
}
32 changes: 17 additions & 15 deletions lib/home_page.dart
Expand Up @@ -8,21 +8,23 @@ class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
bottom: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 8),
Header(),
SizedBox(height: 40),
Tabs(),
SizedBox(height: 8),
SlidingCardsView(),
Spacer(),
ExhibitionBottomSheet(),
],
),
body: Stack(
children: <Widget>[
SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 8),
Header(),
SizedBox(height: 40),
Tabs(),
SizedBox(height: 8),
SlidingCardsView(),
],
),
),
ExhibitionBottomSheet(),
],
),
);
}
Expand Down

0 comments on commit aa25f6d

Please sign in to comment.