From 11e2d13e28309da4cb6f98a1f8dd9eb9b4d86f1b Mon Sep 17 00:00:00 2001 From: Marcelo Amaro Date: Mon, 20 Nov 2023 10:30:27 -0300 Subject: [PATCH] feat: Add content models to DSInteractiveListMessageBubble --- lib/blip_ds.dart | 2 +- lib/src/models/ds_message_item.model.dart | 10 +- lib/src/models/ds_select_option.model.dart | 8 +- .../ds_interactive_list_message.model.dart | 32 +++ ...interactive_list_message_action.model.dart | 30 +++ ...s_interactive_list_message_body.model.dart | 14 ++ ...interactive_list_message_footer.model.dart | 14 ++ ...ds_interactive_list_message_row.model.dart | 14 ++ ...nteractive_list_message_section.model.dart | 24 +++ ...pplication_json_message_bubble.widget.dart | 3 +- ...nteractive_list_message_bubble.widget.dart | 198 +++++++++++------- .../widgets/chat/ds_quick_reply.widget.dart | 5 +- .../widgets/chat/ds_select_menu.widget.dart | 5 +- .../widgets/utils/ds_group_card.widget.dart | 16 +- .../sample_group_card.controller.dart | 4 +- .../showcase/sample_group_card.showcase.dart | 2 +- 16 files changed, 273 insertions(+), 108 deletions(-) create mode 100644 lib/src/models/interactive_list_message/ds_interactive_list_message.model.dart create mode 100644 lib/src/models/interactive_list_message/ds_interactive_list_message_action.model.dart create mode 100644 lib/src/models/interactive_list_message/ds_interactive_list_message_body.model.dart create mode 100644 lib/src/models/interactive_list_message/ds_interactive_list_message_footer.model.dart create mode 100644 lib/src/models/interactive_list_message/ds_interactive_list_message_row.model.dart create mode 100644 lib/src/models/interactive_list_message/ds_interactive_list_message_section.model.dart diff --git a/lib/blip_ds.dart b/lib/blip_ds.dart index 8ab14ac5..540559ca 100644 --- a/lib/blip_ds.dart +++ b/lib/blip_ds.dart @@ -18,7 +18,7 @@ export 'src/models/ds_message_bubble_avatar_config.model.dart' show DSMessageBubbleAvatarConfig; export 'src/models/ds_message_bubble_style.model.dart' show DSMessageBubbleStyle; -export 'src/models/ds_message_item.model.dart' show DSMessageItemModel; +export 'src/models/ds_message_item.model.dart' show DSMessageItem; export 'src/models/ds_toast_props.model.dart' show DSToastProps; export 'src/services/ds_auth.service.dart' show DSAuthService; export 'src/services/ds_bottom_sheet.service.dart' show DSBottomSheetService; diff --git a/lib/src/models/ds_message_item.model.dart b/lib/src/models/ds_message_item.model.dart index e4d01fcf..3070ce8e 100644 --- a/lib/src/models/ds_message_item.model.dart +++ b/lib/src/models/ds_message_item.model.dart @@ -2,7 +2,7 @@ import '../enums/ds_align.enum.dart'; import '../enums/ds_delivery_report_status.enum.dart'; /// A Design System message model used with [DSGroupCard] to display grouped bubble -class DSMessageItemModel { +class DSMessageItem { /// Identifier of message String? id; @@ -30,8 +30,8 @@ class DSMessageItemModel { /// Used to define if a message detail (typicament a messages date and time) should be displayed or not bool? hideMessageDetail; - /// Creates a new Design System's [DSMessageItemModel] model - DSMessageItemModel({ + /// Creates a new Design System's [DSMessageItem] model + DSMessageItem({ this.id, required this.date, required this.displayDate, @@ -43,8 +43,8 @@ class DSMessageItemModel { this.hideMessageDetail, }); - factory DSMessageItemModel.fromJson(Map json) { - final messageItem = DSMessageItemModel( + factory DSMessageItem.fromJson(Map json) { + final messageItem = DSMessageItem( id: json['id'], date: json['date'], displayDate: json['displayDate'], diff --git a/lib/src/models/ds_select_option.model.dart b/lib/src/models/ds_select_option.model.dart index e5ebdc07..1ee09cfd 100644 --- a/lib/src/models/ds_select_option.model.dart +++ b/lib/src/models/ds_select_option.model.dart @@ -1,19 +1,19 @@ /// A Design System select options model used with [DSSelectMenu], [DSQuickReply] to display a options menu -class DSSelectOptionModel { +class DSSelectOption { String text; int? order; String? type; dynamic value; - DSSelectOptionModel({ + DSSelectOption({ required this.text, this.order, this.type, this.value, }); - factory DSSelectOptionModel.fromJson(Map json) { - final option = DSSelectOptionModel( + factory DSSelectOption.fromJson(Map json) { + final option = DSSelectOption( text: json['text'], ); diff --git a/lib/src/models/interactive_list_message/ds_interactive_list_message.model.dart b/lib/src/models/interactive_list_message/ds_interactive_list_message.model.dart new file mode 100644 index 00000000..8a084fc1 --- /dev/null +++ b/lib/src/models/interactive_list_message/ds_interactive_list_message.model.dart @@ -0,0 +1,32 @@ +import 'ds_interactive_list_message_action.model.dart'; +import 'ds_interactive_list_message_body.model.dart'; +import 'ds_interactive_list_message_footer.model.dart'; + +class DSInteractiveListMessage { + final DSInteractiveListMessageBody? body; + final DSInteractiveListMessageAction? action; + final DSInteractiveListMessageFooter? footer; + + DSInteractiveListMessage({ + this.body, + this.action, + this.footer, + }); + + DSInteractiveListMessage.fromJson(Map json) + : body = json['body'] != null + ? DSInteractiveListMessageBody.fromJson(json['body']) + : null, + action = json['action'] != null + ? DSInteractiveListMessageAction.fromJson(json['action']) + : null, + footer = json['footer'] != null + ? DSInteractiveListMessageFooter.fromJson(json['footer']) + : null; + + Map toJson() => { + 'body': body?.toJson(), + 'action': action?.toJson(), + 'footer': footer?.toJson(), + }; +} diff --git a/lib/src/models/interactive_list_message/ds_interactive_list_message_action.model.dart b/lib/src/models/interactive_list_message/ds_interactive_list_message_action.model.dart new file mode 100644 index 00000000..a9598655 --- /dev/null +++ b/lib/src/models/interactive_list_message/ds_interactive_list_message_action.model.dart @@ -0,0 +1,30 @@ +import 'ds_interactive_list_message_section.model.dart'; + +class DSInteractiveListMessageAction { + final String? button; + final List? sections; + + DSInteractiveListMessageAction({ + this.button, + this.sections, + }); + + DSInteractiveListMessageAction.fromJson(Map json) + : button = json['button'], + sections = json['sections'] != null + ? List.from(json['sections']) + .map( + (e) => DSInteractiveListMessageSection.fromJson(e), + ) + .toList() + : null; + + Map toJson() => { + 'button': button, + 'sections': sections + ?.map( + (e) => e.toJson(), + ) + .toList(), + }; +} diff --git a/lib/src/models/interactive_list_message/ds_interactive_list_message_body.model.dart b/lib/src/models/interactive_list_message/ds_interactive_list_message_body.model.dart new file mode 100644 index 00000000..6b9f6a4b --- /dev/null +++ b/lib/src/models/interactive_list_message/ds_interactive_list_message_body.model.dart @@ -0,0 +1,14 @@ +class DSInteractiveListMessageBody { + final String? text; + + DSInteractiveListMessageBody({ + this.text, + }); + + DSInteractiveListMessageBody.fromJson(Map json) + : text = json['text']; + + Map toJson() => { + 'text': text, + }; +} diff --git a/lib/src/models/interactive_list_message/ds_interactive_list_message_footer.model.dart b/lib/src/models/interactive_list_message/ds_interactive_list_message_footer.model.dart new file mode 100644 index 00000000..67da9c05 --- /dev/null +++ b/lib/src/models/interactive_list_message/ds_interactive_list_message_footer.model.dart @@ -0,0 +1,14 @@ +class DSInteractiveListMessageFooter { + final String? text; + + DSInteractiveListMessageFooter({ + this.text, + }); + + DSInteractiveListMessageFooter.fromJson(Map json) + : text = json['text']; + + Map toJson() => { + 'text': text, + }; +} diff --git a/lib/src/models/interactive_list_message/ds_interactive_list_message_row.model.dart b/lib/src/models/interactive_list_message/ds_interactive_list_message_row.model.dart new file mode 100644 index 00000000..7b8b1007 --- /dev/null +++ b/lib/src/models/interactive_list_message/ds_interactive_list_message_row.model.dart @@ -0,0 +1,14 @@ +class DSInteractiveListMessageRow { + final String? title; + + DSInteractiveListMessageRow({ + this.title, + }); + + DSInteractiveListMessageRow.fromJson(Map json) + : title = json['title']; + + Map toJson() => { + 'title': title, + }; +} diff --git a/lib/src/models/interactive_list_message/ds_interactive_list_message_section.model.dart b/lib/src/models/interactive_list_message/ds_interactive_list_message_section.model.dart new file mode 100644 index 00000000..b45da441 --- /dev/null +++ b/lib/src/models/interactive_list_message/ds_interactive_list_message_section.model.dart @@ -0,0 +1,24 @@ +import 'ds_interactive_list_message_row.model.dart'; + +class DSInteractiveListMessageSection { + final List? rows; + + DSInteractiveListMessageSection({ + this.rows, + }); + + DSInteractiveListMessageSection.fromJson(Map json) + : rows = List.from(json['rows']) + .map( + (e) => DSInteractiveListMessageRow.fromJson(e), + ) + .toList(); + + Map toJson() => { + 'rows': rows + ?.map( + (e) => e.toJson(), + ) + .toList(), + }; +} diff --git a/lib/src/widgets/chat/ds_application_json_message_bubble.widget.dart b/lib/src/widgets/chat/ds_application_json_message_bubble.widget.dart index 1bdafd15..9d91f236 100644 --- a/lib/src/widgets/chat/ds_application_json_message_bubble.widget.dart +++ b/lib/src/widgets/chat/ds_application_json_message_bubble.widget.dart @@ -4,6 +4,7 @@ import '../../enums/ds_align.enum.dart'; import '../../enums/ds_border_radius.enum.dart'; import '../../enums/ds_delivery_report_status.enum.dart'; import '../../models/ds_message_bubble_style.model.dart'; +import '../../models/interactive_list_message/ds_interactive_list_message.model.dart'; import '../../themes/colors/ds_colors.theme.dart'; import '../../themes/icons/ds_icons.dart'; import 'ds_interactive_list_message_bubble.widget.dart'; @@ -62,7 +63,7 @@ class DSApplicationJsonMessageBubble extends StatelessWidget { }; Widget _buildInteractiveList() => DSInteractiveListMessageBubble( - content: interactive, + content: DSInteractiveListMessage.fromJson(interactive), align: align, borderRadius: borderRadius, style: style, diff --git a/lib/src/widgets/chat/ds_interactive_list_message_bubble.widget.dart b/lib/src/widgets/chat/ds_interactive_list_message_bubble.widget.dart index e3c087c2..78a71606 100644 --- a/lib/src/widgets/chat/ds_interactive_list_message_bubble.widget.dart +++ b/lib/src/widgets/chat/ds_interactive_list_message_bubble.widget.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../enums/ds_align.enum.dart'; import '../../enums/ds_border_radius.enum.dart'; import '../../models/ds_message_bubble_style.model.dart'; +import '../../models/interactive_list_message/ds_interactive_list_message.model.dart'; import '../../themes/colors/ds_colors.theme.dart'; import '../../themes/icons/ds_icons.dart'; import '../../themes/texts/utils/ds_font_weights.theme.dart'; @@ -13,28 +14,31 @@ import '../utils/ds_divider.widget.dart'; import 'ds_message_bubble.widget.dart'; class DSInteractiveListMessageBubble extends StatelessWidget { - final Map content; + final DSInteractiveListMessage content; final DSAlign align; final List borderRadius; final DSMessageBubbleStyle style; - final dynamic body; - final dynamic action; - final dynamic footer; final bool isDefaultBubbleColors; final bool isLightBubbleBackground; + final bool hasBodyText; + final bool hasFooterText; + final bool hasButtonText; + final bool hasSections; + DSInteractiveListMessageBubble({ super.key, required this.content, required this.align, required this.borderRadius, required this.style, - }) : body = content['body'], - action = content['action'], - footer = content['footer'], - isDefaultBubbleColors = style.isDefaultBubbleBackground(align), - isLightBubbleBackground = style.isLightBubbleBackground(align); + }) : isDefaultBubbleColors = style.isDefaultBubbleBackground(align), + isLightBubbleBackground = style.isLightBubbleBackground(align), + hasBodyText = content.body?.text?.isNotEmpty ?? false, + hasFooterText = content.footer?.text?.isNotEmpty ?? false, + hasButtonText = content.action?.button?.isNotEmpty ?? false, + hasSections = content.action?.sections?.isNotEmpty ?? false; @override Widget build(BuildContext context) => Column( @@ -43,7 +47,13 @@ class DSInteractiveListMessageBubble extends StatelessWidget { align: align, borderRadius: borderRadius, style: style, - child: _buildHeader(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildHeaderText(), + _buildHeaderButton(), + ], + ), ), const SizedBox( height: 3.0, @@ -52,16 +62,25 @@ class DSInteractiveListMessageBubble extends StatelessWidget { align: align, borderRadius: borderRadius, style: style, - child: _buildList(), + child: Column( + children: _buildList(), + ), ), ], ); - Widget _buildHeader() => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DSBodyText( - body['text'], + List _buildHeaderText() { + const bottomPadding = 8.0; + final widgets = []; + + if (hasBodyText) { + widgets.add( + Padding( + padding: const EdgeInsets.only( + bottom: bottomPadding, + ), + child: DSBodyText( + content.body!.text, overflow: TextOverflow.visible, color: isLightBubbleBackground ? isDefaultBubbleColors @@ -71,14 +90,64 @@ class DSInteractiveListMessageBubble extends StatelessWidget { ? DSColors.primaryLight : DSColors.neutralLightSnow, ), + ), + ); + } + + if (hasFooterText) { + widgets.add( + Padding( + padding: const EdgeInsets.only( + bottom: bottomPadding, + ), + child: DSCaptionText( + content.footer!.text, + fontStyle: FontStyle.italic, + overflow: TextOverflow.visible, + color: isLightBubbleBackground + ? isDefaultBubbleColors + ? DSColors.primaryNight + : DSColors.neutralDarkCity + : isDefaultBubbleColors + ? DSColors.primaryLight + : DSColors.neutralLightSnow, + ), + ), + ); + } + + if (widgets.isNotEmpty) { + widgets.add( + Padding( + padding: const EdgeInsets.only( + bottom: bottomPadding, + ), + child: DSDivider( + color: isLightBubbleBackground + ? isDefaultBubbleColors + ? DSColors.neutralMediumWave + : DSColors.neutralDarkCity + : isDefaultBubbleColors + ? DSColors.neutralDarkRooftop + : DSColors.neutralLightSnow, + ), + ), + ); + } + + return widgets; + } + + Widget _buildHeaderButton() => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, + padding: EdgeInsets.only( + right: hasButtonText ? 4.0 : 0.0, ), - child: DSCaptionText( - footer['text'], - fontStyle: FontStyle.italic, - overflow: TextOverflow.visible, + child: Icon( + DSIcons.list_outline, + size: 20.0, color: isLightBubbleBackground ? isDefaultBubbleColors ? DSColors.primaryNight @@ -88,79 +157,48 @@ class DSInteractiveListMessageBubble extends StatelessWidget { : DSColors.neutralLightSnow, ), ), - Padding( - padding: const EdgeInsets.only( - bottom: 8.0, - ), - child: DSDivider( + if (hasButtonText) + DSCaptionText( + content.action!.button, + fontWeight: DSFontWeights.bold, + overflow: TextOverflow.visible, color: isLightBubbleBackground ? isDefaultBubbleColors - ? DSColors.neutralMediumWave + ? DSColors.primaryNight : DSColors.neutralDarkCity : isDefaultBubbleColors - ? DSColors.neutralDarkRooftop + ? DSColors.primaryLight : DSColors.neutralLightSnow, ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only( - right: 4.0, - ), - child: Icon( - DSIcons.list_outline, - size: 20.0, - color: isLightBubbleBackground - ? isDefaultBubbleColors - ? DSColors.primaryNight - : DSColors.neutralDarkCity - : isDefaultBubbleColors - ? DSColors.primaryLight - : DSColors.neutralLightSnow, - ), - ), - DSCaptionText( - action['button'], - fontWeight: DSFontWeights.bold, - overflow: TextOverflow.visible, - color: isLightBubbleBackground - ? isDefaultBubbleColors - ? DSColors.primaryNight - : DSColors.neutralDarkCity - : isDefaultBubbleColors - ? DSColors.primaryLight - : DSColors.neutralLightSnow, - ), - ], - ), ], ); - Widget _buildList() { + List _buildList() { final widgets = []; - for (final section in action['sections']) { - final rows = section['rows'] as List; - var count = 1; + if (hasSections) { + for (final section in content.action!.sections!) { + var count = 1; - for (final row in rows) { - widgets.add( - DSMenuItem( - text: row['title'], - align: align, - style: style, - showBorder: count != rows.length, - ), - ); + if (section.rows?.isNotEmpty ?? false) { + for (final row in section.rows!) { + if (row.title?.isNotEmpty ?? false) { + widgets.add( + DSMenuItem( + text: row.title!, + align: align, + style: style, + showBorder: count != section.rows!.length, + ), + ); + } - ++count; + ++count; + } + } } } - return Column( - children: widgets, - ); + return widgets; } } diff --git a/lib/src/widgets/chat/ds_quick_reply.widget.dart b/lib/src/widgets/chat/ds_quick_reply.widget.dart index 72ffc114..72318cd1 100644 --- a/lib/src/widgets/chat/ds_quick_reply.widget.dart +++ b/lib/src/widgets/chat/ds_quick_reply.widget.dart @@ -38,9 +38,8 @@ class DSQuickReply extends StatelessWidget { borderRadius = []; - List options = content['options'] - .map((doc) => DSSelectOptionModel.fromJson(doc)) - .toList(); + List options = + content['options'].map((doc) => DSSelectOption.fromJson(doc)).toList(); for (var option in options) { children.add( diff --git a/lib/src/widgets/chat/ds_select_menu.widget.dart b/lib/src/widgets/chat/ds_select_menu.widget.dart index a19259e7..70889ccc 100644 --- a/lib/src/widgets/chat/ds_select_menu.widget.dart +++ b/lib/src/widgets/chat/ds_select_menu.widget.dart @@ -35,9 +35,8 @@ class DSSelectMenu extends StatelessWidget { int count = 0; - List options = content['options'] - .map((doc) => DSSelectOptionModel.fromJson(doc)) - .toList(); + List options = + content['options'].map((doc) => DSSelectOption.fromJson(doc)).toList(); for (final option in options) { count++; diff --git a/lib/src/widgets/utils/ds_group_card.widget.dart b/lib/src/widgets/utils/ds_group_card.widget.dart index 5224557c..25946c0f 100644 --- a/lib/src/widgets/utils/ds_group_card.widget.dart +++ b/lib/src/widgets/utils/ds_group_card.widget.dart @@ -22,7 +22,7 @@ import 'ds_user_avatar.widget.dart'; // Default compare message function // ignore: prefer_function_declarations_over_variables final _defaultCompareMessageFuntion = - (DSMessageItemModel firstMsg, DSMessageItemModel secondMsg) { + (DSMessageItem firstMsg, DSMessageItem secondMsg) { bool shouldGroupSelect = true; if (firstMsg.type == DSMessageContentType.select || @@ -62,14 +62,14 @@ class DSGroupCard extends StatefulWidget { this.onInfinitScroll, this.shrinkWrap = false, DSMessageBubbleStyle? style, - bool Function(DSMessageItemModel, DSMessageItemModel)? compareMessages, + bool Function(DSMessageItem, DSMessageItem)? compareMessages, ScrollController? scrollController, }) : compareMessages = compareMessages ?? _defaultCompareMessageFuntion, style = style ?? DSMessageBubbleStyle(), scrollController = scrollController ?? ScrollController(); - final List documents; - final bool Function(DSMessageItemModel, DSMessageItemModel) compareMessages; + final List documents; + final bool Function(DSMessageItem, DSMessageItem) compareMessages; final bool isComposing; final bool sortMessages; final void Function(String, Map)? onSelected; @@ -190,9 +190,9 @@ class _DSGroupCardState extends State { }; for (int i = 1; i < widget.documents.length; i++) { - DSMessageItemModel message = widget.documents[i]; + DSMessageItem message = widget.documents[i]; - List groupMsgs = group['msgs']; + List groupMsgs = group['msgs']; if (widget.compareMessages(message, groupMsgs.last)) { group['msgs'].add(message); @@ -239,10 +239,10 @@ class _DSGroupCardState extends State { int msgCount = 1; final sentMessage = group['align'] == DSAlign.right; - DSMessageItemModel? lastMessageQuickReply; + DSMessageItem? lastMessageQuickReply; group['msgs'].forEach( - (DSMessageItemModel message) { + (DSMessageItem message) { final rows = []; final int length = group['msgs'].length; List borderRadius = diff --git a/sample/lib/controllers/sample_group_card.controller.dart b/sample/lib/controllers/sample_group_card.controller.dart index af45068e..57514571 100644 --- a/sample/lib/controllers/sample_group_card.controller.dart +++ b/sample/lib/controllers/sample_group_card.controller.dart @@ -5,14 +5,14 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; class SampleGroupCardController { - Future> getMessages() async { + Future> getMessages() async { String data = await DefaultAssetBundle.of(Get.context!) .loadString("assets/messages.json"); final jsonResult = jsonDecode(data); final messages = (jsonResult as List) .map( - (doc) => DSMessageItemModel.fromJson( + (doc) => DSMessageItem.fromJson( { "id": doc["id"], "date": doc["date"], diff --git a/sample/lib/widgets/showcase/sample_group_card.showcase.dart b/sample/lib/widgets/showcase/sample_group_card.showcase.dart index 17cf24fb..46055753 100644 --- a/sample/lib/widgets/showcase/sample_group_card.showcase.dart +++ b/sample/lib/widgets/showcase/sample_group_card.showcase.dart @@ -26,7 +26,7 @@ class SampleGroupCardShowcase extends StatelessWidget { debugPrint('Infos de callback: $text / $payload'); }, isComposing: false, - documents: snapshot.data as List, + documents: snapshot.data as List, style: DSMessageBubbleStyle( sentBackgroundColor: DSColors.neutralLightSnow, receivedBackgroundColor: const Color(0xff02afff),