diff --git a/lib/features/backend/api_requests/api_calls.dart b/lib/features/backend/api_requests/api_calls.dart index 87424040..0e0a7421 100644 --- a/lib/features/backend/api_requests/api_calls.dart +++ b/lib/features/backend/api_requests/api_calls.dart @@ -5,8 +5,7 @@ import 'dart:convert'; import 'dart:developer'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; -import 'package:hub/features/documents/index.dart' as doc; +import 'package:hub/features/documents/documents.dart' as doc; import 'package:hub/features/notification/index.dart'; import 'package:hub/features/storage/index.dart'; diff --git a/lib/features/documents/archive_item_component.dart b/lib/features/documents/archive_item_component.dart index 0063e562..9ec58ac2 100644 --- a/lib/features/documents/archive_item_component.dart +++ b/lib/features/documents/archive_item_component.dart @@ -1,3 +1 @@ part of 'index.dart'; - -abstract interface class Archive extends Entity {} diff --git a/lib/features/documents/category_item_component.dart b/lib/features/documents/category_item_component.dart index 84659d09..9ec58ac2 100644 --- a/lib/features/documents/category_item_component.dart +++ b/lib/features/documents/category_item_component.dart @@ -1,68 +1 @@ part of 'index.dart'; - -interface class Category extends Archive { - final int id; - final Color color; - final String title; - - Category({ - required this.id, - required this.color, - required this.title, - }); - - factory Category.fromDesc(String desc) { - return Category( - id: 0, - color: Colors.transparent, - title: desc, - ); - } - - static Color isSelected() => Colors.black; -} - -class CategoryItem extends StatelessComponent { - final Category category; - - const CategoryItem({ - super.key, - required this.category, - }); - - @override - Widget build(BuildContext context) { - final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground; - return ColoredBox( - color: backgroundTheme, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: category.color, - shape: BoxShape.circle, - ), - child: Icon( - Icons.folder, - color: Colors.white, - size: 40, - ), - ), - const SizedBox(height: 8), - Text( - category.title, - style: TextStyle( - color: category.color, - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - ); - } -} diff --git a/lib/features/documents/document_item_component.dart b/lib/features/documents/document_item_component.dart index 29b91a96..9ec58ac2 100644 --- a/lib/features/documents/document_item_component.dart +++ b/lib/features/documents/document_item_component.dart @@ -1,177 +1 @@ part of 'index.dart'; - -interface class Document extends Archive { - final int id; - final String description; - final String type; - final Category category; - final String person; - final String property; - String createdAt; - String updatedAt; - - Document({ - required this.id, - required this.description, - required this.type, - required this.category, - required this.person, - required this.property, - required this.createdAt, - required this.updatedAt, - }); - - factory Document.fromDesc(String desc) => Document( - id: 0, - description: desc, - type: '', - category: Category.fromDesc(''), - person: '', - property: '', - createdAt: '', - updatedAt: '', - ); -} - -// ignore: must_be_immutable -class DocumentItem extends StatelessComponent { - final Document document; - void Function(Document, BuildContext) onPressed; - - DocumentItem({ - super.key, - required this.document, - required this.onPressed, - }); - - Tooltip _buildTooltip(String text, Color color, BuildContext context, - BoxConstraints constraints) { - final Color textColor = FlutterFlowTheme.of(context).info; - - final area = (MediaQuery.of(context).size.height + - MediaQuery.of(context).size.width) / - 2; - - final double boxHeight = area * 0.033; - final double boxWidth = area * 0.19; - - return Tooltip( - message: text, - child: Container( - width: boxWidth, - height: boxHeight, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(10), - ), - child: Center( - child: AutoText( - text, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: textColor, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - final Color primaryText = FlutterFlowTheme.of(context).primaryText; - final Color primaryColor = FlutterFlowTheme.of(context).primary; - - final TextStyle textStyleMajor = TextStyle( - color: primaryText, - fontWeight: FontWeight.bold, - ); - final TextStyle textStyleMinor = TextStyle( - color: primaryText, - fontWeight: FontWeight.normal, - fontStyle: FontStyle.italic, - ); - - return Padding( - padding: const EdgeInsets.all(8), - child: LayoutBuilder( - builder: (context, constraints) { - final double boxHeight = constraints.maxHeight > 350 - ? MediaQuery.of(context).size.height * 0.07 - : MediaQuery.of(context).size.height * 2; - - return InkWell( - onTap: () => onPressed(document, context), - enableFeedback: true, - overlayColor: WidgetStateProperty.all(primaryColor), - borderRadius: BorderRadius.circular(10), - child: SizedBox( - height: boxHeight, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // const SizedBox(width: 10), - Icon(Icons.description, color: document.category.color), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Tooltip( - message: document.description, - child: AutoText( - document.description, - style: textStyleMajor, - overflow: TextOverflow.ellipsis, - ), - ), - AutoText( - ValidatorUtil.toLocalDateTime( - 'yyyy-MM-dd', document.updatedAt), - style: textStyleMinor, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - _buildTooltip( - document.category.title, - document.category.color, - context, - constraints, - ), - ], - ), - ), - // const SizedBox(width: 10), - Center( - child: Icon( - Icons.arrow_right, - color: primaryText, - ), - ), - ], - ), - ), - ); - }, - ), - ); - } - - DocumentItem copyWith({ - Document? document, - }) { - return DocumentItem( - document: document ?? this.document, - onPressed: onPressed, - ); - } -} diff --git a/lib/features/documents/document_page_bloc.dart b/lib/features/documents/document_page_bloc.dart index 81406d99..9ec58ac2 100644 --- a/lib/features/documents/document_page_bloc.dart +++ b/lib/features/documents/document_page_bloc.dart @@ -1,148 +1 @@ part of 'index.dart'; - -/// ----------------------------------------------- -/// [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.vihicleScreenManager.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.vihicleScreenManager.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, - ), - ); - } -} - -/// ----------------------------------------------- -/// [DocumentPageEvent] -/// ----------------------------------------------- - -abstract class DocumentPageEvent {} - -class SelectDocumentEvent extends DocumentPageEvent { - final Document document; - SelectDocumentEvent( - this.document, - ); -} - -class UnselectDocumentEvent extends DocumentPageEvent {} - -class FilterCategoryEvent extends DocumentPageEvent { - final Query query; - FilterCategoryEvent(this.query); -} - -/// ----------------------------------------------- -/// [DocumentPageState] -/// ----------------------------------------------- - -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, - ); - } -} diff --git a/lib/features/documents/document_page_model.dart b/lib/features/documents/document_page_model.dart index 78b696ae..9ec58ac2 100644 --- a/lib/features/documents/document_page_model.dart +++ b/lib/features/documents/document_page_model.dart @@ -1,175 +1 @@ part of 'index.dart'; - -class DocumentPageModel extends FlutterFlowModel { - DocumentPageModel._privateConstructor(); - - static final DocumentPageModel _instance = - DocumentPageModel._privateConstructor(); - - factory DocumentPageModel() { - return _instance; - } - - late EnhancedListViewKey vihicleScreenManager; - late DocumentKey vehicleScreenViewer; - late PagingController _pagingController; - - /// ------------ - - @override - void initState(BuildContext context) { - vihicleScreenManager = EnhancedListViewKey(); - vehicleScreenViewer = DocumentKey(); - - _pagingController = PagingController(firstPageKey: 1); - } - - @override - void dispose() { - _pagingController.dispose(); - vihicleScreenManager.currentState?.dispose(); - vehicleScreenViewer.currentState?.dispose(); - } - - /// ------------ - - /// [onView] - void onView(Document document, BuildContext context) async { - context.read().add(SelectDocumentEvent(document)); - } - - /// [itemBodyBuilder] - DocumentItem 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 { - log('generateDocuments: $query'); - - final List error = [null]; - print('Query: ${query is Document}'); - final GetDocuments getDocuments = FreAccessWSGlobal.getDocuments; - final ApiCallResponse newItems = await getDocuments.call(pageKey, query); - - if (newItems.jsonBody == null) return error; - if (newItems.jsonBody['error'] == true) return error; - - final List list = newItems.jsonBody['value']['list']; - - late final List docs = []; - - for (var item in list) { - log('-> generateDocuments: $item'); - final String description = item['description']; - final String type = item['type']; - final String category = item['category']['description']; - final String color = item['category']['color']; - final String person = item['person'] ?? ''; - final String property = item['property'] ?? ''; - final String createdAt = item['createdAt']; - final String updatedAt = item['updatedAt']; - final int categoryId = item['category']['id']; - final int documentId = item['id']; - - final doc = Document( - id: documentId, - description: description, - type: type, - category: Category( - id: categoryId, - color: color.toColor(), - title: category, - ), - person: person, - property: property, - createdAt: createdAt, - updatedAt: updatedAt, - ); - - docs.add(doc); - } - - return docs as List; - } - - /// [generateHeaderItems] - Future> generateHeaderItems() async { - log('generateCategories: '); - final List error = [null]; - - final GetCategories getCategories = FreAccessWSGlobal.getCategories; - final ApiCallResponse newItems = await getCategories.call(); - - if (newItems.jsonBody['error'] == true) return error; - if (newItems.jsonBody == null) return error; - final list = newItems.jsonBody['value'] as List; - late final List cats = []; - for (var item in list) { - final String color = item['color']; - final String title = item['description']; - final int id = item['id']; - - final cat = Category( - id: id, - color: color.toColor(), - title: title, - ); - cats.add(cat); - } - log('cats: $cats'); - return cats as List; - } - - /// [filter] - void filter(T query, BuildContext context) { - context - .read() - .add(FilterCategoryEvent(query as Archive?)); - } - - /// [onFetchError] - void onFetchError(Object e, StackTrace s) { - DialogUtil.errorDefault(vehicleScreenViewer.currentContext!); - LogUtil.requestAPIFailed( - "proccessRequest.php", "", "Consulta de Veículo", e, s); - } -} diff --git a/lib/features/documents/document_page_widget.dart b/lib/features/documents/document_page_widget.dart index 815d23bd..9ec58ac2 100644 --- a/lib/features/documents/document_page_widget.dart +++ b/lib/features/documents/document_page_widget.dart @@ -1,44 +1 @@ part of 'index.dart'; - -typedef DocumentKey = GlobalKey; - -class DocumentPage extends StatefulPage { - const DocumentPage({super.key}); - - @override - State createState() => FREDocumentPageState(); -} - -class FREDocumentPageState - extends PageState { - @override - Widget build(BuildContext context) => buildBody(context); - DocumentPageModel model = DocumentPageModel(); - - @override - void initState() { - super.initState(); - model.initState(context); - } - - Widget buildBody(BuildContext context) { - return BlocProvider( - create: (context) => DocumentPageBloc(model), - child: BlocBuilder( - builder: (context, state) { - print('Bloc -> ${state.isCategorySelected}'); - - if (state.isDocumentSelected) - return DocumentViewScreen( - doc: state.currentDocument!, - uri: state.uri!, - ); - else - return DocumentManagerScreen( - model: model, - state: state, - ); - }), - ); - } -} diff --git a/lib/features/documents/document_screen_manager.dart b/lib/features/documents/document_screen_manager.dart index d2b52f3c..9ec58ac2 100644 --- a/lib/features/documents/document_screen_manager.dart +++ b/lib/features/documents/document_screen_manager.dart @@ -1,49 +1 @@ part of 'index.dart'; - -class DocumentManagerScreen extends StatelessScreen { - final DocumentPageModel model; - final DocumentPageState state; - - const DocumentManagerScreen({ - super.key, - required this.model, - required this.state, - }); - - @override - Widget build(BuildContext context) { - final String title = FFLocalizations.of(context).getVariableText( - enText: 'Documents', - ptText: 'Documentos', - ); - final theme = FlutterFlowTheme.of(context); - action() => Navigator.pop(context); - - return Scaffold( - backgroundColor: theme.primaryBackground, - appBar: buildAppBar(title, context, action), - body: buildBody(context), - ); - } - - Widget buildBody(BuildContext context) { - final SizedBox space = SizedBox(height: 30); - return Column( - children: [ - Expanded( - child: EnhancedListView( - key: model.vihicleScreenManager, - headerBuilder: model.itemHeaderBuilder, - headerItems: model.generateHeaderItems, - bodyBuilder: model.itemBodyBuilder, - bodyItems: model.generateBodyItems, - footerBuilder: null, - footerItems: null, - ), - ), - ] // - .addToStart(space) - .addToEnd(space), - ); - } -} diff --git a/lib/features/documents/document_screen_viewer.dart b/lib/features/documents/document_screen_viewer.dart index b54bfa55..9ec58ac2 100644 --- a/lib/features/documents/document_screen_viewer.dart +++ b/lib/features/documents/document_screen_viewer.dart @@ -1,46 +1 @@ part of 'index.dart'; - -class DocumentViewScreen extends StatefulScreen { - const DocumentViewScreen({ - super.key, - required this.doc, - required this.uri, - }); - - final Document doc; - final Uri uri; - - @override - ScreenState createState() => _DocumentViewScreenState(); -} - -class _DocumentViewScreenState extends ScreenState { - @override - Widget build(BuildContext context) { - action() { - context.read().add(UnselectDocumentEvent()); - } - - final String title = widget.doc.description; - final theme = FlutterFlowTheme.of(context); - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, result) => action(), - child: Scaffold( - backgroundColor: theme.primaryBackground, - appBar: buildAppBar(title, context, action), - body: buildBody(context), - ), - ); - } - - Widget buildBody(BuildContext context) { - // final PDFViewerKey _viewerKey = PDFViewerKey(); - - return ReadView( - // search: _viewerKey, - title: widget.doc.description, - url: widget.uri.toString(), - ); - } -} diff --git a/lib/features/documents/documents.dart b/lib/features/documents/documents.dart new file mode 100644 index 00000000..66e68194 --- /dev/null +++ b/lib/features/documents/documents.dart @@ -0,0 +1,745 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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'; + +/// ----------------------------------------------- +/// [TypeDefs] +/// ----------------------------------------------- + +typedef DocumentKey = GlobalKey; + +/// ----------------------------------------------- + +/// [Page] +/// ----------------------------------------------- + +class DocumentPage extends StatefulPage { + const DocumentPage({super.key}); + + @override + State createState() => FREDocumentPageState(); +} + +class FREDocumentPageState + extends PageState { + @override + Widget build(BuildContext context) => buildBody(context); + DocumentPageModel model = DocumentPageModel(); + + @override + void initState() { + super.initState(); + model.initState(context); + } + + Widget buildBody(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, + ); + }), + ); + } +} + +/// ----------------------------------------------- + +/// [Model] +/// ----------------------------------------------- + +class DocumentPageModel extends FlutterFlowModel { + DocumentPageModel._privateConstructor(); + + static final DocumentPageModel _instance = + DocumentPageModel._privateConstructor(); + + factory DocumentPageModel() { + return _instance; + } + + late EnhancedListViewKey vehicleScreenManager; + late DocumentKey vehicleScreenViewer; + late PagingController _pagingController; + + /// ------------ + + @override + void initState(BuildContext context) { + vehicleScreenManager = EnhancedListViewKey(); + vehicleScreenViewer = DocumentKey(); + + _pagingController = PagingController(firstPageKey: 1); + } + + @override + void dispose() { + _pagingController.dispose(); + vehicleScreenManager.currentState?.dispose(); + vehicleScreenViewer.currentState?.dispose(); + } + + /// ------------ + + /// [onView] + void onView(Document document, BuildContext context) async { + vehicleScreenManager.currentContext! + .read() + .add(SelectDocumentEvent(document)); + } + + /// [itemBodyBuilder] + DocumentItem 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 { + log('generateDocuments: $query'); + + final List error = [null]; + print('Query: ${query is Document}'); + final GetDocuments getDocuments = FreAccessWSGlobal.getDocuments; + final ApiCallResponse newItems = await getDocuments.call(pageKey, query); + + if (newItems.jsonBody == null) return error; + if (newItems.jsonBody['error'] == true) return error; + + final List list = newItems.jsonBody['value']['list']; + + late final List docs = []; + + for (var item in list) { + log('-> generateDocuments: $item'); + final String description = item['description']; + final String type = item['type']; + final String category = item['category']['description']; + final String color = item['category']['color']; + final String person = item['person'] ?? ''; + final String property = item['property'] ?? ''; + final String createdAt = item['createdAt']; + final String updatedAt = item['updatedAt']; + final int categoryId = item['category']['id']; + final int documentId = item['id']; + + final doc = Document( + id: documentId, + description: description, + type: type, + category: Category( + id: categoryId, + color: color.toColor(), + title: category, + ), + person: person, + property: property, + createdAt: createdAt, + updatedAt: updatedAt, + ); + + docs.add(doc); + } + + return docs as List; + } + + /// [generateHeaderItems] + Future> generateHeaderItems() async { + log('generateCategories: '); + final List error = [null]; + + final GetCategories getCategories = FreAccessWSGlobal.getCategories; + final ApiCallResponse newItems = await getCategories.call(); + + if (newItems.jsonBody['error'] == true) return error; + if (newItems.jsonBody == null) return error; + final list = newItems.jsonBody['value'] as List; + late final List cats = []; + for (var item in list) { + final String color = item['color']; + final String title = item['description']; + final int id = item['id']; + + final cat = Category( + id: id, + color: color.toColor(), + title: title, + ); + cats.add(cat); + } + log('cats: $cats'); + return cats as List; + } + + /// [filter] + void filter(T query, BuildContext context) { + context + .read() + .add(FilterCategoryEvent(query as Archive?)); + } + + /// [onFetchError] + void onFetchError(Object e, StackTrace s) { + DialogUtil.errorDefault(vehicleScreenViewer.currentContext!); + LogUtil.requestAPIFailed( + "proccessRequest.php", "", "Consulta de Veículo", e, s); + } +} + +/// ----------------------------------------------- +/// [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, + ), + ); + } +} + +/// [DocumentPageEvent] + +abstract class DocumentPageEvent {} + +class SelectDocumentEvent extends DocumentPageEvent { + final Document document; + SelectDocumentEvent( + this.document, + ); +} + +class UnselectDocumentEvent extends DocumentPageEvent {} + +class FilterCategoryEvent extends DocumentPageEvent { + final Query query; + FilterCategoryEvent(this.query); +} + +/// [DocumentPageState] + +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, + ); + } +} + +/// ----------------------------------------------- +/// [Screens] +/// ----------------------------------------------- + +/// [DocumentManagerScreen] + +class DocumentManagerScreen extends StatelessScreen { + final DocumentPageModel model; + final DocumentPageState state; + + const DocumentManagerScreen({ + super.key, + required this.model, + required this.state, + }); + + @override + Widget build(BuildContext context) { + final String title = FFLocalizations.of(context).getVariableText( + enText: 'Documents', + ptText: 'Documentos', + ); + final theme = FlutterFlowTheme.of(context); + action() => Navigator.pop(context); + + return Scaffold( + backgroundColor: theme.primaryBackground, + appBar: buildAppBar(title, context, action), + body: buildBody(context), + ); + } + + Widget buildBody(BuildContext context) { + log('Build -> DocumentManagerScreen'); + final SizedBox space = SizedBox(height: 30); + return Column( + children: [ + Expanded( + child: EnhancedListView( + key: model.vehicleScreenManager, + headerBuilder: model.itemHeaderBuilder, + headerItems: model.generateHeaderItems, + bodyBuilder: model.itemBodyBuilder, + bodyItems: model.generateBodyItems, + footerBuilder: null, + footerItems: null, + ), + ), + ] // + .addToStart(space) + .addToEnd(space), + ); + } +} + +/// [DocumentViewScreen] + +class DocumentViewScreen extends StatefulScreen { + const DocumentViewScreen({ + super.key, + required this.doc, + required this.uri, + }); + + final Document doc; + final Uri uri; + + @override + ScreenState createState() => _DocumentViewScreenState(); +} + +class _DocumentViewScreenState extends ScreenState { + @override + Widget build(BuildContext context) { + action() { + context.read().add(UnselectDocumentEvent()); + } + + final String title = widget.doc.description; + final theme = FlutterFlowTheme.of(context); + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) => action(), + child: Scaffold( + backgroundColor: theme.primaryBackground, + appBar: buildAppBar(title, context, action), + body: buildBody(context), + ), + ); + } + + Widget buildBody(BuildContext context) { + // final PDFViewerKey _viewerKey = PDFViewerKey(); + + return ReadView( + // search: _viewerKey, + title: widget.doc.description, + url: widget.uri.toString(), + ); + } +} + +/// ----------------------------------------------- +/// [Interfaces] +/// ----------------------------------------------- + +abstract interface class Archive extends Entity {} + +interface class Document extends Archive { + final int id; + final String description; + final String type; + final Category category; + final String person; + final String property; + String createdAt; + String updatedAt; + + Document({ + required this.id, + required this.description, + required this.type, + required this.category, + required this.person, + required this.property, + required this.createdAt, + required this.updatedAt, + }); + + factory Document.fromDesc(String desc) => Document( + id: 0, + description: desc, + type: '', + category: Category.fromDesc(''), + person: '', + property: '', + createdAt: '', + updatedAt: '', + ); +} + +interface class Category extends Archive { + final int id; + final Color color; + final String title; + + Category({ + required this.id, + required this.color, + required this.title, + }); + + factory Category.fromDesc(String desc) { + return Category( + id: 0, + color: Colors.transparent, + title: desc, + ); + } + + static Color isSelected() => Colors.black; +} + +/// ----------------------------------------------- +/// [Widgets] +/// ----------------------------------------------- + +// ignore: must_be_immutable +class DocumentItem extends StatelessComponent { + final Document document; + void Function(Document, BuildContext) onPressed; + + DocumentItem({ + super.key, + required this.document, + required this.onPressed, + }); + + Tooltip _buildTooltip(String text, Color color, BuildContext context, + BoxConstraints constraints) { + final Color textColor = FlutterFlowTheme.of(context).info; + + final area = (MediaQuery.of(context).size.height + + MediaQuery.of(context).size.width) / + 2; + + final double boxHeight = area * 0.033; + final double boxWidth = area * 0.19; + + return Tooltip( + message: text, + child: Container( + width: boxWidth, + height: boxHeight, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(10), + ), + child: Center( + child: AutoText( + text, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: textColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final Color primaryText = FlutterFlowTheme.of(context).primaryText; + final Color primaryColor = FlutterFlowTheme.of(context).primary; + + final TextStyle textStyleMajor = TextStyle( + color: primaryText, + fontWeight: FontWeight.bold, + ); + final TextStyle textStyleMinor = TextStyle( + color: primaryText, + fontWeight: FontWeight.normal, + fontStyle: FontStyle.italic, + ); + + return Padding( + padding: const EdgeInsets.all(8), + child: LayoutBuilder( + builder: (context, constraints) { + final double boxHeight = constraints.maxHeight > 350 + ? MediaQuery.of(context).size.height * 0.07 + : MediaQuery.of(context).size.height * 2; + + return InkWell( + onTap: () => onPressed(document, context), + enableFeedback: true, + overlayColor: WidgetStateProperty.all(primaryColor), + borderRadius: BorderRadius.circular(10), + child: SizedBox( + height: boxHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // const SizedBox(width: 10), + Icon(Icons.description, color: document.category.color), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Tooltip( + message: document.description, + child: AutoText( + document.description, + style: textStyleMajor, + overflow: TextOverflow.ellipsis, + ), + ), + AutoText( + ValidatorUtil.toLocalDateTime( + 'yyyy-MM-dd', document.updatedAt), + style: textStyleMinor, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + _buildTooltip( + document.category.title, + document.category.color, + context, + constraints, + ), + ], + ), + ), + // const SizedBox(width: 10), + Center( + child: Icon( + Icons.arrow_right, + color: primaryText, + ), + ), + ], + ), + ), + ); + }, + ), + ); + } + + DocumentItem copyWith({ + Document? document, + }) { + return DocumentItem( + document: document ?? this.document, + onPressed: onPressed, + ); + } +} + +class CategoryItem extends StatelessComponent { + final Category category; + + const CategoryItem({ + super.key, + required this.category, + }); + + @override + Widget build(BuildContext context) { + final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground; + return ColoredBox( + color: backgroundTheme, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: category.color, + shape: BoxShape.circle, + ), + child: Icon( + Icons.folder, + color: Colors.white, + size: 40, + ), + ), + const SizedBox(height: 8), + Text( + category.title, + style: TextStyle( + color: category.color, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/documents/index.dart b/lib/features/documents/index.dart deleted file mode 100644 index d3648a6d..00000000 --- a/lib/features/documents/index.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:developer'; -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:http/http.dart' as http; -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/mixins/pegeable_mixin.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:share_plus/share_plus.dart'; - -part 'document_page_widget.dart'; -part 'document_screen_manager.dart'; -part 'document_screen_viewer.dart'; -part 'document_page_model.dart'; -part 'document_item_component.dart'; -part 'document_page_bloc.dart'; -part 'category_item_component.dart'; -part 'archive_item_component.dart'; diff --git a/lib/flutter_flow/nav/nav.dart b/lib/flutter_flow/nav/nav.dart index 42b6e6de..bf1a1051 100644 --- a/lib/flutter_flow/nav/nav.dart +++ b/lib/flutter_flow/nav/nav.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hub/features/backend/index.dart'; -import 'package:hub/features/documents/index.dart'; +import 'package:hub/features/documents/documents.dart'; import 'package:hub/features/history/index.dart'; import 'package:hub/features/home/index.dart'; import 'package:hub/features/local/index.dart'; diff --git a/lib/shared/widgets/enhanced_list_view.dart b/lib/shared/widgets/enhanced_list_view.dart index eb2ec5a1..506ffccf 100644 --- a/lib/shared/widgets/enhanced_list_view.dart +++ b/lib/shared/widgets/enhanced_list_view.dart @@ -179,117 +179,50 @@ class EnhancedListView class EnhancedListViewState extends State> { - final ScrollController _scrollController = ScrollController(); - bool _isLoadingMore = false; - List items = []; - List headerItems = []; - List footerItems = []; - int currentPage = 1; - Query query; + late EnhancedListViewBloc bloc; @override void initState() { super.initState(); - _scrollController.addListener(_onScroll); - _loadBodyItems(); - _loadHeaderItems(); - _loadFooterItems(); - } - - void _onScroll() { - if (_scrollController.position.pixels == - _scrollController.position.maxScrollExtent && - !_isLoadingMore) { - _loadMoreBodyItems(); - } - } - - Future _loadBodyItems() async { - log('loadInitialItems'); - final newItems = await widget.bodyItems(1, 10, query); - setState(() { - items = newItems; - }); - } - - Future _loadMoreBodyItems() async { - log('loadMoreItems'); - setState(() { - _isLoadingMore = true; - }); - final newItems = await widget.bodyItems(currentPage + 1, 10, query); - setState(() { - _isLoadingMore = false; - if (newItems.isNotEmpty) { - items.addAll(newItems); - currentPage++; - } - }); - } - - Future _loadHeaderItems() async { - if (widget.headerItems != null) { - log('loadHeaderItems'); - final newHeaderItems = await widget.headerItems!(); - setState(() { - headerItems = newHeaderItems; - }); - } - } - - Future _loadFooterItems() async { - if (widget.footerItems != null) { - log('loadFooterItems'); - final newFooterItems = await widget.footerItems!(); - setState(() { - footerItems = newFooterItems; - }); - } - } - - Future filterBodyItems(Query newQuery) async { - log('filterItems B: ${newQuery.toString()}'); - setState(() { - query = newQuery; - items = []; - currentPage = 1; - }); - await _loadBodyItems(); + bloc = EnhancedListViewBloc( + bodyItemsBuilder: widget.bodyItems, + headerItemsBuilder: widget.headerItems ?? () async => [], + footerItemsBuilder: widget.footerItems ?? () async => [], + ); + bloc.events.loadBodyItems(); + bloc.events.loadHeaderItems(); + bloc.events.loadFooterItems(); } @override Widget build(BuildContext context) { - log('key: ${widget.key}'); - return ListView.builder( - controller: _scrollController, - itemCount: items.length + - (headerItems.isNotEmpty ? 1 : 0) + - (footerItems.isNotEmpty ? 1 : 0) + - (_isLoadingMore ? 1 : 0), - itemBuilder: (context, index) { - if (headerItems.isNotEmpty && index == 0) { - return widget.headerBuilder!(() => Future.value(headerItems)); - } - if (footerItems.isNotEmpty && - index == items.length + (headerItems.isNotEmpty ? 1 : 0)) { - return widget.footerBuilder!(() => Future.value(footerItems)); - } - if (_isLoadingMore && - index == - items.length + - (headerItems.isNotEmpty ? 1 : 0) + - (footerItems.isNotEmpty ? 1 : 0)) { - return const EnhancedProgressIndicator(); - } - final item = items[index - (headerItems.isNotEmpty ? 1 : 0)]; - return widget.bodyBuilder(context, item as ItemType, index); + 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); + }, + ); + }, + ); + }, + ); }, ); } @override void dispose() { - _scrollController.dispose(); + bloc.dispose(); super.dispose(); } } @@ -358,7 +291,7 @@ class AuthorizationError implements Exception { AuthorizationError(this.message); } -/// [State Managment] +/// [State Management] Stream get loadingState => Stream.empty(); Stream get errorState => Stream.empty(); @@ -367,111 +300,108 @@ abstract class EnhancedListViewRepository { int page, int pageSize, PaginatedListViewBodyBuilder builder); } -abstract class EnhancedListViewBlocStates { +abstract class EnhancedListViewEvents { + void loadBodyItems({bool reset = false}); + void loadHeaderItems(); + void loadFooterItems(); +} + +abstract class EnhancedListViewStates { + Stream> get bodyItems; + Stream> get headerItems; + Stream> get footerItems; Stream get isLoading; Stream get errors; - Stream> get paginatedList; - - @RxBlocIgnoreState() - Future get refreshDone; } -abstract class EnhancedListViewEvents { - void loadPage({bool reset = false}); -} - -abstract class EnhancedListViewBlocType extends RxBlocTypeBase { - EnhancedListViewEvents get events; - EnhancedListViewBlocStates get states; -} - -abstract class $EnhancedListViewBloc extends RxBlocBase - implements - EnhancedListViewEvents, - EnhancedListViewBlocStates, - EnhancedListViewBlocType { - final _compositeSubscription = CompositeSubscription(); - - final _$loadPageEvent = PublishSubject(); - - late final Stream _isLoadingState = _mapToIsLoadingState(); - late final Stream _errorsState = _mapToErrorsState(); - late final Stream> _paginatedListState = - _mapToPaginatedListState(); - - @override - void loadPage({bool reset = false}) => _$loadPageEvent.add(reset); - - @override - Stream get isLoading => _isLoadingState; - @override - Stream get errors => _errorsState; - @override - Stream> get paginatedList => _paginatedListState; - - Stream _mapToIsLoadingState(); - Stream _mapToErrorsState(); - Stream> _mapToPaginatedListState(); - - @override - EnhancedListViewEvents get events => this; - @override - EnhancedListViewBlocStates get states => this; - - @override - void dispose() { - _$loadPageEvent.close(); - _compositeSubscription.dispose(); - super.dispose(); - } -} - -class EnhancedListViewBloc extends $EnhancedListViewBloc { +@RxBloc() +class EnhancedListViewBloc extends $EnhancedListViewBloc { EnhancedListViewBloc({ - required EnhancedListViewRepository repository, - required PaginatedListViewBodyBuilder builder, - required T item, - int initialPageSize = 50, + required this.bodyItemsBuilder, + required this.headerItemsBuilder, + required this.footerItemsBuilder, }) { - _$loadPageEvent + _$loadBodyItemsEvent .startWith(true) - .fetchData( - repository, - (context, item, index) => builder(context, item, index), - _paginatedList, - ) - .setResultStateHandler(this) - .mergeWithPaginatedList(_paginatedList) - .bind(_paginatedList) + .switchMap((reset) => _fetchBodyItems(reset)) + .bind(_bodyItems) + .addTo(_compositeSubscription); + + _$loadHeaderItemsEvent + .switchMap((_) => _fetchHeaderItems()) + .bind(_headerItems) + .addTo(_compositeSubscription); + + _$loadFooterItemsEvent + .switchMap((_) => _fetchFooterItems()) + .bind(_footerItems) .addTo(_compositeSubscription); } - final _paginatedList = BehaviorSubject>.seeded( - EnhancedPaginatedList( - items: [], - pageSize: 1, - currentPage: 1, - error: Exception(), - isInitialized: true, - isLoading: false, - totalCount: 0, - ), - ); + final BodyItemsBuilder bodyItemsBuilder; + final HeaderItemsBuilder headerItemsBuilder; + final FooterItemsBuilder footerItemsBuilder; - @override - Future get refreshDone async => _paginatedList.value.awaitLoad(); + final _bodyItems = BehaviorSubject>.seeded([]); + final _headerItems = BehaviorSubject>.seeded([]); + final _footerItems = BehaviorSubject>.seeded([]); + final _isLoading = BehaviorSubject.seeded(false); + final _errors = BehaviorSubject(); - @override - Stream> _mapToPaginatedListState() => _paginatedList; - @override - Stream _mapToErrorsState() => - errorState.map((error) => error.toString()); - @override - Stream _mapToIsLoadingState() => loadingState; + Stream> _fetchBodyItems(bool reset) async* { + try { + _isLoading.add(true); + final items = await bodyItemsBuilder(1, 10, null); + yield items.whereType().toList(); + } catch (e) { + _errors.add(e.toString()); + } finally { + _isLoading.add(false); + } + } + + Stream> _fetchHeaderItems() async* { + try { + _isLoading.add(true); + final items = await headerItemsBuilder(); + yield items.whereType().toList(); + } catch (e) { + _errors.add(e.toString()); + } finally { + _isLoading.add(false); + } + } + + Stream> _fetchFooterItems() async* { + try { + _isLoading.add(true); + final items = await footerItemsBuilder(); + yield items.whereType().toList(); + } catch (e) { + _errors.add(e.toString()); + } finally { + _isLoading.add(false); + } + } @override void dispose() { - _paginatedList.close(); + _bodyItems.close(); + _headerItems.close(); + _footerItems.close(); + _isLoading.close(); + _errors.close(); super.dispose(); } + + @override + Stream> _mapToBodyItemsState() => _bodyItems.stream; + @override + Stream _mapToErrorsState() => _errors.stream; + @override + Stream> _mapToFooterItemsState() => _footerItems.stream; + @override + Stream> _mapToHeaderItemsState() => _headerItems.stream; + @override + Stream _mapToIsLoadingState() => _isLoading.stream; } diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index c09aeccf..be46da76 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -2,14 +2,10 @@ import 'dart:developer'; import 'dart:io'; import 'package:auto_size_text/auto_size_text.dart'; -import 'package:easy_debounce/easy_debounce.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; -import 'package:hub/features/documents/index.dart'; import 'package:hub/flutter_flow/index.dart'; -import 'package:hub/shared/mixins/pegeable_mixin.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pdfx/pdfx.dart'; import 'package:share_plus/share_plus.dart'; @@ -17,6 +13,8 @@ import 'package:rx_bloc_list/rx_bloc_list.dart'; import 'package:rxdart/rxdart.dart'; import 'package:rx_bloc/rx_bloc.dart'; +part 'widgets.rxb.g.dart'; + /// [Base] part 'page.dart'; part 'component.dart'; diff --git a/lib/shared/widgets/widgets.rxb.g.dart b/lib/shared/widgets/widgets.rxb.g.dart new file mode 100644 index 00000000..e8298e8e --- /dev/null +++ b/lib/shared/widgets/widgets.rxb.g.dart @@ -0,0 +1,98 @@ +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// Generator: RxBlocGeneratorForAnnotation +// ************************************************************************** + +part of 'widgets.dart'; + +/// Used as a contractor for the bloc, events and states classes +/// @nodoc +abstract class EnhancedListViewBlocType extends RxBlocTypeBase { + EnhancedListViewEvents get events; + EnhancedListViewStates get states; +} + +/// [$EnhancedListViewBloc] extended by the [EnhancedListViewBloc] +/// @nodoc +abstract class $EnhancedListViewBloc extends RxBlocBase + implements + EnhancedListViewEvents, + EnhancedListViewStates, + EnhancedListViewBlocType { + final _compositeSubscription = CompositeSubscription(); + + /// Тhe [Subject] where events sink to by calling [loadBodyItems] + final _$loadBodyItemsEvent = PublishSubject(); + + /// Тhe [Subject] where events sink to by calling [loadHeaderItems] + final _$loadHeaderItemsEvent = PublishSubject(); + + /// Тhe [Subject] where events sink to by calling [loadFooterItems] + final _$loadFooterItemsEvent = PublishSubject(); + + /// The state of [bodyItems] implemented in [_mapToBodyItemsState] + late final Stream> _bodyItemsState = _mapToBodyItemsState(); + + /// The state of [headerItems] implemented in [_mapToHeaderItemsState] + late final Stream> _headerItemsState = _mapToHeaderItemsState(); + + /// The state of [footerItems] implemented in [_mapToFooterItemsState] + late final Stream> _footerItemsState = _mapToFooterItemsState(); + + /// The state of [isLoading] implemented in [_mapToIsLoadingState] + late final Stream _isLoadingState = _mapToIsLoadingState(); + + /// The state of [errors] implemented in [_mapToErrorsState] + late final Stream _errorsState = _mapToErrorsState(); + + @override + void loadBodyItems({bool reset = false}) => _$loadBodyItemsEvent.add(reset); + + @override + void loadHeaderItems() => _$loadHeaderItemsEvent.add(null); + + @override + void loadFooterItems() => _$loadFooterItemsEvent.add(null); + + @override + Stream> get bodyItems => _bodyItemsState; + + @override + Stream> get headerItems => _headerItemsState; + + @override + Stream> get footerItems => _footerItemsState; + + @override + Stream get isLoading => _isLoadingState; + + @override + Stream get errors => _errorsState; + + Stream> _mapToBodyItemsState(); + + Stream> _mapToHeaderItemsState(); + + Stream> _mapToFooterItemsState(); + + Stream _mapToIsLoadingState(); + + Stream _mapToErrorsState(); + + @override + EnhancedListViewEvents get events => this; + + @override + EnhancedListViewStates get states => this; + + @override + void dispose() { + _$loadBodyItemsEvent.close(); + _$loadHeaderItemsEvent.close(); + _$loadFooterItemsEvent.close(); + _compositeSubscription.dispose(); + super.dispose(); + } +}