This commit is contained in:
jantunesmessias 2025-02-17 18:00:02 -03:00
parent 07f55bbceb
commit 866e438c87
15 changed files with 965 additions and 921 deletions

View File

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

View File

@ -1,3 +1 @@
part of 'index.dart';
abstract interface class Archive extends Entity {}

View File

@ -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,
),
],
),
),
);
}
}

View File

@ -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<Color>(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,
);
}
}

View File

@ -1,148 +1 @@
part of 'index.dart';
/// -----------------------------------------------
/// [DocumentPageBloc]
/// -----------------------------------------------
class DocumentPageBloc extends Bloc<DocumentPageEvent, DocumentPageState> {
final DocumentPageModel model;
static DocumentPageBloc? _singleton;
factory DocumentPageBloc(DocumentPageModel model) {
_singleton ??= DocumentPageBloc._internal(model);
return _singleton!;
}
DocumentPageBloc._internal(this.model) : super(DocumentPageState()) {
on<SelectDocumentEvent>(_selectDocument);
on<UnselectDocumentEvent>(_unselectDocument);
on<FilterCategoryEvent>(_filterCategoryEvent);
}
Future<void> _filterCategoryEvent(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
_selectCategory(event, emit);
state.isCategorySelected
? _unselectCategory(event, emit)
: _selectCategory(event, emit);
}
Future<void> _selectCategory(
FilterCategoryEvent event, Emitter<DocumentPageState> 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<Document>;
}
Future<void> _unselectCategory(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
emit(state.copyWith(
isCategorySelected: false,
));
final listViewState = model.vihicleScreenManager.currentState!;
listViewState.widget.bodyItems = (await model.generateBodyItems(
1, 10, null)) as BodyItemsBuilder<Document>;
}
Future<void> _selectDocument(
SelectDocumentEvent event, Emitter<DocumentPageState> emit) async {
print('-> select');
emit(
state.copyWith(
uri: await GetPDF().call(
event.document.id,
),
currentDocument: event.document,
isDocumentSelected: true,
),
);
}
Future<void> _unselectDocument(
UnselectDocumentEvent event, Emitter<DocumentPageState> 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<Document?>? documents,
Document? currentDocument,
bool? isDocumentSelected,
List<Category?>? 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,
);
}
}

View File

@ -1,175 +1 @@
part of 'index.dart';
class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
DocumentPageModel._privateConstructor();
static final DocumentPageModel _instance =
DocumentPageModel._privateConstructor();
factory DocumentPageModel() {
return _instance;
}
late EnhancedListViewKey<Document, Category, Null> vihicleScreenManager;
late DocumentKey vehicleScreenViewer;
late PagingController<int, Document> _pagingController;
/// ------------
@override
void initState(BuildContext context) {
vihicleScreenManager = EnhancedListViewKey<Document, Category, Null>();
vehicleScreenViewer = DocumentKey();
_pagingController = PagingController<int, Document>(firstPageKey: 1);
}
@override
void dispose() {
_pagingController.dispose();
vihicleScreenManager.currentState?.dispose();
vehicleScreenViewer.currentState?.dispose();
}
/// ------------
/// [onView]
void onView(Document document, BuildContext context) async {
context.read<DocumentPageBloc>().add(SelectDocumentEvent(document));
}
/// [itemBodyBuilder]
DocumentItem itemBodyBuilder<T extends Document>(
BuildContext context, T item, int index) {
print('ItemBuilder -> $index');
return DocumentItem(
document: item,
onPressed: onView,
);
}
CategoryItem categoryItemBuilder<T>(T? item) {
return CategoryItem(category: item! as Category);
}
/// [itemHeaderBuilder]
Widget itemHeaderBuilder<T>(Future<List<T?>> 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<T>(
generateItems: gen,
itemBuilder: categoryItemBuilder,
filter: filter<T>,
),
],
);
});
/// [generateBodyItems]
Future<List<T?>> generateBodyItems<T>(
int pageKey, int pageSize, dynamic query) async {
log('generateDocuments: $query');
final List<T?> 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<dynamic> list = newItems.jsonBody['value']['list'];
late final List<Document> 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<T?>;
}
/// [generateHeaderItems]
Future<List<T?>> generateHeaderItems<T>() async {
log('generateCategories: ');
final List<T?> 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<dynamic>;
late final List<Category> 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<T?>;
}
/// [filter]
void filter<T>(T query, BuildContext context) {
context
.read<DocumentPageBloc>()
.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);
}
}

