loadable error screen, reload actions, autoreload
This commit is contained in:
@ -2,21 +2,47 @@ import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../loading_error.dart';
|
||||
import 'loadable_state_event.dart';
|
||||
import 'loadable_state_state.dart';
|
||||
|
||||
class LoadableStateBloc extends Bloc<void, LoadableStateState> {
|
||||
class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> {
|
||||
late StreamSubscription<List<ConnectivityResult>> _updateStream;
|
||||
LoadingError? loadingError;
|
||||
|
||||
LoadableStateBloc() : super(const LoadableStateState(connections: null)) {
|
||||
emitState(List<ConnectivityResult> v) => emit(state.copyWith(connections: v));
|
||||
on<ConnectivityChanged>((event, emit) {
|
||||
emit(event.state);
|
||||
if(connectivityStatusKnown() && isConnected() && loadingError != null) {
|
||||
if(!loadingError!.enableRetry) return;
|
||||
loadingError!.retry!();
|
||||
}
|
||||
});
|
||||
|
||||
Connectivity().checkConnectivity().then(emitState);
|
||||
_updateStream = Connectivity().onConnectivityChanged.listen(emitState);
|
||||
emitConnectivity(List<ConnectivityResult> result) => add(ConnectivityChanged(LoadableStateState(connections: result)));
|
||||
|
||||
Connectivity().checkConnectivity().then(emitConnectivity);
|
||||
_updateStream = Connectivity().onConnectivityChanged.listen(emitConnectivity);
|
||||
}
|
||||
|
||||
bool connectivityStatusKnown() => state.connections != null;
|
||||
bool isConnected({bool? def}) => !(state.connections?.contains(ConnectivityResult.none) ?? def!);
|
||||
bool isConnected() => !(state.connections?.contains(ConnectivityResult.none) ?? true);
|
||||
bool allowRetry() => loadingError?.retry != null;
|
||||
bool showErrorMessage() => isConnected() && loadingError != null;
|
||||
|
||||
IconData connectionIcon() => connectivityStatusKnown()
|
||||
? isConnected()
|
||||
? Icons.nearby_error
|
||||
: Icons.signal_wifi_connected_no_internet_4
|
||||
: Icons.device_unknown;
|
||||
|
||||
String connectionText() => connectivityStatusKnown()
|
||||
? isConnected()
|
||||
? 'Verbindung fehlgeschlagen'
|
||||
: 'Offline'
|
||||
: 'Unbekannte Fehlerursache';
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
|
@ -0,0 +1,7 @@
|
||||
import 'loadable_state_state.dart';
|
||||
|
||||
sealed class LoadableStateEvent {}
|
||||
final class ConnectivityChanged extends LoadableStateEvent {
|
||||
final LoadableStateState state;
|
||||
ConnectivityChanged(this.state);
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'loading_error.dart';
|
||||
|
||||
part 'loadable_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
@ -9,10 +11,15 @@ class LoadableState<TState> with _$LoadableState {
|
||||
const factory LoadableState({
|
||||
@Default(true) bool isLoading,
|
||||
@Default(null) TState? data,
|
||||
@Default(null) LoadingError? error,
|
||||
}) = _LoadableState;
|
||||
|
||||
bool showPrimaryLoading() => isLoading && data == null;
|
||||
bool showBackgroundLoading() => isLoading && data != null;
|
||||
bool showError() => !isLoading && data == null;
|
||||
bool showContent() => data != null;
|
||||
bool _hasError() => error != null;
|
||||
bool _hasData() => data != null;
|
||||
|
||||
bool showPrimaryLoading() => isLoading && !_hasData();
|
||||
bool showBackgroundLoading() => isLoading && _hasData();
|
||||
bool showErrorBar() => _hasError() && _hasData();
|
||||
bool showError() => _hasError() && !_hasData();
|
||||
bool showContent() => _hasData();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError(
|
||||
mixin _$LoadableState<TState> {
|
||||
bool get isLoading => throw _privateConstructorUsedError;
|
||||
TState? get data => throw _privateConstructorUsedError;
|
||||
LoadingError? get error => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$LoadableStateCopyWith<TState, LoadableState<TState>> get copyWith =>
|
||||
@ -30,7 +31,9 @@ abstract class $LoadableStateCopyWith<TState, $Res> {
|
||||
$Res Function(LoadableState<TState>) then) =
|
||||
_$LoadableStateCopyWithImpl<TState, $Res, LoadableState<TState>>;
|
||||
@useResult
|
||||
$Res call({bool isLoading, TState? data});
|
||||
$Res call({bool isLoading, TState? data, LoadingError? error});
|
||||
|
||||
$LoadingErrorCopyWith<$Res>? get error;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -49,6 +52,7 @@ class _$LoadableStateCopyWithImpl<TState, $Res,
|
||||
$Res call({
|
||||
Object? isLoading = null,
|
||||
Object? data = freezed,
|
||||
Object? error = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isLoading: null == isLoading
|
||||
@ -59,8 +63,24 @@ class _$LoadableStateCopyWithImpl<TState, $Res,
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
as TState?,
|
||||
error: freezed == error
|
||||
? _value.error
|
||||
: error // ignore: cast_nullable_to_non_nullable
|
||||
as LoadingError?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$LoadingErrorCopyWith<$Res>? get error {
|
||||
if (_value.error == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $LoadingErrorCopyWith<$Res>(_value.error!, (value) {
|
||||
return _then(_value.copyWith(error: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -71,7 +91,10 @@ abstract class _$$LoadableStateImplCopyWith<TState, $Res>
|
||||
__$$LoadableStateImplCopyWithImpl<TState, $Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool isLoading, TState? data});
|
||||
$Res call({bool isLoading, TState? data, LoadingError? error});
|
||||
|
||||
@override
|
||||
$LoadingErrorCopyWith<$Res>? get error;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -88,6 +111,7 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
|
||||
$Res call({
|
||||
Object? isLoading = null,
|
||||
Object? data = freezed,
|
||||
Object? error = freezed,
|
||||
}) {
|
||||
return _then(_$LoadableStateImpl<TState>(
|
||||
isLoading: null == isLoading
|
||||
@ -98,6 +122,10 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
as TState?,
|
||||
error: freezed == error
|
||||
? _value.error
|
||||
: error // ignore: cast_nullable_to_non_nullable
|
||||
as LoadingError?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -105,7 +133,8 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
|
||||
/// @nodoc
|
||||
|
||||
class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
|
||||
const _$LoadableStateImpl({this.isLoading = true, this.data = null})
|
||||
const _$LoadableStateImpl(
|
||||
{this.isLoading = true, this.data = null, this.error = null})
|
||||
: super._();
|
||||
|
||||
@override
|
||||
@ -114,10 +143,13 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
|
||||
@override
|
||||
@JsonKey()
|
||||
final TState? data;
|
||||
@override
|
||||
@JsonKey()
|
||||
final LoadingError? error;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoadableState<$TState>(isLoading: $isLoading, data: $data)';
|
||||
return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, error: $error)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -127,12 +159,13 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
|
||||
other is _$LoadableStateImpl<TState> &&
|
||||
(identical(other.isLoading, isLoading) ||
|
||||
other.isLoading == isLoading) &&
|
||||
const DeepCollectionEquality().equals(other.data, data));
|
||||
const DeepCollectionEquality().equals(other.data, data) &&
|
||||
(identical(other.error, error) || other.error == error));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, isLoading, const DeepCollectionEquality().hash(data));
|
||||
runtimeType, isLoading, const DeepCollectionEquality().hash(data), error);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@ -143,8 +176,10 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
|
||||
}
|
||||
|
||||
abstract class _LoadableState<TState> extends LoadableState<TState> {
|
||||
const factory _LoadableState({final bool isLoading, final TState? data}) =
|
||||
_$LoadableStateImpl<TState>;
|
||||
const factory _LoadableState(
|
||||
{final bool isLoading,
|
||||
final TState? data,
|
||||
final LoadingError? error}) = _$LoadableStateImpl<TState>;
|
||||
const _LoadableState._() : super._();
|
||||
|
||||
@override
|
||||
@ -152,6 +187,8 @@ abstract class _LoadableState<TState> extends LoadableState<TState> {
|
||||
@override
|
||||
TState? get data;
|
||||
@override
|
||||
LoadingError? get error;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
|
@ -0,0 +1,12 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'loading_error.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class LoadingError with _$LoadingError {
|
||||
const factory LoadingError({
|
||||
required String message,
|
||||
@Default(false) bool enableRetry,
|
||||
@Default(null) void Function()? retry,
|
||||
}) = _LoadingError;
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
// 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 'loading_error.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 _$LoadingError {
|
||||
String get message => throw _privateConstructorUsedError;
|
||||
bool get enableRetry => throw _privateConstructorUsedError;
|
||||
void Function()? get retry => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$LoadingErrorCopyWith<LoadingError> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LoadingErrorCopyWith<$Res> {
|
||||
factory $LoadingErrorCopyWith(
|
||||
LoadingError value, $Res Function(LoadingError) then) =
|
||||
_$LoadingErrorCopyWithImpl<$Res, LoadingError>;
|
||||
@useResult
|
||||
$Res call({String message, bool enableRetry, void Function()? retry});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LoadingErrorCopyWithImpl<$Res, $Val extends LoadingError>
|
||||
implements $LoadingErrorCopyWith<$Res> {
|
||||
_$LoadingErrorCopyWithImpl(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? message = null,
|
||||
Object? enableRetry = null,
|
||||
Object? retry = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
message: null == message
|
||||
? _value.message
|
||||
: message // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
enableRetry: null == enableRetry
|
||||
? _value.enableRetry
|
||||
: enableRetry // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
retry: freezed == retry
|
||||
? _value.retry
|
||||
: retry // ignore: cast_nullable_to_non_nullable
|
||||
as void Function()?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$LoadingErrorImplCopyWith<$Res>
|
||||
implements $LoadingErrorCopyWith<$Res> {
|
||||
factory _$$LoadingErrorImplCopyWith(
|
||||
_$LoadingErrorImpl value, $Res Function(_$LoadingErrorImpl) then) =
|
||||
__$$LoadingErrorImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String message, bool enableRetry, void Function()? retry});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$LoadingErrorImplCopyWithImpl<$Res>
|
||||
extends _$LoadingErrorCopyWithImpl<$Res, _$LoadingErrorImpl>
|
||||
implements _$$LoadingErrorImplCopyWith<$Res> {
|
||||
__$$LoadingErrorImplCopyWithImpl(
|
||||
_$LoadingErrorImpl _value, $Res Function(_$LoadingErrorImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? message = null,
|
||||
Object? enableRetry = null,
|
||||
Object? retry = freezed,
|
||||
}) {
|
||||
return _then(_$LoadingErrorImpl(
|
||||
message: null == message
|
||||
? _value.message
|
||||
: message // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
enableRetry: null == enableRetry
|
||||
? _value.enableRetry
|
||||
: enableRetry // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
retry: freezed == retry
|
||||
? _value.retry
|
||||
: retry // ignore: cast_nullable_to_non_nullable
|
||||
as void Function()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$LoadingErrorImpl implements _LoadingError {
|
||||
const _$LoadingErrorImpl(
|
||||
{required this.message, this.enableRetry = false, this.retry = null});
|
||||
|
||||
@override
|
||||
final String message;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool enableRetry;
|
||||
@override
|
||||
@JsonKey()
|
||||
final void Function()? retry;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoadingError(message: $message, enableRetry: $enableRetry, retry: $retry)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LoadingErrorImpl &&
|
||||
(identical(other.message, message) || other.message == message) &&
|
||||
(identical(other.enableRetry, enableRetry) ||
|
||||
other.enableRetry == enableRetry) &&
|
||||
(identical(other.retry, retry) || other.retry == retry));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, message, enableRetry, retry);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith =>
|
||||
__$$LoadingErrorImplCopyWithImpl<_$LoadingErrorImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _LoadingError implements LoadingError {
|
||||
const factory _LoadingError(
|
||||
{required final String message,
|
||||
final bool enableRetry,
|
||||
final void Function()? retry}) = _$LoadingErrorImpl;
|
||||
|
||||
@override
|
||||
String get message;
|
||||
@override
|
||||
bool get enableRetry;
|
||||
@override
|
||||
void Function()? get retry;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -2,10 +2,14 @@ import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../utilityWidgets/bloc_module.dart';
|
||||
import '../../utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
|
||||
import '../bloc/loadable_state_bloc.dart';
|
||||
import '../bloc/loadable_state_state.dart';
|
||||
import '../loadable_state.dart';
|
||||
import 'loadable_state_background_loading.dart';
|
||||
import 'loadable_state_error_bar.dart';
|
||||
import 'loadable_state_error_screen.dart';
|
||||
import 'loadable_state_primary_loading.dart';
|
||||
|
||||
class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>>, TState> extends StatelessWidget {
|
||||
@ -17,26 +21,51 @@ class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<T
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var loadableState = context.watch<TController>().state;
|
||||
var childWidget = RefreshIndicator(
|
||||
onRefresh: () {
|
||||
loadableState.error != null && loadableState.error!.retry != null
|
||||
? loadableState.error!.retry!()
|
||||
: null;
|
||||
return Future.value();
|
||||
},
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: loadableState.showContent()
|
||||
? child(loadableState.data, loadableState.isLoading)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
// SingleChildScrollView( // TODO not all childs are reloadable
|
||||
// physics: const AlwaysScrollableScrollPhysics(),
|
||||
// child:
|
||||
// ),
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
LoadableStateErrorBar(visible: loadableState.showError()),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
LoadableStatePrimaryLoading(visible: loadableState.showPrimaryLoading()),
|
||||
LoadableStateBackgroundLoading(visible: loadableState.showBackgroundLoading()),
|
||||
return BlocModule<LoadableStateBloc, LoadableStateState>(
|
||||
create: (context) => LoadableStateBloc(),
|
||||
child: (context, bloc, state) {
|
||||
bloc.loadingError = loadableState.error;
|
||||
return Column(
|
||||
children: [
|
||||
LoadableStateErrorBar(visible: loadableState.showErrorBar()),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
LoadableStatePrimaryLoading(visible: loadableState.showPrimaryLoading()),
|
||||
LoadableStateBackgroundLoading(visible: loadableState.showBackgroundLoading()),
|
||||
LoadableStateErrorScreen(visible: loadableState.showError()),
|
||||
|
||||
AnimatedOpacity(
|
||||
opacity: loadableState.showContent() ? 1.0 : 0.0,
|
||||
duration: animationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
child: loadableState.showContent() ? child(loadableState.data, loadableState.isLoading) : const SizedBox.shrink()
|
||||
AnimatedOpacity(
|
||||
opacity: loadableState.showContent() ? 1.0 : 0.0,
|
||||
duration: animationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
child: childWidget,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../utilityWidgets/bloc_module.dart';
|
||||
import '../bloc/loadable_state_bloc.dart';
|
||||
import '../bloc/loadable_state_state.dart';
|
||||
|
||||
class LoadableStateErrorBar extends StatelessWidget {
|
||||
final bool visible;
|
||||
@ -12,48 +10,49 @@ class LoadableStateErrorBar extends StatelessWidget {
|
||||
final Duration animationDuration = const Duration(milliseconds: 200);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => BlocModule<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);
|
||||
Widget build(BuildContext context) => 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 bloc = context.watch<LoadableStateBloc>();
|
||||
var status = (
|
||||
icon: bloc.connectionIcon(),
|
||||
text: bloc.connectionText(),
|
||||
color: bloc.connectivityStatusKnown() && !bloc.isConnected()
|
||||
? Colors.grey.shade600
|
||||
: 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))
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
)
|
||||
),
|
||||
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,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../bloc/loadable_state_bloc.dart';
|
||||
import 'loadable_state_consumer.dart';
|
||||
|
||||
class LoadableStateErrorScreen extends StatelessWidget {
|
||||
final bool visible;
|
||||
const LoadableStateErrorScreen({required this.visible, super.key});
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = context.watch<LoadableStateBloc>();
|
||||
return AnimatedOpacity(
|
||||
opacity: visible ? 1.0 : 0.0,
|
||||
duration: LoadableStateConsumer.animationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
child: !visible ? null : Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(bloc.connectionIcon(), size: 40),
|
||||
const SizedBox(height: 10),
|
||||
Text(bloc.connectionText(), style: const TextStyle(fontSize: 20)),
|
||||
|
||||
if(bloc.allowRetry()) ...[
|
||||
const SizedBox(height: 10),
|
||||
TextButton(onPressed: () => bloc.loadingError!.retry!(), child: const Text('Erneut versuschen')),
|
||||
],
|
||||
|
||||
if(bloc.showErrorMessage()) ...[
|
||||
const SizedBox(height: 40),
|
||||
Text(bloc.loadingError!.message, style: TextStyle(color: Theme.of(context).hintColor, fontSize: 12))
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user