diff --git a/lib/features/backend/api_requests/api_calls.dart b/lib/features/backend/api_requests/api_calls.dart index 2ef8b0e4..87424040 100644 --- a/lib/features/backend/api_requests/api_calls.dart +++ b/lib/features/backend/api_requests/api_calls.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'dart:developer'; import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; import 'package:hub/features/documents/index.dart' as doc; import 'package:hub/features/notification/index.dart'; import 'package:hub/features/storage/index.dart'; @@ -79,6 +80,23 @@ class FreAccessWSGlobal extends Api { static GetDocuments getDocuments = GetDocuments(); } +class GetPDF extends Endpoint { + Future 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 { @override Future call() async { diff --git a/lib/features/documents/archive_item_component.dart b/lib/features/documents/archive_item_component.dart new file mode 100644 index 00000000..0063e562 --- /dev/null +++ b/lib/features/documents/archive_item_component.dart @@ -0,0 +1,3 @@ +part of 'index.dart'; + +abstract interface class Archive extends Entity {} diff --git a/lib/features/documents/category_item_component.dart b/lib/features/documents/category_item_component.dart new file mode 100644 index 00000000..84659d09 --- /dev/null +++ b/lib/features/documents/category_item_component.dart @@ -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, + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/documents/document_item_component.dart b/lib/features/documents/document_item_component.dart index 706dfb49..5545cf20 100644 --- a/lib/features/documents/document_item_component.dart +++ b/lib/features/documents/document_item_component.dart @@ -1,30 +1,6 @@ part of 'index.dart'; -abstract interface class DocumentEntity extends Entity {} - -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 { +interface class Document extends Archive { final int id; final String description; 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; + 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, BoxConstraints constraints) { @@ -106,16 +88,6 @@ class DocumentItem extends StatelessWidget { fontWeight: FontWeight.normal, fontStyle: FontStyle.italic, ); - final Map extra = { - 'document': document, - kTransitionInfoKey: const TransitionInfo( - hasTransition: true, - transitionType: PageTransitionType.rightToLeft, - alignment: Alignment.bottomCenter, - ), - }; - Future onTap() => - context.push('/documentViewerScreen', extra: extra); return Padding( padding: const EdgeInsets.all(8), @@ -126,7 +98,7 @@ class DocumentItem extends StatelessWidget { : MediaQuery.of(context).size.height * 2; return InkWell( - onTap: onTap, + onTap: () => onPressed(document, context), enableFeedback: true, overlayColor: WidgetStateProperty.all(primaryColor), borderRadius: BorderRadius.circular(10), @@ -189,4 +161,13 @@ class DocumentItem extends StatelessWidget { ), ); } + + DocumentItem copyWith({ + Document? document, + }) { + return DocumentItem( + document: document ?? this.document, + onPressed: onPressed, + ); + } } diff --git a/lib/features/documents/document_manager_screen.dart b/lib/features/documents/document_manager_screen.dart index 354a0825..3374cb1b 100644 --- a/lib/features/documents/document_manager_screen.dart +++ b/lib/features/documents/document_manager_screen.dart @@ -2,24 +2,41 @@ part of 'index.dart'; class DocumentManagerScreen extends StatelessScreen { final DocumentPageModel model; + final DocumentPageState state; const DocumentManagerScreen({ super.key, required this.model, + required this.state, }); @override Widget build(BuildContext context) { - final 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( children: [ Expanded( - child: RemoteSearchView( - key: model.searchKey, + child: EnhancedRemoteListView( + key: model.managerKey, pagingController: model._pagingController, headerBuilder: model.listHeaderBuilder, - bodyBuilder: model.listBodyBuilder, + headerItems: model.generateCategories, + bodyBuilder: model.documentItemBuilder, dataProvider: model.generateDocuments, onFetchError: model.onFetchError, ), diff --git a/lib/features/documents/document_page_bloc.dart b/lib/features/documents/document_page_bloc.dart new file mode 100644 index 00000000..5128ee03 --- /dev/null +++ b/lib/features/documents/document_page_bloc.dart @@ -0,0 +1,147 @@ +part of 'index.dart'; + +/// ----------------------------------------------- +/// [DocumentPageBloc] +/// ----------------------------------------------- + +class DocumentPageBloc extends Bloc { + final DocumentPageModel model; + + DocumentPageBloc._(this.model, DocumentPageState initialState) + : super(initialState) { + on(_selectDocument); + on(_unselectDocument); + on(_selectCategory); + on(_unselectCategory); + } + + static DocumentPageBloc create(DocumentPageModel model) { + final initialState = DocumentPageState( + categories: [], + documents: [], + ); + return DocumentPageBloc._(model, initialState); + } + + Future _selectCategory( + SelectCategoryEvent event, Emitter 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 _unselectCategory( + UnselectCategoryEvent event, Emitter emit) async { + emit(state); + } + + Future _selectDocument( + SelectDocumentEvent event, Emitter emit) async { + print('-> select'); + emit(state.copyWith( + uri: await GetPDF().call(event.document.id), + currentDocument: event.document, + isDocumentSelected: true, + )); + } + + Future _unselectDocument( + UnselectDocumentEvent event, Emitter emit) async { + 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 documents; + final List 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? documents, + Document? currentDocument, + bool? isDocumentSelected, + List? categories, + Category? currentCategory, + bool? isCategorySelected, + }) { + return DocumentPageState( + uri: uri ?? this.uri, + query: query ?? this.query, + count: count ?? this.count, + page: page ?? this.page, + // + documents: documents ?? this.documents, + currentDocument: currentDocument ?? this.currentDocument, + isDocumentSelected: isDocumentSelected ?? this.isDocumentSelected, + // + categories: categories ?? this.categories, + currentCategory: currentCategory ?? this.currentCategory, + isCategorySelected: isCategorySelected ?? this.isCategorySelected, + ); + } +} diff --git a/lib/features/documents/document_page_model.dart b/lib/features/documents/document_page_model.dart index 4439deae..5c883e1b 100644 --- a/lib/features/documents/document_page_model.dart +++ b/lib/features/documents/document_page_model.dart @@ -1,59 +1,80 @@ part of 'index.dart'; class DocumentPageModel extends FlutterFlowModel { - @override - void dispose() {} + DocumentPageModel(); + + late final GlobalKey> pageKey; + late final SearchKey managerKey; + late final DocumentKey viewerKey; + late final PagingController _pagingController; + + /// ------------ @override - void initState(BuildContext context) {} + void initState(BuildContext context) { + pageKey = GlobalKey>(); + managerKey = SearchKey(); + viewerKey = DocumentKey(); - final SearchKey searchKey = SearchKey(); - final DocumentKey docKey = DocumentKey(); + _pagingController = PagingController(firstPageKey: 1); + } - final PagingController _pagingController = - PagingController(firstPageKey: 1); - int count = 0; - final dynamic page = 1; + @override + void dispose() { + _pagingController.dispose(); + // isCategorySelected = false; + // isDocumentSelected = false; + } - Query query = Document.fromDesc(''); + /// ------------ - late Document currentDocument; - bool isCategorySelected = false; - late Category currentCategory; + /// [onView] + void onView(Document document, BuildContext context) async { + context.read().add(SelectDocumentEvent(document)); + } - List documents = []; - List categories = []; + /// [documentItemBuilder] + DocumentItem documentItemBuilder( + BuildContext context, T item, int index) { + return DocumentItem( + document: item, + onPressed: onView, + ); + } - /// [listBodyBuilder] - Widget listBodyBuilder(BuildContext context, Document item, int index) { - return DocumentItem(document: item); + CategoryItem categoryItemBuilder(T? item) { + return CategoryItem(category: item! as Category); } /// [listHeaderBuilder] - Widget listHeaderBuilder(BuildContext context) => Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(15, 0, 50, 0), - child: Text( - 'Últimos Documentos', - style: TextStyle( - color: FlutterFlowTheme.of(context).primaryText, - fontSize: LimitedFontSizeUtil.getHeaderFontSize(context), + Widget listHeaderBuilder(Future> Function() gen) => + Builder(builder: (context) { + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(15, 0, 50, 0), + child: Text( + 'Últimos Documentos', + style: TextStyle( + color: FlutterFlowTheme.of(context).primaryText, + fontSize: LimitedFontSizeUtil.getHeaderFontSize(context), + ), ), ), - ), - CategoryCarousel( - categories: categories, - filter: filterByCategory, - ), - ], - ); + EnhancedCarouselView( + generateItems: gen, + itemBuilder: categoryItemBuilder, + filter: filter, + ), + ], + ); + }); /// [generateDocuments] - Future<(bool, List)> generateDocuments( + Future<(bool, List?)> generateDocuments( pageKey, Query query) async { final List error = [null]; print('Query: ${query is Document}'); @@ -103,9 +124,8 @@ class DocumentPageModel extends FlutterFlowModel { } /// [generateCategories] - Future> generateCategories(List documents) async { + Future> generateCategories() async { final List error = [null]; - if (documents == []) return error; final GetCategories getCategories = FreAccessWSGlobal.getCategories; final ApiCallResponse newItems = await getCategories.call(); @@ -130,26 +150,32 @@ class DocumentPageModel extends FlutterFlowModel { return cats; } - void filterByCategory(Category query) { - final state = searchKey.currentState; - - if (state != null) { - log('filterByCategories: '); - - state.safeSetState(() { - if (isCategorySelected) { - state.filter(null); - isCategorySelected = false; - } else { - state.filter(query); - isCategorySelected = true; - } - }); - } + /// [filter] + void filter(T query, BuildContext context) { + context + .read() + .add(SelectCategoryEvent(query as Archive?)); } + // { + // 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) { - DialogUtil.errorDefault(docKey.currentContext!); + DialogUtil.errorDefault(viewerKey.currentContext!); LogUtil.requestAPIFailed( "proccessRequest.php", "", "Consulta de Veículo", e, s); } diff --git a/lib/features/documents/document_page_widget.dart b/lib/features/documents/document_page_widget.dart index ec558fda..ae7b00e2 100644 --- a/lib/features/documents/document_page_widget.dart +++ b/lib/features/documents/document_page_widget.dart @@ -11,44 +11,34 @@ class DocumentPage extends StatefulPage { class FREDocumentPageState extends PageState { - DocumentPageModel model = DocumentPageModel(); - @override - Widget build(BuildContext context) { - final String title = FFLocalizations.of(context).getVariableText( - enText: 'Documents', - ptText: 'Documentos', - ); - final theme = FlutterFlowTheme.of(context); - - return Scaffold( - backgroundColor: theme.primaryBackground, - appBar: buildAppBar(title, context), - body: buildBody(context), - ); - } + Widget build(BuildContext context) => buildBody(context); + DocumentPageModel model = DocumentPageModel(); @override void initState() { super.initState(); + model.initState(context); } Widget buildBody(BuildContext context) { - return FutureBuilder( - future: initAsync(), - builder: (context, snapshot) { - return DocumentManagerScreen(model: model); - }, - ); - // return DocumentViewScreen(document: documents.first); - } + return BlocProvider( + create: (context) => DocumentPageBloc.create(model), + child: BlocBuilder( + builder: (context, state) { + print('Bloc -> ${state.isCategorySelected}'); - Future initAsync() async { - final documents = await model.generateDocuments(model.page, model.query); - final categories = await model.generateCategories(model.documents); - model.documents = documents.$2; - model.categories = categories; - log('-> generateDocuments: $documents'); - log('-> generateCategories: $categories'); + if (state.isDocumentSelected) + return DocumentViewScreen( + doc: state.currentDocument!, + uri: state.uri!, + ); + else + return DocumentManagerScreen( + model: model, + state: state, + ); + }), + ); } } diff --git a/lib/features/documents/document_viewer_screen.dart b/lib/features/documents/document_viewer_screen.dart index 7b9b704e..526b02cc 100644 --- a/lib/features/documents/document_viewer_screen.dart +++ b/lib/features/documents/document_viewer_screen.dart @@ -3,43 +3,52 @@ part of 'index.dart'; class DocumentViewScreen extends StatefulScreen { const DocumentViewScreen({ super.key, - required this.document, + required this.doc, + required this.uri, }); - final Document document; + final Document doc; + final Uri uri; @override - State createState() => _DocumentViewScreenState(); + ScreenState createState() => _DocumentViewScreenState(); } -class _DocumentViewScreenState extends State { +class _DocumentViewScreenState extends ScreenState { 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 Widget build(BuildContext context) { - final Uri url = Uri.parse( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf'); + action() => context.read().add(UnselectDocumentEvent()); - void onPressed() async { - final response = await http.get(url); - if (response.statusCode == 200) { - final XFile xfile = XFile.fromData(response.bodyBytes, - name: - '${widget.document.description}_${widget.document.category.title}.pdf', - mimeType: 'application/pdf'); - await Share.shareXFiles([xfile], text: 'Confira este PDF!'); - } else { - print('Erro ao baixar o arquivo: ${response.statusCode}'); - } - } + final String title = widget.doc.description; + final theme = FlutterFlowTheme.of(context); + return Scaffold( + backgroundColor: theme.primaryBackground, + appBar: buildAppBar(title, context, action), + body: buildBody(context), + ); + } + Widget buildBody(BuildContext context) { return Stack( children: [ Padding( padding: EdgeInsets.all(10), child: FREViewerPDF( search: _viewerKey, - src: url.toString(), + src: widget.uri.toString(), ), ), Positioned( @@ -51,7 +60,7 @@ class _DocumentViewScreenState extends State { color: Colors.black, ), color: Colors.black, - onPressed: onPressed, + onPressed: onShare, ), ), ], diff --git a/lib/features/documents/index.dart b/lib/features/documents/index.dart index 864743e1..c5441c4d 100644 --- a/lib/features/documents/index.dart +++ b/lib/features/documents/index.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:http/http.dart' as http; import 'package:hub/features/backend/index.dart'; @@ -19,3 +20,6 @@ part 'document_page_widget.dart'; part 'document_viewer_screen.dart'; part 'document_page_model.dart'; part 'document_item_component.dart'; +part 'document_page_bloc.dart'; +part 'category_item_component.dart'; +part 'archive_item_component.dart'; diff --git a/lib/flutter_flow/nav/nav.dart b/lib/flutter_flow/nav/nav.dart index 357346f8..42b6e6de 100644 --- a/lib/flutter_flow/nav/nav.dart +++ b/lib/flutter_flow/nav/nav.dart @@ -310,11 +310,12 @@ GoRouter createRouter(AppStateNotifier appStateNotifier) { name: 'documentViewerScreen', path: '/documentViewerScreen', builder: (context, params) { - final Document document = - params.getParam('document', ParamType.Function); + final Document doc = params.getParam('doc', ParamType.Function); + final Uri uri = params.getParam('uri', ParamType.Function); return DocumentViewScreen( key: UniqueKey(), - document: document, + doc: doc, + uri: uri, ); }, ), diff --git a/lib/shared/mixins/pegeable_mixin.dart b/lib/shared/mixins/pegeable_mixin.dart index e54db8f5..95b60b71 100644 --- a/lib/shared/mixins/pegeable_mixin.dart +++ b/lib/shared/mixins/pegeable_mixin.dart @@ -6,25 +6,30 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; extension PagedListViewExtension on PagedSliverList {} +typedef PaginatedListViewHeaderBuilder = Widget Function( + Future> Function() gen); +typedef PaginatedListViewBodyBuilder = Widget Function(BuildContext, T, int); + mixin Pageable on State { - Expanded buildPaginatedListView( + Expanded buildPaginatedListView( String noDataFound, - PagingController pg, - Widget Function(BuildContext) headerBuilder, - Widget Function(BuildContext, ItemType, int) bodyBuilder) { + PagingController pg, + Future> Function() headerItems, + PaginatedListViewHeaderBuilder headerBuilder, + PaginatedListViewBodyBuilder bodyBuilder) { final theme = FlutterFlowTheme.of(context); return Expanded( child: RefreshIndicator( backgroundColor: theme.primaryBackground, color: theme.primary, onRefresh: () async => pg.refresh(), - child: PagedListView( + child: PagedListView( pagingController: pg, - builderDelegate: PagedChildBuilderDelegate( + builderDelegate: PagedChildBuilderDelegate( animateTransitions: true, itemBuilder: (context, item, int index) { return Column(children: [ - if (index == 0) headerBuilder(context), + if (index == 0) headerBuilder(headerItems), bodyBuilder(context, item, index), ]); }, @@ -33,7 +38,8 @@ mixin Pageable on State { firstPageProgressIndicatorBuilder: (context) => buildLoadingIndicator(context), noItemsFoundIndicatorBuilder: (context) => - buildNoDataFound(context, noDataFound, headerBuilder), + buildNoDataFound( + context, noDataFound, headerItems, headerBuilder), firstPageErrorIndicatorBuilder: (context) => const Placeholder(), newPageErrorIndicatorBuilder: (context) => const Placeholder(), ), @@ -92,16 +98,17 @@ mixin Pageable on State { showSnackbar(context, message, true); } - Widget buildNoDataFound( + Widget buildNoDataFound( BuildContext context, String title, - Widget Function(BuildContext) headerBuilder, + Future> Function() items, + Widget Function(Future> Function() items) headerBuilder, ) { final headerFontSize = LimitedFontSizeUtil.getHeaderFontSize(context); // final bodyFontSize = LimitedFontSizeUtil.getBodyFontSize(context); return Column( children: [ - headerBuilder(context), + headerBuilder(items), Expanded( child: Center( child: Text( diff --git a/lib/shared/widgets/component.dart b/lib/shared/widgets/component.dart index fb1383dc..74fc9ae1 100644 --- a/lib/shared/widgets/component.dart +++ b/lib/shared/widgets/component.dart @@ -20,3 +20,5 @@ abstract class StatefulComponent extends StatefulWidget implements ComponentWidget { const StatefulComponent({super.key}); } + +abstract class ComponentState extends State {} diff --git a/lib/shared/widgets/page.dart b/lib/shared/widgets/page.dart index ed08ad58..87059a12 100644 --- a/lib/shared/widgets/page.dart +++ b/lib/shared/widgets/page.dart @@ -1,7 +1,12 @@ part of 'widgets.dart'; -mixin MixinPage { - PreferredSizeWidget buildAppBar(String title, BuildContext context) { +mixin Template { + PreferredSizeWidget buildAppBar( + String title, + BuildContext context, + dynamic Function()? backAction, + ) { + final theme = FlutterFlowTheme.of(context); return AppBar( backgroundColor: FlutterFlowTheme.of(context).primaryBackground, automaticallyImplyLeading: false, @@ -17,14 +22,15 @@ mixin MixinPage { FlutterFlowTheme.of(context).headlineMediumFamily), ), ), - leading: _backButton(context, FlutterFlowTheme.of(context)), + leading: _backButton(context, theme, backAction), centerTitle: true, elevation: 0.0, actions: [], ); } - Widget _backButton(BuildContext context, FlutterFlowTheme theme) { + Widget _backButton(BuildContext context, FlutterFlowTheme theme, + dynamic Function()? onPressed) { return FlutterFlowIconButton( key: ValueKey('BackNavigationAppBar'), borderColor: Colors.transparent, @@ -36,7 +42,7 @@ mixin MixinPage { color: theme.primaryText, size: 30.0, ), - onPressed: () => Navigator.of(context).pop(), + onPressed: onPressed, ); } } @@ -52,6 +58,7 @@ abstract class ModelPage extends ModelWidget implements PageWidget { } abstract class StatelessPage extends StatelessWidget + with Template implements PageWidget { const StatelessPage({super.key}); } @@ -61,4 +68,4 @@ abstract class StatefulPage extends StatefulWidget implements PageWidget { } abstract class PageState extends State - with MixinPage {} + with Template {} diff --git a/lib/shared/widgets/screen.dart b/lib/shared/widgets/screen.dart index 754a3094..f4dd20c8 100644 --- a/lib/shared/widgets/screen.dart +++ b/lib/shared/widgets/screen.dart @@ -9,6 +9,7 @@ abstract class ModelScreen extends ModelWidget implements ScreenWidget { } abstract class StatelessScreen extends StatelessWidget + with Template implements ScreenWidget { const StatelessScreen({super.key}); } @@ -17,3 +18,6 @@ abstract class StatefulScreen extends StatefulWidget implements ScreenWidget { const StatefulScreen({super.key}); } + +abstract class ScreenState extends State + with Template {} diff --git a/lib/shared/widgets/view/carousel_view.dart b/lib/shared/widgets/view/carousel_view.dart index c5c1d152..402a2567 100644 --- a/lib/shared/widgets/view/carousel_view.dart +++ b/lib/shared/widgets/view/carousel_view.dart @@ -1,59 +1,32 @@ part of '../widgets.dart'; -class CategoryCarousel extends StatelessWidget { - final List categories; - final void Function(T) filter; +class EnhancedCarouselView extends StatelessWidget { + final Future> Function() generateItems; + final void Function(T, BuildContext) filter; + final Widget Function(T? item) itemBuilder; - const CategoryCarousel({ + const EnhancedCarouselView({ super.key, - required this.categories, + required this.generateItems, required this.filter, + required this.itemBuilder, }); @override Widget build(BuildContext context) { - final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground; - return SizedBox( height: 120, - child: CarouselView( - itemExtent: 100, - onTap: (index) => filter(categories[index] as T), - children: categories.map((category) { - category as Category?; - 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, - ), - ], - ), - ), - ); - }).toList(), - ), + child: FutureBuilder>( + future: generateItems(), + builder: (context, snapshot) { + if (!snapshot.hasData) return SizedBox(); + return CarouselView( + itemExtent: 100, + onTap: (index) => filter(snapshot.data![index] as T, context), + children: + snapshot.data!.map((item) => itemBuilder(item)).toList(), + ); + }), ); } } diff --git a/lib/shared/widgets/view/search_view.dart b/lib/shared/widgets/view/list_view.dart similarity index 88% rename from lib/shared/widgets/view/search_view.dart rename to lib/shared/widgets/view/list_view.dart index b22eb7fe..17a72c6a 100644 --- a/lib/shared/widgets/view/search_view.dart +++ b/lib/shared/widgets/view/list_view.dart @@ -1,39 +1,32 @@ part of '../widgets.dart'; -typedef SearchKey = GlobalKey; +typedef SearchKey = GlobalKey; -typedef Query = X?; +typedef Query = X?; /// ----------------------------------------------- -/// [SearchView] +/// [EnhancedListView] /// ----------------------------------------------- -class SearchView extends StatefulComponent { - const SearchView({super.key}); - - @override - State createState() => _SearchViewState(); +abstract interface class EnhancedListView extends StatefulWidget { + const EnhancedListView({super.key}); } -class _SearchViewState extends State { - @override - Widget build(BuildContext context) { - return const Placeholder(); - } -} +abstract interface class EnhancedListViewState + extends State {} /// ----------------------------------------------- -/// [LocalSearchView] +/// [EnhancedLocalListView] /// ----------------------------------------------- -class LocalSearchView extends SearchView { +class EnhancedLocalListView extends EnhancedListView { final List list; final Widget Function(T) itemBuilder; final bool Function(T, String) filter; final Widget header; final List Function(String)? onSearch; - LocalSearchView({ + EnhancedLocalListView({ Key? key, required this.list, required this.itemBuilder, @@ -49,10 +42,11 @@ class LocalSearchView extends SearchView { // return documents.where((documents) => filter(documents, query)).toList(); @override - LocalSearchViewState createState() => LocalSearchViewState(); + EnhancedLocalListViewState createState() => + EnhancedLocalListViewState(); } -class LocalSearchViewState extends State> { +class EnhancedLocalListViewState extends State> { TextEditingController editingController = TextEditingController(); late List filteredItems; @@ -141,22 +135,25 @@ class LocalSearchViewState extends State> { } /// ----------------------------------------------- -/// [RemoteSearchView] +/// [EnhancedRemoteListView] /// ----------------------------------------------- -class RemoteSearchView extends SearchView { +// ignore: must_be_immutable +class EnhancedRemoteListView extends EnhancedListView { final Widget Function(BuildContext, T, int) bodyBuilder; - Widget Function(BuildContext) headerBuilder; + final Future> Function() headerItems; + Widget Function(Future> Function() gen) headerBuilder; final PagingController pagingController; - final Future<(bool, List)> Function(int pageKey, Query query) + final Future<(bool, List?)> Function(int pageKey, Query query) dataProvider; final void Function(Object, StackTrace) onFetchError; - RemoteSearchView({ + EnhancedRemoteListView({ Key? key, // required this.fetchItems, required this.bodyBuilder, + required this.headerItems, required this.headerBuilder, required this.pagingController, required this.dataProvider, @@ -164,11 +161,12 @@ class RemoteSearchView extends SearchView { }) : super(key: key); @override - RemoteSearchViewState createState() => RemoteSearchViewState(); + EnhancedRemoteListViewState createState() => + EnhancedRemoteListViewState(); } -class RemoteSearchViewState extends State> - with Pageable { +class EnhancedRemoteListViewState + extends State> with Pageable { TextEditingController editingController = TextEditingController(); bool isLoading = false; Query query = Document.fromDesc(''); @@ -246,9 +244,10 @@ class RemoteSearchViewState extends State> mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ - buildPaginatedListView( + buildPaginatedListView( noDataFound, widget.pagingController, + widget.headerItems, widget.headerBuilder, widget.bodyBuilder, ), diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index c930bc37..f56fce44 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -17,7 +17,7 @@ part 'model.dart'; part 'entity.dart'; /// [View]'s -part 'view/search_view.dart'; +part 'view/list_view.dart'; part 'view/carousel_view.dart'; /// [Viewer]