View File

@ -1,44 +1 @@
part of 'index.dart';
typedef DocumentKey = GlobalKey<FREDocumentPageState>;
class DocumentPage extends StatefulPage {
const DocumentPage({super.key});
@override
State<DocumentPage> createState() => FREDocumentPageState();
}
class FREDocumentPageState<T extends DocumentPage>
extends PageState<DocumentPage> {
@override
Widget build(BuildContext context) => buildBody(context);
DocumentPageModel model = DocumentPageModel();
@override
void initState() {
super.initState();
model.initState(context);
}
Widget buildBody(BuildContext context) {
return BlocProvider<DocumentPageBloc>(
create: (context) => DocumentPageBloc(model),
child: BlocBuilder<DocumentPageBloc, DocumentPageState>(
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,
);
}),
);
}
}

View File

@ -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<Document, Category, Null>(
key: model.vihicleScreenManager,
headerBuilder: model.itemHeaderBuilder<Category>,
headerItems: model.generateHeaderItems<Category>,
bodyBuilder: model.itemBodyBuilder<Document>,
bodyItems: model.generateBodyItems<Document>,
footerBuilder: null,
footerItems: null,
),
),
] //
.addToStart(space)
.addToEnd(space),
);
}
}

View File

@ -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<DocumentViewScreen> createState() => _DocumentViewScreenState();
}
class _DocumentViewScreenState extends ScreenState<DocumentViewScreen> {
@override
Widget build(BuildContext context) {
action() {
context.read<DocumentPageBloc>().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(),
);
}
}

View File

