From 6e613a32792e48550022e1457cc2f886fa03dd00 Mon Sep 17 00:00:00 2001 From: Augsorn Chanklad Date: Sun, 24 Mar 2024 19:13:31 +0700 Subject: [PATCH] Add Shimmer as Placeholder --- .../common_widgets/async_value_widget.dart | 10 ++- .../common_widgets/custom_network_image.dart | 6 +- .../album_card/album_card_placeholder.dart | 35 +++++++++- .../top_songs_daily_list_view.dart | 3 + .../top_songs_monthly_list_view.dart | 2 + .../top_songs_overall_list_view.dart | 2 + .../top_songs_weekly_list_view.dart | 2 + .../song_card/song_card_placeholder.dart | 36 ++++++++-- .../song_tile/song_tile_placeholder.dart | 66 +++++++++++++++++++ .../song_placeholder_list_view.dart | 10 +++ pubspec.lock | 8 +++ pubspec.yaml | 1 + 12 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 lib/src/features/songs/presentation/song_tile/song_tile_placeholder.dart diff --git a/lib/src/common_widgets/async_value_widget.dart b/lib/src/common_widgets/async_value_widget.dart index 490c232f..a2f127c6 100644 --- a/lib/src/common_widgets/async_value_widget.dart +++ b/lib/src/common_widgets/async_value_widget.dart @@ -3,9 +3,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:vocadb_app/src/common_widgets/error_message_widget.dart'; class AsyncValueWidget extends StatelessWidget { - const AsyncValueWidget({super.key, required this.value, required this.data}); + const AsyncValueWidget({ + super.key, + required this.value, + required this.data, + this.loadingWidget = const Center(child: CircularProgressIndicator()), + }); final AsyncValue value; final Widget Function(T) data; + final Widget loadingWidget; @override Widget build(BuildContext context) { @@ -15,7 +21,7 @@ class AsyncValueWidget extends StatelessWidget { return Center(child: ErrorMessageWidget(e.toString())); }, loading: () { - return const Center(child: CircularProgressIndicator()); + return loadingWidget; }, ); } diff --git a/lib/src/common_widgets/custom_network_image.dart b/lib/src/common_widgets/custom_network_image.dart index 84ba9a24..2eba6520 100644 --- a/lib/src/common_widgets/custom_network_image.dart +++ b/lib/src/common_widgets/custom_network_image.dart @@ -7,7 +7,7 @@ class CustomNetworkImage extends StatelessWidget { final String imageUrl; /// A placeholder widget to display when image loading. - final Widget placeholder; + // final Widget placeholder; /// A widget to display error when fail to load image. final Widget errorWidget; @@ -21,7 +21,7 @@ class CustomNetworkImage extends StatelessWidget { const CustomNetworkImage( this.imageUrl, { super.key, - this.placeholder = const Center(child: CircularProgressIndicator()), + // this.placeholder, this.errorWidget = const Icon(Icons.error), this.fit = BoxFit.cover, this.width = 140, @@ -35,7 +35,7 @@ class CustomNetworkImage extends StatelessWidget { imageUrl: imageUrl, width: width, height: height, - placeholder: (context, url) => placeholder, + placeholder: (context, url) => Container(color: Colors.grey), errorWidget: (context, url, error) => errorWidget, ); } diff --git a/lib/src/features/albums/presentation/album_card/album_card_placeholder.dart b/lib/src/features/albums/presentation/album_card/album_card_placeholder.dart index 1f07e9b7..d5b68cda 100644 --- a/lib/src/features/albums/presentation/album_card/album_card_placeholder.dart +++ b/lib/src/features/albums/presentation/album_card/album_card_placeholder.dart @@ -1,14 +1,43 @@ import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; /// A widget display placeholder for album class AlbumCardPlaceholder extends StatelessWidget { const AlbumCardPlaceholder({super.key}); - @override Widget build(BuildContext context) { return Container( - margin: const EdgeInsets.symmetric(horizontal: 8.0), - child: const Text('Placeholder'), + margin: EdgeInsets.symmetric(horizontal: 8.0), + child: Shimmer.fromColors( + baseColor: Colors.grey.shade300, + highlightColor: Colors.grey.shade100, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 130, + height: 120, + color: Colors.white, + ), + const SizedBox( + height: 6, + ), + Container( + width: 130, + height: 8, + color: Colors.white, + ), + const SizedBox( + height: 6, + ), + Container( + width: 80, + height: 8, + color: Colors.white, + ), + ], + ), + ), ); } } diff --git a/lib/src/features/home/presentation/ranking_screen/top_songs_daily_list_view.dart b/lib/src/features/home/presentation/ranking_screen/top_songs_daily_list_view.dart index e819f0e7..60f088a8 100644 --- a/lib/src/features/home/presentation/ranking_screen/top_songs_daily_list_view.dart +++ b/lib/src/features/home/presentation/ranking_screen/top_songs_daily_list_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:vocadb_app/src/common_widgets/async_value_widget.dart'; import 'package:vocadb_app/src/features/songs/data/song_repository.dart'; import 'package:vocadb_app/src/features/songs/domain/song.dart'; +import 'package:vocadb_app/src/features/songs/presentation/songs_list/song_placeholder_list_view.dart'; import 'package:vocadb_app/src/features/songs/presentation/songs_list/songs_list_view.dart'; class TopSongsDailyListView extends ConsumerWidget { @@ -13,8 +14,10 @@ class TopSongsDailyListView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final value = ref.watch(songsDailyProvider); + // return SongPlaceholderListView(scrollDirection: Axis.vertical,); return AsyncValueWidget( value: value, + loadingWidget: const SongPlaceholderListView(scrollDirection: Axis.vertical,), data: (data) => SongListView( songs: data, displayOrderNumber: true, diff --git a/lib/src/features/home/presentation/ranking_screen/top_songs_monthly_list_view.dart b/lib/src/features/home/presentation/ranking_screen/top_songs_monthly_list_view.dart index 293e30e7..e1077431 100644 --- a/lib/src/features/home/presentation/ranking_screen/top_songs_monthly_list_view.dart +++ b/lib/src/features/home/presentation/ranking_screen/top_songs_monthly_list_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:vocadb_app/src/common_widgets/async_value_widget.dart'; import 'package:vocadb_app/src/features/songs/data/song_repository.dart'; import 'package:vocadb_app/src/features/songs/domain/song.dart'; +import 'package:vocadb_app/src/features/songs/presentation/songs_list/song_placeholder_list_view.dart'; import 'package:vocadb_app/src/features/songs/presentation/songs_list/songs_list_view.dart'; class TopSongsMonthlyListView extends ConsumerWidget { @@ -14,6 +15,7 @@ class TopSongsMonthlyListView extends ConsumerWidget { final value = ref.watch(songsMonthlyProvider); return AsyncValueWidget( value: value, + loadingWidget: const SongPlaceholderListView(scrollDirection: Axis.vertical,), data: (data) => SongListView( songs: data, displayOrderNumber: true, diff --git a/lib/src/features/home/presentation/ranking_screen/top_songs_overall_list_view.dart b/lib/src/features/home/presentation/ranking_screen/top_songs_overall_list_view.dart index d3ba1f8a..5cfdfd27 100644 --- a/lib/src/features/home/presentation/ranking_screen/top_songs_overall_list_view.dart +++ b/lib/src/features/home/presentation/ranking_screen/top_songs_overall_list_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:vocadb_app/src/common_widgets/async_value_widget.dart'; import 'package:vocadb_app/src/features/songs/data/song_repository.dart'; import 'package:vocadb_app/src/features/songs/domain/song.dart'; +import 'package:vocadb_app/src/features/songs/presentation/songs_list/song_placeholder_list_view.dart'; import 'package:vocadb_app/src/features/songs/presentation/songs_list/songs_list_view.dart'; class TopSongsOverallListView extends ConsumerWidget { @@ -15,6 +16,7 @@ class TopSongsOverallListView extends ConsumerWidget { final value = ref.watch(songsOverallProvider); return AsyncValueWidget( value: value, + loadingWidget: const SongPlaceholderListView(scrollDirection: Axis.vertical,), data: (data) => SongListView( songs: data, displayOrderNumber: true, diff --git a/lib/src/features/home/presentation/ranking_screen/top_songs_weekly_list_view.dart b/lib/src/features/home/presentation/ranking_screen/top_songs_weekly_list_view.dart index 124281d2..795aa2bc 100644 --- a/lib/src/features/home/presentation/ranking_screen/top_songs_weekly_list_view.dart +++ b/lib/src/features/home/presentation/ranking_screen/top_songs_weekly_list_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:vocadb_app/src/common_widgets/async_value_widget.dart'; import 'package:vocadb_app/src/features/songs/data/song_repository.dart'; import 'package:vocadb_app/src/features/songs/domain/song.dart'; +import 'package:vocadb_app/src/features/songs/presentation/songs_list/song_placeholder_list_view.dart'; import 'package:vocadb_app/src/features/songs/presentation/songs_list/songs_list_view.dart'; class TopSongsWeeklyListView extends ConsumerWidget { @@ -15,6 +16,7 @@ class TopSongsWeeklyListView extends ConsumerWidget { final value = ref.watch(songsWeeklyProvider); return AsyncValueWidget( value: value, + loadingWidget: const SongPlaceholderListView(scrollDirection: Axis.vertical,), data: (data) => SongListView( songs: data, displayOrderNumber: true, diff --git a/lib/src/features/songs/presentation/song_card/song_card_placeholder.dart b/lib/src/features/songs/presentation/song_card/song_card_placeholder.dart index a90ab3b0..5ceb95ac 100644 --- a/lib/src/features/songs/presentation/song_card/song_card_placeholder.dart +++ b/lib/src/features/songs/presentation/song_card/song_card_placeholder.dart @@ -1,15 +1,43 @@ import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; /// A widget display placeholder for song class SongCardPlaceholder extends StatelessWidget { const SongCardPlaceholder({super.key}); - @override +@override Widget build(BuildContext context) { return Container( - margin: const EdgeInsets.symmetric(horizontal: 8.0), - child: const Center( - child: CircularProgressIndicator(), + margin: EdgeInsets.symmetric(horizontal: 8.0), + child: Shimmer.fromColors( + baseColor: Colors.grey.shade300, + highlightColor: Colors.grey.shade100, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 130, + height: 80, + color: Colors.white, + ), + const SizedBox( + height: 6, + ), + Container( + width: 130, + height: 8, + color: Colors.white, + ), + const SizedBox( + height: 6, + ), + Container( + width: 80, + height: 8, + color: Colors.white, + ), + ], + ), ), ); } diff --git a/lib/src/features/songs/presentation/song_tile/song_tile_placeholder.dart b/lib/src/features/songs/presentation/song_tile/song_tile_placeholder.dart new file mode 100644 index 00000000..5d8acc79 --- /dev/null +++ b/lib/src/features/songs/presentation/song_tile/song_tile_placeholder.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +class SongTilePlaceholder extends StatelessWidget { + const SongTilePlaceholder({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8.0), + child: Shimmer.fromColors( + baseColor: Colors.grey.shade300, + highlightColor: Colors.grey.shade100, + child: SizedBox( + height: 100, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: 120, + height: 80, + color: Colors.white, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Padding( + padding: const EdgeInsets.fromLTRB(0.0, 2.0, 0.0, 2.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 100, + height: 16, + color: Colors.white, + ), + const SizedBox( + height: 8, + ), + Container( + width: 200, + height: 16, + color: Colors.white, + ), + const SizedBox( + height: 8, + ), + Container( + width: 40, + height: 16, + color: Colors.white, + ), + ], + ), + ), + ),) + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/features/songs/presentation/songs_list/song_placeholder_list_view.dart b/lib/src/features/songs/presentation/songs_list/song_placeholder_list_view.dart index 5d5b6b1f..a2ba6db8 100644 --- a/lib/src/features/songs/presentation/songs_list/song_placeholder_list_view.dart +++ b/lib/src/features/songs/presentation/songs_list/song_placeholder_list_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:vocadb_app/src/features/songs/presentation/song_card/song_card_placeholder.dart'; +import 'package:vocadb_app/src/features/songs/presentation/song_tile/song_tile_placeholder.dart'; class SongPlaceholderListView extends StatelessWidget { const SongPlaceholderListView( @@ -13,6 +14,15 @@ class SongPlaceholderListView extends StatelessWidget { @override Widget build(BuildContext context) { + + if(scrollDirection == Axis.vertical) { + return ListView.builder( + itemCount: size, + scrollDirection: Axis.vertical, + itemBuilder: (context, index) => const SongTilePlaceholder(), + ); + } + return SizedBox( height: 180, child: ListView.builder( diff --git a/pubspec.lock b/pubspec.lock index e61d8bf7..d0454398 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -789,6 +789,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index b394005b..97b7b54c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,7 @@ dependencies: hive_flutter: 1.1.0 youtube_player_flutter: 8.1.2 fluttertoast: 8.1.1 + shimmer: ^3.0.0 dev_dependencies: flutter_test: