This commit is contained in:
jantunesmessias 2025-02-13 16:00:28 -03:00
parent ed66fc86a2
commit 263013930e
18 changed files with 499 additions and 243 deletions

View File

@ -5,6 +5,7 @@ import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:hub/features/documents/index.dart' as doc; import 'package:hub/features/documents/index.dart' as doc;
import 'package:hub/features/notification/index.dart'; import 'package:hub/features/notification/index.dart';
import 'package:hub/features/storage/index.dart'; import 'package:hub/features/storage/index.dart';
@ -79,6 +80,23 @@ class FreAccessWSGlobal extends Api {
static GetDocuments getDocuments = GetDocuments(); static GetDocuments getDocuments = GetDocuments();
} }
class GetPDF extends Endpoint {
Future<Uri> call(final int id) async {
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
final String devUUID =
(await StorageHelper().get(ProfileStorageKey.devUUID.key)) ?? '';
final String userUUID =
(await StorageHelper().get(ProfileStorageKey.userUUID.key)) ?? '';
final String cliUUID =
(await StorageHelper().get(ProfileStorageKey.clientUUID.key)) ?? '';
const String atividade = 'visualizarDocumento';
const String callname = 'getDocumento.php';
return Uri.parse(
"$baseUrl/$callname?devUUID=$devUUID&userUUID=$userUUID&cliID=$cliUUID&atividade=$atividade&documentId=$id");
}
}
class GetCategories extends Endpoint { class GetCategories extends Endpoint {
@override @override
Future<ApiCallResponse> call() async { Future<ApiCallResponse> call() async {

View File

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

View File

@ -0,0 +1,68 @@
part of 'index.dart';
interface class Category extends Archive {
final int id;
final Color color;
final String title;
Category({
required this.id,
required this.color,
required this.title,
});
factory Category.fromDesc(String desc) {
return Category(
id: 0,
color: Colors.transparent,
title: desc,
);
}
static Color isSelected() => Colors.black;
}
class CategoryItem extends StatelessComponent {
final Category category;
const CategoryItem({
super.key,
required this.category,
});
@override
Widget build(BuildContext context) {
final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground;
return ColoredBox(
color: backgroundTheme,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: category.color,
shape: BoxShape.circle,
),
child: Icon(
Icons.folder,
color: Colors.white,
size: 40,
),
),
const SizedBox(height: 8),
Text(
category.title,
style: TextStyle(
color: category.color,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
}

View File

@ -1,30 +1,6 @@
part of 'index.dart'; part of 'index.dart';
abstract interface class DocumentEntity extends Entity {} interface class Document extends Archive {
interface class Category extends DocumentEntity {
final int id;
final Color color;
final String title;
Category({
required this.id,
required this.color,
required this.title,
});
factory Category.fromDesc(String desc) {
return Category(
id: 0,
color: Colors.transparent,
title: desc,
);
}
static Color isSelected() => Colors.black;
}
interface class Document extends DocumentEntity {
final int id; final int id;
final String description; final String description;
final String type; final String type;
@ -57,10 +33,16 @@ interface class Document extends DocumentEntity {
); );
} }
class DocumentItem extends StatelessWidget { // ignore: must_be_immutable
class DocumentItem extends StatelessComponent {
final Document document; final Document document;
void Function(Document, BuildContext) onPressed;
const DocumentItem({super.key, required this.document}); DocumentItem({
super.key,
required this.document,
required this.onPressed,
});
Tooltip _buildTooltip(String text, Color color, BuildContext context, Tooltip _buildTooltip(String text, Color color, BuildContext context,
BoxConstraints constraints) { BoxConstraints constraints) {
@ -106,16 +88,6 @@ class DocumentItem extends StatelessWidget {
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
); );
final Map<String, dynamic> extra = <String, dynamic>{
'document': document,
kTransitionInfoKey: const TransitionInfo(
hasTransition: true,
transitionType: PageTransitionType.rightToLeft,
alignment: Alignment.bottomCenter,
),
};
Future<Object?> onTap() =>
context.push('/documentViewerScreen', extra: extra);
return Padding( return Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
@ -126,7 +98,7 @@ class DocumentItem extends StatelessWidget {
: MediaQuery.of(context).size.height * 2; : MediaQuery.of(context).size.height * 2;
return InkWell( return InkWell(
onTap: onTap, onTap: () => onPressed(document, context),
enableFeedback: true, enableFeedback: true,
overlayColor: WidgetStateProperty.all<Color>(primaryColor), overlayColor: WidgetStateProperty.all<Color>(primaryColor),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -189,4 +161,13 @@ class DocumentItem extends StatelessWidget {
), ),
); );
} }
DocumentItem copyWith({
Document? document,
}) {
return DocumentItem(
document: document ?? this.document,
onPressed: onPressed,
);
}
} }

View File

@ -2,24 +2,41 @@ part of 'index.dart';
class DocumentManagerScreen extends StatelessScreen { class DocumentManagerScreen extends StatelessScreen {
final DocumentPageModel model; final DocumentPageModel model;
final DocumentPageState state;
const DocumentManagerScreen({ const DocumentManagerScreen({
super.key, super.key,
required this.model, required this.model,
required this.state,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final SizedBox space = SizedBox(height: 30); final String title = FFLocalizations.of(context).getVariableText(
enText: 'Documents',
ptText: 'Documentos',
);
final theme = FlutterFlowTheme.of(context);
action() => Navigator.pop(context);
return Scaffold(
backgroundColor: theme.primaryBackground,
appBar: buildAppBar(title, context, action),
body: buildBody(context),
);
}
Widget buildBody(BuildContext context) {
final SizedBox space = SizedBox(height: 30);
return Column( return Column(
children: [ children: [
Expanded( Expanded(
child: RemoteSearchView<Document>( child: EnhancedRemoteListView<Document, Category>(
key: model.searchKey, key: model.managerKey,
pagingController: model._pagingController, pagingController: model._pagingController,
headerBuilder: model.listHeaderBuilder, headerBuilder: model.listHeaderBuilder,
bodyBuilder: model.listBodyBuilder, headerItems: model.generateCategories,
bodyBuilder: model.documentItemBuilder,
dataProvider: model.generateDocuments, dataProvider: model.generateDocuments,
onFetchError: model.onFetchError, onFetchError: model.onFetchError,
), ),

View File

@ -0,0 +1,147 @@
part of 'index.dart';
/// -----------------------------------------------
/// [DocumentPageBloc]
/// -----------------------------------------------
class DocumentPageBloc extends Bloc<DocumentPageEvent, DocumentPageState> {
final DocumentPageModel model;
DocumentPageBloc._(this.model, DocumentPageState initialState)
: super(initialState) {
on<SelectDocumentEvent>(_selectDocument);
on<UnselectDocumentEvent>(_unselectDocument);
on<SelectCategoryEvent>(_selectCategory);
on<UnselectCategoryEvent>(_unselectCategory);
}
static DocumentPageBloc create(DocumentPageModel model) {
final initialState = DocumentPageState(
categories: [],
documents: [],
);
return DocumentPageBloc._(model, initialState);
}
Future<void> _selectCategory(
SelectCategoryEvent event, Emitter<DocumentPageState> emit) async {
print('select: ${event.query}');
final docs = await model.generateDocuments(state.page, event.query);
final bool isSelected = !state.isCategorySelected;
emit(state.copyWith(
isCategorySelected: isSelected,
documents: isSelected ? docs.$2 : state.documents,
));
}
Future<void> _unselectCategory(
UnselectCategoryEvent event, Emitter<DocumentPageState> emit) async {
emit(state);
}
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 {
final docs = await model.generateDocuments(state.page, state.query);
final cats = await model.generateCategories();
emit(
state.copyWith(
currentDocument: null,
isDocumentSelected: false,
documents: docs.$2,
categories: cats,
),
);
}
}
/// -----------------------------------------------
/// [DocumentPageEvent]
/// -----------------------------------------------
abstract class DocumentPageEvent {}
class SelectDocumentEvent extends DocumentPageEvent {
final Document document;
SelectDocumentEvent(
this.document,
);
}
class UnselectDocumentEvent extends DocumentPageEvent {}
class UnselectCategoryEvent extends DocumentPageEvent {}
class SelectCategoryEvent extends DocumentPageEvent {
final Query query;
SelectCategoryEvent(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;
final List<Document?> documents;
final List<Category?> categories;
const DocumentPageState({
this.query,
this.count,
this.page,
this.uri,
required this.documents,
this.currentDocument,
this.isCategorySelected = false,
required this.categories,
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,
//
documents: documents ?? this.documents,
currentDocument: currentDocument ?? this.currentDocument,
isDocumentSelected: isDocumentSelected ?? this.isDocumentSelected,
//
categories: categories ?? this.categories,
currentCategory: currentCategory ?? this.currentCategory,
isCategorySelected: isCategorySelected ?? this.isCategorySelected,
);
}
}

View File

@ -1,59 +1,80 @@
part of 'index.dart'; part of 'index.dart';
class DocumentPageModel extends FlutterFlowModel<DocumentPage> { class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
@override DocumentPageModel();
void dispose() {}
late final GlobalKey<State<FutureBuilder>> pageKey;
late final SearchKey managerKey;
late final DocumentKey viewerKey;
late final PagingController<int, Document> _pagingController;
/// ------------
@override @override
void initState(BuildContext context) {} void initState(BuildContext context) {
pageKey = GlobalKey<State<FutureBuilder>>();
managerKey = SearchKey();
viewerKey = DocumentKey();
final SearchKey searchKey = SearchKey(); _pagingController = PagingController<int, Document>(firstPageKey: 1);
final DocumentKey docKey = DocumentKey(); }
final PagingController<int, Document> _pagingController = @override
PagingController<int, Document>(firstPageKey: 1); void dispose() {
int count = 0; _pagingController.dispose();
final dynamic page = 1; // isCategorySelected = false;
// isDocumentSelected = false;
}
Query query = Document.fromDesc(''); /// ------------
late Document currentDocument; /// [onView]
bool isCategorySelected = false; void onView(Document document, BuildContext context) async {
late Category currentCategory; context.read<DocumentPageBloc>().add(SelectDocumentEvent(document));
}
List<Document?> documents = []; /// [documentItemBuilder]
List<Category?> categories = []; DocumentItem documentItemBuilder<T extends Document>(
BuildContext context, T item, int index) {
return DocumentItem(
document: item,
onPressed: onView,
);
}
/// [listBodyBuilder] CategoryItem categoryItemBuilder<T>(T? item) {
Widget listBodyBuilder<T>(BuildContext context, Document item, int index) { return CategoryItem(category: item! as Category);
return DocumentItem(document: item);
} }
/// [listHeaderBuilder] /// [listHeaderBuilder]
Widget listHeaderBuilder(BuildContext context) => Column( Widget listHeaderBuilder<T>(Future<List<T?>> Function() gen) =>
mainAxisSize: MainAxisSize.max, Builder(builder: (context) {
crossAxisAlignment: CrossAxisAlignment.start, return Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max,
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Padding( mainAxisAlignment: MainAxisAlignment.start,
padding: const EdgeInsets.fromLTRB(15, 0, 50, 0), children: [
child: Text( Padding(
'Últimos Documentos', padding: const EdgeInsets.fromLTRB(15, 0, 50, 0),
style: TextStyle( child: Text(
color: FlutterFlowTheme.of(context).primaryText, 'Últimos Documentos',
fontSize: LimitedFontSizeUtil.getHeaderFontSize(context), style: TextStyle(
color: FlutterFlowTheme.of(context).primaryText,
fontSize: LimitedFontSizeUtil.getHeaderFontSize(context),
),
), ),
), ),
), EnhancedCarouselView<T>(
CategoryCarousel<Category>( generateItems: gen,
categories: categories, itemBuilder: categoryItemBuilder,
filter: filterByCategory, filter: filter<T>,
), ),
], ],
); );
});
/// [generateDocuments] /// [generateDocuments]
Future<(bool, List<Document?>)> generateDocuments( Future<(bool, List<Document?>?)> generateDocuments(
pageKey, Query query) async { pageKey, Query query) async {
final List<Document?> error = [null]; final List<Document?> error = [null];
print('Query: ${query is Document}'); print('Query: ${query is Document}');
@ -103,9 +124,8 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
} }
/// [generateCategories] /// [generateCategories]
Future<List<Category?>> generateCategories(List<Document?> documents) async { Future<List<Category?>> generateCategories() async {
final List<Category?> error = [null]; final List<Category?> error = [null];
if (documents == []) return error;
final GetCategories getCategories = FreAccessWSGlobal.getCategories; final GetCategories getCategories = FreAccessWSGlobal.getCategories;
final ApiCallResponse newItems = await getCategories.call(); final ApiCallResponse newItems = await getCategories.call();
@ -130,26 +150,32 @@ class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
return cats; return cats;
} }
void filterByCategory(Category query) { /// [filter]
final state = searchKey.currentState; void filter<T>(T query, BuildContext context) {
context
if (state != null) { .read<DocumentPageBloc>()
log('filterByCategories: '); .add(SelectCategoryEvent(query as Archive?));
state.safeSetState(() {
if (isCategorySelected) {
state.filter(null);
isCategorySelected = false;
} else {
state.filter(query);
isCategorySelected = true;
}
});
}
} }
// {
// log('filterByCategories: ');
// final state = managerKey.currentState;
// if (state != null) {
// // safeSetState(() {
// // if (isCategorySelected) {
// // filter(null);
// // isCategorySelected = false;
// // } else {
// // filter(query);
// // isCategorySelected = true;
// // }
// // });
// }
// }
/// [onFetchError]
void onFetchError(Object e, StackTrace s) { void onFetchError(Object e, StackTrace s) {
DialogUtil.errorDefault(docKey.currentContext!); DialogUtil.errorDefault(viewerKey.currentContext!);
LogUtil.requestAPIFailed( LogUtil.requestAPIFailed(
"proccessRequest.php", "", "Consulta de Veículo", e, s); "proccessRequest.php", "", "Consulta de Veículo", e, s);
} }

View File

@ -11,44 +11,34 @@ class DocumentPage extends StatefulPage {
class FREDocumentPageState<T extends DocumentPage> class FREDocumentPageState<T extends DocumentPage>
extends PageState<DocumentPage> { extends PageState<DocumentPage> {
DocumentPageModel model = DocumentPageModel();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => buildBody(context);
final String title = FFLocalizations.of(context).getVariableText( DocumentPageModel model = DocumentPageModel();
enText: 'Documents',
ptText: 'Documentos',
);
final theme = FlutterFlowTheme.of(context);
return Scaffold(
backgroundColor: theme.primaryBackground,
appBar: buildAppBar(title, context),
body: buildBody(context),
);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
model.initState(context);
} }
Widget buildBody(BuildContext context) { Widget buildBody(BuildContext context) {
return FutureBuilder<void>( return BlocProvider<DocumentPageBloc>(
future: initAsync(), create: (context) => DocumentPageBloc.create(model),
builder: (context, snapshot) { child: BlocBuilder<DocumentPageBloc, DocumentPageState>(
return DocumentManagerScreen(model: model); builder: (context, state) {
}, print('Bloc -> ${state.isCategorySelected}');
);
// return DocumentViewScreen(document: documents.first);
}
Future<void> initAsync() async { if (state.isDocumentSelected)
final documents = await model.generateDocuments(model.page, model.query); return DocumentViewScreen(
final categories = await model.generateCategories(model.documents); doc: state.currentDocument!,
model.documents = documents.$2; uri: state.uri!,
model.categories = categories; );
log('-> generateDocuments: $documents'); else
log('-> generateCategories: $categories'); return DocumentManagerScreen(
model: model,
state: state,
);
}),
);
} }
} }

View File

@ -3,43 +3,52 @@ part of 'index.dart';
class DocumentViewScreen extends StatefulScreen { class DocumentViewScreen extends StatefulScreen {
const DocumentViewScreen({ const DocumentViewScreen({
super.key, super.key,
required this.document, required this.doc,
required this.uri,
}); });
final Document document; final Document doc;
final Uri uri;
@override @override
State<DocumentViewScreen> createState() => _DocumentViewScreenState(); ScreenState<DocumentViewScreen> createState() => _DocumentViewScreenState();
} }
class _DocumentViewScreenState extends State<DocumentViewScreen> { class _DocumentViewScreenState extends ScreenState<DocumentViewScreen> {
final PDFViewerKey _viewerKey = PDFViewerKey(); final PDFViewerKey _viewerKey = PDFViewerKey();
void onShare() async {
final response = await http.get(widget.uri);
if (response.statusCode == 200) {
final XFile xfile = XFile.fromData(response.bodyBytes,
name: '${widget.doc.description}.pdf', mimeType: 'application/pdf');
await Share.shareXFiles([xfile], text: 'Confira este PDF!');
} else {
print('Erro ao baixar o arquivo: ${response.statusCode}');
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Uri url = Uri.parse( action() => context.read<DocumentPageBloc>().add(UnselectDocumentEvent());
'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf');
void onPressed() async { final String title = widget.doc.description;
final response = await http.get(url); final theme = FlutterFlowTheme.of(context);
if (response.statusCode == 200) { return Scaffold(
final XFile xfile = XFile.fromData(response.bodyBytes, backgroundColor: theme.primaryBackground,
name: appBar: buildAppBar(title, context, action),
'${widget.document.description}_${widget.document.category.title}.pdf', body: buildBody(context),
mimeType: 'application/pdf'); );
await Share.shareXFiles([xfile], text: 'Confira este PDF!'); }
} else {
print('Erro ao baixar o arquivo: ${response.statusCode}');
}
}
Widget buildBody(BuildContext context) {
return Stack( return Stack(
children: [ children: [
Padding( Padding(
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
child: FREViewerPDF( child: FREViewerPDF(
search: _viewerKey, search: _viewerKey,
src: url.toString(), src: widget.uri.toString(),
), ),
), ),
Positioned( Positioned(
@ -51,7 +60,7 @@ class _DocumentViewScreenState extends State<DocumentViewScreen> {
color: Colors.black, color: Colors.black,
), ),
color: Colors.black, color: Colors.black,
onPressed: onPressed, onPressed: onShare,
), ),
), ),
], ],

View File

@ -2,6 +2,7 @@ import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:hub/features/backend/index.dart'; import 'package:hub/features/backend/index.dart';
@ -19,3 +20,6 @@ part 'document_page_widget.dart';
part 'document_viewer_screen.dart'; part 'document_viewer_screen.dart';
part 'document_page_model.dart'; part 'document_page_model.dart';
part 'document_item_component.dart'; part 'document_item_component.dart';
part 'document_page_bloc.dart';
part 'category_item_component.dart';
part 'archive_item_component.dart';

View File

@ -310,11 +310,12 @@ GoRouter createRouter(AppStateNotifier appStateNotifier) {
name: 'documentViewerScreen', name: 'documentViewerScreen',
path: '/documentViewerScreen', path: '/documentViewerScreen',
builder: (context, params) { builder: (context, params) {
final Document document = final Document doc = params.getParam('doc', ParamType.Function);
params.getParam('document', ParamType.Function); final Uri uri = params.getParam('uri', ParamType.Function);
return DocumentViewScreen( return DocumentViewScreen(
key: UniqueKey(), key: UniqueKey(),
document: document, doc: doc,
uri: uri,
); );
}, },
), ),

View File

@ -6,25 +6,30 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
extension PagedListViewExtension<PageKeyType, ItemType> extension PagedListViewExtension<PageKeyType, ItemType>
on PagedSliverList<PageKeyType, ItemType> {} on PagedSliverList<PageKeyType, ItemType> {}
typedef PaginatedListViewHeaderBuilder<T> = Widget Function<T>(
Future<List<T?>> Function() gen);
typedef PaginatedListViewBodyBuilder<T> = Widget Function(BuildContext, T, int);
mixin Pageable<T extends StatefulWidget> on State<T> { mixin Pageable<T extends StatefulWidget> on State<T> {
Expanded buildPaginatedListView<PageKeyType, ItemType>( Expanded buildPaginatedListView<PageKeyType, BodyType, HeaderType>(
String noDataFound, String noDataFound,
PagingController<PageKeyType, ItemType> pg, PagingController<PageKeyType, BodyType> pg,
Widget Function(BuildContext) headerBuilder, Future<List<HeaderType?>> Function() headerItems,
Widget Function(BuildContext, ItemType, int) bodyBuilder) { PaginatedListViewHeaderBuilder<BodyType> headerBuilder,
PaginatedListViewBodyBuilder<BodyType> bodyBuilder) {
final theme = FlutterFlowTheme.of(context); final theme = FlutterFlowTheme.of(context);
return Expanded( return Expanded(
child: RefreshIndicator( child: RefreshIndicator(
backgroundColor: theme.primaryBackground, backgroundColor: theme.primaryBackground,
color: theme.primary, color: theme.primary,
onRefresh: () async => pg.refresh(), onRefresh: () async => pg.refresh(),
child: PagedListView<PageKeyType, ItemType>( child: PagedListView<PageKeyType, BodyType>(
pagingController: pg, pagingController: pg,
builderDelegate: PagedChildBuilderDelegate<ItemType>( builderDelegate: PagedChildBuilderDelegate<BodyType>(
animateTransitions: true, animateTransitions: true,
itemBuilder: (context, item, int index) { itemBuilder: (context, item, int index) {
return Column(children: [ return Column(children: [
if (index == 0) headerBuilder(context), if (index == 0) headerBuilder(headerItems),
bodyBuilder(context, item, index), bodyBuilder(context, item, index),
]); ]);
}, },
@ -33,7 +38,8 @@ mixin Pageable<T extends StatefulWidget> on State<T> {
firstPageProgressIndicatorBuilder: (context) => firstPageProgressIndicatorBuilder: (context) =>
buildLoadingIndicator(context), buildLoadingIndicator(context),
noItemsFoundIndicatorBuilder: (context) => noItemsFoundIndicatorBuilder: (context) =>
buildNoDataFound(context, noDataFound, headerBuilder), buildNoDataFound<HeaderType>(
context, noDataFound, headerItems, headerBuilder),
firstPageErrorIndicatorBuilder: (context) => const Placeholder(), firstPageErrorIndicatorBuilder: (context) => const Placeholder(),
newPageErrorIndicatorBuilder: (context) => const Placeholder(), newPageErrorIndicatorBuilder: (context) => const Placeholder(),
), ),
@ -92,16 +98,17 @@ mixin Pageable<T extends StatefulWidget> on State<T> {
showSnackbar(context, message, true); showSnackbar(context, message, true);
} }
Widget buildNoDataFound( Widget buildNoDataFound<T>(
BuildContext context, BuildContext context,
String title, String title,
Widget Function(BuildContext) headerBuilder, Future<List<T?>> Function() items,
Widget Function<T>(Future<List<T?>> Function() items) headerBuilder,
) { ) {
final headerFontSize = LimitedFontSizeUtil.getHeaderFontSize(context); final headerFontSize = LimitedFontSizeUtil.getHeaderFontSize(context);
// final bodyFontSize = LimitedFontSizeUtil.getBodyFontSize(context); // final bodyFontSize = LimitedFontSizeUtil.getBodyFontSize(context);
return Column( return Column(
children: [ children: [
headerBuilder(context), headerBuilder(items),
Expanded( Expanded(
child: Center( child: Center(
child: Text( child: Text(

View File

@ -20,3 +20,5 @@ abstract class StatefulComponent<T> extends StatefulWidget
implements ComponentWidget<T> { implements ComponentWidget<T> {
const StatefulComponent({super.key}); const StatefulComponent({super.key});
} }
abstract class ComponentState<T extends StatefulComponent> extends State<T> {}

View File

@ -1,7 +1,12 @@
part of 'widgets.dart'; part of 'widgets.dart';
mixin MixinPage { mixin Template {
PreferredSizeWidget buildAppBar(String title, BuildContext context) { PreferredSizeWidget buildAppBar(
String title,
BuildContext context,
dynamic Function()? backAction,
) {
final theme = FlutterFlowTheme.of(context);
return AppBar( return AppBar(
backgroundColor: FlutterFlowTheme.of(context).primaryBackground, backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
@ -17,14 +22,15 @@ mixin MixinPage {
FlutterFlowTheme.of(context).headlineMediumFamily), FlutterFlowTheme.of(context).headlineMediumFamily),
), ),
), ),
leading: _backButton(context, FlutterFlowTheme.of(context)), leading: _backButton(context, theme, backAction),
centerTitle: true, centerTitle: true,
elevation: 0.0, elevation: 0.0,
actions: [], actions: [],
); );
} }
Widget _backButton(BuildContext context, FlutterFlowTheme theme) { Widget _backButton(BuildContext context, FlutterFlowTheme theme,
dynamic Function()? onPressed) {
return FlutterFlowIconButton( return FlutterFlowIconButton(
key: ValueKey<String>('BackNavigationAppBar'), key: ValueKey<String>('BackNavigationAppBar'),
borderColor: Colors.transparent, borderColor: Colors.transparent,
@ -36,7 +42,7 @@ mixin MixinPage {
color: theme.primaryText, color: theme.primaryText,
size: 30.0, size: 30.0,
), ),
onPressed: () => Navigator.of(context).pop(), onPressed: onPressed,
); );
} }
} }
@ -52,6 +58,7 @@ abstract class ModelPage<T> extends ModelWidget implements PageWidget<T> {
} }
abstract class StatelessPage<T> extends StatelessWidget abstract class StatelessPage<T> extends StatelessWidget
with Template
implements PageWidget<T> { implements PageWidget<T> {
const StatelessPage({super.key}); const StatelessPage({super.key});
} }
@ -61,4 +68,4 @@ abstract class StatefulPage<T> extends StatefulWidget implements PageWidget<T> {
} }
abstract class PageState<T extends StatefulPage> extends State<T> abstract class PageState<T extends StatefulPage> extends State<T>
with MixinPage {} with Template {}

View File

@ -9,6 +9,7 @@ abstract class ModelScreen<T> extends ModelWidget implements ScreenWidget<T> {
} }
abstract class StatelessScreen<T> extends StatelessWidget abstract class StatelessScreen<T> extends StatelessWidget
with Template
implements ScreenWidget<T> { implements ScreenWidget<T> {
const StatelessScreen({super.key}); const StatelessScreen({super.key});
} }
@ -17,3 +18,6 @@ abstract class StatefulScreen<T> extends StatefulWidget
implements ScreenWidget<T> { implements ScreenWidget<T> {
const StatefulScreen({super.key}); const StatefulScreen({super.key});
} }
abstract class ScreenState<T extends StatefulScreen> extends State<T>
with Template {}

View File

@ -1,59 +1,32 @@
part of '../widgets.dart'; part of '../widgets.dart';
class CategoryCarousel<T> extends StatelessWidget { class EnhancedCarouselView<T> extends StatelessWidget {
final List<T?> categories; final Future<List<T?>> Function() generateItems;
final void Function(T) filter; final void Function(T, BuildContext) filter;
final Widget Function<T>(T? item) itemBuilder;
const CategoryCarousel({ const EnhancedCarouselView({
super.key, super.key,
required this.categories, required this.generateItems,
required this.filter, required this.filter,
required this.itemBuilder,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground;
return SizedBox( return SizedBox(
height: 120, height: 120,
child: CarouselView( child: FutureBuilder<List<T?>>(
itemExtent: 100, future: generateItems(),
onTap: (index) => filter(categories[index] as T), builder: (context, snapshot) {
children: categories.map((category) { if (!snapshot.hasData) return SizedBox();
category as Category?; return CarouselView(
return ColoredBox( itemExtent: 100,
color: backgroundTheme, onTap: (index) => filter(snapshot.data![index] as T, context),
child: Padding( children:
padding: const EdgeInsets.all(8.0), snapshot.data!.map((item) => itemBuilder(item)).toList(),
child: Column( );
children: [ }),
Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: category!.color,
shape: BoxShape.circle,
),
child: Icon(
Icons.folder,
color: Colors.white,
size: 40,
),
),
const SizedBox(height: 8),
Text(
category.title,
style: TextStyle(
color: category.color,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}).toList(),
),
); );
} }
} }

View File

@ -1,39 +1,32 @@
part of '../widgets.dart'; part of '../widgets.dart';
typedef SearchKey = GlobalKey<RemoteSearchViewState>; typedef SearchKey = GlobalKey<EnhancedRemoteListViewState>;
typedef Query<X extends DocumentEntity> = X?; typedef Query<X extends Archive> = X?;
/// ----------------------------------------------- /// -----------------------------------------------
/// [SearchView] /// [EnhancedListView]
/// ----------------------------------------------- /// -----------------------------------------------
class SearchView<T> extends StatefulComponent { abstract interface class EnhancedListView<T> extends StatefulWidget {
const SearchView({super.key}); const EnhancedListView({super.key});
@override
State<SearchView> createState() => _SearchViewState();
} }
class _SearchViewState<T> extends State<SearchView> { abstract interface class EnhancedListViewState<T>
@override extends State<EnhancedListView> {}
Widget build(BuildContext context) {
return const Placeholder();
}
}
/// ----------------------------------------------- /// -----------------------------------------------
/// [LocalSearchView] /// [EnhancedLocalListView]
/// ----------------------------------------------- /// -----------------------------------------------
class LocalSearchView<T> extends SearchView<T> { class EnhancedLocalListView<T> extends EnhancedListView<T> {
final List<T> list; final List<T> list;
final Widget Function(T) itemBuilder; final Widget Function(T) itemBuilder;
final bool Function(T, String) filter; final bool Function(T, String) filter;
final Widget header; final Widget header;
final List<T> Function(String)? onSearch; final List<T> Function(String)? onSearch;
LocalSearchView({ EnhancedLocalListView({
Key? key, Key? key,
required this.list, required this.list,
required this.itemBuilder, required this.itemBuilder,
@ -49,10 +42,11 @@ class LocalSearchView<T> extends SearchView<T> {
// return documents.where((documents) => filter(documents, query)).toList(); // return documents.where((documents) => filter(documents, query)).toList();
@override @override
LocalSearchViewState<T> createState() => LocalSearchViewState<T>(); EnhancedLocalListViewState<T> createState() =>
EnhancedLocalListViewState<T>();
} }
class LocalSearchViewState<T> extends State<LocalSearchView<T>> { class EnhancedLocalListViewState<T> extends State<EnhancedLocalListView<T>> {
TextEditingController editingController = TextEditingController(); TextEditingController editingController = TextEditingController();
late List<T> filteredItems; late List<T> filteredItems;
@ -141,22 +135,25 @@ class LocalSearchViewState<T> extends State<LocalSearchView<T>> {
} }
/// ----------------------------------------------- /// -----------------------------------------------
/// [RemoteSearchView] /// [EnhancedRemoteListView]
/// ----------------------------------------------- /// -----------------------------------------------
class RemoteSearchView<T> extends SearchView<T> { // ignore: must_be_immutable
class EnhancedRemoteListView<T, Y> extends EnhancedListView<T> {
final Widget Function(BuildContext, T, int) bodyBuilder; final Widget Function(BuildContext, T, int) bodyBuilder;
Widget Function(BuildContext) headerBuilder; final Future<List<Y?>> Function() headerItems;
Widget Function<T>(Future<List<T?>> Function() gen) headerBuilder;
final PagingController<int, T> pagingController; final PagingController<int, T> pagingController;
final Future<(bool, List<T?>)> Function(int pageKey, Query query) final Future<(bool, List<T?>?)> Function(int pageKey, Query query)
dataProvider; dataProvider;
final void Function(Object, StackTrace) onFetchError; final void Function(Object, StackTrace) onFetchError;
RemoteSearchView({ EnhancedRemoteListView({
Key? key, Key? key,
// required this.fetchItems, // required this.fetchItems,
required this.bodyBuilder, required this.bodyBuilder,
required this.headerItems,
required this.headerBuilder, required this.headerBuilder,
required this.pagingController, required this.pagingController,
required this.dataProvider, required this.dataProvider,
@ -164,11 +161,12 @@ class RemoteSearchView<T> extends SearchView<T> {
}) : super(key: key); }) : super(key: key);
@override @override
RemoteSearchViewState<T> createState() => RemoteSearchViewState<T>(); EnhancedRemoteListViewState<T, Y> createState() =>
EnhancedRemoteListViewState<T, Y>();
} }
class RemoteSearchViewState<T> extends State<RemoteSearchView<T>> class EnhancedRemoteListViewState<T, Y>
with Pageable { extends State<EnhancedRemoteListView<T, Y>> with Pageable {
TextEditingController editingController = TextEditingController(); TextEditingController editingController = TextEditingController();
bool isLoading = false; bool isLoading = false;
Query query = Document.fromDesc(''); Query query = Document.fromDesc('');
@ -246,9 +244,10 @@ class RemoteSearchViewState<T> extends State<RemoteSearchView<T>>
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
buildPaginatedListView<int, T>( buildPaginatedListView<int, T, Y>(
noDataFound, noDataFound,
widget.pagingController, widget.pagingController,
widget.headerItems,
widget.headerBuilder, widget.headerBuilder,
widget.bodyBuilder, widget.bodyBuilder,
), ),

View File

@ -17,7 +17,7 @@ part 'model.dart';
part 'entity.dart'; part 'entity.dart';
/// [View]'s /// [View]'s
part 'view/search_view.dart'; part 'view/list_view.dart';
part 'view/carousel_view.dart'; part 'view/carousel_view.dart';
/// [Viewer] /// [Viewer]