@ -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<FREDocumentPageState>;
/// -----------------------------------------------
/// [Page]
/// -----------------------------------------------
class DocumentPage extends StatefulPage {
const DocumentPage({super.key});
@override
State<DocumentPage> createState() => FREDocumentPageState();
}
class FREDocumentPageState<T extends DocumentPage>
extends PageState<DocumentPage> {
@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<DocumentPageBloc>(
create: (context) => DocumentPageBloc(model),
child: BlocBuilder<DocumentPageBloc, DocumentPageState>(
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<DocumentPage> {
DocumentPageModel._privateConstructor();
static final DocumentPageModel _instance =
DocumentPageModel._privateConstructor();
factory DocumentPageModel() {
return _instance;
}
late EnhancedListViewKey<Document, Category, Null> vehicleScreenManager;
late DocumentKey vehicleScreenViewer;
late PagingController<int, Document> _pagingController;
/// ------------
@override
void initState(BuildContext context) {
vehicleScreenManager = EnhancedListViewKey<Document, Category, Null>();
vehicleScreenViewer = DocumentKey();
_pagingController = PagingController<int, Document>(firstPageKey: 1);
}
@override
void dispose() {
_pagingController.dispose();
vehicleScreenManager.currentState?.dispose();
vehicleScreenViewer.currentState?.dispose();
}
/// ------------
/// [onView]
void onView(Document document, BuildContext context) async {
vehicleScreenManager.currentContext!
.read<DocumentPageBloc>()
.add(SelectDocumentEvent(document));
}
/// [itemBodyBuilder]
DocumentItem itemBodyBuilder<T extends Document>(
BuildContext context, T item, int index) {
print('ItemBuilder -> $index');
return DocumentItem(
document: item,
onPressed: onView,
);
}
CategoryItem categoryItemBuilder<T>(T? item) {
return CategoryItem(category: item! as Category);
}
/// [itemHeaderBuilder]
Widget itemHeaderBuilder<T>(Future<List<T?>> 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<T>(
generateItems: gen,
itemBuilder: categoryItemBuilder,
filter: filter<T>,
),
],
);
});
/// [generateBodyItems]
Future<List<T?>> generateBodyItems<T>(
int pageKey, int pageSize, dynamic query) async {
log('generateDocuments: $query');
final List<T?> 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<dynamic> list = newItems.jsonBody['value']['list'];
late final List<Document> 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<T?>;
}
/// [generateHeaderItems]
Future<List<T?>> generateHeaderItems<T>() async {
log('generateCategories: ');
final List<T?> 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<dynamic>;
late final List<Category> 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<T?>;
}
/// [filter]
void filter<T>(T query, BuildContext context) {
context
.read<DocumentPageBloc>()
.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<DocumentPageEvent, DocumentPageState> {
final DocumentPageModel model;
static DocumentPageBloc? _singleton;
factory DocumentPageBloc(DocumentPageModel model) {
_singleton ??= DocumentPageBloc._internal(model);
return _singleton!;
}
DocumentPageBloc._internal(this.model) : super(DocumentPageState()) {
on<SelectDocumentEvent>(_selectDocument);
on<UnselectDocumentEvent>(_unselectDocument);
on<FilterCategoryEvent>(_filterCategoryEvent);
}
Future<void> _filterCategoryEvent(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
_selectCategory(event, emit);
state.isCategorySelected
? _unselectCategory(event, emit)
: _selectCategory(event, emit);
}
Future<void> _selectCategory(
FilterCategoryEvent event, Emitter<DocumentPageState> 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<Document>;
}
Future<void> _unselectCategory(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
emit(state.copyWith(
isCategorySelected: false,
));
final listViewState = model.vehicleScreenManager.currentState!;
listViewState.widget.bodyItems = (await model.generateBodyItems(
1, 10, null)) as BodyItemsBuilder<Document>;
}
Future<void> _selectDocument(
SelectDocumentEvent event, Emitter<DocumentPageState> emit) async {
print('-> select');
emit(
state.copyWith(
uri: await GetPDF().call(
event.document.id,
),
currentDocument: event.document,
isDocumentSelected: true,
),
);
}
Future<void> _unselectDocument(
UnselectDocumentEvent event, Emitter<DocumentPageState> 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<Document?>? documents,
Document? currentDocument,
bool? isDocumentSelected,
List<Category?>? 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<Document, Category, Null>(
key: model.vehicleScreenManager,
headerBuilder: model.itemHeaderBuilder<Category>,
headerItems: model.generateHeaderItems<Category>,
bodyBuilder: model.itemBodyBuilder<Document>,
bodyItems: model.generateBodyItems<Document>,
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<DocumentViewScreen> createState() => _DocumentViewScreenState();
}
class _DocumentViewScreenState extends ScreenState<DocumentViewScreen> {
@override
Widget build(BuildContext context) {
action() {
context.read<DocumentPageBloc>().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<Color>(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,
),
],
),
),
);
}
}

View File

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

View File

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

View File

@ -179,117 +179,50 @@ class EnhancedListView<BodyType, HeaderType, FooterType>
class EnhancedListViewState<ItemType, HeaderType, FooterType>
extends State<EnhancedListView<ItemType, HeaderType, FooterType>> {
final ScrollController _scrollController = ScrollController();
bool _isLoadingMore = false;
List<ItemType?> items = [];
List<HeaderType?> headerItems = [];
List<FooterType?> footerItems = [];
int currentPage = 1;
Query<ItemType> query;
late EnhancedListViewBloc<ItemType, HeaderType, FooterType> bloc;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
_loadBodyItems();
_loadHeaderItems();
_loadFooterItems();
}
void _onScroll() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent &&
!_isLoadingMore) {
_loadMoreBodyItems();
}
}
Future<void> _loadBodyItems() async {
log('loadInitialItems');
final newItems = await widget.bodyItems(1, 10, query);
setState(() {
items = newItems;
});
}
Future<void> _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<void> _loadHeaderItems() async {
if (widget.headerItems != null) {
log('loadHeaderItems');
final newHeaderItems = await widget.headerItems!();
setState(() {
headerItems = newHeaderItems;
});
}
}
Future<void> _loadFooterItems() async {
if (widget.footerItems != null) {
log('loadFooterItems');
final newFooterItems = await widget.footerItems!();
setState(() {
footerItems = newFooterItems;
});
}
}
Future<void> filterBodyItems(Query<ItemType> newQuery) async {
log('filterItems B: ${newQuery.toString()}');
setState(() {
query = newQuery;
items = [];
currentPage = 1;
});
await _loadBodyItems();
bloc = EnhancedListViewBloc<ItemType, HeaderType, FooterType>(
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 StreamBuilder<List<ItemType>>(
stream: bloc.states.bodyItems.cast<List<ItemType>>(),
builder: (context, bodySnapshot) {
return StreamBuilder<List<HeaderType>>(
stream: bloc.states.headerItems.cast<List<HeaderType>>(),
builder: (context, headerSnapshot) {
return StreamBuilder<List<FooterType>>(
stream: bloc.states.footerItems.cast<List<FooterType>>(),
builder: (context, footerSnapshot) {
return ListView.builder(
controller: _scrollController,
itemCount: items.length +
(headerItems.isNotEmpty ? 1 : 0) +
(footerItems.isNotEmpty ? 1 : 0) +
(_isLoadingMore ? 1 : 0),
itemCount: bodySnapshot.data?.length ?? 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 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<bool> get loadingState => Stream<bool>.empty();
Stream<Exception> get errorState => Stream<Exception>.empty();
@ -367,111 +300,108 @@ abstract class EnhancedListViewRepository<T> {
int page, int pageSize, PaginatedListViewBodyBuilder<T> builder);
}
abstract class EnhancedListViewBlocStates<T> {
abstract class EnhancedListViewEvents<T, H, F> {
void loadBodyItems({bool reset = false});
void loadHeaderItems();
void loadFooterItems();
}
abstract class EnhancedListViewStates<T, H, F> {
Stream<List<T>> get bodyItems;
Stream<List<H>> get headerItems;
Stream<List<F>> get footerItems;
Stream<bool> get isLoading;
Stream<String> get errors;
Stream<EnhancedPaginatedList<T>> get paginatedList;
@RxBlocIgnoreState()
Future<void> get refreshDone;
}
abstract class EnhancedListViewEvents<T> {
void loadPage({bool reset = false});
}
abstract class EnhancedListViewBlocType<T> extends RxBlocTypeBase {
EnhancedListViewEvents<T> get events;
EnhancedListViewBlocStates<T> get states;
}
abstract class $EnhancedListViewBloc<T> extends RxBlocBase
implements
EnhancedListViewEvents<T>,
EnhancedListViewBlocStates<T>,
EnhancedListViewBlocType<T> {
final _compositeSubscription = CompositeSubscription();
final _$loadPageEvent = PublishSubject<bool>();
late final Stream<bool> _isLoadingState = _mapToIsLoadingState();
late final Stream<String> _errorsState = _mapToErrorsState();
late final Stream<EnhancedPaginatedList<T>> _paginatedListState =
_mapToPaginatedListState();
@override
void loadPage({bool reset = false}) => _$loadPageEvent.add(reset);
@override
Stream<bool> get isLoading => _isLoadingState;
@override
Stream<String> get errors => _errorsState;
@override
Stream<EnhancedPaginatedList<T>> get paginatedList => _paginatedListState;
Stream<bool> _mapToIsLoadingState();
Stream<String> _mapToErrorsState();
Stream<EnhancedPaginatedList<T>> _mapToPaginatedListState();
@override
EnhancedListViewEvents<T> get events => this;
@override
EnhancedListViewBlocStates<T> get states => this;
@override
void dispose() {
_$loadPageEvent.close();
_compositeSubscription.dispose();
super.dispose();
}
}
class EnhancedListViewBloc<T> extends $EnhancedListViewBloc<T> {
@RxBloc()
class EnhancedListViewBloc<T, H, F> extends $EnhancedListViewBloc<T, H, F> {
EnhancedListViewBloc({
required EnhancedListViewRepository<T> repository,
required PaginatedListViewBodyBuilder<T> 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<EnhancedPaginatedList<T>>.seeded(
EnhancedPaginatedList<T>(
items: [],
pageSize: 1,
currentPage: 1,
error: Exception(),
isInitialized: true,
isLoading: false,
totalCount: 0,
),
);
final BodyItemsBuilder<T> bodyItemsBuilder;
final HeaderItemsBuilder<H> headerItemsBuilder;
final FooterItemsBuilder<F> footerItemsBuilder;
@override
Future<void> get refreshDone async => _paginatedList.value.awaitLoad();
final _bodyItems = BehaviorSubject<List<T>>.seeded([]);
final _headerItems = BehaviorSubject<List<H>>.seeded([]);
final _footerItems = BehaviorSubject<List<F>>.seeded([]);
final _isLoading = BehaviorSubject<bool>.seeded(false);
final _errors = BehaviorSubject<String>();
@override
Stream<EnhancedPaginatedList<T>> _mapToPaginatedListState() => _paginatedList;
@override
Stream<String> _mapToErrorsState() =>
errorState.map((error) => error.toString());
@override
Stream<bool> _mapToIsLoadingState() => loadingState;
Stream<List<T>> _fetchBodyItems(bool reset) async* {
try {
_isLoading.add(true);
final items = await bodyItemsBuilder(1, 10, null);
yield items.whereType<T>().toList();
} catch (e) {
_errors.add(e.toString());
} finally {
_isLoading.add(false);
}
}
Stream<List<H>> _fetchHeaderItems() async* {
try {
_isLoading.add(true);
final items = await headerItemsBuilder();
yield items.whereType<H>().toList();
} catch (e) {
_errors.add(e.toString());
} finally {
_isLoading.add(false);
}
}
Stream<List<F>> _fetchFooterItems() async* {
try {
_isLoading.add(true);
final items = await footerItemsBuilder();
yield items.whereType<F>().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<List<T>> _mapToBodyItemsState() => _bodyItems.stream;
@override
Stream<String> _mapToErrorsState() => _errors.stream;
@override
Stream<List<F>> _mapToFooterItemsState() => _footerItems.stream;
@override
Stream<List<H>> _mapToHeaderItemsState() => _headerItems.stream;
@override
Stream<bool> _mapToIsLoadingState() => _isLoading.stream;
}

View File

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

View File

@ -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<T, H, F>] extended by the [EnhancedListViewBloc<T, H, F>]
/// @nodoc
abstract class $EnhancedListViewBloc<T, H, F> extends RxBlocBase
implements
EnhancedListViewEvents,
EnhancedListViewStates,
EnhancedListViewBlocType {
final _compositeSubscription = CompositeSubscription();
/// Тhe [Subject] where events sink to by calling [loadBodyItems]
final _$loadBodyItemsEvent = PublishSubject<bool>();
/// Тhe [Subject] where events sink to by calling [loadHeaderItems]
final _$loadHeaderItemsEvent = PublishSubject<void>();
/// Тhe [Subject] where events sink to by calling [loadFooterItems]
final _$loadFooterItemsEvent = PublishSubject<void>();
/// The state of [bodyItems] implemented in [_mapToBodyItemsState]
late final Stream<List<T>> _bodyItemsState = _mapToBodyItemsState();
/// The state of [headerItems] implemented in [_mapToHeaderItemsState]
late final Stream<List<H>> _headerItemsState = _mapToHeaderItemsState();
/// The state of [footerItems] implemented in [_mapToFooterItemsState]
late final Stream<List<F>> _footerItemsState = _mapToFooterItemsState();
/// The state of [isLoading] implemented in [_mapToIsLoadingState]
late final Stream<bool> _isLoadingState = _mapToIsLoadingState();
/// The state of [errors] implemented in [_mapToErrorsState]
late final Stream<String> _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<List<T>> get bodyItems => _bodyItemsState;
@override
Stream<List<H>> get headerItems => _headerItemsState;
@override
Stream<List<F>> get footerItems => _footerItemsState;
@override
Stream<bool> get isLoading => _isLoadingState;
@override
Stream<String> get errors => _errorsState;
Stream<List<T>> _mapToBodyItemsState();
Stream<List<H>> _mapToHeaderItemsState();
Stream<List<F>> _mapToFooterItemsState();
Stream<bool> _mapToIsLoadingState();
Stream<String> _mapToErrorsState();
@override
EnhancedListViewEvents get events => this;
@override
EnhancedListViewStates get states => this;
@override
void dispose() {
_$loadBodyItemsEvent.close();
_$loadHeaderItemsEvent.close();
_$loadFooterItemsEvent.close();
_compositeSubscription.dispose();
super.dispose();
}
}