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

744 lines
20 KiB
Dart

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<FREDocumentPageState>;
/// -----------------------------------------------
/// [Page]
/// -----------------------------------------------
class DocumentPage extends StatefulPage {
const DocumentPage({super.key});
@override
State<DocumentPage> createState() => FREDocumentPageState();
}
class FREDocumentPageState<T extends DocumentPage>
extends PageState<DocumentPage> {
@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<DocumentPageBloc>(
create: (context) => DocumentPageBloc(model),
child: BlocBuilder<DocumentPageBloc, DocumentPageState>(
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<DocumentPage> {
DocumentPageModel._privateConstructor();
static final DocumentPageModel _instance =
DocumentPageModel._privateConstructor();
factory DocumentPageModel() {
return _instance;
}
late EnhancedListViewKey<Document, Category, Null> vehicleScreenManager;
late DocumentKey vehicleScreenViewer;
late PagingController<int, Document> _pagingController;
/// ------------
@override
void initState(BuildContext context) {
vehicleScreenManager = EnhancedListViewKey<Document, Category, Null>();
vehicleScreenViewer = DocumentKey();
_pagingController = PagingController<int, Document>(firstPageKey: 1);
}
@override
void dispose() {
_pagingController.dispose();
vehicleScreenManager.currentState?.dispose();
vehicleScreenViewer.currentState?.dispose();
}
/// ------------
/// [onView]
void onView(Document document, BuildContext context) async {
vehicleScreenManager.currentContext!
.read<DocumentPageBloc>()
.add(SelectDocumentEvent(document));
}
/// [itemBodyBuilder]
DocumentItem itemBodyBuilder<T extends Document>(
BuildContext context, T item, int index) {
print('ItemBuilder -> $index');
return DocumentItem(
document: item,
onPressed: onView,
);
}
CategoryItem categoryItemBuilder<T>(T? item) {
return CategoryItem(category: item! as Category);
}
/// [itemHeaderBuilder]
Widget itemHeaderBuilder<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(
enText: 'Recent Documents', ptText: 'Últimos Documentos'),
style: TextStyle(
color: FlutterFlowTheme.of(context).primaryText,
fontSize: LimitedFontSizeUtil.getHeaderFontSize(context),
),
),
),
EnhancedCarouselView<T>(
generateItems: gen,
itemBuilder: categoryItemBuilder,
filter: filter<T>,
),
],
);
});
/// [generateBodyItems]
Future<List<T?>> generateBodyItems<T>(
int pageKey, int pageSize, dynamic query) async {
log('generateDocuments: $query');
final List<T?> error = [null];
print('Query: ${query is Document}');
final GetDocuments getDocuments = FreAccessWSGlobal.getDocuments;
final ApiCallResponse newItems = await getDocuments.call(pageKey, query);
if (newItems.jsonBody == null) return 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) {
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<T?>;
}
/// [generateHeaderItems]
Future<List<T?>> generateHeaderItems<T>() async {
log('generateCategories: ');
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);
}
log('cats: $cats');
return cats as List<T?>;
}
/// [filter]
void filter<T>(T query, BuildContext context) {
vehicleScreenManager.currentState!.filterBodyItems(query);
}
/// [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<DocumentPageEvent, DocumentPageState> {
final DocumentPageModel model;
static DocumentPageBloc? _singleton;
factory DocumentPageBloc(DocumentPageModel model) {
_singleton ??= DocumentPageBloc._internal(model);
return _singleton!;
}
DocumentPageBloc._internal(this.model) : super(DocumentPageState()) {
on<SelectDocumentEvent>(_selectDocument);
on<UnselectDocumentEvent>(_unselectDocument);
on<FilterCategoryEvent>(_filterCategoryEvent);
}
Future<void> _filterCategoryEvent(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
_selectCategory(event, emit);
state.isCategorySelected
? _unselectCategory(event, emit)
: _selectCategory(event, emit);
}
Future<void> _selectCategory(
FilterCategoryEvent event, Emitter<DocumentPageState> 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<Document>;
}
Future<void> _unselectCategory(
FilterCategoryEvent event, Emitter<DocumentPageState> emit) async {
emit(state.copyWith(
isCategorySelected: false,
));
// final listViewState = model.vehicleScreenManager.currentState!;
// listViewState.widget.bodyItems = (await model.generateBodyItems(
// 1, 10, null)) as BodyItemsBuilder<Document>;
}
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 {
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<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,
//
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<Document, Category, Null>(
key: model.vehicleScreenManager,
headerBuilder: model.itemHeaderBuilder<Category>,
headerItems: model.generateHeaderItems<Category>,
bodyBuilder: model.itemBodyBuilder<Document>,
bodyItems: model.generateBodyItems<Document>,
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<DocumentViewScreen> createState() => _DocumentViewScreenState();
}
class _DocumentViewScreenState extends ScreenState<DocumentViewScreen> {
@override
Widget build(BuildContext context) {
action() {
context.read<DocumentPageBloc>().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<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,
),
],
),
),
);
}
}