import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hub/features/backend/index.dart'; import 'package:hub/flutter_flow/index.dart'; import 'package:hub/shared/extensions/index.dart'; import 'package:hub/shared/utils/index.dart'; import 'package:hub/shared/widgets/widgets.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; /// ----------------------------------------------- /// [TypeDefs] /// ----------------------------------------------- typedef DocumentKey = GlobalKey; /// ----------------------------------------------- /// [Page] /// ----------------------------------------------- class DocumentPage extends StatefulPage { const DocumentPage({super.key}); @override State createState() => FREDocumentPageState(); } class FREDocumentPageState extends PageState { @override Widget build(BuildContext context) => buildBody(context); DocumentPageModel model = DocumentPageModel(); @override void initState() { super.initState(); model.initState(context); } Widget buildBody(BuildContext context) { log('Build -> DocumentPage'); return BlocProvider( create: (context) => DocumentPageBloc(model), child: BlocBuilder( builder: (context, state) { log('Build -> DocumentPageBloc'); print('Bloc -> ${state.isCategorySelected}'); if (state.isDocumentSelected) return DocumentViewScreen( doc: state.currentDocument!, uri: state.uri!, ); else return DocumentManagerScreen( model: model, state: state, ); }), ); } } /// ----------------------------------------------- /// [Model] /// ----------------------------------------------- class DocumentPageModel extends FlutterFlowModel { DocumentPageModel._privateConstructor(); static final DocumentPageModel _instance = DocumentPageModel._privateConstructor(); factory DocumentPageModel() { return _instance; } late EnhancedListViewKey vehicleScreenManager; late DocumentKey vehicleScreenViewer; late PagingController _pagingController; /// ------------ @override void initState(BuildContext context) { vehicleScreenManager = EnhancedListViewKey(); vehicleScreenViewer = DocumentKey(); _pagingController = PagingController(firstPageKey: 1); } @override void dispose() { _pagingController.dispose(); vehicleScreenManager.currentState?.dispose(); vehicleScreenViewer.currentState?.dispose(); } /// ------------ /// [onView] void onView(Document document, BuildContext context) async { vehicleScreenManager.currentContext! .read() .add(SelectDocumentEvent(document)); } /// [itemBodyBuilder] DocumentItem itemBodyBuilder( BuildContext context, T item, int index) { print('ItemBuilder -> $index'); return DocumentItem( document: item, onPressed: onView, ); } CategoryItem categoryItemBuilder(T? item) { return CategoryItem(category: item! as Category); } /// [itemHeaderBuilder] Widget itemHeaderBuilder(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( FFLocalizations.of(context).getVariableText( enText: 'Recent Documents', ptText: 'Últimos Documentos'), style: TextStyle( color: FlutterFlowTheme.of(context).primaryText, fontSize: LimitedFontSizeUtil.getHeaderFontSize(context), ), ), ), EnhancedCarouselView( generateItems: gen, itemBuilder: categoryItemBuilder, filter: filter, ), ], ); }); /// [generateBodyItems] Future> generateBodyItems( int pageKey, int pageSize, dynamic query) async { log('generateDocuments: $query'); final List error = [null]; print('Query: ${query is Document}'); final GetDocuments getDocuments = FreAccessWSGlobal.getDocuments; final ApiCallResponse newItems = await getDocuments.call(pageKey, query); if (newItems.jsonBody == null) return error; if (newItems.jsonBody['error'] == true) return error; final List list = newItems.jsonBody['value']['list']; late final List docs = []; for (var item in list) { log('-> generateDocuments: $item'); final String description = item['description']; final String type = item['type']; final String category = item['category']['description']; final String color = item['category']['color']; final String person = item['person'] ?? ''; final String property = item['property'] ?? ''; final String createdAt = item['createdAt']; final String updatedAt = item['updatedAt']; final int categoryId = item['category']['id']; final int documentId = item['id']; final doc = Document( id: documentId, description: description, type: type, category: Category( id: categoryId, color: color.toColor(), title: category, ), person: person, property: property, createdAt: createdAt, updatedAt: updatedAt, ); docs.add(doc); } return docs as List; } /// [generateHeaderItems] Future> generateHeaderItems() async { log('generateCategories: '); final List 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; late final List cats = []; for (var item in list) { final String color = item['color']; final String title = item['description']; final int id = item['id']; final cat = Category( id: id, color: color.toColor(), title: title, ); cats.add(cat); } log('cats: $cats'); return cats as List; } /// [filter] void filter(T query, BuildContext context) { context .read() .add(FilterCategoryEvent(query as Archive?)); } /// [onFetchError] void onFetchError(Object e, StackTrace s) { DialogUtil.errorDefault(vehicleScreenViewer.currentContext!); LogUtil.requestAPIFailed( "proccessRequest.php", "", "Consulta de Veículo", e, s); } } /// ----------------------------------------------- /// [BLoC] /// ----------------------------------------------- /// [DocumentPageBloc] class DocumentPageBloc extends Bloc { final DocumentPageModel model; static DocumentPageBloc? _singleton; factory DocumentPageBloc(DocumentPageModel model) { _singleton ??= DocumentPageBloc._internal(model); return _singleton!; } DocumentPageBloc._internal(this.model) : super(DocumentPageState()) { on(_selectDocument); on(_unselectDocument); on(_filterCategoryEvent); } Future _filterCategoryEvent( FilterCategoryEvent event, Emitter emit) async { _selectCategory(event, emit); state.isCategorySelected ? _unselectCategory(event, emit) : _selectCategory(event, emit); } Future _selectCategory( FilterCategoryEvent event, Emitter emit) async { log('filterItems A: ${event.query}'); emit(state.copyWith( isCategorySelected: true, )); final listViewState = model.vehicleScreenManager.currentState!; listViewState.widget.bodyItems = (await model.generateBodyItems( 1, 10, event.query)) as BodyItemsBuilder; } Future _unselectCategory( FilterCategoryEvent event, Emitter emit) async { emit(state.copyWith( isCategorySelected: false, )); final listViewState = model.vehicleScreenManager.currentState!; listViewState.widget.bodyItems = (await model.generateBodyItems( 1, 10, null)) as BodyItemsBuilder; } 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 { emit( state.copyWith( currentDocument: null, isDocumentSelected: false, ), ); } } /// [DocumentPageEvent] abstract class DocumentPageEvent {} class SelectDocumentEvent extends DocumentPageEvent { final Document document; SelectDocumentEvent( this.document, ); } class UnselectDocumentEvent extends DocumentPageEvent {} class FilterCategoryEvent extends DocumentPageEvent { final Query query; FilterCategoryEvent(this.query); } /// [DocumentPageState] class DocumentPageState { final bool isCategorySelected; final bool isDocumentSelected; final Document? currentDocument; final Category? currentCategory; final Uri? uri; final int? count; final dynamic page; final Query? query; const DocumentPageState({ this.query, this.count, this.page, this.uri, this.currentDocument, this.isCategorySelected = false, this.currentCategory, this.isDocumentSelected = false, }); DocumentPageState copyWith({ Uri? uri, Query? query, int? count, dynamic page, List? 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, // currentDocument: currentDocument ?? this.currentDocument, isDocumentSelected: isDocumentSelected ?? this.isDocumentSelected, // currentCategory: currentCategory ?? this.currentCategory, isCategorySelected: isCategorySelected ?? this.isCategorySelected, ); } } /// ----------------------------------------------- /// [Screens] /// ----------------------------------------------- /// [DocumentManagerScreen] class DocumentManagerScreen extends StatelessScreen { final DocumentPageModel model; final DocumentPageState state; const DocumentManagerScreen({ super.key, required this.model, required this.state, }); @override Widget build(BuildContext context) { final String title = FFLocalizations.of(context).getVariableText( enText: 'Documents', ptText: 'Documentos', ); final theme = FlutterFlowTheme.of(context); action() => Navigator.pop(context); return Scaffold( backgroundColor: theme.primaryBackground, appBar: buildAppBar(title, context, action), body: buildBody(context), ); } Widget buildBody(BuildContext context) { log('Build -> DocumentManagerScreen'); final SizedBox space = SizedBox(height: 30); return Column( children: [ Expanded( child: EnhancedListView( key: model.vehicleScreenManager, headerBuilder: model.itemHeaderBuilder, headerItems: model.generateHeaderItems, bodyBuilder: model.itemBodyBuilder, bodyItems: model.generateBodyItems, footerBuilder: null, footerItems: null, ), ), ] // .addToStart(space) .addToEnd(space), ); } } /// [DocumentViewScreen] class DocumentViewScreen extends StatefulScreen { const DocumentViewScreen({ super.key, required this.doc, required this.uri, }); final Document doc; final Uri uri; @override ScreenState createState() => _DocumentViewScreenState(); } class _DocumentViewScreenState extends ScreenState { @override Widget build(BuildContext context) { action() { context.read().add(UnselectDocumentEvent()); } final String title = widget.doc.description; final theme = FlutterFlowTheme.of(context); return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) => action(), child: Scaffold( backgroundColor: theme.primaryBackground, appBar: buildAppBar(title, context, action), body: buildBody(context), ), ); } Widget buildBody(BuildContext context) { // final PDFViewerKey _viewerKey = PDFViewerKey(); return ReadView( // search: _viewerKey, title: widget.doc.description, url: widget.uri.toString(), ); } } /// ----------------------------------------------- /// [Interfaces] /// ----------------------------------------------- abstract interface class Archive extends Entity {} interface class Document extends Archive { final int id; final String description; final String type; final Category category; final String person; final String property; String createdAt; String updatedAt; Document({ required this.id, required this.description, required this.type, required this.category, required this.person, required this.property, required this.createdAt, required this.updatedAt, }); factory Document.fromDesc(String desc) => Document( id: 0, description: desc, type: '', category: Category.fromDesc(''), person: '', property: '', createdAt: '', updatedAt: '', ); } interface class Category extends Archive { final int id; final Color color; final String title; Category({ required this.id, required this.color, required this.title, }); factory Category.fromDesc(String desc) { return Category( id: 0, color: Colors.transparent, title: desc, ); } static Color isSelected() => Colors.black; } /// ----------------------------------------------- /// [Widgets] /// ----------------------------------------------- // ignore: must_be_immutable class DocumentItem extends StatelessComponent { final Document document; void Function(Document, BuildContext) onPressed; DocumentItem({ super.key, required this.document, required this.onPressed, }); Tooltip _buildTooltip(String text, Color color, BuildContext context, BoxConstraints constraints) { final Color textColor = FlutterFlowTheme.of(context).info; final area = (MediaQuery.of(context).size.height + MediaQuery.of(context).size.width) / 2; final double boxHeight = area * 0.033; final double boxWidth = area * 0.19; return Tooltip( message: text, child: Container( width: boxWidth, height: boxHeight, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(10), ), child: Center( child: AutoText( text, overflow: TextOverflow.ellipsis, style: TextStyle( color: textColor, fontWeight: FontWeight.bold, ), ), ), ), ); } @override Widget build(BuildContext context) { final Color primaryText = FlutterFlowTheme.of(context).primaryText; final Color primaryColor = FlutterFlowTheme.of(context).primary; final TextStyle textStyleMajor = TextStyle( color: primaryText, fontWeight: FontWeight.bold, ); final TextStyle textStyleMinor = TextStyle( color: primaryText, fontWeight: FontWeight.normal, fontStyle: FontStyle.italic, ); return Padding( padding: const EdgeInsets.all(8), child: LayoutBuilder( builder: (context, constraints) { final double boxHeight = constraints.maxHeight > 350 ? MediaQuery.of(context).size.height * 0.07 : MediaQuery.of(context).size.height * 2; return InkWell( onTap: () => onPressed(document, context), enableFeedback: true, overlayColor: WidgetStateProperty.all(primaryColor), borderRadius: BorderRadius.circular(10), child: SizedBox( height: boxHeight, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // const SizedBox(width: 10), Icon(Icons.description, color: document.category.color), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Tooltip( message: document.description, child: AutoText( document.description, style: textStyleMajor, overflow: TextOverflow.ellipsis, ), ), AutoText( ValidatorUtil.toLocalDateTime( 'yyyy-MM-dd', document.updatedAt), style: textStyleMinor, overflow: TextOverflow.ellipsis, ), ], ), ), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ _buildTooltip( document.category.title, document.category.color, context, constraints, ), ], ), ), // const SizedBox(width: 10), Center( child: Icon( Icons.arrow_right, color: primaryText, ), ), ], ), ), ); }, ), ); } DocumentItem copyWith({ Document? document, }) { return DocumentItem( document: document ?? this.document, onPressed: onPressed, ); } } class CategoryItem extends StatelessComponent { final Category category; const CategoryItem({ super.key, required this.category, }); @override Widget build(BuildContext context) { final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground; return ColoredBox( color: backgroundTheme, child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: category.color, shape: BoxShape.circle, ), child: Icon( Icons.folder, color: Colors.white, size: 40, ), ), const SizedBox(height: 8), Text( category.title, style: TextStyle( color: category.color, fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, ), ], ), ), ); } }