diff --git a/lib/state/app/infrastructure/loadableState/bloc/loadable_state_bloc.dart b/lib/state/app/infrastructure/loadableState/bloc/loadable_state_bloc.dart index 2ca0717..1738a81 100644 --- a/lib/state/app/infrastructure/loadableState/bloc/loadable_state_bloc.dart +++ b/lib/state/app/infrastructure/loadableState/bloc/loadable_state_bloc.dart @@ -4,20 +4,19 @@ 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 { late StreamSubscription> _updateStream; - LoadingError? loadingError; + void Function()? reFetch; LoadableStateBloc() : super(const LoadableStateState(connections: null)) { on((event, emit) { emit(event.state); - if(connectivityStatusKnown() && isConnected() && loadingError != null) { - if(!loadingError!.enableRetry) return; - loadingError!.retry!(); + if(connectivityStatusKnown() && isConnected()) { + if(reFetch == null) return; + reFetch!(); } }); @@ -29,8 +28,8 @@ class LoadableStateBloc extends Bloc { bool connectivityStatusKnown() => state.connections != null; bool isConnected() => !(state.connections?.contains(ConnectivityResult.none) ?? true); - bool allowRetry() => loadingError?.retry != null; - bool showErrorMessage() => isConnected() && loadingError != null; + bool allowRetry() => reFetch != null; + bool showErrorMessage() => isConnected() && reFetch != null; IconData connectionIcon() => connectivityStatusKnown() ? isConnected() diff --git a/lib/state/app/infrastructure/loadableState/loadable_state.dart b/lib/state/app/infrastructure/loadableState/loadable_state.dart index 98c87c1..4a6c22a 100644 --- a/lib/state/app/infrastructure/loadableState/loadable_state.dart +++ b/lib/state/app/infrastructure/loadableState/loadable_state.dart @@ -11,6 +11,7 @@ class LoadableState with _$LoadableState { const factory LoadableState({ @Default(true) bool isLoading, @Default(null) TState? data, + @Default(null) void Function()? reFetch, @Default(null) LoadingError? error, }) = _LoadableState; diff --git a/lib/state/app/infrastructure/loadableState/loadable_state.freezed.dart b/lib/state/app/infrastructure/loadableState/loadable_state.freezed.dart index 3b805ac..03ac3a3 100644 --- a/lib/state/app/infrastructure/loadableState/loadable_state.freezed.dart +++ b/lib/state/app/infrastructure/loadableState/loadable_state.freezed.dart @@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$LoadableState { bool get isLoading => throw _privateConstructorUsedError; TState? get data => throw _privateConstructorUsedError; + void Function()? get reFetch => throw _privateConstructorUsedError; LoadingError? get error => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -31,7 +32,11 @@ abstract class $LoadableStateCopyWith { $Res Function(LoadableState) then) = _$LoadableStateCopyWithImpl>; @useResult - $Res call({bool isLoading, TState? data, LoadingError? error}); + $Res call( + {bool isLoading, + TState? data, + void Function()? reFetch, + LoadingError? error}); $LoadingErrorCopyWith<$Res>? get error; } @@ -52,6 +57,7 @@ class _$LoadableStateCopyWithImpl __$$LoadableStateImplCopyWithImpl; @override @useResult - $Res call({bool isLoading, TState? data, LoadingError? error}); + $Res call( + {bool isLoading, + TState? data, + void Function()? reFetch, + LoadingError? error}); @override $LoadingErrorCopyWith<$Res>? get error; @@ -111,6 +125,7 @@ class __$$LoadableStateImplCopyWithImpl $Res call({ Object? isLoading = null, Object? data = freezed, + Object? reFetch = freezed, Object? error = freezed, }) { return _then(_$LoadableStateImpl( @@ -122,6 +137,10 @@ class __$$LoadableStateImplCopyWithImpl ? _value.data : data // ignore: cast_nullable_to_non_nullable as TState?, + reFetch: freezed == reFetch + ? _value.reFetch + : reFetch // ignore: cast_nullable_to_non_nullable + as void Function()?, error: freezed == error ? _value.error : error // ignore: cast_nullable_to_non_nullable @@ -134,7 +153,10 @@ class __$$LoadableStateImplCopyWithImpl class _$LoadableStateImpl extends _LoadableState { const _$LoadableStateImpl( - {this.isLoading = true, this.data = null, this.error = null}) + {this.isLoading = true, + this.data = null, + this.reFetch = null, + this.error = null}) : super._(); @override @@ -145,11 +167,14 @@ class _$LoadableStateImpl extends _LoadableState { final TState? data; @override @JsonKey() + final void Function()? reFetch; + @override + @JsonKey() final LoadingError? error; @override String toString() { - return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, error: $error)'; + return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, reFetch: $reFetch, error: $error)'; } @override @@ -160,12 +185,13 @@ class _$LoadableStateImpl extends _LoadableState { (identical(other.isLoading, isLoading) || other.isLoading == isLoading) && const DeepCollectionEquality().equals(other.data, data) && + (identical(other.reFetch, reFetch) || other.reFetch == reFetch) && (identical(other.error, error) || other.error == error)); } @override - int get hashCode => Object.hash( - runtimeType, isLoading, const DeepCollectionEquality().hash(data), error); + int get hashCode => Object.hash(runtimeType, isLoading, + const DeepCollectionEquality().hash(data), reFetch, error); @JsonKey(ignore: true) @override @@ -179,6 +205,7 @@ abstract class _LoadableState extends LoadableState { const factory _LoadableState( {final bool isLoading, final TState? data, + final void Function()? reFetch, final LoadingError? error}) = _$LoadableStateImpl; const _LoadableState._() : super._(); @@ -187,6 +214,8 @@ abstract class _LoadableState extends LoadableState { @override TState? get data; @override + void Function()? get reFetch; + @override LoadingError? get error; @override @JsonKey(ignore: true) diff --git a/lib/state/app/infrastructure/loadableState/loading_error.dart b/lib/state/app/infrastructure/loadableState/loading_error.dart index 909f6d7..b68ac7d 100644 --- a/lib/state/app/infrastructure/loadableState/loading_error.dart +++ b/lib/state/app/infrastructure/loadableState/loading_error.dart @@ -6,7 +6,6 @@ part 'loading_error.freezed.dart'; class LoadingError with _$LoadingError { const factory LoadingError({ required String message, - @Default(false) bool enableRetry, - @Default(null) void Function()? retry, + @Default(false) bool allowRetry, }) = _LoadingError; } diff --git a/lib/state/app/infrastructure/loadableState/loading_error.freezed.dart b/lib/state/app/infrastructure/loadableState/loading_error.freezed.dart index 2570c98..6515a46 100644 --- a/lib/state/app/infrastructure/loadableState/loading_error.freezed.dart +++ b/lib/state/app/infrastructure/loadableState/loading_error.freezed.dart @@ -17,8 +17,7 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$LoadingError { String get message => throw _privateConstructorUsedError; - bool get enableRetry => throw _privateConstructorUsedError; - void Function()? get retry => throw _privateConstructorUsedError; + bool get allowRetry => throw _privateConstructorUsedError; @JsonKey(ignore: true) $LoadingErrorCopyWith get copyWith => @@ -31,7 +30,7 @@ abstract class $LoadingErrorCopyWith<$Res> { LoadingError value, $Res Function(LoadingError) then) = _$LoadingErrorCopyWithImpl<$Res, LoadingError>; @useResult - $Res call({String message, bool enableRetry, void Function()? retry}); + $Res call({String message, bool allowRetry}); } /// @nodoc @@ -48,22 +47,17 @@ class _$LoadingErrorCopyWithImpl<$Res, $Val extends LoadingError> @override $Res call({ Object? message = null, - Object? enableRetry = null, - Object? retry = freezed, + Object? allowRetry = null, }) { 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 + allowRetry: null == allowRetry + ? _value.allowRetry + : allowRetry // 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); } } @@ -76,7 +70,7 @@ abstract class _$$LoadingErrorImplCopyWith<$Res> __$$LoadingErrorImplCopyWithImpl<$Res>; @override @useResult - $Res call({String message, bool enableRetry, void Function()? retry}); + $Res call({String message, bool allowRetry}); } /// @nodoc @@ -91,22 +85,17 @@ class __$$LoadingErrorImplCopyWithImpl<$Res> @override $Res call({ Object? message = null, - Object? enableRetry = null, - Object? retry = freezed, + Object? allowRetry = null, }) { 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 + allowRetry: null == allowRetry + ? _value.allowRetry + : allowRetry // ignore: cast_nullable_to_non_nullable as bool, - retry: freezed == retry - ? _value.retry - : retry // ignore: cast_nullable_to_non_nullable - as void Function()?, )); } } @@ -114,21 +103,17 @@ class __$$LoadingErrorImplCopyWithImpl<$Res> /// @nodoc class _$LoadingErrorImpl implements _LoadingError { - const _$LoadingErrorImpl( - {required this.message, this.enableRetry = false, this.retry = null}); + const _$LoadingErrorImpl({required this.message, this.allowRetry = false}); @override final String message; @override @JsonKey() - final bool enableRetry; - @override - @JsonKey() - final void Function()? retry; + final bool allowRetry; @override String toString() { - return 'LoadingError(message: $message, enableRetry: $enableRetry, retry: $retry)'; + return 'LoadingError(message: $message, allowRetry: $allowRetry)'; } @override @@ -137,13 +122,12 @@ class _$LoadingErrorImpl implements _LoadingError { (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)); + (identical(other.allowRetry, allowRetry) || + other.allowRetry == allowRetry)); } @override - int get hashCode => Object.hash(runtimeType, message, enableRetry, retry); + int get hashCode => Object.hash(runtimeType, message, allowRetry); @JsonKey(ignore: true) @override @@ -155,15 +139,12 @@ class _$LoadingErrorImpl implements _LoadingError { abstract class _LoadingError implements LoadingError { const factory _LoadingError( {required final String message, - final bool enableRetry, - final void Function()? retry}) = _$LoadingErrorImpl; + final bool allowRetry}) = _$LoadingErrorImpl; @override String get message; @override - bool get enableRetry; - @override - void Function()? get retry; + bool get allowRetry; @override @JsonKey(ignore: true) _$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith => diff --git a/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart b/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart index c895b7c..3313846 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart @@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../../../../../widget/conditional_wrapper.dart'; import '../../utilityWidgets/bloc_module.dart'; import '../../utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart'; import '../bloc/loadable_state_bloc.dart'; @@ -14,36 +15,42 @@ import 'loadable_state_primary_loading.dart'; class LoadableStateConsumer, LoadableState>, TState> extends StatelessWidget { final Widget Function(TState state, bool loading) child; - const LoadableStateConsumer({required this.child, super.key}); + final bool wrapWithScrollView; + const LoadableStateConsumer({required this.child, this.wrapWithScrollView = false, super.key}); static Duration animationDuration = const Duration(milliseconds: 200); @override Widget build(BuildContext context) { var loadableState = context.watch().state; - var childWidget = RefreshIndicator( - onRefresh: () { - loadableState.error != null && loadableState.error!.retry != null - ? loadableState.error!.retry!() - : null; - return Future.value(); - }, + var childWidget = ConditionalWrapper( + condition: loadableState.reFetch != null, + wrapper: (child) => RefreshIndicator( + onRefresh: () { + if(loadableState.reFetch != null) loadableState.reFetch!(); + return Future.value(); + }, + child: ConditionalWrapper( + condition: wrapWithScrollView, + wrapper: (child) => SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: child + ), + child: child, + ) + ), 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 BlocModule( create: (context) => LoadableStateBloc(), child: (context, bloc, state) { - bloc.loadingError = loadableState.error; + bloc.reFetch = loadableState.reFetch; return Column( children: [ LoadableStateErrorBar(visible: loadableState.showErrorBar()), diff --git a/lib/state/app/infrastructure/loadableState/view/loadable_state_error_screen.dart b/lib/state/app/infrastructure/loadableState/view/loadable_state_error_screen.dart index 0737d28..3ed7d4c 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_error_screen.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_error_screen.dart @@ -27,16 +27,16 @@ class LoadableStateErrorScreen extends StatelessWidget { if(bloc.allowRetry()) ...[ const SizedBox(height: 10), - TextButton(onPressed: () => bloc.loadingError!.retry!(), child: const Text('Erneut versuschen')), + TextButton(onPressed: () => bloc.reFetch!(), 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)) + Text("bloc.loadingError!.message", style: TextStyle(color: Theme.of(context).hintColor, fontSize: 12)) ], ], ), ), ); } -} \ No newline at end of file +} diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart index 7751907..4a55906 100644 --- a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart @@ -17,10 +17,10 @@ abstract class LoadableHydratedBloc< > { late TRepository _repository; LoadableHydratedBloc() : super(const LoadableState()) { - on>((event, emit) => emit(LoadableState(isLoading: event.loading, data: event.state(innerState ?? fromNothing())))); - on>((event, emit) => emit(LoadableState(isLoading: true, data: innerState))); + on>((event, emit) => emit(LoadableState(isLoading: event.loading, data: event.state(innerState ?? fromNothing()), reFetch: retry))); + on>((event, emit) => emit(LoadableState(isLoading: true, data: innerState))); on>((event, emit) => emit(const LoadableState())); - on>((event, emit) => emit(LoadableState(isLoading: false, data: innerState, error: event.error))); + on>((event, emit) => emit(LoadableState(isLoading: false, data: innerState, reFetch: retry, error: event.error))); _repository = repository(); fetch(); @@ -29,22 +29,25 @@ abstract class LoadableHydratedBloc< TState? get innerState => state.data; TRepository get repo => _repository; + void retry() { + log('Fetch retry triggered for ${TState.toString()}'); + add(RefetchStarted()); + fetch(); + } + void fetch() { + log('Fetching data for ${TState.toString()}'); gatherData().catchError( - (e) => add( - Error( - LoadingError( - message: e.toString(), - enableRetry: true, - retry: () { - log('Fetch retry on ${TState.toString()}'); - add(Reload()); - fetch(); - } - ) - ) - ) - ); + (e) { + log('Error while fetching ${TState.toString()}: ${e.toString()}'); + add(Error(LoadingError( + message: e.toString(), + allowRetry: true, + ))); + } + ).then((value) { + log('Fetch for ${TState.toString()} completed!'); + }); } @override diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart index c38a1ba..06462de 100644 --- a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart @@ -11,4 +11,4 @@ class Error extends LoadableHydratedBlocEvent { final LoadingError error; Error(this.error); } -class Reload extends LoadableHydratedBlocEvent {} +class RefetchStarted extends LoadableHydratedBlocEvent {} diff --git a/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart b/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart index c2193f5..375dae0 100644 --- a/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart +++ b/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart @@ -23,7 +23,7 @@ class MarianumMessageBloc extends LoadableHydratedBloc MarianumMessageRepository(); @override - MarianumMessageState fromNothing() => const MarianumMessageState(messageList: MarianumMessageList(base: "", messages: [])); + MarianumMessageState fromNothing() => const MarianumMessageState(messageList: MarianumMessageList(base: '', messages: [])); @override MarianumMessageState fromStorage(Map json) => MarianumMessageState.fromJson(json); diff --git a/lib/state/app/modules/marianumMessage/dataProvider/marianum_message_get_messages.dart b/lib/state/app/modules/marianumMessage/dataProvider/marianum_message_get_messages.dart index 7957329..1a7d8f6 100644 --- a/lib/state/app/modules/marianumMessage/dataProvider/marianum_message_get_messages.dart +++ b/lib/state/app/modules/marianumMessage/dataProvider/marianum_message_get_messages.dart @@ -6,7 +6,7 @@ class MarianumMessageGetMessages extends DataLoader { @override Future fetch() async { await Future.delayed(const Duration(seconds: 3)); - throw UnimplementedError("Test"); + // throw UnimplementedError("Test"); return const MarianumMessageList(base: '', messages: [MarianumMessage(date: '', name: 'RepoTest', url: '')]); } } diff --git a/lib/state/app/modules/marianumMessage/view/marianum_message_list_view.dart b/lib/state/app/modules/marianumMessage/view/marianum_message_list_view.dart index aaf6c68..a56bc36 100644 --- a/lib/state/app/modules/marianumMessage/view/marianum_message_list_view.dart +++ b/lib/state/app/modules/marianumMessage/view/marianum_message_list_view.dart @@ -20,8 +20,8 @@ class MarianumMessageListView extends StatelessWidget { appBar: AppBar( title: const Text('Marianum Message'), actions: [ - IconButton(onPressed: () => context.read().add(Emit((state) => MarianumMessageState(messageList: MarianumMessageList(base: "", messages: [MarianumMessage(url: "", name: "Teeest", date: "now")])))), icon: Icon(Icons.add)), - IconButton(onPressed: () => context.read().add(ClearState()), icon: Icon(Icons.add)) + IconButton(onPressed: () => context.read().add(Emit((state) => const MarianumMessageState(messageList: MarianumMessageList(base: '', messages: [MarianumMessage(url: '', name: 'Teeest', date: 'now')])))), icon: const Icon(Icons.add)), + IconButton(onPressed: () => context.read().add(ClearState()), icon: const Icon(Icons.add)) ], ), body: LoadableStateConsumer( diff --git a/lib/view/pages/overhang.dart b/lib/view/pages/overhang.dart index dcbe81c..08a5f0f 100644 --- a/lib/view/pages/overhang.dart +++ b/lib/view/pages/overhang.dart @@ -72,8 +72,8 @@ class Overhang extends StatelessWidget { trailing: const Icon(Icons.arrow_right), onTap: () => pushScreen(context, withNavBar: false, screen: const FeedbackDialog()), ), - ListTile( - leading: const Icon(Icons.science_outlined), + const ListTile( + leading: Icon(Icons.science_outlined), // onTap: () => pushScreen(context, withNavBar: false, screen: const GradeAveragesScreen()), ) ], diff --git a/lib/widget/conditional_wrapper.dart b/lib/widget/conditional_wrapper.dart new file mode 100644 index 0000000..5526cf3 --- /dev/null +++ b/lib/widget/conditional_wrapper.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class ConditionalWrapper extends StatelessWidget { + final bool condition; + final Widget child; + final Widget Function(Widget child) wrapper; + + const ConditionalWrapper({ + required this.condition, + required this.child, + required this.wrapper, + super.key, + }); + + @override + Widget build(BuildContext context) => condition ? wrapper(child) : child; +}