diff --git a/lib/state/app/infrastructure/dataLoader/data_loader.dart b/lib/state/app/infrastructure/dataLoader/data_loader.dart index 6f84cb1..47306ba 100644 --- a/lib/state/app/infrastructure/dataLoader/data_loader.dart +++ b/lib/state/app/infrastructure/dataLoader/data_loader.dart @@ -1,4 +1,3 @@ abstract class DataLoader<TResult> { - - Future<TResult> fetch(); + Future<TResult> fetch(); } diff --git a/lib/state/app/infrastructure/loadableState/loadable_hydrated_bloc.dart b/lib/state/app/infrastructure/loadableState/loadable_hydrated_bloc.dart deleted file mode 100644 index 5694249..0000000 --- a/lib/state/app/infrastructure/loadableState/loadable_hydrated_bloc.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:hydrated_bloc/hydrated_bloc.dart'; - -import '../repository/repository.dart'; -import 'loadable_state.dart'; - -sealed class LoadableHydratedBlocEvent {} -class DataRecieved extends LoadableHydratedBlocEvent {} - -abstract class LoadableHydratedBloc<TEvent, TState> extends HydratedBloc<TEvent, LoadableState<TState>> { - LoadableHydratedBloc() : super(const LoadableState()) { - repository().load(); - - } - - Repository repository(); - - - @override - fromJson(Map<String, dynamic> json) => LoadableState(isLoading: true, data: fromStorage(json)); - @override - Map<String, dynamic>? toJson(state) => state.data.toJson(); - - TState fromStorage(Map<String, dynamic> json); - Map<String, dynamic>? toStorage(TState state); -} diff --git a/lib/state/app/infrastructure/loadableState/view/loadable_state_background_loading.dart b/lib/state/app/infrastructure/loadableState/view/loadable_state_background_loading.dart index 1c10bb0..5147e45 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_background_loading.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_background_loading.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; +import 'loadable_state_consumer.dart'; + class LoadableStateBackgroundLoading extends StatelessWidget { final bool visible; const LoadableStateBackgroundLoading({required this.visible, super.key}); - final Duration animationDuration = const Duration(milliseconds: 200); - @override Widget build(BuildContext context) => AnimatedSwitcher( - duration: animationDuration, + duration: LoadableStateConsumer.animationDuration, transitionBuilder: (Widget child, Animation<double> animation) => SlideTransition( position: Tween<Offset>( begin: const Offset(0.0, -1.0), 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 c3d3c75..513a13e 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart @@ -2,36 +2,36 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../../utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart'; import '../loadable_state.dart'; import 'loadable_state_background_loading.dart'; import 'loadable_state_error_bar.dart'; import 'loadable_state_primary_loading.dart'; -// TODO might be a simpler way -class LoadableStateConsumer<TController extends Bloc<dynamic, TWrappedState>, TWrappedState extends LoadableState<TState>, TState> extends StatelessWidget { - final Widget Function(TState state) child; +class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>>, TState> extends StatelessWidget { + final Widget Function(TState state, bool loading) child; const LoadableStateConsumer({required this.child, super.key}); static Duration animationDuration = const Duration(milliseconds: 200); @override Widget build(BuildContext context) { - var state = context.read<TController>().state as LoadableState<TState>; + var loadableState = context.watch<TController>().state; return Column( children: [ - LoadableStateErrorBar(visible: state.showError()), + LoadableStateErrorBar(visible: loadableState.showError()), Expanded( child: Stack( children: [ - LoadableStatePrimaryLoading(visible: state.showPrimaryLoading()), - LoadableStateBackgroundLoading(visible: state.showBackgroundLoading()), + LoadableStatePrimaryLoading(visible: loadableState.showPrimaryLoading()), + LoadableStateBackgroundLoading(visible: loadableState.showBackgroundLoading()), AnimatedOpacity( - opacity: state.showContent() ? 1.0 : 0.0, + opacity: loadableState.showContent() ? 1.0 : 0.0, duration: animationDuration, curve: Curves.easeInOut, - child: state.showContent() ? child(state.data) : const SizedBox.shrink() + child: loadableState.showContent() ? child(loadableState.data, loadableState.isLoading) : const SizedBox.shrink() ), ], ), 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 4e21c72..b5c1f55 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,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../utilityWidgets/bloc_providing_builder.dart'; +import '../../utilityWidgets/bloc_module.dart'; import '../bloc/loadable_state_bloc.dart'; import '../bloc/loadable_state_state.dart'; @@ -12,7 +12,7 @@ class LoadableStateErrorBar extends StatelessWidget { final Duration animationDuration = const Duration(milliseconds: 200); @override - Widget build(BuildContext context) => BlocProvidingBuilder<LoadableStateBloc, LoadableStateState>( + Widget build(BuildContext context) => BlocModule<LoadableStateBloc, LoadableStateState>( create: (context) => LoadableStateBloc(), child: (context, state) => AnimatedSize( duration: animationDuration, diff --git a/lib/state/app/infrastructure/loadableState/view/loadable_state_primary_loading.dart b/lib/state/app/infrastructure/loadableState/view/loadable_state_primary_loading.dart index a01ba28..053aaba 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_primary_loading.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_primary_loading.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; +import 'loadable_state_consumer.dart'; + class LoadableStatePrimaryLoading extends StatelessWidget { final bool visible; const LoadableStatePrimaryLoading({required this.visible, super.key}); - final Duration animationDuration = const Duration(milliseconds: 200); - @override Widget build(BuildContext context) => AnimatedOpacity( opacity: visible ? 1.0 : 0.0, - duration: animationDuration, + duration: LoadableStateConsumer.animationDuration, curve: Curves.easeInOut, child: const Center(child: CircularProgressIndicator()), ); diff --git a/lib/state/app/infrastructure/repository/repository.dart b/lib/state/app/infrastructure/repository/repository.dart index 4a6caae..58a4e85 100644 --- a/lib/state/app/infrastructure/repository/repository.dart +++ b/lib/state/app/infrastructure/repository/repository.dart @@ -1,13 +1 @@ -import '../dataLoader/data_loader.dart'; - -abstract class Repository { - final List<DataLoader> dataLoaders; - - Repository(this.dataLoaders); - - Future<void> load() async { - dataLoaders.forEach((element) { - element.fetch(); - }); - } -} +abstract class Repository<TState> {} diff --git a/lib/state/app/infrastructure/utilityWidgets/bloc_providing_builder.dart b/lib/state/app/infrastructure/utilityWidgets/bloc_module.dart similarity index 64% rename from lib/state/app/infrastructure/utilityWidgets/bloc_providing_builder.dart rename to lib/state/app/infrastructure/utilityWidgets/bloc_module.dart index 4c98579..3a61b9c 100644 --- a/lib/state/app/infrastructure/utilityWidgets/bloc_providing_builder.dart +++ b/lib/state/app/infrastructure/utilityWidgets/bloc_module.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class BlocProvidingBuilder<TBloc extends StateStreamableSource<TState>, TState> extends StatelessWidget { +class BlocModule<TBloc extends StateStreamableSource<TState>, TState> extends StatelessWidget { final TBloc Function(BuildContext context) create; final Widget Function(BuildContext context, TState state) child; - const BlocProvidingBuilder({required this.create, required this.child, super.key}); + const BlocModule({required this.create, required this.child, super.key}); @override Widget build(BuildContext context) => BlocProvider<TBloc>(create: create, child: BlocBuilder<TBloc, TState>(builder: child)); diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart new file mode 100644 index 0000000..7f6d8a8 --- /dev/null +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart @@ -0,0 +1,31 @@ +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import '../../repository/repository.dart'; +import 'loadable_hydrated_bloc_event.dart'; +import '../../loadableState/loadable_state.dart'; + +abstract class LoadableHydratedBloc<TEvent extends LoadableHydratedBlocEvent<TState>, TState, TRepository extends Repository<TState>> extends HydratedBloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>> { + late TRepository _repository; + LoadableHydratedBloc() : super(const LoadableState()) { + on<Emit<TState>>((event, emit) => emit(LoadableState(isLoading: event.loading, data: event.state(innerState ?? fromNothing())))); + on<ClearState<TState>>((event, emit) => emit(const LoadableState())); + + _repository = repository(); + loadState(); + } + + TState? get innerState => state.data; + TRepository get repo => _repository; + + @override + fromJson(Map<String, dynamic> json) => LoadableState(isLoading: true, data: fromStorage(json)); + @override + Map<String, dynamic>? toJson(LoadableState<TState> state) => state.data == null ? {} : state.data.toJson(); + + Future<void> loadState(); + TRepository repository(); + + TState fromNothing(); + TState fromStorage(Map<String, dynamic> json); + Map<String, dynamic>? toStorage(TState state); +} 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 new file mode 100644 index 0000000..a4b3580 --- /dev/null +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart @@ -0,0 +1,7 @@ +class LoadableHydratedBlocEvent<TState> {} +class Emit<TState> extends LoadableHydratedBlocEvent<TState> { + final TState Function(TState state) state; + final bool loading; + Emit(this.state, {this.loading = false}); +} +class ClearState<TState> extends LoadableHydratedBlocEvent<TState> {} \ No newline at end of file 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 0563ae3..b960e9c 100644 --- a/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart +++ b/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart @@ -1,17 +1,32 @@ -import '../../../infrastructure/loadableState/loadable_hydrated_bloc.dart'; -import '../../../infrastructure/repository/repository.dart'; +import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart'; +import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart'; import '../repository/marianum_message_repository.dart'; import 'marianum_message_state.dart'; -class MarianumMessageBloc extends LoadableHydratedBloc<void, MarianumMessageState> { - MarianumMessageBloc(); +sealed class MarianumMessageEvent extends LoadableHydratedBlocEvent<MarianumMessageState> {} +class MessageEvent extends MarianumMessageEvent {} +class MarianumMessageBloc extends LoadableHydratedBloc<MarianumMessageEvent, MarianumMessageState, MarianumMessageRepository> { + MarianumMessageBloc() { + on<MessageEvent>((event, emit) { + add(Emit((state) => state.copyWith.messageList(messages: []))); + }); + } + + @override + Future<void> loadState() async { + var messages = await repo.getMessages(); + add(Emit((state) => state.copyWith(messageList: messages))); + } + + @override + MarianumMessageRepository repository() => MarianumMessageRepository(); + + @override + MarianumMessageState fromNothing() => const MarianumMessageState(messageList: MarianumMessageList(base: "", messages: [])); @override MarianumMessageState fromStorage(Map<String, dynamic> json) => MarianumMessageState.fromJson(json); @override Map<String, dynamic>? toStorage(MarianumMessageState state) => state.toJson(); - - @override - Repository repository() => MarianumMessageRepository(emit); } 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 694b304..4e0f188 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 @@ -1,6 +1,11 @@ import '../../../infrastructure/dataLoader/data_loader.dart'; +import '../bloc/marianum_message_state.dart'; + +class MarianumMessageGetMessages extends DataLoader<MarianumMessageList> { -class MarianumMessageGetMessages extends DataLoader { @override - Future fetch() => Future(() => 'Test'); + Future<MarianumMessageList> fetch() async { + await Future.delayed(const Duration(seconds: 3)); + return const MarianumMessageList(base: '', messages: [MarianumMessage(date: '', name: 'RepoTest', url: '')]); + } } diff --git a/lib/state/app/modules/marianumMessage/repository/marianum_message_repository.dart b/lib/state/app/modules/marianumMessage/repository/marianum_message_repository.dart index 9679f44..033f821 100644 --- a/lib/state/app/modules/marianumMessage/repository/marianum_message_repository.dart +++ b/lib/state/app/modules/marianumMessage/repository/marianum_message_repository.dart @@ -1,9 +1,7 @@ -import '../../../infrastructure/loadableState/loadable_state.dart'; import '../../../infrastructure/repository/repository.dart'; +import '../bloc/marianum_message_state.dart'; import '../dataProvider/marianum_message_get_messages.dart'; -class MarianumMessageRepository<TState> extends Repository { - MarianumMessageRepository(void Function(LoadableState<TState> content) emit) : super([ - MarianumMessageGetMessages(), - ]); +class MarianumMessageRepository extends Repository<MarianumMessageState> { + Future<MarianumMessageList> getMessages() => MarianumMessageGetMessages().fetch(); } 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 fa323c7..1fddef3 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 @@ -1,12 +1,14 @@ import 'dart:developer'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import '../../../../../api/mhsl/message/getMessages/getMessagesResponse.dart'; import '../../../../../view/pages/more/message/messageView.dart'; import '../../../infrastructure/loadableState/loadable_state.dart'; import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart'; -import '../../../infrastructure/utilityWidgets/bloc_providing_builder.dart'; +import '../../../infrastructure/utilityWidgets/bloc_module.dart'; +import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart'; import '../bloc/marianum_message_bloc.dart'; import '../bloc/marianum_message_state.dart'; @@ -14,36 +16,41 @@ class MarianumMessageListView extends StatelessWidget { const MarianumMessageListView({super.key}); @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: const Text('Marianum Message'), - ), - body: BlocProvidingBuilder<MarianumMessageBloc, LoadableState<MarianumMessageState>>( - create: (context) => MarianumMessageBloc(), - child: (context, state) { - // if(value.primaryLoading()) return const LoadingSpinner(); - log(state.toString()); - return LoadableStateConsumer<MarianumMessageBloc, LoadableState<MarianumMessageState>, MarianumMessageState>( - child: (state) => ListView.builder( - itemCount: state.messageList.messages.length, - itemBuilder: (context, index) { - var message = state.messageList.messages.toList()[index]; - return ListTile( - leading: const Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [Icon(Icons.newspaper)], - ), - title: Text(message.name, overflow: TextOverflow.ellipsis), - subtitle: Text('vom ${message.date}'), - trailing: const Icon(Icons.arrow_right), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => MessageView(basePath: state.messageList.base, message: message as GetMessagesResponseObject))); - }, - ); - } + Widget build(BuildContext context) => BlocModule<MarianumMessageBloc, LoadableState<MarianumMessageState>>( + create: (context) => MarianumMessageBloc(), + child: (context, state) { + // if(value.primaryLoading()) return const LoadingSpinner(); + log(state.toString()); + return Scaffold( + appBar: AppBar( + title: const Text('Marianum Message'), + actions: [ + IconButton(onPressed: () => context.read<MarianumMessageBloc>().add(MessageEvent()), icon: Icon(Icons.abc)), + 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(ClearState()), icon: Icon(Icons.add)) + ], + ), + body: LoadableStateConsumer<MarianumMessageBloc, MarianumMessageState>( + child: (state, loading) => ListView.builder( + itemCount: state.messageList.messages.length, + itemBuilder: (context, index) { + var message = state.messageList.messages.toList()[index]; + return ListTile( + leading: const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [Icon(Icons.newspaper)], + ), + title: Text("${message.name}${loading ? "loading" : "notloading"}", overflow: TextOverflow.ellipsis), + subtitle: Text('vom ${message.date}'), + trailing: const Icon(Icons.arrow_right), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => MessageView(basePath: state.messageList.base, message: message as GetMessagesResponseObject))); + }, + ); + } ), - ); - } - ), + ), + ); + } ); }