797 lines
23 KiB
Dart
797 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>;
|
|
|
|
/// -----------------------------------------------
|
|
/// [Extensions] ---------------------------------
|
|
/// -----------------------------------------------
|
|
|
|
extension ExplicitRxdartStartWithExtension<T> on Stream<T?> {
|
|
Stream<T?> rxdartStartWith(T? value) =>
|
|
rx.StartWithExtension(this).startWith(value);
|
|
}
|
|
|
|
/// -----------------------------------------------
|
|
/// [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) {
|
|
const SizedBox space = SizedBox(height: 10);
|
|
final controller = EnhancedListViewController(
|
|
headerBuilder: model.itemHeaderBuilder<SearchField>,
|
|
bodyBuilder: model.itemBodyBuilder<Document>,
|
|
footerBuilder: model.itemFooterBuilder<Category>,
|
|
);
|
|
final repository = EnhancedListViewRepository(
|
|
fetchHeader: model.generateHeaderItems<SearchField>,
|
|
fetchBody: model.generateBodyItems<Document, Query>,
|
|
fetchFooter: model.generateFooterItems<Category>,
|
|
);
|
|
|
|
return Column(
|
|
children: [
|
|
Expanded(
|
|
child: EnhancedListView<Document, SearchField, Category, Query>(
|
|
key: model.vehicleScreenManager,
|
|
controller: controller,
|
|
repository: repository,
|
|
),
|
|
),
|
|
] //
|
|
.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
|
|
void initState() {
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final String title = widget.doc.$1.description;
|
|
final theme = FlutterFlowTheme.of(context);
|
|
final locale = FFLocalizations.of(context);
|
|
final Color color = widget.doc.$1.category.color;
|
|
;
|
|
|
|
backAction() => widget.bloc.events.unselectDocument();
|
|
infoAction() => DetailsComponentWidget(
|
|
buttons: [],
|
|
statusHashMap: [
|
|
Map<String, Color>.from({
|
|
widget.doc.$1.description: widget.doc.$1.category.color,
|
|
})
|
|
],
|
|
labelsHashMap: Map<String, String>.from({
|
|
locale.getVariableText(
|
|
enText: 'Description',
|
|
ptText: 'Descrição',
|
|
): widget.doc.$1.description,
|
|
locale.getVariableText(
|
|
enText: 'Category',
|
|
ptText: 'Categoria',
|
|
): widget.doc.$1.category.title,
|
|
if (widget.doc.$1.person.isNotEmpty)
|
|
locale.getVariableText(
|
|
enText: 'Person',
|
|
ptText: 'Pessoa',
|
|
): widget.doc.$1.person,
|
|
if (widget.doc.$1.property.isNotEmpty)
|
|
locale.getVariableText(
|
|
enText: 'Property',
|
|
ptText: 'Propriedade',
|
|
): widget.doc.$1.property,
|
|
locale.getVariableText(
|
|
enText: 'Created At',
|
|
ptText: 'Criado em',
|
|
): ValidatorUtil.toLocalDateTime(
|
|
'yyyy-MM-dd', widget.doc.$1.createdAt),
|
|
locale.getVariableText(
|
|
enText: 'Updated At',
|
|
ptText: 'Atualizado em',
|
|
): ValidatorUtil.toLocalDateTime(
|
|
'yyyy-MM-dd', 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) {
|
|
return ReadView(
|
|
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, SearchField, Category, Query>
|
|
vehicleScreenManager;
|
|
late DocumentKey vehicleScreenViewer;
|
|
late PagingController<int, Document> _pagingController;
|
|
|
|
late bool categoryIsSelected;
|
|
|
|
/// ------------
|
|
|
|
@override
|
|
void initState(BuildContext context) {
|
|
vehicleScreenManager =
|
|
EnhancedListViewKey<Document, SearchField, 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 DocumentComponent(
|
|
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]
|
|
|
|
CategoryComponent categoryItemBuilder<T>(T? item) {
|
|
return CategoryComponent(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 SearchField>(
|
|
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 SearchField>() async {
|
|
final SearchField item = SearchField();
|
|
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] ---------------------------------------
|
|
/// -----------------------------------------------
|
|
|
|
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 SearchField extends Archive {
|
|
SearchField();
|
|
}
|
|
|
|
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 DocumentComponent extends StatelessComponent {
|
|
final Document document;
|
|
void Function(Document, BuildContext) onPressed;
|
|
|
|
DocumentComponent({
|
|
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
DocumentComponent copyWith({
|
|
Document? document,
|
|
}) {
|
|
return DocumentComponent(
|
|
document: document ?? this.document,
|
|
onPressed: onPressed,
|
|
);
|
|
}
|
|
}
|
|
|
|
class CategoryComponent extends StatelessComponent {
|
|
final Category category;
|
|
|
|
const CategoryComponent({
|
|
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,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|