From 8ab5f80d004ead52a40a69f33482f371fe0f8c8b Mon Sep 17 00:00:00 2001 From: Jagandeep Brar Date: Wed, 9 Feb 2022 00:07:40 -0500 Subject: [PATCH] feat(ui): support skeleton-state loading on LunaBlock --- lib/core/ui.dart | 4 +- lib/core/ui/block/block.dart | 93 +++++++++++++++++++++++++++++++++++- lib/core/ui/shimmer.dart | 21 ++++++++ pubspec.lock | 7 +++ pubspec.yaml | 1 + 5 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 lib/core/ui/shimmer.dart diff --git a/lib/core/ui.dart b/lib/core/ui.dart index 7b565d33..96fb126a 100644 --- a/lib/core/ui.dart +++ b/lib/core/ui.dart @@ -30,6 +30,7 @@ export 'ui/popup_menu_button.dart'; export 'ui/refresh_indicator.dart'; export 'ui/scaffold.dart'; export 'ui/shape.dart'; +export 'ui/shimmer.dart'; export 'ui/snackbar.dart'; export 'ui/switch.dart'; export 'ui/table.dart'; @@ -70,8 +71,9 @@ class LunaUI { // <--> Animations static const int ANIMATION_SPEED = 250; - static const int ANIMATION_SPEED_IMAGES = 125; + static const int ANIMATION_SPEED_IMAGES = ANIMATION_SPEED ~/ 2; static const int ANIMATION_SPEED_SCROLLING = ANIMATION_SPEED * 2; + static const int ANIMATION_SPEED_SHIMMER = ANIMATION_SPEED * 4; // <--> Other static const double BORDER_RADIUS = 10.0; diff --git a/lib/core/ui/block/block.dart b/lib/core/ui/block/block.dart index 6cf37bc8..0fbdb149 100644 --- a/lib/core/ui/block/block.dart +++ b/lib/core/ui/block/block.dart @@ -5,6 +5,11 @@ class LunaBlock extends StatelessWidget { static const TITLE_HEIGHT = LunaUI.FONT_SIZE_H2 + 4.0; static const SUBTITLE_HEIGHT = LunaUI.FONT_SIZE_H3 + 4.0; + // If true, will load a skeleton-form version of the block. + final bool skeletonEnabled; + final bool skeletonPoster; + final int skeletonSubtitles; + final bool? disabled; final String? title; final Color titleColor; @@ -37,8 +42,11 @@ class LunaBlock extends StatelessWidget { const LunaBlock({ Key? key, + this.skeletonEnabled = false, + this.skeletonPoster = true, + this.skeletonSubtitles = 2, this.disabled = false, - required this.title, + this.title, this.titleColor = Colors.white, this.body, this.bodyLeadingIcons, @@ -92,11 +100,36 @@ class LunaBlock extends StatelessWidget { ); } + double _calculateSkeletonHeight() { + return calculateItemHeight( + skeletonSubtitles, + hasBottom: bottom != null, + bottomHeight: bottomHeight, + ); + } + @override Widget build(BuildContext context) { + if (skeletonEnabled) return _buildSkeletonBlock(context); return _buildBlock(context); } + Widget _buildSkeletonBlock(BuildContext context) { + double _height = _calculateSkeletonHeight(); + return LunaCard( + context: context, + child: LunaShimmer( + child: Row( + children: [ + if (skeletonPoster) _poster(context, _height), + _tile(context, _height), + ], + ), + ), + height: _height, + ); + } + Widget _buildBlock(BuildContext context) { double _height = _calculateHeight(); return LunaCard( @@ -160,11 +193,29 @@ class LunaBlock extends StatelessWidget { } Widget _poster(BuildContext context, double height) { + double _dimension = height - LunaUI.DEFAULT_MARGIN_SIZE; + + if (skeletonEnabled) { + return Padding( + padding: const EdgeInsets.only(left: LunaUI.MARGIN_SIZE_HALF), + child: Container( + height: _dimension, + width: _dimension / (posterIsSquare ? 1.0 : 1.5), + decoration: BoxDecoration( + color: Theme.of(context).canvasColor, + borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS), + border: LunaUI.shouldUseBorder + ? Border.all(color: LunaColours.white10) + : null, + ), + ), + ); + } + if (posterUrl == null && posterPlaceholderIcon == null) { return const SizedBox(width: 0.0, height: 0.0); } - double _dimension = height - LunaUI.DEFAULT_MARGIN_SIZE; return Padding( padding: const EdgeInsets.only(left: LunaUI.MARGIN_SIZE_HALF), child: LunaNetworkImage( @@ -179,6 +230,44 @@ class LunaBlock extends StatelessWidget { } Widget _tile(BuildContext context, double height) { + if (skeletonEnabled) { + return Expanded( + child: Padding( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: LunaBlock.TITLE_HEIGHT - 4.0, + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).canvasColor, + borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS), + border: LunaUI.shouldUseBorder + ? Border.all(color: LunaColours.white10) + : null, + ), + ), + ...List.generate(skeletonSubtitles, (_) { + return Container( + height: LunaBlock.SUBTITLE_HEIGHT - 6.0, + width: MediaQuery.of(context).size.width / 2.5, + decoration: BoxDecoration( + color: Theme.of(context).canvasColor, + borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS), + border: LunaUI.shouldUseBorder + ? Border.all(color: LunaColours.white10) + : null, + ), + ); + }), + ], + ), + padding: LunaUI.MARGIN_DEFAULT, + ), + ); + } + return Expanded( // ignore: deprecated_member_use_from_same_package child: LunaListTile( diff --git a/lib/core/ui/shimmer.dart b/lib/core/ui/shimmer.dart new file mode 100644 index 00000000..a992e920 --- /dev/null +++ b/lib/core/ui/shimmer.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:shimmer/shimmer.dart'; + +class LunaShimmer extends StatelessWidget { + final Widget child; + + const LunaShimmer({ + Key? key, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + child: child, + baseColor: Theme.of(context).primaryColor, + highlightColor: LunaColours.accent, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index fdb7c70a..bfee39ce 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1143,6 +1143,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" simple_gesture_detector: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f26c8218..609c41bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: quick_actions: ^0.6.0+9 retrofit: ^3.0.1 share_plus: ^3.0.4 + shimmer: ^2.0.0 stack_trace: ^1.10.0 stash_memory: ^4.0.1 supercharged: ^2.1.1