diff --git a/lib/state/app/base/account/account_controller.dart b/lib/state/app/base/account/account_controller.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/state/app/infrastructure/dataLoader/mhsl_data_loader.dart b/lib/state/app/infrastructure/dataLoader/mhsl_data_loader.dart index 060e37d..0825587 100644 --- a/lib/state/app/infrastructure/dataLoader/mhsl_data_loader.dart +++ b/lib/state/app/infrastructure/dataLoader/mhsl_data_loader.dart @@ -4,6 +4,6 @@ import 'data_loader.dart'; abstract class MhslDataLoader<TResult> extends DataLoader<TResult> { MhslDataLoader() : super(Dio(BaseOptions( - baseUrl: 'https://mhsl.eu/marianum/marianummobile/' + baseUrl: 'https://mhsl.eu/marianum/marianummobile/' ))); } 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 1738a81..89998bb 100644 --- a/lib/state/app/infrastructure/loadableState/bloc/loadable_state_bloc.dart +++ b/lib/state/app/infrastructure/loadableState/bloc/loadable_state_bloc.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; import 'loadable_state_event.dart'; import 'loadable_state_state.dart'; @@ -29,7 +30,6 @@ class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> { bool connectivityStatusKnown() => state.connections != null; bool isConnected() => !(state.connections?.contains(ConnectivityResult.none) ?? true); bool allowRetry() => reFetch != null; - bool showErrorMessage() => isConnected() && reFetch != null; IconData connectionIcon() => connectivityStatusKnown() ? isConnected() @@ -37,10 +37,10 @@ class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> { : Icons.signal_wifi_connected_no_internet_4 : Icons.device_unknown; - String connectionText() => connectivityStatusKnown() + String connectionText({int? lastUpdated}) => connectivityStatusKnown() ? isConnected() ? 'Verbindung fehlgeschlagen' - : 'Offline' + : 'Offline${lastUpdated == null ? '' : ' - Stand von ${Jiffy.parseFromMillisecondsSinceEpoch(lastUpdated).fromNow()}'}' : 'Unbekannte Fehlerursache'; @override diff --git a/lib/state/app/infrastructure/loadableState/loadable_state.dart b/lib/state/app/infrastructure/loadableState/loadable_state.dart index 4a6c22a..ac1626a 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<TState> with _$LoadableState { const factory LoadableState({ @Default(true) bool isLoading, @Default(null) TState? data, + @Default(null) int? lastFetch, @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 03ac3a3..e66b4a8 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<TState> { bool get isLoading => throw _privateConstructorUsedError; TState? get data => throw _privateConstructorUsedError; + int? get lastFetch => throw _privateConstructorUsedError; void Function()? get reFetch => throw _privateConstructorUsedError; LoadingError? get error => throw _privateConstructorUsedError; @@ -35,6 +36,7 @@ abstract class $LoadableStateCopyWith<TState, $Res> { $Res call( {bool isLoading, TState? data, + int? lastFetch, void Function()? reFetch, LoadingError? error}); @@ -57,6 +59,7 @@ class _$LoadableStateCopyWithImpl<TState, $Res, $Res call({ Object? isLoading = null, Object? data = freezed, + Object? lastFetch = freezed, Object? reFetch = freezed, Object? error = freezed, }) { @@ -69,6 +72,10 @@ class _$LoadableStateCopyWithImpl<TState, $Res, ? _value.data : data // ignore: cast_nullable_to_non_nullable as TState?, + lastFetch: freezed == lastFetch + ? _value.lastFetch + : lastFetch // ignore: cast_nullable_to_non_nullable + as int?, reFetch: freezed == reFetch ? _value.reFetch : reFetch // ignore: cast_nullable_to_non_nullable @@ -104,6 +111,7 @@ abstract class _$$LoadableStateImplCopyWith<TState, $Res> $Res call( {bool isLoading, TState? data, + int? lastFetch, void Function()? reFetch, LoadingError? error}); @@ -125,6 +133,7 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res> $Res call({ Object? isLoading = null, Object? data = freezed, + Object? lastFetch = freezed, Object? reFetch = freezed, Object? error = freezed, }) { @@ -137,6 +146,10 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res> ? _value.data : data // ignore: cast_nullable_to_non_nullable as TState?, + lastFetch: freezed == lastFetch + ? _value.lastFetch + : lastFetch // ignore: cast_nullable_to_non_nullable + as int?, reFetch: freezed == reFetch ? _value.reFetch : reFetch // ignore: cast_nullable_to_non_nullable @@ -155,6 +168,7 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> { const _$LoadableStateImpl( {this.isLoading = true, this.data = null, + this.lastFetch = null, this.reFetch = null, this.error = null}) : super._(); @@ -167,6 +181,9 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> { final TState? data; @override @JsonKey() + final int? lastFetch; + @override + @JsonKey() final void Function()? reFetch; @override @JsonKey() @@ -174,7 +191,7 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> { @override String toString() { - return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, reFetch: $reFetch, error: $error)'; + return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, lastFetch: $lastFetch, reFetch: $reFetch, error: $error)'; } @override @@ -185,13 +202,15 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> { (identical(other.isLoading, isLoading) || other.isLoading == isLoading) && const DeepCollectionEquality().equals(other.data, data) && + (identical(other.lastFetch, lastFetch) || + other.lastFetch == lastFetch) && (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), reFetch, error); + const DeepCollectionEquality().hash(data), lastFetch, reFetch, error); @JsonKey(ignore: true) @override @@ -205,6 +224,7 @@ abstract class _LoadableState<TState> extends LoadableState<TState> { const factory _LoadableState( {final bool isLoading, final TState? data, + final int? lastFetch, final void Function()? reFetch, final LoadingError? error}) = _$LoadableStateImpl<TState>; const _LoadableState._() : super._(); @@ -214,6 +234,8 @@ abstract class _LoadableState<TState> extends LoadableState<TState> { @override TState? get data; @override + int? get lastFetch; + @override void Function()? get reFetch; @override LoadingError? get error; 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 3313846..7d9244e 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart @@ -53,13 +53,13 @@ class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<T bloc.reFetch = loadableState.reFetch; return Column( children: [ - LoadableStateErrorBar(visible: loadableState.showErrorBar()), + LoadableStateErrorBar(visible: loadableState.showErrorBar(), message: loadableState.error?.message, lastUpdated: loadableState.lastFetch), Expanded( child: Stack( children: [ LoadableStatePrimaryLoading(visible: loadableState.showPrimaryLoading()), LoadableStateBackgroundLoading(visible: loadableState.showBackgroundLoading()), - LoadableStateErrorScreen(visible: loadableState.showError()), + LoadableStateErrorScreen(visible: loadableState.showError(), message: loadableState.error?.message), AnimatedOpacity( opacity: loadableState.showContent() ? 1.0 : 0.0, diff --git a/lib/state/app/infrastructure/loadableState/view/loadable_state_error_bar.dart b/lib/state/app/infrastructure/loadableState/view/loadable_state_error_bar.dart index ee3ee67..2752714 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_error_bar.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_error_bar.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../../widget/infoDialog.dart'; import '../bloc/loadable_state_bloc.dart'; class LoadableStateErrorBar extends StatelessWidget { final bool visible; - const LoadableStateErrorBar({required this.visible, super.key}); + final String? message; + final int? lastUpdated; + const LoadableStateErrorBar({required this.visible, this.message, this.lastUpdated, super.key}); final Duration animationDuration = const Duration(milliseconds: 200); @@ -29,25 +32,31 @@ class LoadableStateErrorBar extends StatelessWidget { builder: (context) { var bloc = context.watch<LoadableStateBloc>(); var status = ( - icon: bloc.connectionIcon(), - text: bloc.connectionText(), - color: bloc.connectivityStatusKnown() && !bloc.isConnected() + icon: bloc.connectionIcon(), + text: bloc.connectionText(lastUpdated: lastUpdated), + 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 InkWell( + onTap: () { + if(!bloc.isConnected()) return; + InfoDialog.show(context, 'Exception: ${message.toString()}'); + }, + child: 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)) + ], + ), ), ); }, 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 3ed7d4c..cb68e76 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 @@ -6,7 +6,8 @@ import 'loadable_state_consumer.dart'; class LoadableStateErrorScreen extends StatelessWidget { final bool visible; - const LoadableStateErrorScreen({required this.visible, super.key}); + final String? message; + const LoadableStateErrorScreen({required this.visible, this.message, super.key}); @override @@ -28,11 +29,19 @@ class LoadableStateErrorScreen extends StatelessWidget { if(bloc.allowRetry()) ...[ const SizedBox(height: 10), 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)) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + message ?? 'Task failed successfully :)', + style: TextStyle( + color: Theme.of(context).hintColor, + fontSize: 12 + ), + maxLines: 10, + overflow: TextOverflow.ellipsis, + ), + ), ], ], ), 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 4a55906..116bad2 100644 --- a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart @@ -6,6 +6,7 @@ import '../../loadableState/loading_error.dart'; import '../../repository/repository.dart'; import 'loadable_hydrated_bloc_event.dart'; import '../../loadableState/loadable_state.dart'; +import 'loadable_save_context.dart'; abstract class LoadableHydratedBloc< TEvent extends LoadableHydratedBlocEvent<TState>, @@ -17,10 +18,29 @@ abstract class LoadableHydratedBloc< > { late TRepository _repository; LoadableHydratedBloc() : super(const LoadableState()) { - on<Emit<TState>>((event, emit) => emit(LoadableState(isLoading: event.loading, data: event.state(innerState ?? fromNothing()), reFetch: retry))); - on<RefetchStarted<TState>>((event, emit) => emit(LoadableState(isLoading: true, data: innerState))); + + on<Emit<TState>>((event, emit) => emit(LoadableState( + isLoading: event.loading, + data: event.state(innerState ?? fromNothing()), + lastFetch: DateTime.now().millisecondsSinceEpoch, + reFetch: retry + ))); + + on<RefetchStarted<TState>>((event, emit) => emit(LoadableState( + isLoading: true, + data: innerState, + lastFetch: state.lastFetch + ))); + on<ClearState<TState>>((event, emit) => emit(const LoadableState())); - on<Error<TState>>((event, emit) => emit(LoadableState(isLoading: false, data: innerState, reFetch: retry, error: event.error))); + + on<Error<TState>>((event, emit) => emit(LoadableState( + isLoading: false, + data: innerState, + lastFetch: state.lastFetch, + reFetch: retry, + error: event.error + ))); _repository = repository(); fetch(); @@ -41,19 +61,26 @@ abstract class LoadableHydratedBloc< (e) { log('Error while fetching ${TState.toString()}: ${e.toString()}'); add(Error(LoadingError( - message: e.toString(), + message: e.message, allowRetry: true, ))); - } + }, ).then((value) { log('Fetch for ${TState.toString()} completed!'); }); } @override - fromJson(Map<String, dynamic> json) => LoadableState(isLoading: true, data: fromStorage(json)); + fromJson(Map<String, dynamic> json) { + var rawData = LoadableSaveContext.unwrap(json); + return LoadableState(isLoading: true, lastFetch: rawData.meta.timestamp, data: fromStorage(rawData.data)); + } + @override - Map<String, dynamic>? toJson(LoadableState<TState> state) => state.data == null ? {} : state.data.toJson(); + Map<String, dynamic>? toJson(LoadableState<TState> state) => LoadableSaveContext.wrap( + toStorage(state.data), + state.lastFetch ?? DateTime.now().millisecondsSinceEpoch + ); Future<void> gatherData(); TRepository repository(); diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.dart new file mode 100644 index 0000000..e4d68d5 --- /dev/null +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'loadable_save_context.freezed.dart'; +part 'loadable_save_context.g.dart'; + +@freezed +class LoadableSaveContext with _$LoadableSaveContext { + const LoadableSaveContext._(); + const factory LoadableSaveContext({ + required int timestamp, + }) = _LoadableSaveContext; + + factory LoadableSaveContext.fromJson(Map<String, dynamic> json) => _$LoadableSaveContextFromJson(json); + + static String dataKey = 'data'; + static String metaKey = 'meta'; + + static Map<String, dynamic> wrap(Map<String, dynamic>? data, int lastFetch) => + {dataKey: data, metaKey: LoadableSaveContext(timestamp: lastFetch).toJson()}; + + static ({Map<String, dynamic> data, LoadableSaveContext meta}) unwrap(Map<String, dynamic> data) => + (data: data[dataKey] as Map<String, dynamic>, meta: LoadableSaveContext.fromJson(data[metaKey])); +} + diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.freezed.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.freezed.dart new file mode 100644 index 0000000..649526e --- /dev/null +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.freezed.dart @@ -0,0 +1,155 @@ +// 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_save_context.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'); + +LoadableSaveContext _$LoadableSaveContextFromJson(Map<String, dynamic> json) { + return _LoadableSaveContext.fromJson(json); +} + +/// @nodoc +mixin _$LoadableSaveContext { + int get timestamp => throw _privateConstructorUsedError; + + Map<String, dynamic> toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $LoadableSaveContextCopyWith<LoadableSaveContext> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoadableSaveContextCopyWith<$Res> { + factory $LoadableSaveContextCopyWith( + LoadableSaveContext value, $Res Function(LoadableSaveContext) then) = + _$LoadableSaveContextCopyWithImpl<$Res, LoadableSaveContext>; + @useResult + $Res call({int timestamp}); +} + +/// @nodoc +class _$LoadableSaveContextCopyWithImpl<$Res, $Val extends LoadableSaveContext> + implements $LoadableSaveContextCopyWith<$Res> { + _$LoadableSaveContextCopyWithImpl(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? timestamp = null, + }) { + return _then(_value.copyWith( + timestamp: null == timestamp + ? _value.timestamp + : timestamp // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$LoadableSaveContextImplCopyWith<$Res> + implements $LoadableSaveContextCopyWith<$Res> { + factory _$$LoadableSaveContextImplCopyWith(_$LoadableSaveContextImpl value, + $Res Function(_$LoadableSaveContextImpl) then) = + __$$LoadableSaveContextImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int timestamp}); +} + +/// @nodoc +class __$$LoadableSaveContextImplCopyWithImpl<$Res> + extends _$LoadableSaveContextCopyWithImpl<$Res, _$LoadableSaveContextImpl> + implements _$$LoadableSaveContextImplCopyWith<$Res> { + __$$LoadableSaveContextImplCopyWithImpl(_$LoadableSaveContextImpl _value, + $Res Function(_$LoadableSaveContextImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? timestamp = null, + }) { + return _then(_$LoadableSaveContextImpl( + timestamp: null == timestamp + ? _value.timestamp + : timestamp // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$LoadableSaveContextImpl extends _LoadableSaveContext { + const _$LoadableSaveContextImpl({required this.timestamp}) : super._(); + + factory _$LoadableSaveContextImpl.fromJson(Map<String, dynamic> json) => + _$$LoadableSaveContextImplFromJson(json); + + @override + final int timestamp; + + @override + String toString() { + return 'LoadableSaveContext(timestamp: $timestamp)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadableSaveContextImpl && + (identical(other.timestamp, timestamp) || + other.timestamp == timestamp)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, timestamp); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$LoadableSaveContextImplCopyWith<_$LoadableSaveContextImpl> get copyWith => + __$$LoadableSaveContextImplCopyWithImpl<_$LoadableSaveContextImpl>( + this, _$identity); + + @override + Map<String, dynamic> toJson() { + return _$$LoadableSaveContextImplToJson( + this, + ); + } +} + +abstract class _LoadableSaveContext extends LoadableSaveContext { + const factory _LoadableSaveContext({required final int timestamp}) = + _$LoadableSaveContextImpl; + const _LoadableSaveContext._() : super._(); + + factory _LoadableSaveContext.fromJson(Map<String, dynamic> json) = + _$LoadableSaveContextImpl.fromJson; + + @override + int get timestamp; + @override + @JsonKey(ignore: true) + _$$LoadableSaveContextImplCopyWith<_$LoadableSaveContextImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.g.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.g.dart new file mode 100644 index 0000000..c536225 --- /dev/null +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'loadable_save_context.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LoadableSaveContextImpl _$$LoadableSaveContextImplFromJson( + Map<String, dynamic> json) => + _$LoadableSaveContextImpl( + timestamp: json['timestamp'] as int, + ); + +Map<String, dynamic> _$$LoadableSaveContextImplToJson( + _$LoadableSaveContextImpl instance) => + <String, dynamic>{ + 'timestamp': instance.timestamp, + }; diff --git a/lib/view/pages/overhang.dart b/lib/view/pages/overhang.dart index 08a5f0f..6269307 100644 --- a/lib/view/pages/overhang.dart +++ b/lib/view/pages/overhang.dart @@ -72,10 +72,6 @@ class Overhang extends StatelessWidget { trailing: const Icon(Icons.arrow_right), onTap: () => pushScreen(context, withNavBar: false, screen: const FeedbackDialog()), ), - const ListTile( - leading: Icon(Icons.science_outlined), - // onTap: () => pushScreen(context, withNavBar: false, screen: const GradeAveragesScreen()), - ) ], ), );