implemented new loadable state concept
This commit is contained in:
		| @@ -0,0 +1,26 @@ | ||||
| import 'dart:async'; | ||||
|  | ||||
| import 'package:bloc/bloc.dart'; | ||||
| import 'package:connectivity_plus/connectivity_plus.dart'; | ||||
|  | ||||
| import 'loadable_state_state.dart'; | ||||
|  | ||||
| class LoadableStateBloc extends Bloc<void, LoadableStateState> { | ||||
|   late StreamSubscription<List<ConnectivityResult>> _updateStream; | ||||
|  | ||||
|   LoadableStateBloc() : super(const LoadableStateState(connections: null)) { | ||||
|     emitState(List<ConnectivityResult> v) => emit(state.copyWith(connections: v)); | ||||
|  | ||||
|     Connectivity().checkConnectivity().then(emitState); | ||||
|     _updateStream = Connectivity().onConnectivityChanged.listen(emitState); | ||||
|   } | ||||
|  | ||||
|   bool connectivityStatusKnown() => state.connections != null; | ||||
|   bool isConnected({bool? def}) => !(state.connections?.contains(ConnectivityResult.none) ?? def!); | ||||
|  | ||||
|   @override | ||||
|   Future<void> close() { | ||||
|     _updateStream.cancel(); | ||||
|     return super.close(); | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| import 'package:connectivity_plus/connectivity_plus.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
|  | ||||
| part 'loadable_state_state.freezed.dart'; | ||||
|  | ||||
| @freezed | ||||
| class LoadableStateState with _$LoadableStateState { | ||||
|   const factory LoadableStateState({ | ||||
|     required List<ConnectivityResult>? connections, | ||||
|   }) = _LoadableStateState; | ||||
| } | ||||
| @@ -0,0 +1,147 @@ | ||||
| // coverage:ignore-file | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| // ignore_for_file: type=lint | ||||
| // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||
|  | ||||
| part of 'loadable_state_state.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // FreezedGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| T _$identity<T>(T value) => value; | ||||
|  | ||||
| final _privateConstructorUsedError = UnsupportedError( | ||||
|     'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$LoadableStateState { | ||||
|   List<ConnectivityResult>? get connections => | ||||
|       throw _privateConstructorUsedError; | ||||
|  | ||||
|   @JsonKey(ignore: true) | ||||
|   $LoadableStateStateCopyWith<LoadableStateState> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract class $LoadableStateStateCopyWith<$Res> { | ||||
|   factory $LoadableStateStateCopyWith( | ||||
|           LoadableStateState value, $Res Function(LoadableStateState) then) = | ||||
|       _$LoadableStateStateCopyWithImpl<$Res, LoadableStateState>; | ||||
|   @useResult | ||||
|   $Res call({List<ConnectivityResult>? connections}); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| class _$LoadableStateStateCopyWithImpl<$Res, $Val extends LoadableStateState> | ||||
|     implements $LoadableStateStateCopyWith<$Res> { | ||||
|   _$LoadableStateStateCopyWithImpl(this._value, this._then); | ||||
|  | ||||
|   // ignore: unused_field | ||||
|   final $Val _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function($Val) _then; | ||||
|  | ||||
|   @pragma('vm:prefer-inline') | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? connections = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       connections: freezed == connections | ||||
|           ? _value.connections | ||||
|           : connections // ignore: cast_nullable_to_non_nullable | ||||
|               as List<ConnectivityResult>?, | ||||
|     ) as $Val); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract class _$$LoadableStateStateImplCopyWith<$Res> | ||||
|     implements $LoadableStateStateCopyWith<$Res> { | ||||
|   factory _$$LoadableStateStateImplCopyWith(_$LoadableStateStateImpl value, | ||||
|           $Res Function(_$LoadableStateStateImpl) then) = | ||||
|       __$$LoadableStateStateImplCopyWithImpl<$Res>; | ||||
|   @override | ||||
|   @useResult | ||||
|   $Res call({List<ConnectivityResult>? connections}); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| class __$$LoadableStateStateImplCopyWithImpl<$Res> | ||||
|     extends _$LoadableStateStateCopyWithImpl<$Res, _$LoadableStateStateImpl> | ||||
|     implements _$$LoadableStateStateImplCopyWith<$Res> { | ||||
|   __$$LoadableStateStateImplCopyWithImpl(_$LoadableStateStateImpl _value, | ||||
|       $Res Function(_$LoadableStateStateImpl) _then) | ||||
|       : super(_value, _then); | ||||
|  | ||||
|   @pragma('vm:prefer-inline') | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? connections = freezed, | ||||
|   }) { | ||||
|     return _then(_$LoadableStateStateImpl( | ||||
|       connections: freezed == connections | ||||
|           ? _value._connections | ||||
|           : connections // ignore: cast_nullable_to_non_nullable | ||||
|               as List<ConnectivityResult>?, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
|  | ||||
| class _$LoadableStateStateImpl implements _LoadableStateState { | ||||
|   const _$LoadableStateStateImpl( | ||||
|       {required final List<ConnectivityResult>? connections}) | ||||
|       : _connections = connections; | ||||
|  | ||||
|   final List<ConnectivityResult>? _connections; | ||||
|   @override | ||||
|   List<ConnectivityResult>? get connections { | ||||
|     final value = _connections; | ||||
|     if (value == null) return null; | ||||
|     if (_connections is EqualUnmodifiableListView) return _connections; | ||||
|     // ignore: implicit_dynamic_type | ||||
|     return EqualUnmodifiableListView(value); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'LoadableStateState(connections: $connections)'; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$LoadableStateStateImpl && | ||||
|             const DeepCollectionEquality() | ||||
|                 .equals(other._connections, _connections)); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, const DeepCollectionEquality().hash(_connections)); | ||||
|  | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   @pragma('vm:prefer-inline') | ||||
|   _$$LoadableStateStateImplCopyWith<_$LoadableStateStateImpl> get copyWith => | ||||
|       __$$LoadableStateStateImplCopyWithImpl<_$LoadableStateStateImpl>( | ||||
|           this, _$identity); | ||||
| } | ||||
|  | ||||
| abstract class _LoadableStateState implements LoadableStateState { | ||||
|   const factory _LoadableStateState( | ||||
|           {required final List<ConnectivityResult>? connections}) = | ||||
|       _$LoadableStateStateImpl; | ||||
|  | ||||
|   @override | ||||
|   List<ConnectivityResult>? get connections; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$LoadableStateStateImplCopyWith<_$LoadableStateStateImpl> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
| @@ -0,0 +1,25 @@ | ||||
| import 'package:hydrated_bloc/hydrated_bloc.dart'; | ||||
|  | ||||
| import '../repository/repository.dart'; | ||||
| import 'loadable_state.dart'; | ||||
|  | ||||
| sealed class LoadableHydratedBlocEvent {} | ||||
| class DataRecieved extends LoadableHydratedBlocEvent {} | ||||
|  | ||||
| abstract class LoadableHydratedBloc<TEvent, TState> extends HydratedBloc<TEvent, LoadableState<TState>> { | ||||
|   LoadableHydratedBloc() : super(const LoadableState()) { | ||||
|     repository().load(); | ||||
|  | ||||
|   } | ||||
|  | ||||
|   Repository repository(); | ||||
|  | ||||
|  | ||||
|   @override | ||||
|   fromJson(Map<String, dynamic> json) => LoadableState(isLoading: true, data: fromStorage(json)); | ||||
|   @override | ||||
|   Map<String, dynamic>? toJson(state) => state.data.toJson(); | ||||
|  | ||||
|   TState fromStorage(Map<String, dynamic> json); | ||||
|   Map<String, dynamic>? toStorage(TState state); | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
|  | ||||
| part 'loadable_state.freezed.dart'; | ||||
|  | ||||
| @freezed | ||||
| class LoadableState<TState> with _$LoadableState { | ||||
|   const LoadableState._(); | ||||
|  | ||||
|   const factory LoadableState({ | ||||
|     @Default(true) bool isLoading, | ||||
|     @Default(null) TState? data, | ||||
|   }) = _LoadableState; | ||||
|  | ||||
|   bool showPrimaryLoading() => isLoading && data == null; | ||||
|   bool showBackgroundLoading() => isLoading && data != null; | ||||
|   bool showError() => !isLoading && data == null; | ||||
|   bool showContent() => data != null; | ||||
| } | ||||
| @@ -0,0 +1,158 @@ | ||||
| // coverage:ignore-file | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| // ignore_for_file: type=lint | ||||
| // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||
|  | ||||
| part of 'loadable_state.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // FreezedGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| T _$identity<T>(T value) => value; | ||||
|  | ||||
| final _privateConstructorUsedError = UnsupportedError( | ||||
|     'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); | ||||
|  | ||||
| /// @nodoc | ||||
| mixin _$LoadableState<TState> { | ||||
|   bool get isLoading => throw _privateConstructorUsedError; | ||||
|   TState? get data => throw _privateConstructorUsedError; | ||||
|  | ||||
|   @JsonKey(ignore: true) | ||||
|   $LoadableStateCopyWith<TState, LoadableState<TState>> get copyWith => | ||||
|       throw _privateConstructorUsedError; | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract class $LoadableStateCopyWith<TState, $Res> { | ||||
|   factory $LoadableStateCopyWith(LoadableState<TState> value, | ||||
|           $Res Function(LoadableState<TState>) then) = | ||||
|       _$LoadableStateCopyWithImpl<TState, $Res, LoadableState<TState>>; | ||||
|   @useResult | ||||
|   $Res call({bool isLoading, TState? data}); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| class _$LoadableStateCopyWithImpl<TState, $Res, | ||||
|         $Val extends LoadableState<TState>> | ||||
|     implements $LoadableStateCopyWith<TState, $Res> { | ||||
|   _$LoadableStateCopyWithImpl(this._value, this._then); | ||||
|  | ||||
|   // ignore: unused_field | ||||
|   final $Val _value; | ||||
|   // ignore: unused_field | ||||
|   final $Res Function($Val) _then; | ||||
|  | ||||
|   @pragma('vm:prefer-inline') | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? isLoading = null, | ||||
|     Object? data = freezed, | ||||
|   }) { | ||||
|     return _then(_value.copyWith( | ||||
|       isLoading: null == isLoading | ||||
|           ? _value.isLoading | ||||
|           : isLoading // ignore: cast_nullable_to_non_nullable | ||||
|               as bool, | ||||
|       data: freezed == data | ||||
|           ? _value.data | ||||
|           : data // ignore: cast_nullable_to_non_nullable | ||||
|               as TState?, | ||||
|     ) as $Val); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract class _$$LoadableStateImplCopyWith<TState, $Res> | ||||
|     implements $LoadableStateCopyWith<TState, $Res> { | ||||
|   factory _$$LoadableStateImplCopyWith(_$LoadableStateImpl<TState> value, | ||||
|           $Res Function(_$LoadableStateImpl<TState>) then) = | ||||
|       __$$LoadableStateImplCopyWithImpl<TState, $Res>; | ||||
|   @override | ||||
|   @useResult | ||||
|   $Res call({bool isLoading, TState? data}); | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| class __$$LoadableStateImplCopyWithImpl<TState, $Res> | ||||
|     extends _$LoadableStateCopyWithImpl<TState, $Res, | ||||
|         _$LoadableStateImpl<TState>> | ||||
|     implements _$$LoadableStateImplCopyWith<TState, $Res> { | ||||
|   __$$LoadableStateImplCopyWithImpl(_$LoadableStateImpl<TState> _value, | ||||
|       $Res Function(_$LoadableStateImpl<TState>) _then) | ||||
|       : super(_value, _then); | ||||
|  | ||||
|   @pragma('vm:prefer-inline') | ||||
|   @override | ||||
|   $Res call({ | ||||
|     Object? isLoading = null, | ||||
|     Object? data = freezed, | ||||
|   }) { | ||||
|     return _then(_$LoadableStateImpl<TState>( | ||||
|       isLoading: null == isLoading | ||||
|           ? _value.isLoading | ||||
|           : isLoading // ignore: cast_nullable_to_non_nullable | ||||
|               as bool, | ||||
|       data: freezed == data | ||||
|           ? _value.data | ||||
|           : data // ignore: cast_nullable_to_non_nullable | ||||
|               as TState?, | ||||
|     )); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
|  | ||||
| class _$LoadableStateImpl<TState> extends _LoadableState<TState> { | ||||
|   const _$LoadableStateImpl({this.isLoading = true, this.data = null}) | ||||
|       : super._(); | ||||
|  | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final bool isLoading; | ||||
|   @override | ||||
|   @JsonKey() | ||||
|   final TState? data; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'LoadableState<$TState>(isLoading: $isLoading, data: $data)'; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return identical(this, other) || | ||||
|         (other.runtimeType == runtimeType && | ||||
|             other is _$LoadableStateImpl<TState> && | ||||
|             (identical(other.isLoading, isLoading) || | ||||
|                 other.isLoading == isLoading) && | ||||
|             const DeepCollectionEquality().equals(other.data, data)); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => Object.hash( | ||||
|       runtimeType, isLoading, const DeepCollectionEquality().hash(data)); | ||||
|  | ||||
|   @JsonKey(ignore: true) | ||||
|   @override | ||||
|   @pragma('vm:prefer-inline') | ||||
|   _$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>> | ||||
|       get copyWith => __$$LoadableStateImplCopyWithImpl<TState, | ||||
|           _$LoadableStateImpl<TState>>(this, _$identity); | ||||
| } | ||||
|  | ||||
| abstract class _LoadableState<TState> extends LoadableState<TState> { | ||||
|   const factory _LoadableState({final bool isLoading, final TState? data}) = | ||||
|       _$LoadableStateImpl<TState>; | ||||
|   const _LoadableState._() : super._(); | ||||
|  | ||||
|   @override | ||||
|   bool get isLoading; | ||||
|   @override | ||||
|   TState? get data; | ||||
|   @override | ||||
|   @JsonKey(ignore: true) | ||||
|   _$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>> | ||||
|       get copyWith => throw _privateConstructorUsedError; | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class LoadableStateBackgroundLoading extends StatelessWidget { | ||||
|   final bool visible; | ||||
|   const LoadableStateBackgroundLoading({required this.visible, super.key}); | ||||
|  | ||||
|   final Duration animationDuration = const Duration(milliseconds: 200); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) => AnimatedSwitcher( | ||||
|       duration: animationDuration, | ||||
|       transitionBuilder: (Widget child, Animation<double> animation) => SlideTransition( | ||||
|         position: Tween<Offset>( | ||||
|           begin: const Offset(0.0, -1.0), | ||||
|           end: Offset.zero, | ||||
|         ).animate(animation), | ||||
|         child: child, | ||||
|       ), | ||||
|       child: visible ? const LinearProgressIndicator() : const SizedBox.shrink(), | ||||
|     ); | ||||
| } | ||||
| @@ -0,0 +1,42 @@ | ||||
| import 'package:bloc/bloc.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
|  | ||||
| import '../loadable_state.dart'; | ||||
| import 'loadable_state_background_loading.dart'; | ||||
| import 'loadable_state_error_bar.dart'; | ||||
| import 'loadable_state_primary_loading.dart'; | ||||
|  | ||||
| // TODO might be a simpler way | ||||
| class LoadableStateConsumer<TController extends Bloc<dynamic, TWrappedState>, TWrappedState extends LoadableState<TState>, TState> extends StatelessWidget { | ||||
|   final Widget Function(TState state) child; | ||||
|   const LoadableStateConsumer({required this.child, super.key}); | ||||
|  | ||||
|   static Duration animationDuration = const Duration(milliseconds: 200); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     var state = context.read<TController>().state as LoadableState<TState>; | ||||
|  | ||||
|     return Column( | ||||
|       children: [ | ||||
|         LoadableStateErrorBar(visible: state.showError()), | ||||
|         Expanded( | ||||
|           child: Stack( | ||||
|             children: [ | ||||
|               LoadableStatePrimaryLoading(visible: state.showPrimaryLoading()), | ||||
|               LoadableStateBackgroundLoading(visible: state.showBackgroundLoading()), | ||||
|  | ||||
|               AnimatedOpacity( | ||||
|                   opacity: state.showContent() ? 1.0 : 0.0, | ||||
|                   duration: animationDuration, | ||||
|                   curve: Curves.easeInOut, | ||||
|                   child: state.showContent() ? child(state.data) : const SizedBox.shrink() | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ) | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,59 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
|  | ||||
| import '../../utilityWidgets/bloc_providing_builder.dart'; | ||||
| import '../bloc/loadable_state_bloc.dart'; | ||||
| import '../bloc/loadable_state_state.dart'; | ||||
|  | ||||
| class LoadableStateErrorBar extends StatelessWidget { | ||||
|   final bool visible; | ||||
|   const LoadableStateErrorBar({required this.visible, super.key}); | ||||
|  | ||||
|   final Duration animationDuration = const Duration(milliseconds: 200); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) => BlocProvidingBuilder<LoadableStateBloc, LoadableStateState>( | ||||
|     create: (context) => LoadableStateBloc(), | ||||
|     child: (context, state) => AnimatedSize( | ||||
|       duration: animationDuration, | ||||
|       child: AnimatedSwitcher( | ||||
|           duration: animationDuration, | ||||
|           transitionBuilder: (Widget child, Animation<double> animation) => SlideTransition( | ||||
|             position: Tween<Offset>( | ||||
|               begin: const Offset(0.0, -1.0), | ||||
|               end: Offset.zero, | ||||
|             ).animate(animation), | ||||
|             child: child, | ||||
|           ), | ||||
|           child: Visibility( | ||||
|               key: Key(visible.hashCode.toString()), | ||||
|               visible: visible, | ||||
|               replacement: const SizedBox(width: double.infinity), | ||||
|               child: Builder( | ||||
|                 builder: (context) { | ||||
|                   var controller = context.watch<LoadableStateBloc>(); | ||||
|                   var status = controller.connectivityStatusKnown() && !controller.isConnected() | ||||
|                       ? (icon: Icons.wifi_off_outlined, text: 'Offline', color: Colors.grey.shade600) | ||||
|                       : (icon: Icons.wifi_find_outlined, text: 'Verbindung fehlgeschlagen', color: Theme.of(context).primaryColor); | ||||
|  | ||||
|                   return Container( | ||||
|                     height: 20, | ||||
|                     decoration: BoxDecoration( | ||||
|                       color: status.color, | ||||
|                     ), | ||||
|                     child: Row( | ||||
|                       mainAxisAlignment: MainAxisAlignment.center, | ||||
|                       children: [ | ||||
|                         Icon(status.icon, size: 14), | ||||
|                         const SizedBox(width: 10), | ||||
|                         Text(status.text, style: const TextStyle(fontSize: 12)) | ||||
|                       ], | ||||
|                     ), | ||||
|                   ); | ||||
|                 }, | ||||
|               ) | ||||
|           ) | ||||
|       ), | ||||
|     ), | ||||
|   ); | ||||
| } | ||||
| @@ -0,0 +1,16 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class LoadableStatePrimaryLoading extends StatelessWidget { | ||||
|   final bool visible; | ||||
|   const LoadableStatePrimaryLoading({required this.visible, super.key}); | ||||
|  | ||||
|   final Duration animationDuration = const Duration(milliseconds: 200); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) => AnimatedOpacity( | ||||
|       opacity: visible ? 1.0 : 0.0, | ||||
|       duration: animationDuration, | ||||
|       curve: Curves.easeInOut, | ||||
|       child: const Center(child: CircularProgressIndicator()), | ||||
|     ); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user