WIP
This commit is contained in:
parent
65a70cd8de
commit
2487801ee1
|
@ -21,6 +21,7 @@ class DocumentPageBloc extends Bloc<DocumentPageEvent, DocumentPageState> {
|
|||
|
||||
Future<void> _filterCategoryEvent(
|
||||
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
|
||||
log('generateDocuments -> ${state.isCategorySelected}');
|
||||
state.isCategorySelected
|
||||
? _unselectCategory(event, emit)
|
||||
: _selectCategory(event, emit);
|
||||
|
@ -32,8 +33,12 @@ class DocumentPageBloc extends Bloc<DocumentPageEvent, DocumentPageState> {
|
|||
isCategorySelected: true,
|
||||
));
|
||||
|
||||
final s = model.managerKey.currentState!;
|
||||
state.isCategorySelected ? s.filter(event.query) : s.filter(event.query);
|
||||
final listViewState = model.enhancedListViewKey.currentState!;
|
||||
listViewState.safeSetState(() async {
|
||||
return await listViewState.filterItems(event.query);
|
||||
});
|
||||
|
||||
// state.isCategorySelected ? s.filter(event.query) : s.filter(event.query);
|
||||
}
|
||||
|
||||
Future<void> _unselectCategory(
|
||||
|
@ -42,9 +47,10 @@ class DocumentPageBloc extends Bloc<DocumentPageEvent, DocumentPageState> {
|
|||
isCategorySelected: false,
|
||||
));
|
||||
|
||||
final s = model.managerKey.currentState!;
|
||||
final Query q = Document.fromDesc('');
|
||||
s.filter(q);
|
||||
final listViewState = model.enhancedListViewKey.currentState!;
|
||||
listViewState.safeSetState(() async {
|
||||
return await listViewState.filterItems(null);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _selectDocument(
|
||||
|
|
|
@ -4,7 +4,8 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
|
|||
DocumentPageModel();
|
||||
|
||||
late final GlobalKey<State<FutureBuilder>> pageKey;
|
||||
late final SearchKey managerKey;
|
||||
late final EnhancedRemoteListViewKey<Document, Category, Null>
|
||||
enhancedListViewKey;
|
||||
late final DocumentKey viewerKey;
|
||||
late final PagingController<int, Document> _pagingController;
|
||||
|
||||
|
@ -13,7 +14,7 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
|
|||
@override
|
||||
void initState(BuildContext context) {
|
||||
pageKey = GlobalKey<State<FutureBuilder>>();
|
||||
managerKey = SearchKey();
|
||||
enhancedListViewKey = EnhancedRemoteListViewKey<Document, Category, Null>();
|
||||
viewerKey = DocumentKey();
|
||||
|
||||
_pagingController = PagingController<int, Document>(firstPageKey: 1);
|
||||
|
@ -33,8 +34,8 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
|
|||
context.read<DocumentPageBloc>().add(SelectDocumentEvent(document));
|
||||
}
|
||||
|
||||
/// [documentItemBuilder]
|
||||
DocumentItem documentItemBuilder<T extends Document>(
|
||||
/// [itemBodyBuilder]
|
||||
DocumentItem itemBodyBuilder<T extends Document>(
|
||||
BuildContext context, T item, int index) {
|
||||
print('ItemBuilder -> $index');
|
||||
return DocumentItem(
|
||||
|
@ -47,8 +48,8 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
|
|||
return CategoryItem(category: item! as Category);
|
||||
}
|
||||
|
||||
/// [listHeaderBuilder]
|
||||
Widget listHeaderBuilder<T>(Future<List<T?>> Function() gen) =>
|
||||
/// [itemHeaderBuilder]
|
||||
Widget itemHeaderBuilder<T>(Future<List<T?>> Function() gen) =>
|
||||
Builder(builder: (context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
|
@ -75,81 +76,64 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
|
|||
);
|
||||
});
|
||||
|
||||
/// [generateDocuments]
|
||||
Future<(bool, List<Document?>?)> generateDocuments(
|
||||
pageKey, Query query) async {
|
||||
final List<Document?> error = [null];
|
||||
/// [generateBodyItems]
|
||||
Future<List<T?>> generateBodyItems<T extends Document>(
|
||||
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 (false, error);
|
||||
if (newItems.jsonBody['error'] == true) return (false, 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);
|
||||
if (newItems.jsonBody == null || newItems.jsonBody['error'] == true) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return (true, docs);
|
||||
// listViewKey.currentState!.count = newItems.jsonBody['value']['count'] ?? 0;
|
||||
final List<dynamic> list = newItems.jsonBody['value']['list'];
|
||||
final List<Document> docs = list.map((item) {
|
||||
log('-> generateDocuments: $item');
|
||||
return Document(
|
||||
id: item['id'],
|
||||
description: item['description'],
|
||||
type: item['type'],
|
||||
category: Category(
|
||||
id: item['category']['id'],
|
||||
color: item['category']['color'].toColor(),
|
||||
title: item['category']['description'],
|
||||
),
|
||||
person: item['person'] ?? '',
|
||||
property: item['property'] ?? '',
|
||||
createdAt: item['createdAt'],
|
||||
updatedAt: item['updatedAt'],
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return docs as List<T?>;
|
||||
}
|
||||
|
||||
/// [generateCategories]
|
||||
Future<List<Category?>> generateCategories() async {
|
||||
final List<Category?> error = [null];
|
||||
/// [generateHeaderItems]
|
||||
Future<List<T?>> generateHeaderItems<T extends Category>() async {
|
||||
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);
|
||||
if (newItems.jsonBody == null || newItems.jsonBody['error'] == true) {
|
||||
return error;
|
||||
}
|
||||
log('cats: $cats');
|
||||
return cats;
|
||||
|
||||
final List<dynamic> list = newItems.jsonBody['value'];
|
||||
final List<Category> categories = list.map((item) {
|
||||
return Category(
|
||||
id: item['id'],
|
||||
color: item['color'].toColor(),
|
||||
title: item['description'],
|
||||
);
|
||||
}).toList();
|
||||
|
||||
log('categories: $categories');
|
||||
return categories as List<T?>;
|
||||
}
|
||||
|
||||
/// [filter]
|
||||
|
|
|
@ -31,14 +31,14 @@ class DocumentManagerScreen extends StatelessScreen {
|
|||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: EnhancedRemoteListView<Document, Category>(
|
||||
key: model.managerKey,
|
||||
pagingController: model._pagingController,
|
||||
headerBuilder: model.listHeaderBuilder,
|
||||
headerItems: model.generateCategories,
|
||||
bodyBuilder: model.documentItemBuilder,
|
||||
dataProvider: model.generateDocuments,
|
||||
onFetchError: model.onFetchError,
|
||||
child: EnhancedListView.remote<Document, Category, Null>(
|
||||
key: model.enhancedListViewKey,
|
||||
headerBuilder: model.itemHeaderBuilder<Category>,
|
||||
headerItems: model.generateHeaderItems<Category>,
|
||||
bodyBuilder: model.itemBodyBuilder<Document>,
|
||||
bodyItems: model.generateBodyItems<Document>,
|
||||
footerBuilder: null,
|
||||
footerItems: null,
|
||||
),
|
||||
),
|
||||
] //
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
part of '../widgets.dart';
|
||||
part of 'widgets.dart';
|
||||
|
||||
class EnhancedCarouselView<T> extends StatelessWidget {
|
||||
final Future<List<T?>> Function() generateItems;
|
||||
|
|
|
@ -0,0 +1,637 @@
|
|||
part of 'widgets.dart';
|
||||
|
||||
/// [TypeDefs]
|
||||
|
||||
typedef EnhancedRemoteListViewKey<B, H, F>
|
||||
= GlobalKey<EnhancedRemoteListViewState<B, H, F>>;
|
||||
typedef EnhancedLocalListViewKey<B, H, F>
|
||||
= GlobalKey<EnhancedLocalListViewState<B, H, F>>;
|
||||
|
||||
typedef PaginatedListViewHeaderBuilder<H> = Widget Function(
|
||||
Future<List<H?>> Function() headerItems);
|
||||
typedef PaginatedListViewBodyBuilder<T> = Widget Function(
|
||||
BuildContext context, T item, int index);
|
||||
typedef PaginatedListViewFooterBuilder<F> = Widget Function(
|
||||
Future<List<F?>> Function() footerItems);
|
||||
|
||||
typedef Query<T> = T?;
|
||||
|
||||
typedef BodyItemsBuilder<T> = Future<List<T?>> Function(
|
||||
int page, int pageSize, Query query);
|
||||
typedef HeaderItemsBuilder<H> = Future<List<H?>> Function();
|
||||
typedef FooterItemsBuilder<F> = Future<List<F?>> Function();
|
||||
|
||||
/// [Extensions]
|
||||
extension PaginatedListMergeExtensions<T>
|
||||
on Stream<Result<EnhancedPaginatedList<T>>> {
|
||||
Stream<EnhancedPaginatedList<T>> mergeWithPaginatedList(
|
||||
BehaviorSubject<EnhancedPaginatedList<T>> currentList) {
|
||||
return map(
|
||||
(result) {
|
||||
final current = currentList.value;
|
||||
if (result is ResultSuccess<EnhancedPaginatedList<T>>) {
|
||||
final newPaginated = result.data;
|
||||
return current.items.isEmpty
|
||||
? newPaginated
|
||||
: current.copyWith(
|
||||
list: [...current.items, ...newPaginated.items],
|
||||
currentPage: newPaginated.currentPage,
|
||||
totalCount: newPaginated.totalCount,
|
||||
error: newPaginated.error,
|
||||
);
|
||||
} else if (result is ResultError<EnhancedPaginatedList<T>>) {
|
||||
return current.copyWith(error: result.error);
|
||||
} else {
|
||||
return current;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension PublishSubjectExtensions<T> on PublishSubject<T> {
|
||||
Stream<T> startWith(T initial) => Rx.concat([Stream.value(initial), this]);
|
||||
}
|
||||
|
||||
extension StreamStartWithExtension<T> on Stream<T> {
|
||||
Stream<T> startWith(T initial) => Rx.concat([Stream.value(initial), this]);
|
||||
}
|
||||
|
||||
extension QueryBlocStreamExtensions<T> on Stream<bool> {
|
||||
Stream<Result<EnhancedPaginatedList<T>>> fetchData(
|
||||
EnhancedListViewRepository<T> repository,
|
||||
PaginatedListViewBodyBuilder<T> builder,
|
||||
BehaviorSubject<EnhancedPaginatedList<T>> paginatedList,
|
||||
) =>
|
||||
switchMap((reset) {
|
||||
if (reset) {
|
||||
paginatedList.add(paginatedList.value.resetAll());
|
||||
}
|
||||
final nextPage = paginatedList.value.currentPage + 1;
|
||||
return repository
|
||||
.fetchPage(nextPage, paginatedList.value.pageSize, builder)
|
||||
.asResultStream();
|
||||
});
|
||||
}
|
||||
|
||||
/// [Widgets]
|
||||
|
||||
/// [EnhancedListView]
|
||||
|
||||
interface class EnhancedPaginatedList<T> extends PaginatedList<T> {
|
||||
@override
|
||||
final Exception? error;
|
||||
final bool isInitialized;
|
||||
@override
|
||||
final bool isLoading;
|
||||
final List<T> items;
|
||||
@override
|
||||
final int pageSize;
|
||||
@override
|
||||
final int? totalCount;
|
||||
final int currentPage;
|
||||
|
||||
EnhancedPaginatedList({
|
||||
required this.items,
|
||||
required this.pageSize,
|
||||
this.currentPage = 0,
|
||||
this.totalCount,
|
||||
required this.error,
|
||||
required this.isInitialized,
|
||||
required this.isLoading,
|
||||
}) : super(
|
||||
error: error,
|
||||
isInitialized: isInitialized,
|
||||
isLoading: isLoading,
|
||||
list: items,
|
||||
pageSize: pageSize,
|
||||
totalCount: totalCount,
|
||||
);
|
||||
|
||||
EnhancedPaginatedList<T> resetAll() => EnhancedPaginatedList<T>(
|
||||
error: null,
|
||||
isInitialized: false,
|
||||
isLoading: false,
|
||||
items: const [],
|
||||
pageSize: pageSize,
|
||||
totalCount: null,
|
||||
currentPage: 0,
|
||||
);
|
||||
|
||||
@override
|
||||
EnhancedPaginatedList<T> copyWith({
|
||||
List<T>? list,
|
||||
bool? isLoading,
|
||||
int? totalCount,
|
||||
Exception? error,
|
||||
int? pageSize,
|
||||
bool? isInitialized,
|
||||
int? currentPage,
|
||||
}) =>
|
||||
EnhancedPaginatedList<T>(
|
||||
error: error ?? this.error,
|
||||
isInitialized: isInitialized ?? this.isInitialized,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
items: list ?? this.items,
|
||||
pageSize: pageSize ?? this.pageSize,
|
||||
totalCount: totalCount ?? this.totalCount,
|
||||
currentPage: currentPage ?? this.currentPage,
|
||||
);
|
||||
|
||||
@override
|
||||
int get itemCount => items.length;
|
||||
|
||||
@override
|
||||
T? getItem(int index) => index < items.length ? items[index] : null;
|
||||
|
||||
Future<void> awaitLoad() async => Future.value();
|
||||
}
|
||||
|
||||
abstract interface class EnhancedListViewBase<T, H, F> extends StatefulWidget {
|
||||
const EnhancedListViewBase({super.key});
|
||||
}
|
||||
|
||||
abstract interface class EnhancedListViewBaseState<T>
|
||||
extends State<EnhancedListViewBase> {}
|
||||
|
||||
class EnhancedListView {
|
||||
static EnhancedRemoteListView<BodyType, HeaderType, FooterType>
|
||||
remote<BodyType, HeaderType, FooterType>({
|
||||
required Key? key,
|
||||
required BodyItemsBuilder<BodyType> bodyItems,
|
||||
required PaginatedListViewBodyBuilder<BodyType> bodyBuilder,
|
||||
HeaderItemsBuilder<HeaderType>? headerItems,
|
||||
PaginatedListViewHeaderBuilder<HeaderType>? headerBuilder,
|
||||
FooterItemsBuilder<FooterType>? footerItems,
|
||||
PaginatedListViewFooterBuilder<FooterType>? footerBuilder,
|
||||
}) {
|
||||
return EnhancedRemoteListView<BodyType, HeaderType, FooterType>(
|
||||
key: key,
|
||||
bodyItems: bodyItems,
|
||||
bodyBuilder: bodyBuilder,
|
||||
headerItems: headerItems,
|
||||
headerBuilder: headerBuilder,
|
||||
footerItems: footerItems,
|
||||
footerBuilder: footerBuilder,
|
||||
);
|
||||
}
|
||||
|
||||
static EnhancedLocalListView<T, H, F> local<T, H, F>({
|
||||
required Key? key,
|
||||
required List<T> list,
|
||||
required Widget Function(T) itemBuilder,
|
||||
required bool Function(T, String) filter,
|
||||
List<T> Function(String)? onSearch,
|
||||
Widget? header,
|
||||
}) {
|
||||
return EnhancedLocalListView<T, H, F>(
|
||||
key: key,
|
||||
list: list,
|
||||
itemBuilder: itemBuilder,
|
||||
filter: filter,
|
||||
onSearch: onSearch,
|
||||
header: header,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [EnhancedLocalListView]
|
||||
|
||||
class EnhancedLocalListView<T, H, F> extends EnhancedListViewBase<T, H, F> {
|
||||
final List<T> list;
|
||||
final Widget Function(T) itemBuilder;
|
||||
final bool Function(T, String) filter;
|
||||
final Widget header;
|
||||
final List<T> Function(String)? onSearch;
|
||||
|
||||
EnhancedLocalListView({
|
||||
Key? key,
|
||||
required this.list,
|
||||
required this.itemBuilder,
|
||||
required this.filter,
|
||||
List<T> Function(String)? onSearch,
|
||||
Widget? header,
|
||||
}) : header = header ?? const SizedBox.shrink(),
|
||||
onSearch = onSearch ??
|
||||
((String query) =>
|
||||
list.where((documents) => filter(documents, query)).toList()),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
EnhancedLocalListViewState<T, H, F> createState() =>
|
||||
EnhancedLocalListViewState<T, H, F>();
|
||||
}
|
||||
|
||||
class EnhancedLocalListViewState<T, H, F>
|
||||
extends State<EnhancedLocalListView<T, H, F>> {
|
||||
TextEditingController editingController = TextEditingController();
|
||||
late List<T> filteredItems;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
filteredItems = widget.list;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void filter(value) {
|
||||
setState(() {
|
||||
filteredItems = widget.onSearch!(value);
|
||||
});
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: filteredItems.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) return widget.header;
|
||||
return widget.itemBuilder(filteredItems[index - 1]);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
child: TextFormField(
|
||||
controller: editingController,
|
||||
onChanged: filter,
|
||||
cursorColor: Colors.black,
|
||||
cursorWidth: 2.0,
|
||||
cursorRadius: Radius.circular(2.0),
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.search,
|
||||
autocorrect: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search, color: Colors.black),
|
||||
labelText: 'Pesquisar',
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
hintText: 'Digite sua pesquisa',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.blue),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.red),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.red, width: 2.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [EnhancedRemoteListView]
|
||||
|
||||
class EnhancedRemoteListView<BodyType, HeaderType, FooterType>
|
||||
extends EnhancedListViewBase<BodyType, HeaderType, FooterType> {
|
||||
final BodyItemsBuilder<BodyType> bodyItems;
|
||||
final PaginatedListViewBodyBuilder<BodyType> bodyBuilder;
|
||||
final HeaderItemsBuilder<HeaderType>? headerItems;
|
||||
final PaginatedListViewHeaderBuilder<HeaderType>? headerBuilder;
|
||||
final FooterItemsBuilder<FooterType>? footerItems;
|
||||
final PaginatedListViewFooterBuilder<FooterType>? footerBuilder;
|
||||
|
||||
const EnhancedRemoteListView({
|
||||
Key? key,
|
||||
required this.bodyItems,
|
||||
required this.bodyBuilder,
|
||||
this.headerItems,
|
||||
this.headerBuilder,
|
||||
this.footerItems,
|
||||
this.footerBuilder,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
EnhancedRemoteListViewState<BodyType, HeaderType, FooterType> createState() =>
|
||||
EnhancedRemoteListViewState<BodyType, HeaderType, FooterType>();
|
||||
}
|
||||
|
||||
class EnhancedRemoteListViewState<ItemType, HeaderType, FooterType>
|
||||
extends State<EnhancedRemoteListView<ItemType, HeaderType, FooterType>> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
bool _isLoadingMore = false;
|
||||
List<ItemType?> _items = [];
|
||||
int _currentPage = 1;
|
||||
Query<ItemType> query;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_onScroll);
|
||||
_loadInitialItems();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_scrollController.position.pixels ==
|
||||
_scrollController.position.maxScrollExtent &&
|
||||
!_isLoadingMore) {
|
||||
_loadMoreItems();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadInitialItems() async {
|
||||
final newItems = await widget.bodyItems(1, 10, query);
|
||||
setState(() {
|
||||
_items = newItems;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadMoreItems() async {
|
||||
setState(() {
|
||||
_isLoadingMore = true;
|
||||
});
|
||||
final newItems = await widget.bodyItems(_currentPage + 1, 10, query);
|
||||
setState(() {
|
||||
_isLoadingMore = false;
|
||||
if (newItems.isNotEmpty) {
|
||||
_items.addAll(newItems);
|
||||
_currentPage++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> filterItems(Query<ItemType> newQuery) async {
|
||||
log('filterItems: $newQuery');
|
||||
setState(() {
|
||||
query = newQuery;
|
||||
_items = [];
|
||||
_currentPage = 1;
|
||||
});
|
||||
await _loadInitialItems();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
log('key: ${widget.key}');
|
||||
return ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: _items.length +
|
||||
(widget.headerItems != null ? 1 : 0) +
|
||||
(widget.footerItems != null ? 1 : 0) +
|
||||
(_isLoadingMore ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (widget.headerItems != null && index == 0) {
|
||||
return FutureBuilder<List<HeaderType?>>(
|
||||
future: widget.headerItems!(),
|
||||
builder: (context, headerSnapshot) {
|
||||
if (headerSnapshot.connectionState == ConnectionState.waiting) {
|
||||
return const EnhancedProgressIndicator();
|
||||
} else if (headerSnapshot.hasError) {
|
||||
return EnhancedErrorWidget(error: headerSnapshot.error);
|
||||
} else {
|
||||
return widget
|
||||
.headerBuilder!(() => Future.value(headerSnapshot.data));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
if (widget.footerItems != null &&
|
||||
index == _items.length + (widget.headerItems != null ? 1 : 0)) {
|
||||
return FutureBuilder<List<FooterType?>>(
|
||||
future: widget.footerItems!(),
|
||||
builder: (context, footerSnapshot) {
|
||||
if (footerSnapshot.connectionState == ConnectionState.waiting) {
|
||||
return const EnhancedProgressIndicator();
|
||||
} else if (footerSnapshot.hasError) {
|
||||
return EnhancedErrorWidget(
|
||||
error: footerSnapshot.error as Exception);
|
||||
} else {
|
||||
return widget
|
||||
.footerBuilder!(() => Future.value(footerSnapshot.data));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
if (_isLoadingMore &&
|
||||
index ==
|
||||
_items.length +
|
||||
(widget.headerItems != null ? 1 : 0) +
|
||||
(widget.footerItems != null ? 1 : 0)) {
|
||||
return const EnhancedProgressIndicator();
|
||||
}
|
||||
final item = _items[index - (widget.headerItems != null ? 1 : 0)];
|
||||
return widget.bodyBuilder(context, item as ItemType, index);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// [Utils]
|
||||
|
||||
class EnhancedListTile<T extends Widget> extends StatelessWidget {
|
||||
const EnhancedListTile(
|
||||
{required this.leading, required this.title, super.key});
|
||||
|
||||
final T leading;
|
||||
final T title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: leading,
|
||||
title: title,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EnhancedErrorWidget extends StatelessWidget {
|
||||
final Object? error;
|
||||
const EnhancedErrorWidget({required this.error, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
log('error: $error');
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
error.toString(),
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EnhancedProgressIndicator extends StatelessWidget {
|
||||
const EnhancedProgressIndicator({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class NetworkError implements Exception {
|
||||
final String message;
|
||||
NetworkError(this.message);
|
||||
}
|
||||
|
||||
class ParsingError implements Exception {
|
||||
final String message;
|
||||
ParsingError(this.message);
|
||||
}
|
||||
|
||||
class AuthorizationError implements Exception {
|
||||
final String message;
|
||||
AuthorizationError(this.message);
|
||||
}
|
||||
|
||||
/// [State Managment]
|
||||
Stream<bool> get loadingState => Stream<bool>.empty();
|
||||
Stream<Exception> get errorState => Stream<Exception>.empty();
|
||||
|
||||
abstract class EnhancedListViewRepository<T> {
|
||||
Future<EnhancedPaginatedList<T>> fetchPage(
|
||||
int page, int pageSize, PaginatedListViewBodyBuilder<T> builder);
|
||||
}
|
||||
|
||||
abstract class EnhancedListViewBlocStates<T> {
|
||||
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> {
|
||||
EnhancedListViewBloc({
|
||||
required EnhancedListViewRepository<T> repository,
|
||||
required PaginatedListViewBodyBuilder<T> builder,
|
||||
required T item,
|
||||
int initialPageSize = 50,
|
||||
}) {
|
||||
_$loadPageEvent
|
||||
.startWith(true)
|
||||
.fetchData(
|
||||
repository,
|
||||
(context, item, index) => builder(context, item, index),
|
||||
_paginatedList,
|
||||
)
|
||||
.setResultStateHandler(this)
|
||||
.mergeWithPaginatedList(_paginatedList)
|
||||
.bind(_paginatedList)
|
||||
.addTo(_compositeSubscription);
|
||||
}
|
||||
|
||||
final _paginatedList = BehaviorSubject<EnhancedPaginatedList<T>>.seeded(
|
||||
EnhancedPaginatedList<T>(
|
||||
items: [],
|
||||
pageSize: 1,
|
||||
currentPage: 1,
|
||||
error: Exception(),
|
||||
isInitialized: true,
|
||||
isLoading: false,
|
||||
totalCount: 0,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> get refreshDone async => _paginatedList.value.awaitLoad();
|
||||
|
||||
@override
|
||||
Stream<EnhancedPaginatedList<T>> _mapToPaginatedListState() => _paginatedList;
|
||||
@override
|
||||
Stream<String> _mapToErrorsState() =>
|
||||
errorState.map((error) => error.toString());
|
||||
@override
|
||||
Stream<bool> _mapToIsLoadingState() => loadingState;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_paginatedList.close();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
|
@ -1,325 +1,325 @@
|
|||
part of '../widgets.dart';
|
||||
part of 'widgets.dart';
|
||||
|
||||
typedef SearchKey = GlobalKey<EnhancedRemoteListViewState>;
|
||||
// typedef SearchKey = GlobalKey<EnhancedRemoteListViewState>;
|
||||
|
||||
typedef Query<X extends Archive> = X?;
|
||||
// typedef Query<X extends Archive> = X?;
|
||||
|
||||
/// -----------------------------------------------
|
||||
/// [EnhancedListView]
|
||||
/// -----------------------------------------------
|
||||
// /// -----------------------------------------------
|
||||
// /// [EnhancedListView]
|
||||
// /// -----------------------------------------------
|
||||
|
||||
abstract interface class EnhancedListView<T> extends StatefulWidget {
|
||||
const EnhancedListView({super.key});
|
||||
}
|
||||
// abstract interface class EnhancedListView<T> extends StatefulWidget {
|
||||
// const EnhancedListView({super.key});
|
||||
// }
|
||||
|
||||
abstract interface class EnhancedListViewState<T>
|
||||
extends State<EnhancedListView> {}
|
||||
// abstract interface class EnhancedListViewState<T>
|
||||
// extends State<EnhancedListView> {}
|
||||
|
||||
/// -----------------------------------------------
|
||||
/// [EnhancedLocalListView]
|
||||
/// -----------------------------------------------
|
||||
// /// -----------------------------------------------
|
||||
// /// [EnhancedLocalListView]
|
||||
// /// -----------------------------------------------
|
||||
|
||||
class EnhancedLocalListView<T> extends EnhancedListView<T> {
|
||||
final List<T> list;
|
||||
final Widget Function(T) itemBuilder;
|
||||
final bool Function(T, String) filter;
|
||||
final Widget header;
|
||||
final List<T> Function(String)? onSearch;
|
||||
// class EnhancedLocalListView<T> extends EnhancedListView<T> {
|
||||
// final List<T> list;
|
||||
// final Widget Function(T) itemBuilder;
|
||||
// final bool Function(T, String) filter;
|
||||
// final Widget header;
|
||||
// final List<T> Function(String)? onSearch;
|
||||
|
||||
EnhancedLocalListView({
|
||||
Key? key,
|
||||
required this.list,
|
||||
required this.itemBuilder,
|
||||
required this.filter,
|
||||
List<T> Function(String)? onSearch,
|
||||
Widget? header,
|
||||
}) : header = header ?? const SizedBox.shrink(),
|
||||
onSearch = onSearch ??
|
||||
((String query) =>
|
||||
list.where((documents) => filter(documents, query)).toList()),
|
||||
super(key: key);
|
||||
// EnhancedLocalListView({
|
||||
// Key? key,
|
||||
// required this.list,
|
||||
// required this.itemBuilder,
|
||||
// required this.filter,
|
||||
// List<T> Function(String)? onSearch,
|
||||
// Widget? header,
|
||||
// }) : header = header ?? const SizedBox.shrink(),
|
||||
// onSearch = onSearch ??
|
||||
// ((String query) =>
|
||||
// list.where((documents) => filter(documents, query)).toList()),
|
||||
// super(key: key);
|
||||
|
||||
// return documents.where((documents) => filter(documents, query)).toList();
|
||||
// // return documents.where((documents) => filter(documents, query)).toList();
|
||||
|
||||
@override
|
||||
EnhancedLocalListViewState<T> createState() =>
|
||||
EnhancedLocalListViewState<T>();
|
||||
}
|
||||
// @override
|
||||
// EnhancedLocalListViewState<T> createState() =>
|
||||
// EnhancedLocalListViewState<T>();
|
||||
// }
|
||||
|
||||
class EnhancedLocalListViewState<T> extends State<EnhancedLocalListView<T>> {
|
||||
TextEditingController editingController = TextEditingController();
|
||||
late List<T> filteredItems;
|
||||
// class EnhancedLocalListViewState<T> extends State<EnhancedLocalListView<T>> {
|
||||
// TextEditingController editingController = TextEditingController();
|
||||
// late List<T> filteredItems;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
filteredItems = widget.list;
|
||||
super.initState();
|
||||
}
|
||||
// @override
|
||||
// void initState() {
|
||||
// filteredItems = widget.list;
|
||||
// super.initState();
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void filter(value) {
|
||||
safeSetState(() {
|
||||
filteredItems = widget.onSearch!(value);
|
||||
});
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// void filter(value) {
|
||||
// safeSetState(() {
|
||||
// filteredItems = widget.onSearch!(value);
|
||||
// });
|
||||
// }
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: filteredItems.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) return widget.header;
|
||||
return widget.itemBuilder(filteredItems[index - 1]);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
child: TextFormField(
|
||||
controller: editingController,
|
||||
onChanged: filter,
|
||||
cursorColor: Colors.black,
|
||||
cursorWidth: 2.0,
|
||||
cursorRadius: Radius.circular(2.0),
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.search,
|
||||
autocorrect: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search, color: Colors.black),
|
||||
labelText: 'Pesquisar',
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
hintText: 'Digite sua pesquisa',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.blue),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.red),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.red, width: 2.0),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
// return Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// mainAxisSize: MainAxisSize.max,
|
||||
// children: <Widget>[
|
||||
// Expanded(
|
||||
// child: ListView.builder(
|
||||
// shrinkWrap: true,
|
||||
// itemCount: filteredItems.length + 1,
|
||||
// itemBuilder: (context, index) {
|
||||
// if (index == 0) return widget.header;
|
||||
// return widget.itemBuilder(filteredItems[index - 1]);
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(30.0),
|
||||
// child: TextFormField(
|
||||
// controller: editingController,
|
||||
// onChanged: filter,
|
||||
// cursorColor: Colors.black,
|
||||
// cursorWidth: 2.0,
|
||||
// cursorRadius: Radius.circular(2.0),
|
||||
// style: TextStyle(
|
||||
// color: Colors.black,
|
||||
// fontSize: 16.0,
|
||||
// ),
|
||||
// keyboardType: TextInputType.text,
|
||||
// textInputAction: TextInputAction.search,
|
||||
// autocorrect: true,
|
||||
// textCapitalization: TextCapitalization.sentences,
|
||||
// decoration: InputDecoration(
|
||||
// prefixIcon: Icon(Icons.search, color: Colors.black),
|
||||
// labelText: 'Pesquisar',
|
||||
// labelStyle: TextStyle(
|
||||
// color: Colors.black,
|
||||
// fontSize: 16.0,
|
||||
// ),
|
||||
// hintText: 'Digite sua pesquisa',
|
||||
// hintStyle: TextStyle(
|
||||
// color: Colors.grey,
|
||||
// fontSize: 14.0,
|
||||
// ),
|
||||
// filled: true,
|
||||
// fillColor: Colors.white,
|
||||
// contentPadding:
|
||||
// EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
|
||||
// enabledBorder: OutlineInputBorder(
|
||||
// borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
// borderSide: BorderSide(color: Colors.black),
|
||||
// ),
|
||||
// focusedBorder: OutlineInputBorder(
|
||||
// borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
// borderSide: BorderSide(color: Colors.blue),
|
||||
// ),
|
||||
// errorBorder: OutlineInputBorder(
|
||||
// borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
// borderSide: BorderSide(color: Colors.red),
|
||||
// ),
|
||||
// focusedErrorBorder: OutlineInputBorder(
|
||||
// borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
// borderSide: BorderSide(color: Colors.red, width: 2.0),
|
||||
// ),
|
||||
// ),
|
||||
// )),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
/// -----------------------------------------------
|
||||
/// [EnhancedRemoteListView]
|
||||
/// -----------------------------------------------
|
||||
// /// -----------------------------------------------
|
||||
// /// [EnhancedRemoteListView]
|
||||
// /// -----------------------------------------------
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class EnhancedRemoteListView<T, Y> extends EnhancedListView<T> {
|
||||
final Widget Function(BuildContext, T, int) bodyBuilder;
|
||||
final Future<List<Y?>> Function() headerItems;
|
||||
Widget Function<T>(Future<List<T?>> Function() gen) headerBuilder;
|
||||
final PagingController<int, T> pagingController;
|
||||
final Future<(bool, List<T?>?)> Function(int pageKey, Query query)
|
||||
dataProvider;
|
||||
// // ignore: must_be_immutable
|
||||
// class EnhancedRemoteListView<T, Y> extends EnhancedListView<T> {
|
||||
// final Widget Function(BuildContext, T, int) bodyBuilder;
|
||||
// final Future<List<Y?>> Function() headerItems;
|
||||
// Widget Function<T>(Future<List<T?>> Function() gen) headerBuilder;
|
||||
// final PagingController<int, T> pagingController;
|
||||
// final Future<(bool, List<T?>?)> Function(int pageKey, Query query)
|
||||
// dataProvider;
|
||||
|
||||
final void Function(Object, StackTrace) onFetchError;
|
||||
// final void Function(Object, StackTrace) onFetchError;
|
||||
|
||||
EnhancedRemoteListView({
|
||||
Key? key,
|
||||
// required this.fetchItems,
|
||||
required this.bodyBuilder,
|
||||
required this.headerItems,
|
||||
required this.headerBuilder,
|
||||
required this.pagingController,
|
||||
required this.dataProvider,
|
||||
required this.onFetchError,
|
||||
}) : super(key: key);
|
||||
// EnhancedRemoteListView({
|
||||
// Key? key,
|
||||
// // required this.fetchItems,
|
||||
// required this.bodyBuilder,
|
||||
// required this.headerItems,
|
||||
// required this.headerBuilder,
|
||||
// required this.pagingController,
|
||||
// required this.dataProvider,
|
||||
// required this.onFetchError,
|
||||
// }) : super(key: key);
|
||||
|
||||
@override
|
||||
EnhancedRemoteListViewState<T, Y> createState() =>
|
||||
EnhancedRemoteListViewState<T, Y>();
|
||||
}
|
||||
// @override
|
||||
// EnhancedRemoteListViewState<T, Y> createState() =>
|
||||
// EnhancedRemoteListViewState<T, Y>();
|
||||
// }
|
||||
|
||||
class EnhancedRemoteListViewState<T, Y>
|
||||
extends State<EnhancedRemoteListView<T, Y>> with Pageable {
|
||||
TextEditingController editingController = TextEditingController();
|
||||
bool isLoading = false;
|
||||
Query query = Document.fromDesc('');
|
||||
// class EnhancedRemoteListViewState<T, Y>
|
||||
// extends State<EnhancedRemoteListView<T, Y>> with Pageable {
|
||||
// TextEditingController editingController = TextEditingController();
|
||||
// bool isLoading = false;
|
||||
// Query query = Document.fromDesc('');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
widget.pagingController.addPageRequestListener(
|
||||
(page) => fetchPage(
|
||||
dataProvider: () async => await widget.dataProvider(page, query),
|
||||
onDataUnavailable: () => showNoMoreDataSnackBar(context),
|
||||
onDataAvailable: (data) =>
|
||||
widget.pagingController.appendLastPage(data),
|
||||
onFetchError: (e, s) => widget.onFetchError),
|
||||
);
|
||||
widget.pagingController.addStatusListener(_showError);
|
||||
// @override
|
||||
// void initState() {
|
||||
// widget.pagingController.addPageRequestListener(
|
||||
// (page) => fetchPage(
|
||||
// dataProvider: () async => await widget.dataProvider(page, query),
|
||||
// onDataUnavailable: () => showNoMoreDataSnackBar(context),
|
||||
// onDataAvailable: (data) =>
|
||||
// widget.pagingController.appendLastPage(data),
|
||||
// onFetchError: (e, s) => widget.onFetchError),
|
||||
// );
|
||||
// widget.pagingController.addStatusListener(_showError);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
// super.initState();
|
||||
// }
|
||||
|
||||
Future<void> _showError(PagingStatus status) async {
|
||||
if (status == PagingStatus.subsequentPageError) {
|
||||
final message = FFLocalizations.of(context).getVariableText(
|
||||
enText: 'Something went wrong while fetching a new page.',
|
||||
ptText: 'Algo deu errado ao buscar uma nova página.',
|
||||
);
|
||||
final retry = FFLocalizations.of(context).getVariableText(
|
||||
enText: 'Retry',
|
||||
ptText: 'Recarregar',
|
||||
);
|
||||
// Future<void> _showError(PagingStatus status) async {
|
||||
// if (status == PagingStatus.subsequentPageError) {
|
||||
// final message = FFLocalizations.of(context).getVariableText(
|
||||
// enText: 'Something went wrong while fetching a new page.',
|
||||
// ptText: 'Algo deu errado ao buscar uma nova página.',
|
||||
// );
|
||||
// final retry = FFLocalizations.of(context).getVariableText(
|
||||
// enText: 'Retry',
|
||||
// ptText: 'Recarregar',
|
||||
// );
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
action: SnackBarAction(
|
||||
label: retry,
|
||||
onPressed: () => widget.pagingController.retryLastFailedRequest(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(
|
||||
// content: Text(message),
|
||||
// action: SnackBarAction(
|
||||
// label: retry,
|
||||
// onPressed: () => widget.pagingController.retryLastFailedRequest(),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
void filter(Query data) async {
|
||||
if (data is Category) {
|
||||
safeSetState(() => query = Category(
|
||||
id: data.id,
|
||||
color: data.color,
|
||||
title: data.title,
|
||||
));
|
||||
widget.pagingController.refresh();
|
||||
} else if (data is Document) {
|
||||
log('filter: ${data.description}');
|
||||
// void filter(Query data) async {
|
||||
// if (data is Category) {
|
||||
// safeSetState(() => query = Category(
|
||||
// id: data.id,
|
||||
// color: data.color,
|
||||
// title: data.title,
|
||||
// ));
|
||||
// widget.pagingController.refresh();
|
||||
// } else if (data is Document) {
|
||||
// log('filter: ${data.description}');
|
||||
|
||||
safeSetState(() => query = data);
|
||||
widget.pagingController.refresh();
|
||||
} else {
|
||||
safeSetState(() {
|
||||
query = Document.fromDesc('');
|
||||
});
|
||||
widget.pagingController.refresh();
|
||||
}
|
||||
}
|
||||
// safeSetState(() => query = data);
|
||||
// widget.pagingController.refresh();
|
||||
// } else {
|
||||
// safeSetState(() {
|
||||
// query = Document.fromDesc('');
|
||||
// });
|
||||
// widget.pagingController.refresh();
|
||||
// }
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final noDataFound = FFLocalizations.of(context).getVariableText(
|
||||
ptText: "Nenhum item encontrado!",
|
||||
enText: "No item found",
|
||||
);
|
||||
final theme = FlutterFlowTheme.of(context);
|
||||
final locale = FFLocalizations.of(context);
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final noDataFound = FFLocalizations.of(context).getVariableText(
|
||||
// ptText: "Nenhum item encontrado!",
|
||||
// enText: "No item found",
|
||||
// );
|
||||
// final theme = FlutterFlowTheme.of(context);
|
||||
// final locale = FFLocalizations.of(context);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
buildPaginatedListView<int, T, Y>(
|
||||
noDataFound,
|
||||
widget.pagingController,
|
||||
widget.headerItems,
|
||||
widget.headerBuilder,
|
||||
widget.bodyBuilder,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextFormField(
|
||||
controller: editingController,
|
||||
onChanged: (value) => EasyDebounce.debounce(
|
||||
'_model.keyTextFieldTextController',
|
||||
const Duration(milliseconds: 500),
|
||||
() => filter(Document.fromDesc(value)),
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
// return Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// mainAxisSize: MainAxisSize.max,
|
||||
// children: <Widget>[
|
||||
// buildPaginatedListView<int, T, Y>(
|
||||
// noDataFound,
|
||||
// widget.pagingController,
|
||||
// widget.headerItems,
|
||||
// widget.headerBuilder,
|
||||
// widget.bodyBuilder,
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: TextFormField(
|
||||
// controller: editingController,
|
||||
// onChanged: (value) => EasyDebounce.debounce(
|
||||
// '_model.keyTextFieldTextController',
|
||||
// const Duration(milliseconds: 500),
|
||||
// () => filter(Document.fromDesc(value)),
|
||||
// ),
|
||||
// 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),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -9,12 +9,15 @@ 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:hub/shared/utils/index.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';
|
||||
import 'package:rx_bloc_list/rx_bloc_list.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:rx_bloc/rx_bloc.dart';
|
||||
|
||||
/// [Base]
|
||||
part 'page.dart';
|
||||
part 'component.dart';
|
||||
part 'screen.dart';
|
||||
|
@ -25,5 +28,7 @@ part 'entity.dart';
|
|||
part 'list_view.dart';
|
||||
part 'carousel_view.dart';
|
||||
part 'read_view.dart';
|
||||
part 'enhanced_list_view.dart';
|
||||
|
||||
/// [Component]'s
|
||||
part 'text.dart';
|
||||
|
|
154
pubspec.yaml
154
pubspec.yaml
|
@ -1,77 +1,102 @@
|
|||
# Informações básicas do projeto
|
||||
name: hub
|
||||
description: A new Flutter project.
|
||||
|
||||
publish_to: "none"
|
||||
description: . # Descrição do projeto (adicione mais detalhes se necessário)
|
||||
publish_to: "none" # Destino de publicação
|
||||
|
||||
# Versão do aplicativo
|
||||
version: 1.3.5+24
|
||||
|
||||
# Restrições de versão do SDK Dart
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
sdk: ">=3.5.0-0.0.dev <4.0.0"
|
||||
|
||||
# Dependências do aplicativo
|
||||
dependencies:
|
||||
# Dependências essenciais do Flutter
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
pdfx: ^2.8.0
|
||||
auto_size_text: ^3.0.0
|
||||
|
||||
# Gerenciamento de Estado
|
||||
provider: 6.1.2
|
||||
flutter_bloc: ^9.0.0
|
||||
flutter_riverpod: ^2.5.1
|
||||
rx_bloc: ^6.0.1
|
||||
flutter_rx_bloc: ^7.0.0
|
||||
rx_bloc_list: ^5.0.1
|
||||
rxdart: ^0.28.0
|
||||
rx_bloc_test: ^5.0.0
|
||||
bloc_test: ^10.0.0
|
||||
bloc_concurrency: ^0.3.0
|
||||
hydrated_bloc: ^10.0.0
|
||||
|
||||
# Programação Funcional
|
||||
dart_either: ^2.0.0
|
||||
result_dart: ^2.0.0
|
||||
fpdart: ^1.1.1
|
||||
|
||||
# Pacotes de UI
|
||||
auto_size_text: 3.0.0
|
||||
barcode_widget: ^2.0.4
|
||||
infinite_scroll_pagination: ^4.1.0
|
||||
cached_network_image: ^3.4.0
|
||||
firebase_core: ^3.4.0
|
||||
flutter_inappwebview: ^6.0.0
|
||||
webview_flutter: ^4.8.0
|
||||
rxdart: ^0.28.0
|
||||
collection: ^1.18.0
|
||||
app_links: ^6.3.2
|
||||
# crop_your_image: 1.1.0
|
||||
csv: 6.0.0
|
||||
device_info_plus: ^10.1.2 #11.2.2
|
||||
flutter_spinkit: 5.2.1
|
||||
flutter_staggered_grid_view: 0.7.0
|
||||
flutter_svg: ^2.0.15
|
||||
font_awesome_flutter: ^10.8.0
|
||||
google_fonts: 6.2.1
|
||||
material_symbols_icons: ^4.2784.0
|
||||
fluttertoast: ^8.2.8
|
||||
cupertino_icons: ^1.0.0
|
||||
qr_flutter: ^4.1.0
|
||||
percent_indicator: ^4.2.3
|
||||
page_transition: ^2.2.1
|
||||
share_plus: ^10.1.4
|
||||
pdfx: ^2.8.0
|
||||
dropdown_button2: ^2.3.9
|
||||
|
||||
# Firebase
|
||||
firebase_core: ^3.4.0
|
||||
firebase_messaging: ^15.1.0
|
||||
dropdown_button2: 2.3.9
|
||||
firebase_analytics: ^11.3.0
|
||||
firebase_crashlytics: ^4.0.1
|
||||
|
||||
# Utilidades
|
||||
app_links: ^6.3.3
|
||||
collection: ^1.18.0
|
||||
csv: 6.0.0
|
||||
device_info_plus: ^10.1.2
|
||||
easy_debounce: 2.0.3
|
||||
equatable: ^2.0.6
|
||||
file_picker: ^8.0.7
|
||||
# flutter_expandable_fab: ^2.1.0
|
||||
firebase_analytics: ^11.3.0
|
||||
flutter_animate: ^4.5.2
|
||||
# flutter_cache_manager: ^3.4.1
|
||||
# flutter_plugin_android_lifecycle: ^2.0.23
|
||||
share_plus: ^10.1.4
|
||||
# connectivity_plus: ^6.0.5
|
||||
flutter_secure_storage: ^10.0.0-beta.2
|
||||
flutter_secure_storage_linux: ^2.0.0
|
||||
flutter_secure_storage_macos: ^4.0.0
|
||||
flutter_secure_storage_platform_interface: ^2.0.1
|
||||
flutter_secure_storage_web: ^2.0.0
|
||||
flutter_secure_storage_windows: ^4.0.0
|
||||
flutter_spinkit: 5.2.1
|
||||
flutter_staggered_grid_view: 0.7.0
|
||||
flutter_svg: ^2.0.15
|
||||
font_awesome_flutter: ^10.8.0
|
||||
from_css_color: 2.0.0
|
||||
go_router: ^14.3.0
|
||||
google_fonts: 6.2.1
|
||||
http: 1.3.0
|
||||
http: ^1.3.0
|
||||
image_picker: 1.1.2
|
||||
image_picker_android: ^0.8.12+15
|
||||
image_picker_for_web: ^3.0.5
|
||||
persistent_bottom_nav_bar: ^6.2.1
|
||||
image_picker_ios: ^0.8.12+1
|
||||
image_picker_platform_interface: ^2.10.1
|
||||
local_auth: ^2.2.0
|
||||
intl: ^0.19.0
|
||||
# camera: ^0.11.0+2
|
||||
json_path: ^0.7.4
|
||||
mime_type: ^1.0.1
|
||||
page_transition: ^2.2.1
|
||||
path_provider: ^2.1.4
|
||||
path_provider_android: ^2.2.12
|
||||
google_mlkit_face_detection: ^0.12.0
|
||||
path_provider_foundation: ^2.4.1
|
||||
path_provider_platform_interface: 2.1.2
|
||||
percent_indicator: ^4.2.3
|
||||
plugin_platform_interface: 2.1.8
|
||||
provider: 6.1.2
|
||||
shared_preferences: ^2.3.2
|
||||
shared_preferences_android: ^2.3.3
|
||||
shared_preferences_foundation: ^2.5.3
|
||||
|
@ -85,69 +110,80 @@ dependencies:
|
|||
url_launcher_android: ^6.3.12
|
||||
url_launcher_ios: ^6.3.1
|
||||
url_launcher_platform_interface: 2.3.2
|
||||
infinite_scroll_pagination: ^4.1.0
|
||||
# video_player: 2.8.7
|
||||
# video_player_android: 2.5.0
|
||||
# video_player_avfoundation: 2.6.1
|
||||
# video_player_platform_interface: 6.2.2
|
||||
# video_player_web: 2.3.1
|
||||
material_symbols_icons: ^4.2784.0
|
||||
fluttertoast: ^8.2.8
|
||||
cupertino_icons: ^1.0.0
|
||||
flutter_bloc: ^9.0.0
|
||||
flutter_riverpod: ^2.5.1
|
||||
qr_flutter: ^4.1.0
|
||||
permission_handler: ^11.3.1
|
||||
firebase_crashlytics: ^4.0.1
|
||||
awesome_notifications: ^0.10.0
|
||||
app_tracking_transparency: ^2.0.6
|
||||
# dio: ^5.7.0
|
||||
# crypto: ^3.0.5
|
||||
freezed_annotation: ^2.4.4
|
||||
package_info_plus: ^8.1.1
|
||||
# json_annotation: ^4.9.0
|
||||
sliver_tools: ^0.2.12
|
||||
json_annotation: ^4.9.0
|
||||
|
||||
# Dependências a partir de repositório Git (pacotes personalizados)
|
||||
# base:
|
||||
# git:
|
||||
# url: 'git@github.com:FRE-Informatica/flutter-freaccess-base.git'
|
||||
# path: 'packages/base'
|
||||
# components:
|
||||
# git:
|
||||
# url: 'git@github.com:FRE-Informatica/flutter-freaccess-base.git'
|
||||
# path: 'packages/components'
|
||||
# templates:
|
||||
# git:
|
||||
# url: 'git@github.com:FRE-Informatica/flutter-freaccess-base.git'
|
||||
# path: 'packages/templates'
|
||||
# theme:
|
||||
# git:
|
||||
# url: 'git@github.com:FRE-Informatica/flutter-freaccess-base.git'
|
||||
# path: 'packages/theme'
|
||||
|
||||
# Substituição de versões específicas de pacotes, se necessário
|
||||
dependency_overrides:
|
||||
http: 1.3.0
|
||||
http: ^1.3.0
|
||||
uuid: ^4.0.0
|
||||
win32: 5.5.1
|
||||
|
||||
# Dependências para desenvolvimento e testes
|
||||
dev_dependencies:
|
||||
bloc_lint: ^0.1.0
|
||||
rx_bloc_generator: ^8.0.1
|
||||
flutter_launcher_icons: ^0.14.1
|
||||
flutter_lints: ^5.0.0
|
||||
image: ^4.3.0
|
||||
lints: ^5.0.0
|
||||
# build_runner: ^2.4.13
|
||||
mockito: ^5.4.4
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.4.13
|
||||
freezed: ^2.5.7
|
||||
json_serializable: ^6.9.0
|
||||
build_runner: ^2.4.14
|
||||
freezed: ^3.0.0-0.0.dev
|
||||
json_serializable: ^6.9.4
|
||||
test: ^1.25.7
|
||||
patrol: ^3.13.2
|
||||
patrol_finders: ^2.6.0
|
||||
|
||||
# Configuração do flutter_launcher_icons
|
||||
flutter_launcher_icons:
|
||||
android: "launcher_icon"
|
||||
ios: true
|
||||
android: "launcher_icon" # Nome da pasta/ícone para Android
|
||||
ios: true # Geração de ícones para iOS
|
||||
web:
|
||||
generate: true
|
||||
generate: true # Geração de ícones para Web
|
||||
image_path: "assets/images/app_launcher_icon.svg"
|
||||
adaptive_icon_background: "assets/images/adaptive_background_icon.svg"
|
||||
adaptive_icon_foreground: "assets/images/adaptive_foreground_icon.svg"
|
||||
|
||||
# Configurações específicas do Flutter
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
uses-material-design: true # Habilita o uso do Material Design
|
||||
|
||||
# Definição de assets (imagens, fontes, etc.)
|
||||
assets:
|
||||
- assets/fonts/
|
||||
- assets/images/
|
||||
- assets/images/dark/
|
||||
- assets/images/light/
|
||||
- assets/files/
|
||||
|
||||
# Configuração de fontes customizadas
|
||||
fonts:
|
||||
- family: "SF Pro"
|
||||
fonts:
|
||||
|
@ -160,13 +196,15 @@ fonts:
|
|||
- family: Icons
|
||||
fonts:
|
||||
- asset: assets/fonts/icons.ttf
|
||||
|
||||
- family: Menu
|
||||
fonts:
|
||||
- asset: assets/fonts/menu.ttf
|
||||
|
||||
# Configuração do Patrol (ferramenta para testes de integração)
|
||||
patrol:
|
||||
app_name: FRE ACCESS HUB
|
||||
android:
|
||||
package_name: com.freaccess.hub
|
||||
ios:
|
||||
bundle_id: br.com.freaccess.hub
|
||||
bundle_id: br.com.freaccess.hub
|
||||
|
|
Loading…
Reference in New Issue