|
|
@ -30,12 +30,13 @@ const _horizontalPadding = 32.0;
|
|
|
|
const _carouselItemMargin = 8.0;
|
|
|
|
const _carouselItemMargin = 8.0;
|
|
|
|
const _horizontalDesktopPadding = 81.0;
|
|
|
|
const _horizontalDesktopPadding = 81.0;
|
|
|
|
const _carouselHeightMin = 200.0 + 2 * _carouselItemMargin;
|
|
|
|
const _carouselHeightMin = 200.0 + 2 * _carouselItemMargin;
|
|
|
|
|
|
|
|
const _desktopCardsPerPage = 4;
|
|
|
|
|
|
|
|
|
|
|
|
const shrineTitle = 'Shrine';
|
|
|
|
const _shrineTitle = 'Shrine';
|
|
|
|
const rallyTitle = 'Rally';
|
|
|
|
const _rallyTitle = 'Rally';
|
|
|
|
const craneTitle = 'Crane';
|
|
|
|
const _craneTitle = 'Crane';
|
|
|
|
const homeCategoryMaterial = 'MATERIAL';
|
|
|
|
const _homeCategoryMaterial = 'MATERIAL';
|
|
|
|
const homeCategoryCupertino = 'CUPERTINO';
|
|
|
|
const _homeCategoryCupertino = 'CUPERTINO';
|
|
|
|
|
|
|
|
|
|
|
|
class ToggleSplashNotification extends Notification {}
|
|
|
|
class ToggleSplashNotification extends Notification {}
|
|
|
|
|
|
|
|
|
|
|
@ -52,9 +53,9 @@ class HomePage extends StatelessWidget {
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
var carouselHeight = _carouselHeight(.7, context);
|
|
|
|
var carouselHeight = _carouselHeight(.7, context);
|
|
|
|
final isDesktop = isDisplayDesktop(context);
|
|
|
|
final isDesktop = isDisplayDesktop(context);
|
|
|
|
final carouselCards = <_CarouselCard>[
|
|
|
|
final carouselCards = <Widget>[
|
|
|
|
_CarouselCard(
|
|
|
|
_CarouselCard(
|
|
|
|
title: shrineTitle,
|
|
|
|
title: _shrineTitle,
|
|
|
|
subtitle: GalleryLocalizations.of(context).shrineDescription,
|
|
|
|
subtitle: GalleryLocalizations.of(context).shrineDescription,
|
|
|
|
asset: 'assets/studies/shrine_card.png',
|
|
|
|
asset: 'assets/studies/shrine_card.png',
|
|
|
|
assetDark: 'assets/studies/shrine_card_dark.png',
|
|
|
|
assetDark: 'assets/studies/shrine_card_dark.png',
|
|
|
@ -63,7 +64,7 @@ class HomePage extends StatelessWidget {
|
|
|
|
navigatorKey: NavigatorKeys.shrine,
|
|
|
|
navigatorKey: NavigatorKeys.shrine,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_CarouselCard(
|
|
|
|
_CarouselCard(
|
|
|
|
title: rallyTitle,
|
|
|
|
title: _rallyTitle,
|
|
|
|
subtitle: GalleryLocalizations.of(context).rallyDescription,
|
|
|
|
subtitle: GalleryLocalizations.of(context).rallyDescription,
|
|
|
|
textColor: RallyColors.accountColors[0],
|
|
|
|
textColor: RallyColors.accountColors[0],
|
|
|
|
asset: 'assets/studies/rally_card.png',
|
|
|
|
asset: 'assets/studies/rally_card.png',
|
|
|
@ -72,7 +73,7 @@ class HomePage extends StatelessWidget {
|
|
|
|
navigatorKey: NavigatorKeys.rally,
|
|
|
|
navigatorKey: NavigatorKeys.rally,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_CarouselCard(
|
|
|
|
_CarouselCard(
|
|
|
|
title: craneTitle,
|
|
|
|
title: _craneTitle,
|
|
|
|
subtitle: GalleryLocalizations.of(context).craneDescription,
|
|
|
|
subtitle: GalleryLocalizations.of(context).craneDescription,
|
|
|
|
asset: 'assets/studies/crane_card.png',
|
|
|
|
asset: 'assets/studies/crane_card.png',
|
|
|
|
assetDark: 'assets/studies/crane_card_dark.png',
|
|
|
|
assetDark: 'assets/studies/crane_card_dark.png',
|
|
|
@ -101,12 +102,12 @@ class HomePage extends StatelessWidget {
|
|
|
|
if (isDesktop) {
|
|
|
|
if (isDesktop) {
|
|
|
|
final desktopCategoryItems = <_DesktopCategoryItem>[
|
|
|
|
final desktopCategoryItems = <_DesktopCategoryItem>[
|
|
|
|
_DesktopCategoryItem(
|
|
|
|
_DesktopCategoryItem(
|
|
|
|
title: homeCategoryMaterial,
|
|
|
|
title: _homeCategoryMaterial,
|
|
|
|
imageString: 'assets/icons/material/material.png',
|
|
|
|
imageString: 'assets/icons/material/material.png',
|
|
|
|
demos: materialDemos(context),
|
|
|
|
demos: materialDemos(context),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_DesktopCategoryItem(
|
|
|
|
_DesktopCategoryItem(
|
|
|
|
title: homeCategoryCupertino,
|
|
|
|
title: _homeCategoryCupertino,
|
|
|
|
imageString: 'assets/icons/cupertino/cupertino.png',
|
|
|
|
imageString: 'assets/icons/cupertino/cupertino.png',
|
|
|
|
demos: cupertinoDemos(context),
|
|
|
|
demos: cupertinoDemos(context),
|
|
|
|
),
|
|
|
|
),
|
|
|
@ -120,12 +121,15 @@ class HomePage extends StatelessWidget {
|
|
|
|
return Scaffold(
|
|
|
|
return Scaffold(
|
|
|
|
body: ListView(
|
|
|
|
body: ListView(
|
|
|
|
padding: EdgeInsetsDirectional.only(
|
|
|
|
padding: EdgeInsetsDirectional.only(
|
|
|
|
start: _horizontalDesktopPadding,
|
|
|
|
|
|
|
|
top: isDesktop ? firstHeaderDesktopTopPadding : 21,
|
|
|
|
top: isDesktop ? firstHeaderDesktopTopPadding : 21,
|
|
|
|
end: _horizontalDesktopPadding,
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
children: [
|
|
|
|
ExcludeSemantics(child: _GalleryHeader()),
|
|
|
|
Padding(
|
|
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
|
|
horizontal: _horizontalDesktopPadding,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
child: ExcludeSemantics(child: _GalleryHeader()),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
/// TODO: When Focus widget becomes better remove dummy Focus
|
|
|
|
/// TODO: When Focus widget becomes better remove dummy Focus
|
|
|
|
/// variable.
|
|
|
|
/// variable.
|
|
|
@ -143,15 +147,19 @@ class HomePage extends StatelessWidget {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Container(
|
|
|
|
Container(
|
|
|
|
height: carouselHeight,
|
|
|
|
height: carouselHeight,
|
|
|
|
child: Row(
|
|
|
|
child: _DesktopCarousel(children: carouselCards),
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
),
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
Padding(
|
|
|
|
children: spaceBetween(30, carouselCards),
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
|
|
horizontal: _horizontalDesktopPadding,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
|
|
|
|
child: _CategoriesHeader(),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_CategoriesHeader(),
|
|
|
|
|
|
|
|
Container(
|
|
|
|
Container(
|
|
|
|
height: 585,
|
|
|
|
height: 585,
|
|
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
|
|
horizontal: _horizontalDesktopPadding,
|
|
|
|
|
|
|
|
),
|
|
|
|
child: Row(
|
|
|
|
child: Row(
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
@ -160,7 +168,9 @@ class HomePage extends StatelessWidget {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Padding(
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsetsDirectional.only(
|
|
|
|
padding: const EdgeInsetsDirectional.only(
|
|
|
|
|
|
|
|
start: _horizontalDesktopPadding,
|
|
|
|
bottom: 81,
|
|
|
|
bottom: 81,
|
|
|
|
|
|
|
|
end: _horizontalDesktopPadding,
|
|
|
|
top: 109,
|
|
|
|
top: 109,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
child: Row(
|
|
|
|
child: Row(
|
|
|
@ -332,7 +342,7 @@ class _AnimatedHomePageState extends State<_AnimatedHomePage>
|
|
|
|
startDelayFraction: 0.00,
|
|
|
|
startDelayFraction: 0.00,
|
|
|
|
controller: _animationController,
|
|
|
|
controller: _animationController,
|
|
|
|
child: CategoryListItem(
|
|
|
|
child: CategoryListItem(
|
|
|
|
title: homeCategoryMaterial,
|
|
|
|
title: _homeCategoryMaterial,
|
|
|
|
imageString: 'assets/icons/material/material.png',
|
|
|
|
imageString: 'assets/icons/material/material.png',
|
|
|
|
demos: materialDemos(context),
|
|
|
|
demos: materialDemos(context),
|
|
|
|
),
|
|
|
|
),
|
|
|
@ -341,7 +351,7 @@ class _AnimatedHomePageState extends State<_AnimatedHomePage>
|
|
|
|
startDelayFraction: 0.05,
|
|
|
|
startDelayFraction: 0.05,
|
|
|
|
controller: _animationController,
|
|
|
|
controller: _animationController,
|
|
|
|
child: CategoryListItem(
|
|
|
|
child: CategoryListItem(
|
|
|
|
title: homeCategoryCupertino,
|
|
|
|
title: _homeCategoryCupertino,
|
|
|
|
imageString: 'assets/icons/cupertino/cupertino.png',
|
|
|
|
imageString: 'assets/icons/cupertino/cupertino.png',
|
|
|
|
demos: cupertinoDemos(context),
|
|
|
|
demos: cupertinoDemos(context),
|
|
|
|
),
|
|
|
|
),
|
|
|
@ -715,6 +725,204 @@ class _CarouselState extends State<_Carousel>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// This creates a horizontally scrolling [ListView] of items.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// This class uses a [ListView] with a custom [ScrollPhysics] to enable
|
|
|
|
|
|
|
|
/// snapping behavior. A [PageView] was considered but does not allow for
|
|
|
|
|
|
|
|
/// multiple pages visible without centering the first page.
|
|
|
|
|
|
|
|
class _DesktopCarousel extends StatefulWidget {
|
|
|
|
|
|
|
|
const _DesktopCarousel({Key key, this.children}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final List<Widget> children;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
_DesktopCarouselState createState() => _DesktopCarouselState();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _DesktopCarouselState extends State<_DesktopCarousel> {
|
|
|
|
|
|
|
|
static const cardPadding = 15.0;
|
|
|
|
|
|
|
|
ScrollController _controller;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
void initState() {
|
|
|
|
|
|
|
|
super.initState();
|
|
|
|
|
|
|
|
_controller = ScrollController();
|
|
|
|
|
|
|
|
_controller.addListener(() {
|
|
|
|
|
|
|
|
setState(() {});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
dispose() {
|
|
|
|
|
|
|
|
_controller.dispose();
|
|
|
|
|
|
|
|
super.dispose();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Widget _builder(int index) {
|
|
|
|
|
|
|
|
return Padding(
|
|
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
|
|
vertical: 8,
|
|
|
|
|
|
|
|
horizontal: cardPadding,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
child: widget.children[index],
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
|
|
var showPreviousButton = false;
|
|
|
|
|
|
|
|
var showNextButton = true;
|
|
|
|
|
|
|
|
// Only check this after the _controller has been attached to the ListView.
|
|
|
|
|
|
|
|
if (_controller.hasClients) {
|
|
|
|
|
|
|
|
showPreviousButton = _controller.offset > 0;
|
|
|
|
|
|
|
|
showNextButton =
|
|
|
|
|
|
|
|
_controller.offset < _controller.position.maxScrollExtent;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
final totalWidth = MediaQuery.of(context).size.width -
|
|
|
|
|
|
|
|
(_horizontalDesktopPadding - cardPadding) * 2;
|
|
|
|
|
|
|
|
final itemWidth = totalWidth / _desktopCardsPerPage;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Stack(
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
Padding(
|
|
|
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
|
|
|
horizontal: _horizontalDesktopPadding - cardPadding,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
child: ListView.builder(
|
|
|
|
|
|
|
|
scrollDirection: Axis.horizontal,
|
|
|
|
|
|
|
|
physics: _SnappingScrollPhysics(),
|
|
|
|
|
|
|
|
controller: _controller,
|
|
|
|
|
|
|
|
itemExtent: itemWidth,
|
|
|
|
|
|
|
|
itemCount: widget.children.length,
|
|
|
|
|
|
|
|
itemBuilder: (context, index) => _builder(index),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
if (showPreviousButton)
|
|
|
|
|
|
|
|
_DesktopPageButton(
|
|
|
|
|
|
|
|
onTap: () {
|
|
|
|
|
|
|
|
_controller.animateTo(
|
|
|
|
|
|
|
|
_controller.offset - itemWidth,
|
|
|
|
|
|
|
|
duration: Duration(milliseconds: 200),
|
|
|
|
|
|
|
|
curve: Curves.easeInOut,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
if (showNextButton)
|
|
|
|
|
|
|
|
_DesktopPageButton(
|
|
|
|
|
|
|
|
isEnd: true,
|
|
|
|
|
|
|
|
onTap: () {
|
|
|
|
|
|
|
|
_controller.animateTo(
|
|
|
|
|
|
|
|
_controller.offset + itemWidth,
|
|
|
|
|
|
|
|
duration: Duration(milliseconds: 200),
|
|
|
|
|
|
|
|
curve: Curves.easeInOut,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Scrolling physics that snaps to the new item in the [_DesktopCarousel].
|
|
|
|
|
|
|
|
class _SnappingScrollPhysics extends ScrollPhysics {
|
|
|
|
|
|
|
|
const _SnappingScrollPhysics({ScrollPhysics parent}) : super(parent: parent);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
_SnappingScrollPhysics applyTo(ScrollPhysics ancestor) {
|
|
|
|
|
|
|
|
return _SnappingScrollPhysics(parent: buildParent(ancestor));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
double _getTargetPixels(
|
|
|
|
|
|
|
|
ScrollMetrics position,
|
|
|
|
|
|
|
|
Tolerance tolerance,
|
|
|
|
|
|
|
|
double velocity,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
final itemWidth = position.viewportDimension / _desktopCardsPerPage;
|
|
|
|
|
|
|
|
double item = position.pixels / itemWidth;
|
|
|
|
|
|
|
|
if (velocity < -tolerance.velocity) {
|
|
|
|
|
|
|
|
item -= 0.5;
|
|
|
|
|
|
|
|
} else if (velocity > tolerance.velocity) {
|
|
|
|
|
|
|
|
item += 0.5;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return math.min(
|
|
|
|
|
|
|
|
item.roundToDouble() * itemWidth,
|
|
|
|
|
|
|
|
position.maxScrollExtent,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
Simulation createBallisticSimulation(
|
|
|
|
|
|
|
|
ScrollMetrics position,
|
|
|
|
|
|
|
|
double velocity,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
|
|
|
|
|
|
|
|
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
|
|
|
|
|
|
|
|
return super.createBallisticSimulation(position, velocity);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
final Tolerance tolerance = this.tolerance;
|
|
|
|
|
|
|
|
final double target = _getTargetPixels(position, tolerance, velocity);
|
|
|
|
|
|
|
|
if (target != position.pixels) {
|
|
|
|
|
|
|
|
return ScrollSpringSimulation(
|
|
|
|
|
|
|
|
spring,
|
|
|
|
|
|
|
|
position.pixels,
|
|
|
|
|
|
|
|
target,
|
|
|
|
|
|
|
|
velocity,
|
|
|
|
|
|
|
|
tolerance: tolerance,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
bool get allowImplicitScrolling => false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _DesktopPageButton extends StatelessWidget {
|
|
|
|
|
|
|
|
const _DesktopPageButton({
|
|
|
|
|
|
|
|
Key key,
|
|
|
|
|
|
|
|
this.isEnd = false,
|
|
|
|
|
|
|
|
this.onTap,
|
|
|
|
|
|
|
|
}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final bool isEnd;
|
|
|
|
|
|
|
|
final GestureTapCallback onTap;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
|
|
final buttonSize = 58.0;
|
|
|
|
|
|
|
|
final padding = _horizontalDesktopPadding - buttonSize / 2;
|
|
|
|
|
|
|
|
return Align(
|
|
|
|
|
|
|
|
alignment: isEnd
|
|
|
|
|
|
|
|
? AlignmentDirectional.centerEnd
|
|
|
|
|
|
|
|
: AlignmentDirectional.centerStart,
|
|
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
|
|
width: buttonSize,
|
|
|
|
|
|
|
|
height: buttonSize,
|
|
|
|
|
|
|
|
margin: EdgeInsetsDirectional.only(
|
|
|
|
|
|
|
|
start: isEnd ? 0 : padding,
|
|
|
|
|
|
|
|
end: isEnd ? padding : 0,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
child: Tooltip(
|
|
|
|
|
|
|
|
message: isEnd
|
|
|
|
|
|
|
|
? MaterialLocalizations.of(context).nextPageTooltip
|
|
|
|
|
|
|
|
: MaterialLocalizations.of(context).previousPageTooltip,
|
|
|
|
|
|
|
|
child: Material(
|
|
|
|
|
|
|
|
color: Colors.black.withOpacity(0.5),
|
|
|
|
|
|
|
|
shape: CircleBorder(),
|
|
|
|
|
|
|
|
clipBehavior: Clip.antiAlias,
|
|
|
|
|
|
|
|
child: InkWell(
|
|
|
|
|
|
|
|
onTap: onTap,
|
|
|
|
|
|
|
|
child: Icon(
|
|
|
|
|
|
|
|
isEnd ? Icons.arrow_forward_ios : Icons.arrow_back_ios,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _CarouselCard extends StatelessWidget {
|
|
|
|
class _CarouselCard extends StatelessWidget {
|
|
|
|
const _CarouselCard({
|
|
|
|
const _CarouselCard({
|
|
|
|
Key key,
|
|
|
|
Key key,
|
|
|
|