From 0d7bf98a5b2797c4771543dfb2f2a4357dda292d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 17 Nov 2020 11:38:34 +0100 Subject: [PATCH] add shimmer effect --- example/lib/main.dart | 2 +- example/pubspec.yaml | 2 +- lib/src/channel_list_view.dart | 317 +++++++++++++++++++++------------ pubspec.yaml | 1 + 4 files changed, 203 insertions(+), 119 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 2e3f43e2..c09030f3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -21,7 +21,7 @@ void main() async { logLevel: Level.INFO, showLocalNotification: (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, - persistenceEnabled: true, + persistenceEnabled: false, ); if (userId != null) { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 8fc2dadb..fca549d2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.59+61 +version: 1.0.60+62 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index a10ad4bc..8b66db34 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:shimmer/shimmer.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/channels_bloc.dart'; import 'package:stream_chat_flutter/src/utils.dart'; @@ -163,127 +164,209 @@ class _ChannelListViewState extends State return StreamBuilder>( stream: channelsBlocState.channelsStream, builder: (context, snapshot) { + var child; if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); + child = _buildErrorWidget( + snapshot, + context, + channelsBlocState, + ); + } else if (!snapshot.hasData) { + child = _buildLoadingWidget(); + } else { + final channels = snapshot.data; + + if (channels.isEmpty && widget.emptyBuilder != null) { + child = widget.emptyBuilder(context); } - if (widget.errorBuilder != null) { - return widget.errorBuilder(snapshot.error); + if (channels.isEmpty && widget.emptyBuilder == null) { + child = LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Text('You have no channels currently'), + ), + ), + ); + }, + ); } - var message = snapshot.error.toString(); - if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError; - if (dioError.type == DioErrorType.RESPONSE) { - message = dioError.message; - } else { - message = 'Check your connection and retry'; - } + if (channels.isNotEmpty) { + child = ListView.custom( + physics: AlwaysScrollableScrollPhysics(), + controller: _scrollController, + childrenDelegate: SliverChildBuilderDelegate( + (context, i) { + return _itemBuilder(context, i, channels); + }, + childCount: (channels.length * 2) + 1, + findChildIndexCallback: (key) { + final ValueKey valueKey = key; + final index = channels.indexWhere( + (channel) => 'CHANNEL-${channel.id}' == valueKey.value); + return index != -1 ? (index * 2) : null; + }, + ), + ); } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only( - right: 2.0, - ), - child: Icon(Icons.error_outline), - ), - ), - TextSpan(text: 'Error loading channels'), - ], - ), - style: Theme.of(context).textTheme.headline6, - ), - Padding( - padding: const EdgeInsets.only( - top: 16.0, - ), - child: Text(message), - ), - FlatButton( - onPressed: () { - channelsBlocState.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - ); - }, - child: Text('Retry'), - ), - ], - ), - ); } - if (!snapshot.hasData) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: CircularProgressIndicator(), - ), - ), - ); - }, - ); - } - - final channels = snapshot.data; - - if (channels.isEmpty && widget.emptyBuilder != null) { - return widget.emptyBuilder(context); - } - - if (channels.isEmpty && widget.emptyBuilder == null) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Text('You have no channels currently'), - ), - ), - ); - }, - ); - } - - return ListView.custom( - physics: AlwaysScrollableScrollPhysics(), - controller: _scrollController, - childrenDelegate: SliverChildBuilderDelegate( - (context, i) { - return _itemBuilder(context, i, channels); - }, - childCount: (channels.length * 2) + 1, - findChildIndexCallback: (key) { - final ValueKey valueKey = key; - final index = channels.indexWhere( - (channel) => 'CHANNEL-${channel.id}' == valueKey.value); - return index != -1 ? (index * 2) : null; - }, - ), + return AnimatedSwitcher( + child: child, + duration: Duration(milliseconds: 500), ); }); } + Widget _buildLoadingWidget() { + return ListView( + physics: AlwaysScrollableScrollPhysics(), + children: List.generate( + 25, + (i) { + if (i % 2 != 0) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder(context, i); + } + return _separatorBuilder(context, i); + } + return _buildLoadingItem(); + }, + ), + ); + } + + Shimmer _buildLoadingItem() { + return Shimmer.fromColors( + baseColor: Color(0xffE5E5E5), + highlightColor: Color(0xffffffff), + child: ListTile( + leading: Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + constraints: BoxConstraints.tightFor( + height: 40, + width: 40, + ), + ), + title: Align( + alignment: Alignment.centerLeft, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(11), + ), + constraints: BoxConstraints.tightFor( + height: 16, + width: 82, + ), + ), + ), + subtitle: Row( + children: [ + Align( + alignment: Alignment.centerLeft, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(11), + ), + constraints: BoxConstraints.tightFor( + height: 16, + width: 238, + ), + ), + ), + Container( + margin: const EdgeInsets.only(left: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(11), + ), + constraints: BoxConstraints.tightFor( + height: 16, + width: 42, + ), + ), + ], + ), + ), + ); + } + + Widget _buildErrorWidget( + AsyncSnapshot> snapshot, + BuildContext context, + ChannelsBlocState channelsBlocState, + ) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + + if (widget.errorBuilder != null) { + return widget.errorBuilder(snapshot.error); + } + + var message = snapshot.error.toString(); + if (snapshot.error is DioError) { + final dioError = snapshot.error as DioError; + if (dioError.type == DioErrorType.RESPONSE) { + message = dioError.message; + } else { + message = 'Check your connection and retry'; + } + } + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only( + right: 2.0, + ), + child: Icon(Icons.error_outline), + ), + ), + TextSpan(text: 'Error loading channels'), + ], + ), + style: Theme.of(context).textTheme.headline6, + ), + Padding( + padding: const EdgeInsets.only( + top: 16.0, + ), + child: Text(message), + ), + FlatButton( + onPressed: () { + channelsBlocState.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); + }, + child: Text('Retry'), + ), + ], + ), + ); + } + Widget _itemBuilder(context, int i, List channels) { if (i % 2 != 0) { if (widget.separatorBuilder != null) { @@ -441,7 +524,9 @@ class _ChannelListViewState extends State } Widget _buildQueryProgressIndicator( - context, ChannelsBlocState channelsProvider) { + context, + ChannelsBlocState channelsProvider, + ) { return StreamBuilder( stream: channelsProvider.queryChannelsLoading, initialData: false, @@ -457,13 +542,11 @@ class _ChannelListViewState extends State ), ); } - return Container( - height: 100, - padding: EdgeInsets.all(32), - child: Center( - child: snapshot.data ? CircularProgressIndicator() : Container(), - ), - ); + return snapshot.data + ? _buildLoadingItem() + : Container( + height: 70, + ); }); } diff --git a/pubspec.yaml b/pubspec.yaml index 9c1b0a29..804c9728 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: flutter_svg: ^0.19.1 flutter_portal: ^0.3.0 cached_network_image: ^2.2.0+1 + shimmer: ^1.1.2 flutter_markdown: ^0.5.0 url_launcher: ^5.4.11 emojis: ^0.9.3