From 963ccb64db7304fad024e62d29d6489cbd62a166 Mon Sep 17 00:00:00 2001 From: jantunesmessias Date: Tue, 18 Feb 2025 16:11:55 -0300 Subject: [PATCH] WIP --- lib/features/documents/documents.dart | 527 +++++++++++--------- lib/features/documents/documents.rxb.g.dart | 70 +++ lib/flutter_flow/nav/nav.dart | 30 +- lib/shared/widgets/enhanced_list_view.dart | 149 ++++-- lib/shared/widgets/page.dart | 33 +- lib/shared/widgets/widgets.dart | 1 + lib/shared/widgets/widgets.rxb.g.dart | 18 +- 7 files changed, 517 insertions(+), 311 deletions(-) create mode 100644 lib/features/documents/documents.rxb.g.dart diff --git a/lib/features/documents/documents.dart b/lib/features/documents/documents.dart index cf7f214a..c105a0b5 100644 --- a/lib/features/documents/documents.dart +++ b/lib/features/documents/documents.dart @@ -1,19 +1,27 @@ import 'dart:developer'; +import 'package:easy_debounce/easy_debounce.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hub/components/templates_components/details_component/details_component_widget.dart'; import 'package:hub/features/backend/index.dart'; import 'package:hub/flutter_flow/index.dart'; import 'package:hub/shared/extensions/index.dart'; import 'package:hub/shared/utils/index.dart'; import 'package:hub/shared/widgets/widgets.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_rx_bloc/flutter_rx_bloc.dart'; + +import 'package:rx_bloc/rx_bloc.dart'; +import 'package:rxdart/rxdart.dart' as rx; + +part 'documents.rxb.g.dart'; /// ----------------------------------------------- /// [TypeDefs] /// ----------------------------------------------- -typedef DocumentKey = GlobalKey; +typedef DocumentKey = GlobalKey; /// ----------------------------------------------- @@ -24,43 +32,54 @@ class DocumentPage extends StatefulPage { const DocumentPage({super.key}); @override - State createState() => FREDocumentPageState(); + State createState() => DocumentPageState(); } -class FREDocumentPageState - extends PageState { - @override - Widget build(BuildContext context) => buildBody(context); - DocumentPageModel model = DocumentPageModel(); - +class DocumentPageState extends PageState { @override void initState() { super.initState(); - model.initState(context); } - Widget buildBody(BuildContext context) { + @override + Widget build(BuildContext context) { log('Build -> DocumentPage'); - return BlocProvider( - create: (context) => DocumentPageBloc(model), - child: BlocBuilder( - builder: (context, state) { - log('Build -> DocumentPageBloc'); - print('Bloc -> ${state.isCategorySelected}'); - if (state.isDocumentSelected) - return DocumentViewScreen( - doc: state.currentDocument!, - uri: state.uri!, - ); - else - return DocumentManagerScreen( - model: model, - state: state, - ); - }), + return RxBlocMultiBuilder2( + state1: (bloc) => bloc.states.isDocumentSelected, + state2: (bloc) => bloc.states.currentDocument, + bloc: context.read(), + builder: (context, isSelect, current, bloc) { + log('-> Build -> DocumentPage -> RxBlocMultiBuilder2'); + if (isSelect.hasData && isSelect.data!) { + return _buildDocumentViewScreen(current, bloc); + } else { + return _buildDocumentManagerScreen(); + } + }, ); } + + Widget _buildDocumentManagerScreen() { + final model = context.read().model; + + return DocumentManagerScreen( + model: model, + state: this, + ); + } + + Widget _buildDocumentViewScreen( + AsyncSnapshot<(Document, Uri)?> snapshot, DocumentPageBlocType bloc) { + if (snapshot.hasData) { + return DocumentViewScreen( + doc: snapshot.data!, + bloc: bloc, + ); + } else { + return const Center(child: CircularProgressIndicator()); + } + } } /// ----------------------------------------------- @@ -69,27 +88,26 @@ class FREDocumentPageState /// ----------------------------------------------- class DocumentPageModel extends FlutterFlowModel { - DocumentPageModel._privateConstructor(); + final DocumentPageBlocType bloc; + DocumentPageModel(this.bloc); - static final DocumentPageModel _instance = - DocumentPageModel._privateConstructor(); - - factory DocumentPageModel() { - return _instance; - } - - late EnhancedListViewKey vehicleScreenManager; + late EnhancedListViewKey + vehicleScreenManager; late DocumentKey vehicleScreenViewer; late PagingController _pagingController; + late bool categoryIsSelected; + /// ------------ @override void initState(BuildContext context) { - vehicleScreenManager = EnhancedListViewKey(); + vehicleScreenManager = + EnhancedListViewKey(); vehicleScreenViewer = DocumentKey(); - _pagingController = PagingController(firstPageKey: 1); + + categoryIsSelected = false; } @override @@ -101,58 +119,25 @@ class DocumentPageModel extends FlutterFlowModel { /// ------------ - /// [onView] + /// [Body] + void onView(Document document, BuildContext context) async { - vehicleScreenManager.currentContext! - .read() - .add(SelectDocumentEvent(document)); + log('Disparando evento selectDocument'); + bloc.events.selectDocument(document); } - /// [itemBodyBuilder] - DocumentItem itemBodyBuilder( + Widget itemBodyBuilder( BuildContext context, T item, int index) { print('ItemBuilder -> $index'); + return DocumentItem( document: item, onPressed: onView, ); } - CategoryItem categoryItemBuilder(T? item) { - return CategoryItem(category: item! as Category); - } - - /// [itemHeaderBuilder] - Widget itemHeaderBuilder(Future> Function() gen) => - Builder(builder: (context) { - return Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(15, 0, 50, 0), - child: Text( - FFLocalizations.of(context).getVariableText( - enText: 'Recent Documents', ptText: 'Últimos Documentos'), - style: TextStyle( - color: FlutterFlowTheme.of(context).primaryText, - fontSize: LimitedFontSizeUtil.getHeaderFontSize(context), - ), - ), - ), - EnhancedCarouselView( - generateItems: gen, - itemBuilder: categoryItemBuilder, - filter: filter, - ), - ], - ); - }); - - /// [generateBodyItems] - Future> generateBodyItems( - int pageKey, int pageSize, dynamic query) async { + Future> generateBodyItems( + int pageKey, int pageSize, Q query) async { log('generateDocuments: $query'); final List error = [null]; @@ -201,8 +186,42 @@ class DocumentPageModel extends FlutterFlowModel { return docs as List; } - /// [generateHeaderItems] - Future> generateHeaderItems() async { + /// [Footer] + + CategoryItem categoryItemBuilder(T? item) { + return CategoryItem(category: item! as Category); + } + + Widget itemFooterBuilder(Future> Function() gen) => + Builder(builder: (context) { + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(15, 0, 50, 0), + child: Text( + FFLocalizations.of(context).getVariableText( + ptText: 'Suas Categorias', + enText: 'Your Categories', + ), + style: TextStyle( + color: FlutterFlowTheme.of(context).primaryText, + fontSize: LimitedFontSizeUtil.getHeaderFontSize(context), + ), + ), + ), + EnhancedCarouselView( + generateItems: gen, + itemBuilder: categoryItemBuilder, + filter: filterByCategory, + ), + ], + ); + }); + + Future> generateFooterItems() async { log('generateCategories: '); final List error = [null]; @@ -229,12 +248,105 @@ class DocumentPageModel extends FlutterFlowModel { return cats as List; } - /// [filter] - void filter(T query, BuildContext context) { - vehicleScreenManager.currentState!.filterBodyItems(query); + /// [Header] + + Widget itemHeaderBuilder( + Future> Function() generateHeaderItems) { + return Builder(builder: (context) { + final theme = FlutterFlowTheme.of(context); + final locale = FFLocalizations.of(context); + TextEditingController editingController = TextEditingController(); + return TextFormField( + controller: editingController, + onChanged: (value) => EasyDebounce.debounce( + '_model.keyTextFieldTextController', + const Duration(milliseconds: 500), + () => filterBySearchBar(Document.fromDesc(value), context), + ), + cursorColor: theme.primaryText, + showCursor: false, + cursorWidth: 2.0, + cursorRadius: Radius.circular(100), + style: TextStyle( + color: theme.primaryText, + fontSize: 16.0, + decorationColor: Colors.amber, + ), + keyboardType: TextInputType.text, + textInputAction: TextInputAction.search, + autocorrect: true, + textCapitalization: TextCapitalization.sentences, + decoration: InputDecoration( + prefixIcon: Icon(Icons.search, color: theme.primary), + labelText: locale.getVariableText( + ptText: 'Pesquisar', + enText: 'Search', + ), + labelStyle: TextStyle( + color: theme.primaryText, + fontSize: 16.0, + ), + hintText: locale.getVariableText( + ptText: 'Digite sua pesquisa', + enText: 'Enter your search', + ), + hintStyle: TextStyle( + color: theme.accent2, + fontSize: 14.0, + ), + filled: true, + fillColor: Colors.transparent, + helperStyle: TextStyle( + color: theme.primaryText, + decorationColor: theme.primaryText, + ), + focusColor: theme.primaryText, + contentPadding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), + enabledBorder: UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + borderSide: BorderSide(color: theme.primaryText), + ), + focusedBorder: UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + borderSide: BorderSide(color: theme.primaryText), + ), + errorBorder: UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + borderSide: BorderSide(color: theme.primaryText), + ), + focusedErrorBorder: UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + borderSide: BorderSide(color: theme.primaryText, width: 2.0), + ), + ), + ); + }); } - /// [onFetchError] + Future> generateHeaderItems() async { + final Search item = Search(); + return [item] as List; + } + + /// [Filter] + + void filterBySearchBar(T query, BuildContext context) { + final key = vehicleScreenManager.currentState; + return key?.filterBodyItems(query); + } + + void filterByCategory(T query, BuildContext context) { + final key = vehicleScreenManager.currentState; + + categoryIsSelected + ? key?.filterBodyItems(null) + : key?.filterBodyItems(query); + + categoryIsSelected = !categoryIsSelected; + } + + /// [Exception] void onFetchError(Object e, StackTrace s) { DialogUtil.errorDefault(vehicleScreenViewer.currentContext!); LogUtil.requestAPIFailed( @@ -246,145 +358,45 @@ class DocumentPageModel extends FlutterFlowModel { /// [BLoC] /// ----------------------------------------------- -/// [DocumentPageBloc] - -class DocumentPageBloc extends Bloc { - final DocumentPageModel model; - static DocumentPageBloc? _singleton; - - factory DocumentPageBloc(DocumentPageModel model) { - _singleton ??= DocumentPageBloc._internal(model); - return _singleton!; - } - - DocumentPageBloc._internal(this.model) : super(DocumentPageState()) { - on(_selectDocument); - on(_unselectDocument); - on(_filterCategoryEvent); - } - - Future _filterCategoryEvent( - FilterCategoryEvent event, Emitter emit) async { - _selectCategory(event, emit); - state.isCategorySelected - ? _unselectCategory(event, emit) - : _selectCategory(event, emit); - } - - Future _selectCategory( - FilterCategoryEvent event, Emitter emit) async { - log('filterItems A: ${event.query}'); - emit(state.copyWith( - isCategorySelected: true, - )); - - // final listViewState = model.vehicleScreenManager.currentState!; - // listViewState.widget.bodyItems = (await model.generateBodyItems( - // 1, 10, event.query)) as BodyItemsBuilder; - } - - Future _unselectCategory( - FilterCategoryEvent event, Emitter emit) async { - emit(state.copyWith( - isCategorySelected: false, - )); - - // final listViewState = model.vehicleScreenManager.currentState!; - // listViewState.widget.bodyItems = (await model.generateBodyItems( - // 1, 10, null)) as BodyItemsBuilder; - } - - Future _selectDocument( - SelectDocumentEvent event, Emitter emit) async { - print('-> select'); - emit( - state.copyWith( - uri: await GetPDF().call( - event.document.id, - ), - currentDocument: event.document, - isDocumentSelected: true, - ), - ); - } - - Future _unselectDocument( - UnselectDocumentEvent event, Emitter emit) async { - emit( - state.copyWith( - currentDocument: null, - isDocumentSelected: false, - ), - ); - } +extension RxdartStartWithExtension on Stream { + Stream rxdartStartWith(T? value) => + rx.StartWithExtension(this).startWith(value); } -/// [DocumentPageEvent] - -abstract class DocumentPageEvent {} - -class SelectDocumentEvent extends DocumentPageEvent { - final Document document; - SelectDocumentEvent( - this.document, - ); +abstract class DocumentPageBlocEvents { + void selectDocument(Document document); + void unselectDocument(); } -class UnselectDocumentEvent extends DocumentPageEvent {} - -class FilterCategoryEvent extends DocumentPageEvent { - final Query query; - FilterCategoryEvent(this.query); +abstract class DocumentPageBlocStates { + Stream get isDocumentSelected; + Stream<(Document, Uri)?> get currentDocument; } -/// [DocumentPageState] +@RxBloc() +class DocumentPageBloc extends $DocumentPageBloc { + late final DocumentPageModel model; -class DocumentPageState { - final bool isCategorySelected; - final bool isDocumentSelected; - final Document? currentDocument; - final Category? currentCategory; - final Uri? uri; - final int? count; - final dynamic page; - final Query? query; - - const DocumentPageState({ - this.query, - this.count, - this.page, - this.uri, - this.currentDocument, - this.isCategorySelected = false, - this.currentCategory, - this.isDocumentSelected = false, - }); - - DocumentPageState copyWith({ - Uri? uri, - Query? query, - int? count, - dynamic page, - List? documents, - Document? currentDocument, - bool? isDocumentSelected, - List? categories, - Category? currentCategory, - bool? isCategorySelected, - }) { - return DocumentPageState( - uri: uri ?? this.uri, - query: query ?? this.query, - count: count ?? this.count, - page: page ?? this.page, - // - currentDocument: currentDocument ?? this.currentDocument, - isDocumentSelected: isDocumentSelected ?? this.isDocumentSelected, - // - currentCategory: currentCategory ?? this.currentCategory, - isCategorySelected: isCategorySelected ?? this.isCategorySelected, - ); + DocumentPageBloc(BuildContext context) { + model = DocumentPageModel(this); + model.initState(context); } + + @override + Stream<(Document, Uri)?> _mapToCurrentDocumentState() => _$selectDocumentEvent + .switchMap((event) async* { + log('Evento selectDocument recebido: ${event.description}'); + final uri = await GetPDF().call(event.id); + yield (event, uri); + }) + .rxdartStartWith(null) + .mergeWith([_$unselectDocumentEvent.map((_) => null)]); + + @override + Stream _mapToIsDocumentSelectedState() => _mapToCurrentDocumentState() + .map((document) => document != null) + .rxdartStartWith(false) + .map((isSelected) => isSelected ?? false); } /// ----------------------------------------------- @@ -425,14 +437,14 @@ class DocumentManagerScreen extends StatelessScreen { return Column( children: [ Expanded( - child: EnhancedListView( + child: EnhancedListView( key: model.vehicleScreenManager, - headerBuilder: model.itemHeaderBuilder, - headerItems: model.generateHeaderItems, + headerBuilder: model.itemHeaderBuilder, + headerItems: model.generateHeaderItems, bodyBuilder: model.itemBodyBuilder, - bodyItems: model.generateBodyItems, - footerBuilder: null, - footerItems: null, + bodyItems: model.generateBodyItems, + footerBuilder: model.itemFooterBuilder, + footerItems: model.generateFooterItems, ), ), ] // @@ -448,11 +460,11 @@ class DocumentViewScreen extends StatefulScreen { const DocumentViewScreen({ super.key, required this.doc, - required this.uri, + required this.bloc, }); - final Document doc; - final Uri uri; + final (Document, Uri) doc; + final DocumentPageBlocType bloc; @override ScreenState createState() => _DocumentViewScreenState(); @@ -461,18 +473,59 @@ class DocumentViewScreen extends StatefulScreen { class _DocumentViewScreenState extends ScreenState { @override Widget build(BuildContext context) { - action() { - context.read().add(UnselectDocumentEvent()); - } - - final String title = widget.doc.description; + final String title = widget.doc.$1.description; final theme = FlutterFlowTheme.of(context); + final locale = FFLocalizations.of(context); + + backAction() => widget.bloc.events.unselectDocument(); + infoAction() async => await showDialog( + useSafeArea: true, + context: context, + builder: (context) { + return Material( + child: DetailsComponentWidget( + buttons: [], + statusHashMap: [], + labelsHashMap: Map.from({ + locale.getVariableText( + enText: 'Description', + ptText: 'Descrição', + ): widget.doc.$1.description, + locale.getVariableText( + enText: 'Type', + ptText: 'Tipo', + ): widget.doc.$1.type, + locale.getVariableText( + enText: 'Category', + ptText: 'Categoria', + ): widget.doc.$1.category.title, + locale.getVariableText( + enText: 'Person', + ptText: 'Pessoa', + ): widget.doc.$1.person, + locale.getVariableText( + enText: 'Property', + ptText: 'Propriedade', + ): widget.doc.$1.property, + locale.getVariableText( + enText: 'Created At', + ptText: 'Criado em', + ): widget.doc.$1.createdAt, + locale.getVariableText( + enText: 'Updated At', + ptText: 'Atualizado em', + ): widget.doc.$1.updatedAt, + }), + ), + ); + }); + return PopScope( canPop: false, - onPopInvokedWithResult: (didPop, result) => action(), + onPopInvokedWithResult: (didPop, result) => backAction(), child: Scaffold( backgroundColor: theme.primaryBackground, - appBar: buildAppBar(title, context, action), + appBar: buildAppBar(title, context, backAction, infoAction), body: buildBody(context), ), ); @@ -483,8 +536,8 @@ class _DocumentViewScreenState extends ScreenState { return ReadView( // search: _viewerKey, - title: widget.doc.description, - url: widget.uri.toString(), + title: widget.doc.$1.description, + url: widget.doc.$2.toString(), ); } } @@ -495,6 +548,10 @@ class _DocumentViewScreenState extends ScreenState { abstract interface class Archive extends Entity {} +interface class Search extends Archive { + Search(); +} + interface class Document extends Archive { final int id; final String description; diff --git a/lib/features/documents/documents.rxb.g.dart b/lib/features/documents/documents.rxb.g.dart new file mode 100644 index 00000000..38bc4c6b --- /dev/null +++ b/lib/features/documents/documents.rxb.g.dart @@ -0,0 +1,70 @@ +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// Generator: RxBlocGeneratorForAnnotation +// ************************************************************************** + +part of 'documents.dart'; + +/// Used as a contractor for the bloc, events and states classes +/// @nodoc +abstract class DocumentPageBlocType extends RxBlocTypeBase { + DocumentPageBlocEvents get events; + DocumentPageBlocStates get states; +} + +/// [$DocumentPageBloc] extended by the [DocumentPageBloc] +/// @nodoc +abstract class $DocumentPageBloc extends RxBlocBase + implements + DocumentPageBlocEvents, + DocumentPageBlocStates, + DocumentPageBlocType { + final _compositeSubscription = rx.CompositeSubscription(); + + /// Тhe [Subject] where events sink to by calling [selectDocument] + final _$selectDocumentEvent = rx.PublishSubject(); + + /// Тhe [Subject] where events sink to by calling [unselectDocument] + final _$unselectDocumentEvent = rx.PublishSubject(); + + /// The state of [isDocumentSelected] implemented in + /// [_mapToIsDocumentSelectedState] + late final Stream _isDocumentSelectedState = + _mapToIsDocumentSelectedState(); + + /// The state of [currentDocument] implemented in [_mapToCurrentDocumentState] + late final Stream<(Document, Uri)?> _currentDocumentState = + _mapToCurrentDocumentState(); + + @override + void selectDocument(Document document) => _$selectDocumentEvent.add(document); + + @override + void unselectDocument() => _$unselectDocumentEvent.add(null); + + @override + Stream get isDocumentSelected => _isDocumentSelectedState; + + @override + Stream<(Document, Uri)?> get currentDocument => _currentDocumentState; + + Stream _mapToIsDocumentSelectedState(); + + Stream<(Document, Uri)?> _mapToCurrentDocumentState(); + + @override + DocumentPageBlocEvents get events => this; + + @override + DocumentPageBlocStates get states => this; + + @override + void dispose() { + _$selectDocumentEvent.close(); + _$unselectDocumentEvent.close(); + _compositeSubscription.dispose(); + super.dispose(); + } +} diff --git a/lib/flutter_flow/nav/nav.dart b/lib/flutter_flow/nav/nav.dart index bf1a1051..26396286 100644 --- a/lib/flutter_flow/nav/nav.dart +++ b/lib/flutter_flow/nav/nav.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_rx_bloc/flutter_rx_bloc.dart'; import 'package:hub/features/backend/index.dart'; import 'package:hub/features/documents/documents.dart'; import 'package:hub/features/history/index.dart'; @@ -303,22 +304,25 @@ GoRouter createRouter(AppStateNotifier appStateNotifier) { name: 'documentPage', path: '/documentPage', builder: (context, params) { - return DocumentPage(); - }, - ), - FFRoute( - name: 'documentViewerScreen', - path: '/documentViewerScreen', - builder: (context, params) { - final Document doc = params.getParam('doc', ParamType.Function); - final Uri uri = params.getParam('uri', ParamType.Function); - return DocumentViewScreen( - key: UniqueKey(), - doc: doc, - uri: uri, + return RxBlocProvider( + create: (context) => DocumentPageBloc(context), + child: DocumentPage(), ); }, ), + // FFRoute( + // name: 'documentViewerScreen', + // path: '/documentViewerScreen', + // builder: (context, params) { + // final Document doc = params.getParam('doc', ParamType.Function); + // final Uri uri = params.getParam('uri', ParamType.Function); + // return DocumentViewScreen( + // key: UniqueKey(), + // doc: (doc, + // uri: uri, + // ); + // }, + // ), // FFRoute(name: 'settingsPage', path: '/settingsPage', builder: (context, params) => params.isEmpty ? const NavBarPage(initialPage: 'settingsPage') : const SettingsPageWidget()) ].map((r) => r.toRoute(appStateNotifier)).toList(), ); diff --git a/lib/shared/widgets/enhanced_list_view.dart b/lib/shared/widgets/enhanced_list_view.dart index 96396739..a24af23c 100644 --- a/lib/shared/widgets/enhanced_list_view.dart +++ b/lib/shared/widgets/enhanced_list_view.dart @@ -2,8 +2,8 @@ part of 'widgets.dart'; /// [TypeDefs] -typedef EnhancedListViewKey - = GlobalKey>; +typedef EnhancedListViewKey + = GlobalKey>; typedef PaginatedListViewHeaderBuilder = Widget Function( Future> Function() headerItems); @@ -14,8 +14,8 @@ typedef PaginatedListViewFooterBuilder = Widget Function( typedef Query = T?; -typedef BodyItemsBuilder = Future> Function( - int page, int pageSize, Query query); +typedef BodyItemsBuilder = Future> Function( + int page, int pageSize, Q query); typedef HeaderItemsBuilder = Future> Function(); typedef FooterItemsBuilder = Future> Function(); @@ -145,16 +145,17 @@ interface class EnhancedPaginatedList extends PaginatedList { Future awaitLoad() async => Future.value(); } -abstract interface class EnhancedListViewBase extends StatefulWidget { +abstract interface class EnhancedListViewBase + extends StatefulWidget { const EnhancedListViewBase({super.key}); } abstract interface class EnhancedListViewBaseState extends State {} -class EnhancedListView - extends EnhancedListViewBase { - final BodyItemsBuilder bodyItems; +class EnhancedListView + extends EnhancedListViewBase { + final BodyItemsBuilder bodyItems; final PaginatedListViewBodyBuilder bodyBuilder; final HeaderItemsBuilder? headerItems; final PaginatedListViewHeaderBuilder? headerBuilder; @@ -172,18 +173,21 @@ class EnhancedListView }) : super(key: key); @override - EnhancedListViewState createState() => - EnhancedListViewState(); + EnhancedListViewState + createState() => + EnhancedListViewState(); } -class EnhancedListViewState - extends State> { - late EnhancedListViewBloc bloc; +class EnhancedListViewState + extends State< + EnhancedListView> { + late EnhancedListViewBloc bloc; @override void initState() { super.initState(); - bloc = EnhancedListViewBloc( + + bloc = EnhancedListViewBloc( bodyItemsBuilder: widget.bodyItems, headerItemsBuilder: widget.headerItems ?? () async => [], footerItemsBuilder: widget.footerItems ?? () async => [], @@ -193,41 +197,71 @@ class EnhancedListViewState bloc.events.loadFooterItems(); } - void filterBodyItems(Query query) { - bloc.filterBodyItems(query); - } + void filterBodyItems(Query query) => bloc.filterBodyItems(query); - void filterHeaderItems(Query query) { - bloc.filterHeaderItems(query); - } + void filterHeaderItems(Query query) => bloc.filterHeaderItems(query); - void filterFooterItems(Query query) { - bloc.filterFooterItems(query); - } + void filterFooterItems(Query query) => bloc.filterFooterItems(query); @override Widget build(BuildContext context) { - return StreamBuilder>( - stream: bloc.states.bodyItems.cast>(), - builder: (context, bodySnapshot) { - return StreamBuilder>( - stream: bloc.states.headerItems.cast>(), - builder: (context, headerSnapshot) { - return StreamBuilder>( - stream: bloc.states.footerItems.cast>(), - builder: (context, footerSnapshot) { - return ListView.builder( - itemCount: bodySnapshot.data?.length ?? 0, - itemBuilder: (context, index) { - return widget.bodyBuilder( - context, bodySnapshot.data![index], index); - }, - ); + final header = StreamBuilder>( + stream: bloc.states.headerItems.cast>(), + builder: (context, headerSnapshot) { + if (headerSnapshot.connectionState == ConnectionState.waiting) { + return const EnhancedProgressIndicator(); + } else if (headerSnapshot.hasError) { + return EnhancedErrorWidget(error: headerSnapshot.error); + } else if (!headerSnapshot.hasData || headerSnapshot.data!.isEmpty) { + return const SizedBox.shrink(); + } else { + return widget.headerBuilder!(() async => headerSnapshot.data!); + } + }, + ); + final footer = StreamBuilder>( + stream: bloc.states.footerItems.cast>(), + builder: (context, footerSnapshot) { + if (footerSnapshot.connectionState == ConnectionState.waiting) { + return const EnhancedProgressIndicator(); + } else if (footerSnapshot.hasError) { + return EnhancedErrorWidget(error: footerSnapshot.error); + } else if (!footerSnapshot.hasData || footerSnapshot.data!.isEmpty) { + return const SizedBox.shrink(); + } else { + return widget.footerBuilder!(() async => footerSnapshot.data!); + } + }, + ); + final body = Expanded( + child: StreamBuilder>( + stream: bloc.states.bodyItems.cast>(), + builder: (context, bodySnapshot) { + if (bodySnapshot.connectionState == ConnectionState.waiting) { + return const EnhancedProgressIndicator(); + } else if (bodySnapshot.hasError) { + return EnhancedErrorWidget(error: bodySnapshot.error); + } else if (!bodySnapshot.hasData || bodySnapshot.data!.isEmpty) { + return const SizedBox.shrink(); + } else { + return ListView.builder( + itemCount: bodySnapshot.data?.length ?? 0, + itemBuilder: (context, index) { + return widget.bodyBuilder( + context, bodySnapshot.data![index], index); }, ); - }, - ); - }, + } + }, + ), + ); + + return Column( + children: [ + if (widget.headerBuilder != null) header, + body, + if (widget.footerBuilder != null) footer, + ], ); } @@ -311,13 +345,13 @@ abstract class EnhancedListViewRepository { int page, int pageSize, PaginatedListViewBodyBuilder builder); } -abstract class EnhancedListViewEvents { - void loadBodyItems({bool reset = false}); +abstract class EnhancedListViewEvents { + void loadBodyItems({bool reset = false, dynamic query = null}); void loadHeaderItems(); void loadFooterItems(); } -abstract class EnhancedListViewStates { +abstract class EnhancedListViewStates { Stream> get bodyItems; Stream> get headerItems; Stream> get footerItems; @@ -326,15 +360,16 @@ abstract class EnhancedListViewStates { } @RxBloc() -class EnhancedListViewBloc extends $EnhancedListViewBloc { +class EnhancedListViewBloc + extends $EnhancedListViewBloc { EnhancedListViewBloc({ required this.bodyItemsBuilder, required this.headerItemsBuilder, required this.footerItemsBuilder, }) { _$loadBodyItemsEvent - .startWith(true) - .switchMap((reset) => _fetchBodyItems(reset)) + .startWith((query: null, reset: true)) + .switchMap((item) => _fetchBodyItems(item.reset, item.query)) .bind(_bodyItems) .addTo(_compositeSubscription); @@ -349,20 +384,23 @@ class EnhancedListViewBloc extends $EnhancedListViewBloc { .addTo(_compositeSubscription); } - final BodyItemsBuilder bodyItemsBuilder; + final BodyItemsBuilder bodyItemsBuilder; final HeaderItemsBuilder headerItemsBuilder; final FooterItemsBuilder footerItemsBuilder; final _bodyItems = BehaviorSubject>.seeded([]); + final _headerItems = BehaviorSubject>.seeded([]); + final _footerItems = BehaviorSubject>.seeded([]); + final _isLoading = BehaviorSubject.seeded(false); final _errors = BehaviorSubject(); - Stream> _fetchBodyItems(bool reset) async* { + Stream> _fetchBodyItems(bool reset, Q? query) async* { try { _isLoading.add(true); - final items = await bodyItemsBuilder(1, 10, null); + final items = await bodyItemsBuilder(1, 10, query); yield items.whereType().toList(); } catch (e) { _errors.add(e.toString()); @@ -395,15 +433,16 @@ class EnhancedListViewBloc extends $EnhancedListViewBloc { } } - void filterBodyItems(Query query) { - _$loadBodyItemsEvent.add(true); + void filterBodyItems(Q query) { + print('filterBodyItems Q: ${query == null}'); + _$loadBodyItemsEvent.add((query: query, reset: true)); } - void filterHeaderItems(Query query) { + void filterHeaderItems(Q query) { _$loadHeaderItemsEvent.add(null); } - void filterFooterItems(Query query) { + void filterFooterItems(Q query) { _$loadFooterItemsEvent.add(null); } diff --git a/lib/shared/widgets/page.dart b/lib/shared/widgets/page.dart index 87059a12..cf9a9b76 100644 --- a/lib/shared/widgets/page.dart +++ b/lib/shared/widgets/page.dart @@ -3,9 +3,10 @@ part of 'widgets.dart'; mixin Template { PreferredSizeWidget buildAppBar( String title, - BuildContext context, + BuildContext context, [ dynamic Function()? backAction, - ) { + dynamic Function()? frontAction, + ]) { final theme = FlutterFlowTheme.of(context); return AppBar( backgroundColor: FlutterFlowTheme.of(context).primaryBackground, @@ -25,12 +26,36 @@ mixin Template { leading: _backButton(context, theme, backAction), centerTitle: true, elevation: 0.0, - actions: [], + actions: _frontButton(context, theme, frontAction), ); } - Widget _backButton(BuildContext context, FlutterFlowTheme theme, + List _frontButton(BuildContext context, FlutterFlowTheme theme, + dynamic Function()? action) { + if (action == null) return []; + return [ + IconButton( + onPressed: () async => await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Info'), + content: Text('This is a sample app.'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('Close')) + ], + )), + icon: Icon( + Symbols.info_i_rounded, + color: Colors.black, + )) + ]; + } + + Widget? _backButton(BuildContext context, FlutterFlowTheme theme, dynamic Function()? onPressed) { + if (onPressed == null) return null; return FlutterFlowIconButton( key: ValueKey('BackNavigationAppBar'), borderColor: Colors.transparent, diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index be46da76..4237da5a 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:hub/flutter_flow/index.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pdfx/pdfx.dart'; import 'package:share_plus/share_plus.dart'; diff --git a/lib/shared/widgets/widgets.rxb.g.dart b/lib/shared/widgets/widgets.rxb.g.dart index e8298e8e..44258652 100644 --- a/lib/shared/widgets/widgets.rxb.g.dart +++ b/lib/shared/widgets/widgets.rxb.g.dart @@ -14,9 +14,9 @@ abstract class EnhancedListViewBlocType extends RxBlocTypeBase { EnhancedListViewStates get states; } -/// [$EnhancedListViewBloc] extended by the [EnhancedListViewBloc] +/// [$EnhancedListViewBloc] extended by the [EnhancedListViewBloc] /// @nodoc -abstract class $EnhancedListViewBloc extends RxBlocBase +abstract class $EnhancedListViewBloc extends RxBlocBase implements EnhancedListViewEvents, EnhancedListViewStates, @@ -24,7 +24,7 @@ abstract class $EnhancedListViewBloc extends RxBlocBase final _compositeSubscription = CompositeSubscription(); /// Тhe [Subject] where events sink to by calling [loadBodyItems] - final _$loadBodyItemsEvent = PublishSubject(); + final _$loadBodyItemsEvent = PublishSubject<({bool reset, dynamic query})>(); /// Тhe [Subject] where events sink to by calling [loadHeaderItems] final _$loadHeaderItemsEvent = PublishSubject(); @@ -48,7 +48,14 @@ abstract class $EnhancedListViewBloc extends RxBlocBase late final Stream _errorsState = _mapToErrorsState(); @override - void loadBodyItems({bool reset = false}) => _$loadBodyItemsEvent.add(reset); + void loadBodyItems({ + bool reset = false, + dynamic query = null, + }) => + _$loadBodyItemsEvent.add(( + reset: reset, + query: query, + )); @override void loadHeaderItems() => _$loadHeaderItemsEvent.add(null); @@ -96,3 +103,6 @@ abstract class $EnhancedListViewBloc extends RxBlocBase super.dispose(); } } + +// ignore: unused_element +typedef _LoadBodyItemsEventArgs = ({bool reset, dynamic query});