From a7f1ea418b40d726cff47dd60d2f05b011d5a530 Mon Sep 17 00:00:00 2001 From: jantunesmessias Date: Tue, 25 Feb 2025 17:13:09 -0300 Subject: [PATCH] =?UTF-8?q?corre=C3=A7=C3=B5es=20do=20pr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/api_requests/api_manager.dart | 1 - lib/features/documents/documents.dart | 124 +++++++++-------- .../locals_remote_data_source.dart | 11 +- lib/shared/utils.dart | 1 + lib/shared/utils/color_util.dart | 48 +++++++ lib/shared/widgets.dart | 2 + .../widgets/enhanced_carousel_view.dart | 128 +++++++++++------- lib/shared/widgets/enhanced_list_view.dart | 9 +- lib/shared/widgets/read_view.dart | 34 +++-- 9 files changed, 237 insertions(+), 121 deletions(-) create mode 100644 lib/shared/utils/color_util.dart diff --git a/lib/features/backend/api_requests/api_manager.dart b/lib/features/backend/api_requests/api_manager.dart index d12c03dd..7f13a68f 100644 --- a/lib/features/backend/api_requests/api_manager.dart +++ b/lib/features/backend/api_requests/api_manager.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:core'; -import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; diff --git a/lib/features/documents/documents.dart b/lib/features/documents/documents.dart index b9377c6c..1b84a344 100644 --- a/lib/features/documents/documents.dart +++ b/lib/features/documents/documents.dart @@ -164,13 +164,14 @@ class _DocumentViewerScreenState extends ScreenState { super.initState(); } + backAction() => widget.bloc.events.unselectDocument(); + @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: [ @@ -225,6 +226,7 @@ class _DocumentViewerScreenState extends ScreenState { return ReadView( title: widget.doc.$1.description, url: widget.doc.$2.toString(), + onError: backAction, ); } } @@ -330,14 +332,16 @@ class DocumentModel extends FlutterFlowModel { Widget itemFooterBuilder( Future> Function() fetchData) => Builder(builder: (context) { - CategoryComponent categoryItemBuilder(T? item) { - return CategoryComponent(category: item! as Category); + CategoryComponent categoryItemBuilder(T? item, bool isSelected) { + return CategoryComponent( + category: item! as Category, isSelected: isSelected); } return EnhancedCarouselView( dataProvider: fetchData, itemBuilder: categoryItemBuilder, filter: filterByCategory, + showIndicator: true, ); }); @@ -654,64 +658,71 @@ class DocumentComponent extends StatelessComponent { final description = document.description; final title = document.category.title; const double size = 20; + final date = ValidatorUtil.toLocalDateTime( + 'yyyy-MM-dd', + document.updatedAt, + ); - 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, - spacing: size, - children: [ - // const SizedBox(width: 10), - Icon(icon, color: color), + return Tooltip( + message: description, + child: InkWell( + onTap: () => onPressed(document, context), + enableFeedback: true, + overlayColor: WidgetStateProperty.all(primaryColor), + borderRadius: BorderRadius.circular(10), + child: SizedBox( + height: boxHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: size, + children: [ + // const SizedBox(width: 10), + Icon(icon, color: color), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Tooltip( - message: description, - child: AutoText( - description, - style: textStyleMajor, - overflow: TextOverflow.ellipsis, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Tooltip( + message: description, + child: AutoText( + description, + style: textStyleMajor, + overflow: TextOverflow.ellipsis, + ), ), - ), - AutoText( - ValidatorUtil.toLocalDateTime( - 'yyyy-MM-dd', - document.updatedAt, + Tooltip( + message: date, + child: AutoText( + date, + style: textStyleMinor, + overflow: TextOverflow.ellipsis, + ), ), - style: textStyleMinor, - overflow: TextOverflow.ellipsis, - ), - ], + ], + ), ), - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - _buildTooltip(title, color, context, constraints), - ], + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + _buildTooltip(title, color, context, constraints), + ], + ), ), - ), - // const SizedBox(width: 10), - Center( - child: Icon( - Icons.arrow_right, - color: primaryText, + // const SizedBox(width: 10), + Center( + child: Icon( + Icons.arrow_right, + color: primaryText, + ), ), - ), - ] // - .addToStart(space) - .addToEnd(space), + ] // + .addToStart(space) + .addToEnd(space), + ), ), ), ); @@ -732,16 +743,19 @@ class DocumentComponent extends StatelessComponent { class CategoryComponent extends StatelessComponent { final Category category; + final bool isSelected; const CategoryComponent({ super.key, required this.category, + this.isSelected = false, }); @override Widget build(BuildContext context) { final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground; final textTheme = FlutterFlowTheme.of(context).primaryText; + final color = isSelected ? category.color.highlight : category.color; return ColoredBox( color: backgroundTheme, child: Padding( @@ -751,7 +765,7 @@ class CategoryComponent extends StatelessComponent { Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( - color: category.color, + color: color, shape: BoxShape.circle, ), child: Icon( diff --git a/lib/features/local/data/data_sources/locals_remote_data_source.dart b/lib/features/local/data/data_sources/locals_remote_data_source.dart index 39d4d166..c991ed37 100644 --- a/lib/features/local/data/data_sources/locals_remote_data_source.dart +++ b/lib/features/local/data/data_sources/locals_remote_data_source.dart @@ -34,20 +34,13 @@ class LocalsRemoteDataSourceImpl implements LocalsRemoteDataSource { try { final GetLocalsCall callback = FreAccessWSGlobal.getLocalsCall; var response = await callback.call(); - final bool? isError = response.jsonBody['error']; + if (response.jsonBody == null) return; + final bool? isError = response.jsonBody['error']; if (isError == true) { LocalUtil.handleError(context, response.jsonBody['error_msg']); return; } - if (response.jsonBody == null) { - // final String errorMsg = FFLocalizations.of(context).getVariableText( - // enText: 'Verify your connection', - // ptText: 'Verifique sua conexão', - // ); - // await DialogUtil.error(context, errorMsg).whenComplete(() async => await selectLocal(context, response)); - return; - } final List locals = response.jsonBody['locais'] ?? []; final bool isEmpty = locals.isEmpty; diff --git a/lib/shared/utils.dart b/lib/shared/utils.dart index 09901b4d..c3eae2ca 100644 --- a/lib/shared/utils.dart +++ b/lib/shared/utils.dart @@ -12,3 +12,4 @@ export 'utils/string_util.dart'; export 'utils/text_util.dart'; export 'utils/validator_util.dart'; export 'utils/webview_util.dart'; +export 'utils/color_util.dart'; diff --git a/lib/shared/utils/color_util.dart b/lib/shared/utils/color_util.dart new file mode 100644 index 00000000..291762a8 --- /dev/null +++ b/lib/shared/utils/color_util.dart @@ -0,0 +1,48 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +class ColorUtil { + static Color getContrastColor(Color a, Color b) { + double luminance(Color color) { + return (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255; + } + + double contrastRatio(Color a, Color b) { + final lumA = luminance(a) + 0.05; + final lumB = luminance(b) + 0.05; + return lumA > lumB ? lumA / lumB : lumB / lumA; + } + + if (contrastRatio(a, b) < 4.5) { + // Find a color with higher contrast within the same hue + final hsv = HSVColor.fromColor(a); + double hue = hsv.hue; + double saturation = hsv.saturation; + double brightness = hsv.value; + + // Increase brightness to ensure higher contrast + brightness = brightness > 0.5 ? brightness - 0.5 : brightness + 0.5; + + return HSVColor.fromAHSV(1.0, hue, saturation, brightness).toColor(); + } + + return b; + } + + static Color getSelfContrastColor(Color color) { + final hsv = HSVColor.fromColor(color); + double hue = hsv.hue; + double saturation = hsv.saturation; + double brightness = hsv.value; + + // Increase brightness to ensure higher contrast + brightness = brightness > 0.5 ? brightness - 0.5 : brightness + 0.5; + + return HSVColor.fromAHSV(1.0, hue, saturation, brightness).toColor(); + } +} + +extension ColorUtilExtension on Color { + Color get highlight => ColorUtil.getSelfContrastColor(this); +} diff --git a/lib/shared/widgets.dart b/lib/shared/widgets.dart index 6c68e385..1622e8b2 100644 --- a/lib/shared/widgets.dart +++ b/lib/shared/widgets.dart @@ -1,4 +1,6 @@ /// [Base] +library; + export 'widgets/page.dart'; export 'widgets/component.dart'; export 'widgets/screen.dart'; diff --git a/lib/shared/widgets/enhanced_carousel_view.dart b/lib/shared/widgets/enhanced_carousel_view.dart index 0724ef2d..6cb3a1b1 100644 --- a/lib/shared/widgets/enhanced_carousel_view.dart +++ b/lib/shared/widgets/enhanced_carousel_view.dart @@ -2,70 +2,106 @@ import 'package:flutter/material.dart'; import 'package:hub/flutter_flow/index.dart'; import 'package:hub/shared/utils.dart'; -class EnhancedCarouselView extends StatelessWidget { +class EnhancedCarouselView extends StatefulWidget { final Future> Function() dataProvider; final void Function(T, BuildContext) filter; - final Widget Function(T? item) itemBuilder; + final Widget Function(T? item, bool isSelected) itemBuilder; + final bool showIndicator; const EnhancedCarouselView({ super.key, required this.dataProvider, required this.filter, required this.itemBuilder, + this.showIndicator = false, }); + @override + _EnhancedCarouselViewState createState() => + _EnhancedCarouselViewState(); +} + +class _EnhancedCarouselViewState extends State> { + T? selectedCategory; + @override Widget build(BuildContext context) { final theme = FlutterFlowTheme.of(context); final backgroundColor = theme.primary; final overlayColor = WidgetStateProperty.all(Colors.transparent); - return Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - spacing: 20, + return Stack( 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), - ), - ), - ), - FutureBuilder>( - future: dataProvider(), - builder: (context, snapshot) { - if (!snapshot.hasData) return SizedBox(); - final items = - snapshot.data!.map((item) => itemBuilder(item)).toList(); - return SizedBox( - height: 130, // Set a specific height - child: CarouselView( - itemExtent: 140, - enableSplash: true, - itemSnapping: true, - controller: CarouselController(initialItem: 1), - backgroundColor: backgroundColor, - overlayColor: overlayColor, - padding: EdgeInsets.zero, - elevation: 0, - reverse: true, - shrinkExtent: 10, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - onTap: (index) => filter(snapshot.data![index] as T, context), - children: items, + Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + 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), + ), + ), + ), + FutureBuilder>( + future: widget.dataProvider(), + builder: (context, snapshot) { + if (!snapshot.hasData) return SizedBox(); + final items = snapshot.data! + .map((item) => + widget.itemBuilder(item, item == selectedCategory)) + .toList(); + return SizedBox( + height: 130, // Set a specific height + child: CarouselView( + itemExtent: 140, + enableSplash: true, + itemSnapping: true, + controller: CarouselController(initialItem: 1), + backgroundColor: backgroundColor, + overlayColor: overlayColor, + padding: EdgeInsets.zero, + elevation: 0, + reverse: true, + shrinkExtent: 10, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + onTap: (index) { + setState(() { + if (selectedCategory == snapshot.data![index]) + selectedCategory = null; + else + selectedCategory = snapshot.data![index] as T; + }); + widget.filter(snapshot.data![index] as T, context); + }, + children: items, + ), + ); + }, + ), + ], + ), + if (widget.showIndicator) + Positioned( + left: 0, + top: 50, + child: Icon(Icons.arrow_left, size: 30, color: Colors.grey), + ), + if (widget.showIndicator) + Positioned( + right: 0, + top: 50, + child: Icon(Icons.arrow_right, size: 30, color: Colors.grey), + ), ], ); } diff --git a/lib/shared/widgets/enhanced_list_view.dart b/lib/shared/widgets/enhanced_list_view.dart index a2de85e5..1f370b65 100644 --- a/lib/shared/widgets/enhanced_list_view.dart +++ b/lib/shared/widgets/enhanced_list_view.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; +import 'package:hub/flutter_flow/index.dart'; import 'package:rx_bloc/rx_bloc.dart'; import 'package:rx_bloc_list/rx_bloc_list.dart'; import 'package:rxdart/rxdart.dart'; @@ -211,6 +212,10 @@ class EnhancedListViewState @override Widget build(BuildContext context) { + final String defaultMessage = FFLocalizations.of(context).getVariableText( + ptText: 'Nenhum item encontrado', + enText: 'No items found', + ); final header = StreamBuilder>( stream: bloc.states.headerItems.cast>(), builder: (context, headerSnapshot) { @@ -250,7 +255,9 @@ class EnhancedListViewState } else if (bodySnapshot.hasError) { return EnhancedErrorWidget(error: bodySnapshot.error); } else if (!bodySnapshot.hasData || bodySnapshot.data!.isEmpty) { - return const SizedBox.shrink(); + return Center( + child: Text(defaultMessage), + ); } else { return ListView.builder( itemCount: bodySnapshot.data?.length ?? 0, diff --git a/lib/shared/widgets/read_view.dart b/lib/shared/widgets/read_view.dart index 6df9c6cb..8be31b52 100644 --- a/lib/shared/widgets/read_view.dart +++ b/lib/shared/widgets/read_view.dart @@ -1,7 +1,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:hub/features/backend/index.dart'; +// Removed unnecessary import import 'package:hub/flutter_flow/index.dart'; import 'package:hub/shared/utils/dialog_util.dart'; import 'package:hub/shared/widgets.dart'; @@ -32,9 +32,11 @@ abstract interface class Viewer extends StatelessComponent { class ReadView extends StatefulWidget { final String url; final String title; + final VoidCallback onError; const ReadView({ super.key, + required this.onError, required this.url, required this.title, }); @@ -50,8 +52,12 @@ class ReadViewState extends State { final Future document = DocumentType.openFile(file.path); return ReadViewController(document: document); } catch (e) { - logError('Erro ao baixar o PDF', e); - return Future.error(e); + final message = FFLocalizations.of(context).getVariableText( + ptText: 'Erro ao baixar o PDF', + enText: 'Error downloading PDF', + ); + + return Future.error(message); } } @@ -68,14 +74,19 @@ class ReadViewState extends State { throw Exception('Falha ao baixar o PDF'); } } catch (e) { - logError('Erro ao baixar o PDF', e); + final message = FFLocalizations.of(context).getVariableText( + ptText: 'Erro ao baixar o PDF', + enText: 'Error downloading PDF', + ); + await throwError(message, e); rethrow; } } - void logError(String message, dynamic error) { + Future throwError(String message, dynamic error) async { log('$message: $error'); - DialogUtil.error(context, message); + await DialogUtil.error(context, message) + .whenComplete(() => widget.onError()); } @override @@ -104,7 +115,7 @@ class ReadViewState extends State { ); } - void onShare() async { + Future onShare() async { try { final Uri uri = Uri.parse(widget.url); final response = await http.get(uri); @@ -113,10 +124,15 @@ class ReadViewState extends State { name: '${widget.title}.pdf', mimeType: 'application/pdf'); await Share.shareXFiles([xfile], text: 'Confira este PDF!'); } else { - throw Exception('Erro ao compartilhar o arquivo: ${response.statusCode}'); + throw Exception( + 'Erro ao compartilhar o arquivo: ${response.statusCode}'); } } catch (e) { - logError('Erro ao compartilhar o arquivo', e); + final message = FFLocalizations.of(context).getVariableText( + ptText: 'Erro ao compartilhar o arquivo', + enText: 'Error sharing file', + ); + await throwError(message, e); } }