moved reload actions out of error context

This commit is contained in:
Elias Müller 2024-05-11 17:52:53 +02:00
parent 9fa711e460
commit 181682a424
14 changed files with 129 additions and 93 deletions

@ -4,20 +4,19 @@ import 'package:bloc/bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../loading_error.dart';
import 'loadable_state_event.dart'; import 'loadable_state_event.dart';
import 'loadable_state_state.dart'; import 'loadable_state_state.dart';
class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> { class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> {
late StreamSubscription<List<ConnectivityResult>> _updateStream; late StreamSubscription<List<ConnectivityResult>> _updateStream;
LoadingError? loadingError; void Function()? reFetch;
LoadableStateBloc() : super(const LoadableStateState(connections: null)) { LoadableStateBloc() : super(const LoadableStateState(connections: null)) {
on<ConnectivityChanged>((event, emit) { on<ConnectivityChanged>((event, emit) {
emit(event.state); emit(event.state);
if(connectivityStatusKnown() && isConnected() && loadingError != null) { if(connectivityStatusKnown() && isConnected()) {
if(!loadingError!.enableRetry) return; if(reFetch == null) return;
loadingError!.retry!(); reFetch!();
} }
}); });
@ -29,8 +28,8 @@ class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> {
bool connectivityStatusKnown() => state.connections != null; bool connectivityStatusKnown() => state.connections != null;
bool isConnected() => !(state.connections?.contains(ConnectivityResult.none) ?? true); bool isConnected() => !(state.connections?.contains(ConnectivityResult.none) ?? true);
bool allowRetry() => loadingError?.retry != null; bool allowRetry() => reFetch != null;
bool showErrorMessage() => isConnected() && loadingError != null; bool showErrorMessage() => isConnected() && reFetch != null;
IconData connectionIcon() => connectivityStatusKnown() IconData connectionIcon() => connectivityStatusKnown()
? isConnected() ? isConnected()

@ -11,6 +11,7 @@ class LoadableState<TState> with _$LoadableState {
const factory LoadableState({ const factory LoadableState({
@Default(true) bool isLoading, @Default(true) bool isLoading,
@Default(null) TState? data, @Default(null) TState? data,
@Default(null) void Function()? reFetch,
@Default(null) LoadingError? error, @Default(null) LoadingError? error,
}) = _LoadableState; }) = _LoadableState;

@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$LoadableState<TState> { mixin _$LoadableState<TState> {
bool get isLoading => throw _privateConstructorUsedError; bool get isLoading => throw _privateConstructorUsedError;
TState? get data => throw _privateConstructorUsedError; TState? get data => throw _privateConstructorUsedError;
void Function()? get reFetch => throw _privateConstructorUsedError;
LoadingError? get error => throw _privateConstructorUsedError; LoadingError? get error => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@ -31,7 +32,11 @@ abstract class $LoadableStateCopyWith<TState, $Res> {
$Res Function(LoadableState<TState>) then) = $Res Function(LoadableState<TState>) then) =
_$LoadableStateCopyWithImpl<TState, $Res, LoadableState<TState>>; _$LoadableStateCopyWithImpl<TState, $Res, LoadableState<TState>>;
@useResult @useResult
$Res call({bool isLoading, TState? data, LoadingError? error}); $Res call(
{bool isLoading,
TState? data,
void Function()? reFetch,
LoadingError? error});
$LoadingErrorCopyWith<$Res>? get error; $LoadingErrorCopyWith<$Res>? get error;
} }
@ -52,6 +57,7 @@ class _$LoadableStateCopyWithImpl<TState, $Res,
$Res call({ $Res call({
Object? isLoading = null, Object? isLoading = null,
Object? data = freezed, Object? data = freezed,
Object? reFetch = freezed,
Object? error = freezed, Object? error = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
@ -63,6 +69,10 @@ class _$LoadableStateCopyWithImpl<TState, $Res,
? _value.data ? _value.data
: data // ignore: cast_nullable_to_non_nullable : data // ignore: cast_nullable_to_non_nullable
as TState?, as TState?,
reFetch: freezed == reFetch
? _value.reFetch
: reFetch // ignore: cast_nullable_to_non_nullable
as void Function()?,
error: freezed == error error: freezed == error
? _value.error ? _value.error
: error // ignore: cast_nullable_to_non_nullable : error // ignore: cast_nullable_to_non_nullable
@ -91,7 +101,11 @@ abstract class _$$LoadableStateImplCopyWith<TState, $Res>
__$$LoadableStateImplCopyWithImpl<TState, $Res>; __$$LoadableStateImplCopyWithImpl<TState, $Res>;
@override @override
@useResult @useResult
$Res call({bool isLoading, TState? data, LoadingError? error}); $Res call(
{bool isLoading,
TState? data,
void Function()? reFetch,
LoadingError? error});
@override @override
$LoadingErrorCopyWith<$Res>? get error; $LoadingErrorCopyWith<$Res>? get error;
@ -111,6 +125,7 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
$Res call({ $Res call({
Object? isLoading = null, Object? isLoading = null,
Object? data = freezed, Object? data = freezed,
Object? reFetch = freezed,
Object? error = freezed, Object? error = freezed,
}) { }) {
return _then(_$LoadableStateImpl<TState>( return _then(_$LoadableStateImpl<TState>(
@ -122,6 +137,10 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
? _value.data ? _value.data
: data // ignore: cast_nullable_to_non_nullable : data // ignore: cast_nullable_to_non_nullable
as TState?, as TState?,
reFetch: freezed == reFetch
? _value.reFetch
: reFetch // ignore: cast_nullable_to_non_nullable
as void Function()?,
error: freezed == error error: freezed == error
? _value.error ? _value.error
: error // ignore: cast_nullable_to_non_nullable : error // ignore: cast_nullable_to_non_nullable
@ -134,7 +153,10 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
class _$LoadableStateImpl<TState> extends _LoadableState<TState> { class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
const _$LoadableStateImpl( const _$LoadableStateImpl(
{this.isLoading = true, this.data = null, this.error = null}) {this.isLoading = true,
this.data = null,
this.reFetch = null,
this.error = null})
: super._(); : super._();
@override @override
@ -145,11 +167,14 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
final TState? data; final TState? data;
@override @override
@JsonKey() @JsonKey()
final void Function()? reFetch;
@override
@JsonKey()
final LoadingError? error; final LoadingError? error;
@override @override
String toString() { String toString() {
return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, error: $error)'; return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, reFetch: $reFetch, error: $error)';
} }
@override @override
@ -160,12 +185,13 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
(identical(other.isLoading, isLoading) || (identical(other.isLoading, isLoading) ||
other.isLoading == isLoading) && other.isLoading == isLoading) &&
const DeepCollectionEquality().equals(other.data, data) && const DeepCollectionEquality().equals(other.data, data) &&
(identical(other.reFetch, reFetch) || other.reFetch == reFetch) &&
(identical(other.error, error) || other.error == error)); (identical(other.error, error) || other.error == error));
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(runtimeType, isLoading,
runtimeType, isLoading, const DeepCollectionEquality().hash(data), error); const DeepCollectionEquality().hash(data), reFetch, error);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@ -179,6 +205,7 @@ abstract class _LoadableState<TState> extends LoadableState<TState> {
const factory _LoadableState( const factory _LoadableState(
{final bool isLoading, {final bool isLoading,
final TState? data, final TState? data,
final void Function()? reFetch,
final LoadingError? error}) = _$LoadableStateImpl<TState>; final LoadingError? error}) = _$LoadableStateImpl<TState>;
const _LoadableState._() : super._(); const _LoadableState._() : super._();
@ -187,6 +214,8 @@ abstract class _LoadableState<TState> extends LoadableState<TState> {
@override @override
TState? get data; TState? get data;
@override @override
void Function()? get reFetch;
@override
LoadingError? get error; LoadingError? get error;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)

@ -6,7 +6,6 @@ part 'loading_error.freezed.dart';
class LoadingError with _$LoadingError { class LoadingError with _$LoadingError {
const factory LoadingError({ const factory LoadingError({
required String message, required String message,
@Default(false) bool enableRetry, @Default(false) bool allowRetry,
@Default(null) void Function()? retry,
}) = _LoadingError; }) = _LoadingError;
} }

@ -17,8 +17,7 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc /// @nodoc
mixin _$LoadingError { mixin _$LoadingError {
String get message => throw _privateConstructorUsedError; String get message => throw _privateConstructorUsedError;
bool get enableRetry => throw _privateConstructorUsedError; bool get allowRetry => throw _privateConstructorUsedError;
void Function()? get retry => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
$LoadingErrorCopyWith<LoadingError> get copyWith => $LoadingErrorCopyWith<LoadingError> get copyWith =>
@ -31,7 +30,7 @@ abstract class $LoadingErrorCopyWith<$Res> {
LoadingError value, $Res Function(LoadingError) then) = LoadingError value, $Res Function(LoadingError) then) =
_$LoadingErrorCopyWithImpl<$Res, LoadingError>; _$LoadingErrorCopyWithImpl<$Res, LoadingError>;
@useResult @useResult
$Res call({String message, bool enableRetry, void Function()? retry}); $Res call({String message, bool allowRetry});
} }
/// @nodoc /// @nodoc
@ -48,22 +47,17 @@ class _$LoadingErrorCopyWithImpl<$Res, $Val extends LoadingError>
@override @override
$Res call({ $Res call({
Object? message = null, Object? message = null,
Object? enableRetry = null, Object? allowRetry = null,
Object? retry = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
message: null == message message: null == message
? _value.message ? _value.message
: message // ignore: cast_nullable_to_non_nullable : message // ignore: cast_nullable_to_non_nullable
as String, as String,
enableRetry: null == enableRetry allowRetry: null == allowRetry
? _value.enableRetry ? _value.allowRetry
: enableRetry // ignore: cast_nullable_to_non_nullable : allowRetry // ignore: cast_nullable_to_non_nullable
as bool, as bool,
retry: freezed == retry
? _value.retry
: retry // ignore: cast_nullable_to_non_nullable
as void Function()?,
) as $Val); ) as $Val);
} }
} }
@ -76,7 +70,7 @@ abstract class _$$LoadingErrorImplCopyWith<$Res>
__$$LoadingErrorImplCopyWithImpl<$Res>; __$$LoadingErrorImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({String message, bool enableRetry, void Function()? retry}); $Res call({String message, bool allowRetry});
} }
/// @nodoc /// @nodoc
@ -91,22 +85,17 @@ class __$$LoadingErrorImplCopyWithImpl<$Res>
@override @override
$Res call({ $Res call({
Object? message = null, Object? message = null,
Object? enableRetry = null, Object? allowRetry = null,
Object? retry = freezed,
}) { }) {
return _then(_$LoadingErrorImpl( return _then(_$LoadingErrorImpl(
message: null == message message: null == message
? _value.message ? _value.message
: message // ignore: cast_nullable_to_non_nullable : message // ignore: cast_nullable_to_non_nullable
as String, as String,
enableRetry: null == enableRetry allowRetry: null == allowRetry
? _value.enableRetry ? _value.allowRetry
: enableRetry // ignore: cast_nullable_to_non_nullable : allowRetry // ignore: cast_nullable_to_non_nullable
as bool, 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 /// @nodoc
class _$LoadingErrorImpl implements _LoadingError { class _$LoadingErrorImpl implements _LoadingError {
const _$LoadingErrorImpl( const _$LoadingErrorImpl({required this.message, this.allowRetry = false});
{required this.message, this.enableRetry = false, this.retry = null});
@override @override
final String message; final String message;
@override @override
@JsonKey() @JsonKey()
final bool enableRetry; final bool allowRetry;
@override
@JsonKey()
final void Function()? retry;
@override @override
String toString() { String toString() {
return 'LoadingError(message: $message, enableRetry: $enableRetry, retry: $retry)'; return 'LoadingError(message: $message, allowRetry: $allowRetry)';
} }
@override @override
@ -137,13 +122,12 @@ class _$LoadingErrorImpl implements _LoadingError {
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$LoadingErrorImpl && other is _$LoadingErrorImpl &&
(identical(other.message, message) || other.message == message) && (identical(other.message, message) || other.message == message) &&
(identical(other.enableRetry, enableRetry) || (identical(other.allowRetry, allowRetry) ||
other.enableRetry == enableRetry) && other.allowRetry == allowRetry));
(identical(other.retry, retry) || other.retry == retry));
} }
@override @override
int get hashCode => Object.hash(runtimeType, message, enableRetry, retry); int get hashCode => Object.hash(runtimeType, message, allowRetry);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@ -155,15 +139,12 @@ class _$LoadingErrorImpl implements _LoadingError {
abstract class _LoadingError implements LoadingError { abstract class _LoadingError implements LoadingError {
const factory _LoadingError( const factory _LoadingError(
{required final String message, {required final String message,
final bool enableRetry, final bool allowRetry}) = _$LoadingErrorImpl;
final void Function()? retry}) = _$LoadingErrorImpl;
@override @override
String get message; String get message;
@override @override
bool get enableRetry; bool get allowRetry;
@override
void Function()? get retry;
@override @override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith => _$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith =>

@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../../../../widget/conditional_wrapper.dart';
import '../../utilityWidgets/bloc_module.dart'; import '../../utilityWidgets/bloc_module.dart';
import '../../utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart'; import '../../utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import '../bloc/loadable_state_bloc.dart'; import '../bloc/loadable_state_bloc.dart';
@ -14,36 +15,42 @@ import 'loadable_state_primary_loading.dart';
class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>>, TState> extends StatelessWidget { class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>>, TState> extends StatelessWidget {
final Widget Function(TState state, bool loading) child; 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); static Duration animationDuration = const Duration(milliseconds: 200);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var loadableState = context.watch<TController>().state; var loadableState = context.watch<TController>().state;
var childWidget = RefreshIndicator( var childWidget = ConditionalWrapper(
onRefresh: () { condition: loadableState.reFetch != null,
loadableState.error != null && loadableState.error!.retry != null wrapper: (child) => RefreshIndicator(
? loadableState.error!.retry!() onRefresh: () {
: null; if(loadableState.reFetch != null) loadableState.reFetch!();
return Future.value(); return Future.value();
}, },
child: ConditionalWrapper(
condition: wrapWithScrollView,
wrapper: (child) => SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: child
),
child: child,
)
),
child: SizedBox( child: SizedBox(
height: MediaQuery.of(context).size.height, height: MediaQuery.of(context).size.height,
child: loadableState.showContent() child: loadableState.showContent()
? child(loadableState.data, loadableState.isLoading) ? child(loadableState.data, loadableState.isLoading)
: const SizedBox.shrink(), : const SizedBox.shrink(),
), ),
// SingleChildScrollView( // TODO not all childs are reloadable
// physics: const AlwaysScrollableScrollPhysics(),
// child:
// ),
); );
return BlocModule<LoadableStateBloc, LoadableStateState>( return BlocModule<LoadableStateBloc, LoadableStateState>(
create: (context) => LoadableStateBloc(), create: (context) => LoadableStateBloc(),
child: (context, bloc, state) { child: (context, bloc, state) {
bloc.loadingError = loadableState.error; bloc.reFetch = loadableState.reFetch;
return Column( return Column(
children: [ children: [
LoadableStateErrorBar(visible: loadableState.showErrorBar()), LoadableStateErrorBar(visible: loadableState.showErrorBar()),

@ -27,12 +27,12 @@ class LoadableStateErrorScreen extends StatelessWidget {
if(bloc.allowRetry()) ...[ if(bloc.allowRetry()) ...[
const SizedBox(height: 10), 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()) ...[ if(bloc.showErrorMessage()) ...[
const SizedBox(height: 40), 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))
], ],
], ],
), ),

@ -17,10 +17,10 @@ abstract class LoadableHydratedBloc<
> { > {
late TRepository _repository; late TRepository _repository;
LoadableHydratedBloc() : super(const LoadableState()) { LoadableHydratedBloc() : super(const LoadableState()) {
on<Emit<TState>>((event, emit) => emit(LoadableState(isLoading: event.loading, data: event.state(innerState ?? fromNothing())))); on<Emit<TState>>((event, emit) => emit(LoadableState(isLoading: event.loading, data: event.state(innerState ?? fromNothing()), reFetch: retry)));
on<Reload<TState>>((event, emit) => emit(LoadableState(isLoading: true, data: innerState))); on<RefetchStarted<TState>>((event, emit) => emit(LoadableState(isLoading: true, data: innerState)));
on<ClearState<TState>>((event, emit) => emit(const LoadableState())); on<ClearState<TState>>((event, emit) => emit(const LoadableState()));
on<Error<TState>>((event, emit) => emit(LoadableState(isLoading: false, data: innerState, error: event.error))); on<Error<TState>>((event, emit) => emit(LoadableState(isLoading: false, data: innerState, reFetch: retry, error: event.error)));
_repository = repository(); _repository = repository();
fetch(); fetch();
@ -29,22 +29,25 @@ abstract class LoadableHydratedBloc<
TState? get innerState => state.data; TState? get innerState => state.data;
TRepository get repo => _repository; TRepository get repo => _repository;
void retry() {
log('Fetch retry triggered for ${TState.toString()}');
add(RefetchStarted<TState>());
fetch();
}
void fetch() { void fetch() {
log('Fetching data for ${TState.toString()}');
gatherData().catchError( gatherData().catchError(
(e) => add( (e) {
Error( log('Error while fetching ${TState.toString()}: ${e.toString()}');
LoadingError( add(Error(LoadingError(
message: e.toString(), message: e.toString(),
enableRetry: true, allowRetry: true,
retry: () { )));
log('Fetch retry on ${TState.toString()}'); }
add(Reload<TState>()); ).then((value) {
fetch(); log('Fetch for ${TState.toString()} completed!');
} });
)
)
)
);
} }
@override @override

@ -11,4 +11,4 @@ class Error<TState> extends LoadableHydratedBlocEvent<TState> {
final LoadingError error; final LoadingError error;
Error(this.error); Error(this.error);
} }
class Reload<TState> extends LoadableHydratedBlocEvent<TState> {} class RefetchStarted<TState> extends LoadableHydratedBlocEvent<TState> {}

@ -23,7 +23,7 @@ class MarianumMessageBloc extends LoadableHydratedBloc<MarianumMessageEvent, Mar
MarianumMessageRepository repository() => MarianumMessageRepository(); MarianumMessageRepository repository() => MarianumMessageRepository();
@override @override
MarianumMessageState fromNothing() => const MarianumMessageState(messageList: MarianumMessageList(base: "", messages: [])); MarianumMessageState fromNothing() => const MarianumMessageState(messageList: MarianumMessageList(base: '', messages: []));
@override @override
MarianumMessageState fromStorage(Map<String, dynamic> json) => MarianumMessageState.fromJson(json); MarianumMessageState fromStorage(Map<String, dynamic> json) => MarianumMessageState.fromJson(json);

@ -6,7 +6,7 @@ class MarianumMessageGetMessages extends DataLoader<MarianumMessageList> {
@override @override
Future<MarianumMessageList> fetch() async { Future<MarianumMessageList> fetch() async {
await Future.delayed(const Duration(seconds: 3)); await Future.delayed(const Duration(seconds: 3));
throw UnimplementedError("Test"); // throw UnimplementedError("Test");
return const MarianumMessageList(base: '', messages: [MarianumMessage(date: '', name: 'RepoTest', url: '')]); return const MarianumMessageList(base: '', messages: [MarianumMessage(date: '', name: 'RepoTest', url: '')]);
} }
} }

@ -20,8 +20,8 @@ class MarianumMessageListView extends StatelessWidget {
appBar: AppBar( appBar: AppBar(
title: const Text('Marianum Message'), title: const Text('Marianum Message'),
actions: [ actions: [
IconButton(onPressed: () => context.read<MarianumMessageBloc>().add(Emit((state) => MarianumMessageState(messageList: MarianumMessageList(base: "", messages: [MarianumMessage(url: "", name: "Teeest", date: "now")])))), icon: Icon(Icons.add)), IconButton(onPressed: () => context.read<MarianumMessageBloc>().add(Emit((state) => const MarianumMessageState(messageList: MarianumMessageList(base: '', messages: [MarianumMessage(url: '', name: 'Teeest', date: 'now')])))), icon: const Icon(Icons.add)),
IconButton(onPressed: () => context.read<MarianumMessageBloc>().add(ClearState()), icon: Icon(Icons.add)) IconButton(onPressed: () => context.read<MarianumMessageBloc>().add(ClearState()), icon: const Icon(Icons.add))
], ],
), ),
body: LoadableStateConsumer<MarianumMessageBloc, MarianumMessageState>( body: LoadableStateConsumer<MarianumMessageBloc, MarianumMessageState>(

@ -72,8 +72,8 @@ class Overhang extends StatelessWidget {
trailing: const Icon(Icons.arrow_right), trailing: const Icon(Icons.arrow_right),
onTap: () => pushScreen(context, withNavBar: false, screen: const FeedbackDialog()), onTap: () => pushScreen(context, withNavBar: false, screen: const FeedbackDialog()),
), ),
ListTile( const ListTile(
leading: const Icon(Icons.science_outlined), leading: Icon(Icons.science_outlined),
// onTap: () => pushScreen(context, withNavBar: false, screen: const GradeAveragesScreen()), // onTap: () => pushScreen(context, withNavBar: false, screen: const GradeAveragesScreen()),
) )
], ],

@ -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;
}