flutter-freaccess-hub/lib/features/documents/documents.dart

778 lines
23 KiB
Dart

import 'dart:developer';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
import 'package:hub/components/templates_components/details_component/details_component_widget.dart';
import 'package:hub/features/backend/index.dart';
import 'package:hub/flutter_flow/index.dart';
import 'package:hub/shared/extensions/index.dart';
import 'package:hub/shared/utils/index.dart';
import 'package:hub/shared/widgets/widgets.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rxdart/rxdart.dart' as rx;
part 'documents.rxb.g.dart';
/// -----------------------------------------------
/// [TypeDefs] -----------------------------------
/// -----------------------------------------------
typedef DocumentKey = GlobalKey<DocumentPageState>;
/// -----------------------------------------------
/// [Pages] ---------------------------------------
/// -----------------------------------------------
class DocumentPage extends StatefulPage {
const DocumentPage({super.key});
@override
State<DocumentPage> createState() => DocumentPageState();
}
class DocumentPageState extends PageState<DocumentPage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return RxBlocMultiBuilder2<DocumentPageBlocType, bool, (Document, Uri)?>(
state1: (bloc) => bloc.states.isDocumentSelected,
state2: (bloc) => bloc.states.currentDocument,
bloc: context.read<DocumentPageBloc>(),
builder: (context, isSelect, current, bloc) {
if (isSelect.hasData && isSelect.data!) {
return _buildDocumentViewScreen(current, bloc);
} else {
return _buildDocumentManagerScreen();
}
},
);
}
Widget _buildDocumentManagerScreen() {
final model = context.read<DocumentPageBloc>().model;
return DocumentManagerScreen(
model: model,
state: this,
);
}
Widget _buildDocumentViewScreen(
AsyncSnapshot<(Document, Uri)?> snapshot, DocumentPageBlocType bloc) {
if (snapshot.hasData) {
return DocumentViewerScreen(
doc: snapshot.data!,
bloc: bloc,
);
} else {
return const Center(child: CircularProgressIndicator());
}
}
}
/// -----------------------------------------------
/// [Screens] ------------------------------------
/// -----------------------------------------------
class DocumentManagerScreen extends StatelessScreen {
final DocumentModel 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) {
final SizedBox space = SizedBox(height: 30);
return Column(
children: [
Expanded(
child: EnhancedListView<Document, Search, Category, Query>(
key: model.vehicleScreenManager,
headerBuilder: model.itemHeaderBuilder<Search>,
headerItems: model.generateHeaderItems<Search>,
bodyBuilder: model.itemBodyBuilder<Document>,
bodyItems: model.generateBodyItems<Document, Query>,
footerBuilder: model.itemFooterBuilder<Category>,
footerItems: model.generateFooterItems<Category>,
),
),
] //
.addToStart(space)
.addToEnd(space),
);
}
}
class DocumentViewerScreen extends StatefulScreen {
const DocumentViewerScreen({
super.key,
required this.doc,
required this.bloc,
});
final (Document, Uri) doc;
final DocumentPageBlocType bloc;
@override
ScreenState<DocumentViewerScreen> createState() =>
_DocumentViewerScreenState();
}
class _DocumentViewerScreenState extends ScreenState<DocumentViewerScreen> {
@override
Widget build(BuildContext context) {
final String title = widget.doc.$1.description;
final theme = FlutterFlowTheme.of(context);
final locale = FFLocalizations.of(context);
backAction() => widget.bloc.events.unselectDocument();
infoAction() => DetailsComponentWidget(
buttons: [],
statusHashMap: [],
labelsHashMap: Map<String, String>.from({
locale.getVariableText(
enText: 'Description',
ptText: 'Descrição',
): widget.doc.$1.description,
locale.getVariableText(
enText: 'Type',
ptText: 'Tipo',
): widget.doc.$1.type,
locale.getVariableText(
enText: 'Category',
ptText: 'Categoria',
): widget.doc.$1.category.title,
locale.getVariableText(
enText: 'Person',
ptText: 'Pessoa',
): widget.doc.$1.person,
locale.getVariableText(
enText: 'Property',
ptText: 'Propriedade',
): widget.doc.$1.property,
locale.getVariableText(
enText: 'Created At',
ptText: 'Criado em',
): widget.doc.$1.createdAt,
locale.getVariableText(
enText: 'Updated At',
ptText: 'Atualizado em',
): widget.doc.$1.updatedAt,
}),
);
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) => backAction(),
child: Scaffold(
backgroundColor: theme.primaryBackground,
appBar: buildAppBar(title, context, backAction, infoAction),
body: buildBody(context),
),
);
}
Widget buildBody(BuildContext context) {
// final PDFViewerKey _viewerKey = PDFViewerKey();
return ReadView(
// search: _viewerKey,
title: widget.doc.$1.description,
url: widget.doc.$2.toString(),
);
}
}
/// -----------------------------------------------
/// [Models] --------------------------------------
/// -----------------------------------------------
class DocumentModel extends FlutterFlowModel<DocumentPage> {
final DocumentPageBlocType bloc;
DocumentModel(this.bloc);
late EnhancedListViewKey<Document, Search, Category, Query>
vehicleScreenManager;
late DocumentKey vehicleScreenViewer;
late PagingController<int, Document> _pagingController;
late bool categoryIsSelected;
/// ------------
@override
void initState(BuildContext context) {
vehicleScreenManager =
EnhancedListViewKey<Document, Search, Category, Query>();
vehicleScreenViewer = DocumentKey();
_pagingController = PagingController<int, Document>(firstPageKey: 1);
categoryIsSelected = false;
}
@override
void dispose() {
_pagingController.dispose();
vehicleScreenManager.currentState?.dispose();
vehicleScreenViewer.currentState?.dispose();
}
/// ------------
/// [Body]
void onView(Document document, BuildContext context) async {
bloc.events.selectDocument(document);
}
Widget itemBodyBuilder<T extends Document>(
BuildContext context, T item, int index) {
log('ItemBuilder -> $index');
return DocumentItem(
document: item,
onPressed: onView,
);
}
Future<List<T?>> generateBodyItems<T, Q>(
int pageKey, int pageSize, Q query) async {
final List<T?> error = [null];
log('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<dynamic> list = newItems.jsonBody['value']['list'];
late final List<Document> docs = [];
for (var item in list) {
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<T?>;
}
/// [Footer]
CategoryItem categoryItemBuilder<T>(T? item) {
return CategoryItem(category: item! as Category);
}
Widget itemFooterBuilder<T>(Future<List<T?>> Function() gen) =>
Builder(builder: (context) {
return Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(15, 0, 50, 0),
child: Text(
FFLocalizations.of(context).getVariableText(
ptText: 'Suas Categorias',
enText: 'Your Categories',
),
style: TextStyle(
color: FlutterFlowTheme.of(context).primaryText,
fontSize: LimitedFontSizeUtil.getHeaderFontSize(context),
),
),
),
EnhancedCarouselView<T>(
generateItems: gen,
itemBuilder: categoryItemBuilder,
filter: filterByCategory<T>,
),
],
);
});
Future<List<T?>> generateFooterItems<T>() async {
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);
}
return cats as List<T?>;
}
/// [Header]
Widget itemHeaderBuilder<T extends Search>(
Future<List<T?>> Function() generateHeaderItems) {
return Builder(builder: (context) {
final theme = FlutterFlowTheme.of(context);
final locale = FFLocalizations.of(context);
TextEditingController editingController = TextEditingController();
return TextFormField(
controller: editingController,
onChanged: (value) => EasyDebounce.debounce(
'_model.keyTextFieldTextController',
const Duration(milliseconds: 500),
() => filterBySearchBar(Document.fromDesc(value), context),
),
cursorColor: theme.primaryText,
showCursor: false,
cursorWidth: 2.0,
cursorRadius: Radius.circular(100),
style: TextStyle(
color: theme.primaryText,
fontSize: 16.0,
decorationColor: Colors.amber,
),
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
autocorrect: true,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: theme.primary),
labelText: locale.getVariableText(
ptText: 'Pesquisar',
enText: 'Search',
),
labelStyle: TextStyle(
color: theme.primaryText,
fontSize: 16.0,
),
hintText: locale.getVariableText(
ptText: 'Digite sua pesquisa',
enText: 'Enter your search',
),
hintStyle: TextStyle(
color: theme.accent2,
fontSize: 14.0,
),
filled: true,
fillColor: Colors.transparent,
helperStyle: TextStyle(
color: theme.primaryText,
decorationColor: theme.primaryText,
),
focusColor: theme.primaryText,
contentPadding:
EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
enabledBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
focusedBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
errorBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText),
),
focusedErrorBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
borderSide: BorderSide(color: theme.primaryText, width: 2.0),
),
),
);
});
}
Future<List<T?>> generateHeaderItems<T extends Search>() async {
final Search item = Search();
return [item] as List<T?>;
}
/// [Filter]
void filterBySearchBar<T>(T query, BuildContext context) {
final key = vehicleScreenManager.currentState;
return key?.filterBodyItems(query);
}
void filterByCategory<T>(T query, BuildContext context) {
final key = vehicleScreenManager.currentState;
categoryIsSelected
? key?.filterBodyItems(null)
: key?.filterBodyItems(query);
categoryIsSelected = !categoryIsSelected;
}
/// [Exception]
void onFetchError(Object e, StackTrace s) {
DialogUtil.errorDefault(vehicleScreenViewer.currentContext!);
LogUtil.requestAPIFailed(
"proccessRequest.php", "", "Consulta de Veículo", e, s);
}
}
/// -----------------------------------------------
/// [BLoCs] ---------------------------------------
/// -----------------------------------------------
extension RxdartStartWithExtension<T> on Stream<T?> {
Stream<T?> rxdartStartWith(T? value) =>
rx.StartWithExtension(this).startWith(value);
}
abstract class DocumentPageBlocEvents {
void selectDocument(Document document);
void unselectDocument();
}
abstract class DocumentPageBlocStates {
Stream<bool> get isDocumentSelected;
Stream<(Document, Uri)?> get currentDocument;
}
@RxBloc()
class DocumentPageBloc extends $DocumentPageBloc {
late final DocumentModel model;
DocumentPageBloc(BuildContext context) {
model = DocumentModel(this);
model.initState(context);
}
@override
Stream<(Document, Uri)?> _mapToCurrentDocumentState() => _$selectDocumentEvent
.switchMap((event) async* {
final uri = await GetPDF().call(event.id);
yield (event, uri);
})
.rxdartStartWith(null)
.mergeWith([_$unselectDocumentEvent.map((_) => null)]);
@override
Stream<bool> _mapToIsDocumentSelectedState() => _mapToCurrentDocumentState()
.map((document) => document != null)
.rxdartStartWith(false)
.map((isSelected) => isSelected ?? false);
}
/// -----------------------------------------------
/// [Interfaces] ---------------------------------
/// -----------------------------------------------
abstract interface class Archive extends Entity {}
interface class Search extends Archive {
Search();
}
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;
}
/// -----------------------------------------------
/// [Components] -------------------------------------
/// -----------------------------------------------
// 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<Color>(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,
),
],
),
),
);
}
}