744 lines
20 KiB
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,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|