This commit is contained in:
jantunesmessias 2025-02-18 16:11:55 -03:00
parent e2b0f7538d
commit 963ccb64db
7 changed files with 517 additions and 311 deletions

View File

@ -1,19 +1,27 @@
import 'dart:developer';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hub/components/templates_components/details_component/details_component_widget.dart';
import 'package:hub/features/backend/index.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/shared/extensions/index.dart';
import 'package:hub/shared/utils/index.dart';
import 'package:hub/shared/widgets/widgets.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rxdart/rxdart.dart' as rx;
part 'documents.rxb.g.dart';
/// -----------------------------------------------
/// [TypeDefs]
/// -----------------------------------------------
typedef DocumentKey = GlobalKey<FREDocumentPageState>;
typedef DocumentKey = GlobalKey<DocumentPageState>;
/// -----------------------------------------------
@ -24,42 +32,53 @@ class DocumentPage extends StatefulPage {
const DocumentPage({super.key});
@override
State<DocumentPage> createState() => FREDocumentPageState();
State<DocumentPage> createState() => DocumentPageState();
}
class FREDocumentPageState<T extends DocumentPage>
extends PageState<DocumentPage> {
@override
Widget build(BuildContext context) => buildBody(context);
DocumentPageModel model = DocumentPageModel();
class DocumentPageState extends PageState<DocumentPage> {
@override
void initState() {
super.initState();
model.initState(context);
}
Widget buildBody(BuildContext context) {
@override
Widget build(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!,
return RxBlocMultiBuilder2<DocumentPageBlocType, bool, (Document, Uri)?>(
state1: (bloc) => bloc.states.isDocumentSelected,
state2: (bloc) => bloc.states.currentDocument,
bloc: context.read<DocumentPageBloc>(),
builder: (context, isSelect, current, bloc) {
log('-> Build -> DocumentPage -> RxBlocMultiBuilder2');
if (isSelect.hasData && isSelect.data!) {
return _buildDocumentViewScreen(current, bloc);
} else {
return _buildDocumentManagerScreen();
}
},
);
else
}
Widget _buildDocumentManagerScreen() {
final model = context.read<DocumentPageBloc>().model;
return DocumentManagerScreen(
model: model,
state: state,
state: this,
);
}),
}
Widget _buildDocumentViewScreen(
AsyncSnapshot<(Document, Uri)?> snapshot, DocumentPageBlocType bloc) {
if (snapshot.hasData) {
return DocumentViewScreen(
doc: snapshot.data!,
bloc: bloc,
);
} else {
return const Center(child: CircularProgressIndicator());
}
}
}
@ -69,27 +88,26 @@ class FREDocumentPageState<T extends DocumentPage>
/// -----------------------------------------------
class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
DocumentPageModel._privateConstructor();
final DocumentPageBlocType bloc;
DocumentPageModel(this.bloc);
static final DocumentPageModel _instance =
DocumentPageModel._privateConstructor();
factory DocumentPageModel() {
return _instance;
}
late EnhancedListViewKey<Document, Category, Null> vehicleScreenManager;
late EnhancedListViewKey<Document, Search, Category, Query>
vehicleScreenManager;
late DocumentKey vehicleScreenViewer;
late PagingController<int, Document> _pagingController;
late bool categoryIsSelected;
/// ------------
@override
void initState(BuildContext context) {
vehicleScreenManager = EnhancedListViewKey<Document, Category, Null>();
vehicleScreenManager =
EnhancedListViewKey<Document, Search, Category, Query>();
vehicleScreenViewer = DocumentKey();
_pagingController = PagingController<int, Document>(firstPageKey: 1);
categoryIsSelected = false;
}
@override
@ -101,58 +119,25 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
/// ------------
/// [onView]
/// [Body]
void onView(Document document, BuildContext context) async {
vehicleScreenManager.currentContext!
.read<DocumentPageBloc>()
.add(SelectDocumentEvent(document));
log('Disparando evento selectDocument');
bloc.events.selectDocument(document);
}
/// [itemBodyBuilder]
DocumentItem itemBodyBuilder<T extends Document>(
Widget 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 {
Future<List<T?>> generateBodyItems<T, Q>(
int pageKey, int pageSize, Q query) async {
log('generateDocuments: $query');
final List<T?> error = [null];
@ -201,8 +186,42 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
return docs as List<T?>;
}
/// [generateHeaderItems]
Future<List<T?>> generateHeaderItems<T>() async {
/// [Footer]
CategoryItem categoryItemBuilder<T>(T? item) {
return CategoryItem(category: item! as Category);
}
Widget itemFooterBuilder<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(
ptText: 'Suas Categorias',
enText: 'Your Categories',
),
style: TextStyle(
color: FlutterFlowTheme.of(context).primaryText,
fontSize: LimitedFontSizeUtil.getHeaderFontSize(context),
),
),
),
EnhancedCarouselView<T>(
generateItems: gen,
itemBuilder: categoryItemBuilder,
filter: filterByCategory<T>,
),
],
);
});
Future<List<T?>> generateFooterItems<T>() async {
log('generateCategories: ');
final List<T?> error = [null];
@ -229,12 +248,105 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
return cats as List<T?>;
}
/// [filter]
void filter<T>(T query, BuildContext context) {
vehicleScreenManager.currentState!.filterBodyItems(query);
/// [Header]
Widget itemHeaderBuilder<T extends Search>(
Future<List<T?>> Function() generateHeaderItems) {
return Builder(builder: (context) {
final theme = FlutterFlowTheme.of(context);
final locale = FFLocalizations.of(context);
TextEditingController editingController = TextEditingController();
return TextFormField(
controller: editingController,
onChanged: (value) => EasyDebounce.debounce(
'_model.keyTextFieldTextController',
const Duration(milliseconds: 500),
() => filterBySearchBar(Document.fromDesc(value), context),
),
cursorColor: theme.primaryText,
showCursor: false,
cursorWidth: 2.0,
cursorRadius: Radius.circular(100),
style: TextStyle(
color: theme.primaryText,
fontSize: 16.0,
decorationColor: Colors.amber,
),
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
autocorrect: true,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: theme.primary),
labelText: locale.getVariableText(
ptText: 'Pesquisar',
enText: 'Search',
),
labelStyle: TextStyle(
color: theme.primaryText,
fontSize: 16.0,
),
hintText: locale.getVariableText(
ptText: 'Digite sua pesquisa',
enText: 'Enter your search',
),
hintStyle: TextStyle(
color: theme.accent2,
fontSize: 14.0,
),
filled: true,
fillColor: Colors.transparent,
helperStyle: TextStyle(
color: theme.primaryText,
decorationColor: theme.primaryText,
),
focusColor: theme.primaryText,
contentPadding:
EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
enabledBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
focusedBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
errorBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
focusedErrorBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText, width: 2.0),
),
),
);
});
}
/// [onFetchError]
Future<List<T?>> generateHeaderItems<T extends Search>() async {
final Search item = Search();
return [item] as List<T?>;
}
/// [Filter]
void filterBySearchBar<T>(T query, BuildContext context) {
final key = vehicleScreenManager.currentState;
return key?.filterBodyItems(query);
}
void filterByCategory<T>(T query, BuildContext context) {
final key = vehicleScreenManager.currentState;
categoryIsSelected
? key?.filterBodyItems(null)
: key?.filterBodyItems(query);
categoryIsSelected = !categoryIsSelected;
}
/// [Exception]
void onFetchError(Object e, StackTrace s) {
DialogUtil.errorDefault(vehicleScreenViewer.currentContext!);
LogUtil.requestAPIFailed(
@ -246,145 +358,45 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
/// [BLoC]
/// -----------------------------------------------
/// [DocumentPageBloc]
class DocumentPageBloc extends Bloc<DocumentPageEvent, DocumentPageState> {
final DocumentPageModel model;
static DocumentPageBloc? _singleton;
factory DocumentPageBloc(DocumentPageModel model) {
_singleton ??= DocumentPageBloc._internal(model);
return _singleton!;
extension RxdartStartWithExtension<T> on Stream<T?> {
Stream<T?> rxdartStartWith(T? value) =>
rx.StartWithExtension(this).startWith(value);
}
DocumentPageBloc._internal(this.model) : super(DocumentPageState()) {
on<SelectDocumentEvent>(_selectDocument);
on<UnselectDocumentEvent>(_unselectDocument);
on<FilterCategoryEvent>(_filterCategoryEvent);
abstract class DocumentPageBlocEvents {
void selectDocument(Document document);
void unselectDocument();
}
Future<void> _filterCategoryEvent(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
_selectCategory(event, emit);
state.isCategorySelected
? _unselectCategory(event, emit)
: _selectCategory(event, emit);
abstract class DocumentPageBlocStates {
Stream<bool> get isDocumentSelected;
Stream<(Document, Uri)?> get currentDocument;
}
Future<void> _selectCategory(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
log('filterItems A: ${event.query}');
emit(state.copyWith(
isCategorySelected: true,
));
@RxBloc()
class DocumentPageBloc extends $DocumentPageBloc {
late final DocumentPageModel model;
// final listViewState = model.vehicleScreenManager.currentState!;
// listViewState.widget.bodyItems = (await model.generateBodyItems(
// 1, 10, event.query)) as BodyItemsBuilder<Document>;
DocumentPageBloc(BuildContext context) {
model = DocumentPageModel(this);
model.initState(context);
}
Future<void> _unselectCategory(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
emit(state.copyWith(
isCategorySelected: false,
));
@override
Stream<(Document, Uri)?> _mapToCurrentDocumentState() => _$selectDocumentEvent
.switchMap((event) async* {
log('Evento selectDocument recebido: ${event.description}');
final uri = await GetPDF().call(event.id);
yield (event, uri);
})
.rxdartStartWith(null)
.mergeWith([_$unselectDocumentEvent.map((_) => null)]);
// 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,
);
}
@override
Stream<bool> _mapToIsDocumentSelectedState() => _mapToCurrentDocumentState()
.map((document) => document != null)
.rxdartStartWith(false)
.map((isSelected) => isSelected ?? false);
}
/// -----------------------------------------------
@ -425,14 +437,14 @@ class DocumentManagerScreen extends StatelessScreen {
return Column(
children: [
Expanded(
child: EnhancedListView<Document, Category, Null>(
child: EnhancedListView<Document, Search, Category, Query>(
key: model.vehicleScreenManager,
headerBuilder: model.itemHeaderBuilder<Category>,
headerItems: model.generateHeaderItems<Category>,
headerBuilder: model.itemHeaderBuilder<Search>,
headerItems: model.generateHeaderItems<Search>,
bodyBuilder: model.itemBodyBuilder<Document>,
bodyItems: model.generateBodyItems<Document>,
footerBuilder: null,
footerItems: null,
bodyItems: model.generateBodyItems<Document, Query>,
footerBuilder: model.itemFooterBuilder<Category>,
footerItems: model.generateFooterItems<Category>,
),
),
] //
@ -448,11 +460,11 @@ class DocumentViewScreen extends StatefulScreen {
const DocumentViewScreen({
super.key,
required this.doc,
required this.uri,
required this.bloc,
});
final Document doc;
final Uri uri;
final (Document, Uri) doc;
final DocumentPageBlocType bloc;
@override
ScreenState<DocumentViewScreen> createState() => _DocumentViewScreenState();
@ -461,18 +473,59 @@ class DocumentViewScreen extends StatefulScreen {
class _DocumentViewScreenState extends ScreenState<DocumentViewScreen> {
@override
Widget build(BuildContext context) {
action() {
context.read<DocumentPageBloc>().add(UnselectDocumentEvent());
}
final String title = widget.doc.description;
final String title = widget.doc.$1.description;
final theme = FlutterFlowTheme.of(context);
final locale = FFLocalizations.of(context);
backAction() => widget.bloc.events.unselectDocument();
infoAction() async => await showDialog(
useSafeArea: true,
context: context,
builder: (context) {
return Material(
child: DetailsComponentWidget(
buttons: [],
statusHashMap: [],
labelsHashMap: Map<String, String>.from({
locale.getVariableText(
enText: 'Description',
ptText: 'Descrição',
): widget.doc.$1.description,
locale.getVariableText(
enText: 'Type',
ptText: 'Tipo',
): widget.doc.$1.type,
locale.getVariableText(
enText: 'Category',
ptText: 'Categoria',
): widget.doc.$1.category.title,
locale.getVariableText(
enText: 'Person',
ptText: 'Pessoa',
): widget.doc.$1.person,
locale.getVariableText(
enText: 'Property',
ptText: 'Propriedade',
): widget.doc.$1.property,
locale.getVariableText(
enText: 'Created At',
ptText: 'Criado em',
): widget.doc.$1.createdAt,
locale.getVariableText(
enText: 'Updated At',
ptText: 'Atualizado em',
): widget.doc.$1.updatedAt,
}),
),
);
});
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) => action(),
onPopInvokedWithResult: (didPop, result) => backAction(),
child: Scaffold(
backgroundColor: theme.primaryBackground,
appBar: buildAppBar(title, context, action),
appBar: buildAppBar(title, context, backAction, infoAction),
body: buildBody(context),
),
);
@ -483,8 +536,8 @@ class _DocumentViewScreenState extends ScreenState<DocumentViewScreen> {
return ReadView(
// search: _viewerKey,
title: widget.doc.description,
url: widget.uri.toString(),
title: widget.doc.$1.description,
url: widget.doc.$2.toString(),
);
}
}
@ -495,6 +548,10 @@ class _DocumentViewScreenState extends ScreenState<DocumentViewScreen> {
abstract interface class Archive extends Entity {}
interface class Search extends Archive {
Search();
}
interface class Document extends Archive {
final int id;
final String description;

View File

@ -0,0 +1,70 @@
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: RxBlocGeneratorForAnnotation
// **************************************************************************
part of 'documents.dart';
/// Used as a contractor for the bloc, events and states classes
/// @nodoc
abstract class DocumentPageBlocType extends RxBlocTypeBase {
DocumentPageBlocEvents get events;
DocumentPageBlocStates get states;
}
/// [$DocumentPageBloc] extended by the [DocumentPageBloc]
/// @nodoc
abstract class $DocumentPageBloc extends RxBlocBase
implements
DocumentPageBlocEvents,
DocumentPageBlocStates,
DocumentPageBlocType {
final _compositeSubscription = rx.CompositeSubscription();
/// Тhe [Subject] where events sink to by calling [selectDocument]
final _$selectDocumentEvent = rx.PublishSubject<Document>();
/// Тhe [Subject] where events sink to by calling [unselectDocument]
final _$unselectDocumentEvent = rx.PublishSubject<void>();
/// The state of [isDocumentSelected] implemented in
/// [_mapToIsDocumentSelectedState]
late final Stream<bool> _isDocumentSelectedState =
_mapToIsDocumentSelectedState();
/// The state of [currentDocument] implemented in [_mapToCurrentDocumentState]
late final Stream<(Document, Uri)?> _currentDocumentState =
_mapToCurrentDocumentState();
@override
void selectDocument(Document document) => _$selectDocumentEvent.add(document);
@override
void unselectDocument() => _$unselectDocumentEvent.add(null);
@override
Stream<bool> get isDocumentSelected => _isDocumentSelectedState;
@override
Stream<(Document, Uri)?> get currentDocument => _currentDocumentState;
Stream<bool> _mapToIsDocumentSelectedState();
Stream<(Document, Uri)?> _mapToCurrentDocumentState();
@override
DocumentPageBlocEvents get events => this;
@override
DocumentPageBlocStates get states => this;
@override
void dispose() {
_$selectDocumentEvent.close();
_$unselectDocumentEvent.close();
_compositeSubscription.dispose();
super.dispose();
}
}

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:hub/features/backend/index.dart';
import 'package:hub/features/documents/documents.dart';
import 'package:hub/features/history/index.dart';
@ -303,22 +304,25 @@ GoRouter createRouter(AppStateNotifier appStateNotifier) {
name: 'documentPage',
path: '/documentPage',
builder: (context, params) {
return DocumentPage();
},
),
FFRoute(
name: 'documentViewerScreen',
path: '/documentViewerScreen',
builder: (context, params) {
final Document doc = params.getParam('doc', ParamType.Function);
final Uri uri = params.getParam('uri', ParamType.Function);
return DocumentViewScreen(
key: UniqueKey(),
doc: doc,
uri: uri,
return RxBlocProvider<DocumentPageBloc>(
create: (context) => DocumentPageBloc(context),
child: DocumentPage(),
);
},
),
// FFRoute(
// name: 'documentViewerScreen',
// path: '/documentViewerScreen',
// builder: (context, params) {
// final Document doc = params.getParam('doc', ParamType.Function);
// final Uri uri = params.getParam('uri', ParamType.Function);
// return DocumentViewScreen(
// key: UniqueKey(),
// doc: (doc,
// uri: uri,
// );
// },
// ),
// FFRoute(name: 'settingsPage', path: '/settingsPage', builder: (context, params) => params.isEmpty ? const NavBarPage(initialPage: 'settingsPage') : const SettingsPageWidget())
].map((r) => r.toRoute(appStateNotifier)).toList(),
);

View File

@ -2,8 +2,8 @@ part of 'widgets.dart';
/// [TypeDefs]
typedef EnhancedListViewKey<B, H, F>
= GlobalKey<EnhancedListViewState<B, H, F>>;
typedef EnhancedListViewKey<B, H, F, Q>
= GlobalKey<EnhancedListViewState<B, H, F, Q>>;
typedef PaginatedListViewHeaderBuilder<H> = Widget Function(
Future<List<H?>> Function() headerItems);
@ -14,8 +14,8 @@ typedef PaginatedListViewFooterBuilder<F> = Widget Function(
typedef Query<T> = T?;
typedef BodyItemsBuilder<T> = Future<List<T?>> Function(
int page, int pageSize, Query query);
typedef BodyItemsBuilder<T, Q> = Future<List<T?>> Function(
int page, int pageSize, Q query);
typedef HeaderItemsBuilder<H> = Future<List<H?>> Function();
typedef FooterItemsBuilder<F> = Future<List<F?>> Function();
@ -145,16 +145,17 @@ interface class EnhancedPaginatedList<T> extends PaginatedList<T> {
Future<void> awaitLoad() async => Future.value();
}
abstract interface class EnhancedListViewBase<T, H, F> extends StatefulWidget {
abstract interface class EnhancedListViewBase<T, H, F, Q>
extends StatefulWidget {
const EnhancedListViewBase({super.key});
}
abstract interface class EnhancedListViewBaseState<T>
extends State<EnhancedListViewBase> {}
class EnhancedListView<BodyType, HeaderType, FooterType>
extends EnhancedListViewBase<BodyType, HeaderType, FooterType> {
final BodyItemsBuilder<BodyType> bodyItems;
class EnhancedListView<BodyType, HeaderType, FooterType, QueryType>
extends EnhancedListViewBase<BodyType, HeaderType, FooterType, QueryType> {
final BodyItemsBuilder<BodyType, QueryType?> bodyItems;
final PaginatedListViewBodyBuilder<BodyType> bodyBuilder;
final HeaderItemsBuilder<HeaderType>? headerItems;
final PaginatedListViewHeaderBuilder<HeaderType>? headerBuilder;
@ -172,18 +173,21 @@ class EnhancedListView<BodyType, HeaderType, FooterType>
}) : super(key: key);
@override
EnhancedListViewState<BodyType, HeaderType, FooterType> createState() =>
EnhancedListViewState<BodyType, HeaderType, FooterType>();
EnhancedListViewState<BodyType, HeaderType, FooterType, QueryType>
createState() =>
EnhancedListViewState<BodyType, HeaderType, FooterType, QueryType>();
}
class EnhancedListViewState<ItemType, HeaderType, FooterType>
extends State<EnhancedListView<ItemType, HeaderType, FooterType>> {
late EnhancedListViewBloc<ItemType, HeaderType, FooterType> bloc;
class EnhancedListViewState<ItemType, HeaderType, FooterType, QueryType>
extends State<
EnhancedListView<ItemType, HeaderType, FooterType, QueryType>> {
late EnhancedListViewBloc<ItemType, HeaderType, FooterType, QueryType> bloc;
@override
void initState() {
super.initState();
bloc = EnhancedListViewBloc<ItemType, HeaderType, FooterType>(
bloc = EnhancedListViewBloc<ItemType, HeaderType, FooterType, QueryType>(
bodyItemsBuilder: widget.bodyItems,
headerItemsBuilder: widget.headerItems ?? () async => [],
footerItemsBuilder: widget.footerItems ?? () async => [],
@ -193,29 +197,53 @@ class EnhancedListViewState<ItemType, HeaderType, FooterType>
bloc.events.loadFooterItems();
}
void filterBodyItems(Query query) {
bloc.filterBodyItems(query);
}
void filterBodyItems(Query query) => bloc.filterBodyItems(query);
void filterHeaderItems(Query query) {
bloc.filterHeaderItems(query);
}
void filterHeaderItems(Query query) => bloc.filterHeaderItems(query);
void filterFooterItems(Query query) {
bloc.filterFooterItems(query);
}
void filterFooterItems(Query query) => bloc.filterFooterItems(query);
@override
Widget build(BuildContext context) {
return StreamBuilder<List<ItemType>>(
stream: bloc.states.bodyItems.cast<List<ItemType>>(),
builder: (context, bodySnapshot) {
return StreamBuilder<List<HeaderType>>(
final header = StreamBuilder<List<HeaderType>>(
stream: bloc.states.headerItems.cast<List<HeaderType>>(),
builder: (context, headerSnapshot) {
return StreamBuilder<List<FooterType>>(
if (headerSnapshot.connectionState == ConnectionState.waiting) {
return const EnhancedProgressIndicator();
} else if (headerSnapshot.hasError) {
return EnhancedErrorWidget(error: headerSnapshot.error);
} else if (!headerSnapshot.hasData || headerSnapshot.data!.isEmpty) {
return const SizedBox.shrink();
} else {
return widget.headerBuilder!(() async => headerSnapshot.data!);
}
},
);
final footer = StreamBuilder<List<FooterType>>(
stream: bloc.states.footerItems.cast<List<FooterType>>(),
builder: (context, footerSnapshot) {
if (footerSnapshot.connectionState == ConnectionState.waiting) {
return const EnhancedProgressIndicator();
} else if (footerSnapshot.hasError) {
return EnhancedErrorWidget(error: footerSnapshot.error);
} else if (!footerSnapshot.hasData || footerSnapshot.data!.isEmpty) {
return const SizedBox.shrink();
} else {
return widget.footerBuilder!(() async => footerSnapshot.data!);
}
},
);
final body = Expanded(
child: StreamBuilder<List<ItemType>>(
stream: bloc.states.bodyItems.cast<List<ItemType>>(),
builder: (context, bodySnapshot) {
if (bodySnapshot.connectionState == ConnectionState.waiting) {
return const EnhancedProgressIndicator();
} else if (bodySnapshot.hasError) {
return EnhancedErrorWidget(error: bodySnapshot.error);
} else if (!bodySnapshot.hasData || bodySnapshot.data!.isEmpty) {
return const SizedBox.shrink();
} else {
return ListView.builder(
itemCount: bodySnapshot.data?.length ?? 0,
itemBuilder: (context, index) {
@ -223,11 +251,17 @@ class EnhancedListViewState<ItemType, HeaderType, FooterType>
context, bodySnapshot.data![index], index);
},
);
}
},
),
);
},
);
},
return Column(
children: [
if (widget.headerBuilder != null) header,
body,
if (widget.footerBuilder != null) footer,
],
);
}
@ -311,13 +345,13 @@ abstract class EnhancedListViewRepository<T> {
int page, int pageSize, PaginatedListViewBodyBuilder<T> builder);
}
abstract class EnhancedListViewEvents<T, H, F> {
void loadBodyItems({bool reset = false});
abstract class EnhancedListViewEvents<T, H, F, Q> {
void loadBodyItems({bool reset = false, dynamic query = null});
void loadHeaderItems();
void loadFooterItems();
}
abstract class EnhancedListViewStates<T, H, F> {
abstract class EnhancedListViewStates<T, H, F, Q> {
Stream<List<T>> get bodyItems;
Stream<List<H>> get headerItems;
Stream<List<F>> get footerItems;
@ -326,15 +360,16 @@ abstract class EnhancedListViewStates<T, H, F> {
}
@RxBloc()
class EnhancedListViewBloc<T, H, F> extends $EnhancedListViewBloc<T, H, F> {
class EnhancedListViewBloc<T, H, F, Q>
extends $EnhancedListViewBloc<T, H, F, Q?> {
EnhancedListViewBloc({
required this.bodyItemsBuilder,
required this.headerItemsBuilder,
required this.footerItemsBuilder,
}) {
_$loadBodyItemsEvent
.startWith(true)
.switchMap((reset) => _fetchBodyItems(reset))
.startWith((query: null, reset: true))
.switchMap((item) => _fetchBodyItems(item.reset, item.query))
.bind(_bodyItems)
.addTo(_compositeSubscription);
@ -349,20 +384,23 @@ class EnhancedListViewBloc<T, H, F> extends $EnhancedListViewBloc<T, H, F> {
.addTo(_compositeSubscription);
}
final BodyItemsBuilder<T> bodyItemsBuilder;
final BodyItemsBuilder<T, Q?> bodyItemsBuilder;
final HeaderItemsBuilder<H> headerItemsBuilder;
final FooterItemsBuilder<F> footerItemsBuilder;
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>();
Stream<List<T>> _fetchBodyItems(bool reset) async* {
Stream<List<T>> _fetchBodyItems(bool reset, Q? query) async* {
try {
_isLoading.add(true);
final items = await bodyItemsBuilder(1, 10, null);
final items = await bodyItemsBuilder(1, 10, query);
yield items.whereType<T>().toList();
} catch (e) {
_errors.add(e.toString());
@ -395,15 +433,16 @@ class EnhancedListViewBloc<T, H, F> extends $EnhancedListViewBloc<T, H, F> {
}
}
void filterBodyItems(Query query) {
_$loadBodyItemsEvent.add(true);
void filterBodyItems(Q query) {
print('filterBodyItems Q: ${query == null}');
_$loadBodyItemsEvent.add((query: query, reset: true));
}
void filterHeaderItems(Query query) {
void filterHeaderItems(Q query) {
_$loadHeaderItemsEvent.add(null);
}
void filterFooterItems(Query query) {
void filterFooterItems(Q query) {
_$loadFooterItemsEvent.add(null);
}

View File

@ -3,9 +3,10 @@ part of 'widgets.dart';
mixin Template {
PreferredSizeWidget buildAppBar(
String title,
BuildContext context,
BuildContext context, [
dynamic Function()? backAction,
) {
dynamic Function()? frontAction,
]) {
final theme = FlutterFlowTheme.of(context);
return AppBar(
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
@ -25,12 +26,36 @@ mixin Template {
leading: _backButton(context, theme, backAction),
centerTitle: true,
elevation: 0.0,
actions: [],
actions: _frontButton(context, theme, frontAction),
);
}
Widget _backButton(BuildContext context, FlutterFlowTheme theme,
List<Widget> _frontButton(BuildContext context, FlutterFlowTheme theme,
dynamic Function()? action) {
if (action == null) return [];
return [
IconButton(
onPressed: () async => await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Info'),
content: Text('This is a sample app.'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Close'))
],
)),
icon: Icon(
Symbols.info_i_rounded,
color: Colors.black,
))
];
}
Widget? _backButton(BuildContext context, FlutterFlowTheme theme,
dynamic Function()? onPressed) {
if (onPressed == null) return null;
return FlutterFlowIconButton(
key: ValueKey<String>('BackNavigationAppBar'),
borderColor: Colors.transparent,

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:hub/flutter_flow/index.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdfx/pdfx.dart';
import 'package:share_plus/share_plus.dart';

View File

@ -14,9 +14,9 @@ abstract class EnhancedListViewBlocType extends RxBlocTypeBase {
EnhancedListViewStates get states;
}
/// [$EnhancedListViewBloc<T, H, F>] extended by the [EnhancedListViewBloc<T, H, F>]
/// [$EnhancedListViewBloc<T, H, F, Q>] extended by the [EnhancedListViewBloc<T, H, F, Q>]
/// @nodoc
abstract class $EnhancedListViewBloc<T, H, F> extends RxBlocBase
abstract class $EnhancedListViewBloc<T, H, F, Q> extends RxBlocBase
implements
EnhancedListViewEvents,
EnhancedListViewStates,
@ -24,7 +24,7 @@ abstract class $EnhancedListViewBloc<T, H, F> extends RxBlocBase
final _compositeSubscription = CompositeSubscription();
/// Тhe [Subject] where events sink to by calling [loadBodyItems]
final _$loadBodyItemsEvent = PublishSubject<bool>();
final _$loadBodyItemsEvent = PublishSubject<({bool reset, dynamic query})>();
/// Тhe [Subject] where events sink to by calling [loadHeaderItems]
final _$loadHeaderItemsEvent = PublishSubject<void>();
@ -48,7 +48,14 @@ abstract class $EnhancedListViewBloc<T, H, F> extends RxBlocBase
late final Stream<String> _errorsState = _mapToErrorsState();
@override
void loadBodyItems({bool reset = false}) => _$loadBodyItemsEvent.add(reset);
void loadBodyItems({
bool reset = false,
dynamic query = null,
}) =>
_$loadBodyItemsEvent.add((
reset: reset,
query: query,
));
@override
void loadHeaderItems() => _$loadHeaderItemsEvent.add(null);
@ -96,3 +103,6 @@ abstract class $EnhancedListViewBloc<T, H, F> extends RxBlocBase
super.dispose();
}
}
// ignore: unused_element
typedef _LoadBodyItemsEventArgs = ({bool reset, dynamic query});