diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c0dddba..d918d4b7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,8 +20,8 @@ jobs: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - flutter-version: '3.13.9' - channel: 'stable' + flutter-version: "3.13.9" + channel: "stable" - name: Install dependencies run: flutter pub get - name: Run tests diff --git a/CHANGELOG.md b/CHANGELOG.md index f4dfebed..ade38500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.6 + +- Added several widgets to support Blip Calls. + ## 0.1.5 - [DSDarkColors] Added new Dark theme colors. diff --git a/lib/blip_ds.dart b/lib/blip_ds.dart index c558504a..624125b6 100644 --- a/lib/blip_ds.dart +++ b/lib/blip_ds.dart @@ -17,6 +17,7 @@ export 'src/extensions/ds_border_radius.extension.dart' export 'src/extensions/ds_delivery_report_status.extension.dart' show DSDeliveryReportStatusExtension; export 'src/extensions/ds_string.extension.dart' show DSStringExtension; +export 'src/models/ds_calls_media_message.model.dart' show DSCallsMediaMessage; export 'src/models/ds_message_bubble_avatar_config.model.dart' show DSMessageBubbleAvatarConfig; export 'src/models/ds_message_bubble_style.model.dart' @@ -58,6 +59,7 @@ export 'src/services/ds_auth.service.dart' show DSAuthService; export 'src/services/ds_bottom_sheet.service.dart' show DSBottomSheetService; export 'src/services/ds_dialog.service.dart' show DSDialogService; export 'src/services/ds_file.service.dart' show DSFileService; +export 'src/services/ds_localization.service.dart' show DSLocalizationService; export 'src/services/ds_media_format.service.dart' show DSMediaFormatService; export 'src/services/ds_toast.service.dart' show DSToastService; export 'src/themes/colors/ds_colors.theme.dart' show DSColors; @@ -112,6 +114,7 @@ export 'src/widgets/buttons/ds_attachment_button.widget.dart' export 'src/widgets/buttons/ds_button.widget.dart' show DSButton; export 'src/widgets/buttons/ds_custom_replies_icon_button.widget.dart' show DSCustomRepliesIconButton; +export 'src/widgets/buttons/ds_ghost_button.widget.dart' show DSGhostButton; export 'src/widgets/buttons/ds_icon_button.widget.dart' show DSIconButton; export 'src/widgets/buttons/ds_pause_button.widget.dart' show DSPauseButton; export 'src/widgets/buttons/ds_play_button.widget.dart' show DSPlayButton; @@ -126,6 +129,8 @@ export 'src/widgets/buttons/ds_tertiary_button.widget.dart' export 'src/widgets/chat/audio/ds_audio_message_bubble.widget.dart' show DSAudioMessageBubble; export 'src/widgets/chat/audio/ds_audio_player.widget.dart' show DSAudioPlayer; +export 'src/widgets/chat/calls/ds_end_calls_message_bubble.widget.dart' + show DSEndCallsMessageBubble; export 'src/widgets/chat/ds_application_json_message_bubble.widget.dart' show DSApplicationJsonMessageBubble; export 'src/widgets/chat/ds_carrousel.widget.dart' show DSCarrousel; diff --git a/lib/src/enums/ds_call_direction.enum.dart b/lib/src/enums/ds_call_direction.enum.dart new file mode 100644 index 00000000..4b529a78 --- /dev/null +++ b/lib/src/enums/ds_call_direction.enum.dart @@ -0,0 +1,5 @@ +enum DSCallDirection { + outbound, + inbound, + unknown, +} diff --git a/lib/src/enums/ds_call_provider.enum.dart b/lib/src/enums/ds_call_provider.enum.dart new file mode 100644 index 00000000..c5f3dbd7 --- /dev/null +++ b/lib/src/enums/ds_call_provider.enum.dart @@ -0,0 +1,5 @@ +enum DSCallProvider { + mobcall, + whatsApp, + unknown, +} diff --git a/lib/src/enums/ds_call_status.enum.dart b/lib/src/enums/ds_call_status.enum.dart new file mode 100644 index 00000000..291e9816 --- /dev/null +++ b/lib/src/enums/ds_call_status.enum.dart @@ -0,0 +1,6 @@ +enum DSCallStatus { + completed, + answer, + noAnswer, + unknown, +} diff --git a/lib/src/enums/ds_call_type.enum.dart b/lib/src/enums/ds_call_type.enum.dart new file mode 100644 index 00000000..779633e5 --- /dev/null +++ b/lib/src/enums/ds_call_type.enum.dart @@ -0,0 +1,5 @@ +enum DSCallType { + voice, + video, + unknown, +} diff --git a/lib/src/extensions/ds_enum.extension.dart b/lib/src/extensions/ds_enum.extension.dart new file mode 100644 index 00000000..18ce0d2b --- /dev/null +++ b/lib/src/extensions/ds_enum.extension.dart @@ -0,0 +1,12 @@ +extension EnumByNameOrNull on Iterable { + T? byNameOrNull(String? name) { + for (var value in this) { + if (value.name.toString().toLowerCase() == + name?.toString().toLowerCase()) { + return value; + } + } + + return null; + } +} diff --git a/lib/src/extensions/ds_localization.extension.dart b/lib/src/extensions/ds_localization.extension.dart new file mode 100644 index 00000000..0d423a25 --- /dev/null +++ b/lib/src/extensions/ds_localization.extension.dart @@ -0,0 +1,10 @@ +import '../services/ds_localization.service.dart'; + +extension DSLocalizationExtension on String { + translate() { + final locale = DSLocalizationService.locale ?? 'pt_BR'; + final translations = + DSLocalizationService.translations?[locale.toString()] ?? {}; + return translations[this] ?? this; + } +} diff --git a/lib/src/models/ds_calls_media_message.model.dart b/lib/src/models/ds_calls_media_message.model.dart new file mode 100644 index 00000000..8c6e1acd --- /dev/null +++ b/lib/src/models/ds_calls_media_message.model.dart @@ -0,0 +1,37 @@ +import '../enums/ds_call_direction.enum.dart'; +import '../enums/ds_call_provider.enum.dart'; +import '../enums/ds_call_status.enum.dart'; +import '../enums/ds_call_type.enum.dart'; +import '../extensions/ds_enum.extension.dart'; + +class DSCallsMediaMessage { + final String? sessionId; + final DSCallType? type; + final DSCallProvider? provider; + final DSCallDirection? direction; + final DSCallStatus? status; + final String? ticketId; + final String? identification; + final int? callDuration; + + DSCallsMediaMessage({ + required this.sessionId, + required this.type, + required this.provider, + required this.direction, + required this.status, + required this.ticketId, + required this.identification, + this.callDuration, + }); + + DSCallsMediaMessage.fromJson(Map json) + : sessionId = json['sessionId'], + type = DSCallType.values.byNameOrNull(json['type']), + provider = DSCallProvider.values.byNameOrNull(json['provider']), + direction = DSCallDirection.values.byNameOrNull(json['direction']), + status = DSCallStatus.values.byNameOrNull(json['status']), + ticketId = json['ticketId'], + identification = json['identification'], + callDuration = json['callDuration'] ?? 0; +} diff --git a/lib/src/services/ds_localization.service.dart b/lib/src/services/ds_localization.service.dart new file mode 100644 index 00000000..67432dc6 --- /dev/null +++ b/lib/src/services/ds_localization.service.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +abstract class DSLocalizationService { + static Locale? _locale; + static final Map _translations = {}; + + static setLocale(final Locale locale) => _locale = locale; + static setTranslations(final Map translations) => + _translations.addAll(translations); + + static Locale? get locale => _locale; + static Map? get translations => _translations; +} diff --git a/lib/src/themes/colors/ds_colors.theme.dart b/lib/src/themes/colors/ds_colors.theme.dart index 4b045d80..257f8ac0 100644 --- a/lib/src/themes/colors/ds_colors.theme.dart +++ b/lib/src/themes/colors/ds_colors.theme.dart @@ -58,7 +58,7 @@ abstract class DSColors { static const Color disabledText = Color(0xFF637798); static const Color disabledBg = Color(0xFFE8F2FF); static const Color contentDisable = Color(0xFF636363); - static const Color border1 = Color(0xFF616161); + static const Color border1 = Color(0xFFC9C9C9); static Gradient gradientOcean = DSLinearGradient( colors: const [ diff --git a/lib/src/themes/colors/ds_dark_colors.theme.dart b/lib/src/themes/colors/ds_dark_colors.theme.dart index 768fa719..66405d27 100644 --- a/lib/src/themes/colors/ds_dark_colors.theme.dart +++ b/lib/src/themes/colors/ds_dark_colors.theme.dart @@ -2,14 +2,15 @@ import 'package:flutter/material.dart'; /// All Dark [Color] constants that are used by this Design System. abstract class DSDarkColors { + static const Color surfacePositive = Color(0xFF01562F); static const Color surface0 = Color(0xFF424242); - static const Color surface1 = Color(0xFF292929); + static const Color surface1 = Color(0xFF393939); static const Color surface3 = Color(0xFF141414); - static const Color contentDefault = Color(0xFFF6F6F6); + static const Color contentDefault = Color(0xFFFFFFFF); + static const Color success = Color(0xFF355E4B); + static const Color positive = Color(0xFF6BFFBC); + static const Color error = Color(0xFF7B3D3D); static const Color extendBlue = Color(0xFF1968F0); static const Color extendGreen = Color(0xFF35DE90); - static const Color success = Color(0xFF355E4B); - static const Color positive = Color(0xFF01562F); - static const Color error = Color(0xFF7B3D3D); } diff --git a/lib/src/utils/ds_message_content_type.util.dart b/lib/src/utils/ds_message_content_type.util.dart index eb5d4ab9..2dc3f87b 100644 --- a/lib/src/utils/ds_message_content_type.util.dart +++ b/lib/src/utils/ds_message_content_type.util.dart @@ -13,4 +13,5 @@ abstract class DSMessageContentType { static const String location = 'application/vnd.lime.location+json'; static const String applicationJson = 'application/json'; static const String reply = 'application/vnd.lime.reply+json'; + static const String callsMedia = 'application/vnd.iris.calls.media+json'; } diff --git a/lib/src/widgets/buttons/ds_ghost_button.widget.dart b/lib/src/widgets/buttons/ds_ghost_button.widget.dart new file mode 100644 index 00000000..c05cba52 --- /dev/null +++ b/lib/src/widgets/buttons/ds_ghost_button.widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import '../../../blip_ds.dart'; + +/// A Design System's [ButtonStyleButton] primarily used by call to action. +class DSGhostButton extends DSSecondaryButton { + /// Creates a Design System's [ButtonStyleButton] with ghost style. + DSGhostButton({ + required super.onPressed, + super.key, + super.leadingIcon, + super.label, + super.trailingIcon, + super.isEnabled, + super.isLoading, + super.autoSize, + super.contentAlignment, + super.foregroundColor, + super.borderColor, + }) : super( + backgroundColor: Colors.transparent, + ); +} diff --git a/lib/src/widgets/buttons/ds_primary_button.widget.dart b/lib/src/widgets/buttons/ds_primary_button.widget.dart index d349632e..f14837e5 100644 --- a/lib/src/widgets/buttons/ds_primary_button.widget.dart +++ b/lib/src/widgets/buttons/ds_primary_button.widget.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import '../../themes/colors/ds_colors.theme.dart'; import 'ds_button.widget.dart'; @@ -17,11 +19,14 @@ class DSPrimaryButton extends DSButton { super.isLoading, super.autoSize, super.contentAlignment, + Color? backgroundColor, + Color? foregroundColor, }) : super( - backgroundColor: - isEnabled ? DSColors.primaryNight : DSColors.disabledBg, + backgroundColor: isEnabled + ? backgroundColor ?? DSColors.primaryNight + : DSColors.disabledBg, foregroundColor: isEnabled - ? DSColors.neutralLightSnow + ? foregroundColor ?? DSColors.neutralLightSnow : DSColors.neutralMediumElephant, ); } diff --git a/lib/src/widgets/buttons/ds_secondary_button.widget.dart b/lib/src/widgets/buttons/ds_secondary_button.widget.dart index 0ee61c58..4af637f3 100644 --- a/lib/src/widgets/buttons/ds_secondary_button.widget.dart +++ b/lib/src/widgets/buttons/ds_secondary_button.widget.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + import '../../themes/colors/ds_colors.theme.dart'; import 'ds_button.widget.dart'; @@ -17,13 +19,18 @@ class DSSecondaryButton extends DSButton { super.isLoading, super.autoSize, super.contentAlignment, - super.backgroundColor = DSColors.neutralLightSnow, + Color? backgroundColor, + Color? foregroundColor, + Color? borderColor, }) : super( - foregroundColor: isEnabled - ? DSColors.primaryNight - : DSColors.neutralMediumElephant, - borderColor: isEnabled - ? DSColors.primaryNight - : DSColors.neutralMediumElephant, + backgroundColor: backgroundColor ?? DSColors.neutralLightSnow, + foregroundColor: foregroundColor ?? + (isEnabled + ? DSColors.primaryNight + : DSColors.neutralMediumElephant), + borderColor: borderColor ?? + (isEnabled + ? DSColors.primaryNight + : DSColors.neutralMediumElephant), ); } diff --git a/lib/src/widgets/chat/calls/ds_end_calls_message_bubble.widget.dart b/lib/src/widgets/chat/calls/ds_end_calls_message_bubble.widget.dart new file mode 100644 index 00000000..6e77d727 --- /dev/null +++ b/lib/src/widgets/chat/calls/ds_end_calls_message_bubble.widget.dart @@ -0,0 +1,260 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import '../../../../blip_ds.dart'; +import '../../../enums/ds_call_direction.enum.dart'; +import '../../../enums/ds_call_status.enum.dart'; +import '../../../extensions/ds_localization.extension.dart'; +import 'ds_end_calls_recording_container.widget.dart'; + +class DSEndCallsMessageBubble extends StatefulWidget { + final DSAlign align; + final List borderRadius; + final DSMessageBubbleStyle style; + final DSCallsMediaMessage callsMediaMessage; + final Future Function(String)? onAsyncFetchSession; + final Map? translations; + + DSEndCallsMessageBubble({ + super.key, + required this.align, + required this.callsMediaMessage, + required this.onAsyncFetchSession, + this.borderRadius = const [DSBorderRadius.all], + DSMessageBubbleStyle? style, + this.translations, + }) : style = style ?? DSMessageBubbleStyle(); + + @override + State createState() => + _DSEndCallsMessageBubbleState(); +} + +class _DSEndCallsMessageBubbleState extends State { + final StreamController _streamController = StreamController(); + late final Future _phoneNumber; + Future? _session; + + bool get _isCallAnswered => + [DSCallStatus.completed, DSCallStatus.answer].contains( + widget.callsMediaMessage.status, + ); + + bool get _isInbound => + widget.callsMediaMessage.direction == DSCallDirection.inbound; + + bool get _isLightBubbleBackground => + widget.style.isLightBubbleBackground(widget.align); + + bool get _isDefaultBubbleColors => + widget.style.isDefaultBubbleBackground(widget.align); + + Color get _foregroundColor => _isLightBubbleBackground + ? DSColors.neutralDarkCity + : DSColors.neutralLightSnow; + + @override + void initState() { + super.initState(); + + _phoneNumber = + widget.callsMediaMessage.identification.toString().asPhoneNumber(); + + _session = widget.callsMediaMessage.sessionId != null + ? widget.onAsyncFetchSession?.call( + widget.callsMediaMessage.sessionId!, + ) + : Future.value(null); + } + + @override + Widget build(BuildContext context) { + return DSMessageBubble( + padding: const EdgeInsets.symmetric( + vertical: 12.0, + horizontal: 12.0, + ), + borderRadius: widget.borderRadius, + align: widget.align, + style: widget.style, + child: Column( + children: [ + _buildCallInfo(), + _buildMediaPlayer(), + ], + ), + ); + } + + Widget _buildCallInfo() => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Container( + decoration: BoxDecoration( + color: _isCallAnswered ? DSColors.success : DSColors.error, + borderRadius: const BorderRadius.all( + Radius.circular( + 8.0, + ), + ), + ), + width: 40, + height: 40, + child: Icon( + _isCallAnswered + ? _isInbound + ? DSIcons.voip_receiving_outline + : DSIcons.voip_calling_outline + : DSIcons.voip_ended_outline, + size: 24.0, + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DSHeadlineSmallText( + 'calls.voice-text'.translate(), + color: _foregroundColor, + ), + DSCaptionText( + _isCallAnswered + ? 'calls.answered'.translate() + : 'calls.unanswered'.translate(), + color: _foregroundColor, + fontWeight: DSFontWeights.semiBold, + ), + ], + ), + ], + ), + Flexible( + child: FutureBuilder( + future: _phoneNumber, + builder: (_, snapshot) { + if (snapshot.hasData && !snapshot.hasError) { + return DSCaptionSmallText( + snapshot.data, + color: _foregroundColor, + ); + } + return const DSSpinnerLoading(); + }, + ), + ), + ], + ); + + Widget _buildMediaPlayer() => _isCallAnswered && _session != null + ? StreamBuilder( + stream: _streamController.stream, + builder: (_, __) { + return Padding( + padding: const EdgeInsets.only( + top: 8.0, + ), + child: DSAnimatedSize( + child: FutureBuilder( + future: _session, + builder: (_, snapshot) { + return switch (snapshot.connectionState) { + ConnectionState.done => + snapshot.hasError || (snapshot.data?.isEmpty ?? true) + ? _buildError() + : _buildAudioPlayer(snapshot.data!), + _ => _buildLoading(), + }; + }, + ), + ), + ); + }, + ) + : const SizedBox.shrink(); + + Widget _buildAudioPlayer(final String data) => DSEndCallsRecordingContainer( + isLighBubbleBackground: _isLightBubbleBackground, + child: DSAudioPlayer( + uri: Uri.parse(data), + controlForegroundColor: _isLightBubbleBackground + ? DSColors.neutralDarkRooftop + : DSColors.neutralLightSnow, + labelColor: _isLightBubbleBackground + ? DSColors.neutralDarkCity + : DSColors.neutralLightSnow, + bufferActiveTrackColor: _isLightBubbleBackground + ? DSColors.neutralMediumWave + : DSColors.neutralMediumElephant, + bufferInactiveTrackColor: _isLightBubbleBackground + ? DSColors.neutralDarkRooftop + : DSColors.neutralLightBox, + sliderActiveTrackColor: _isLightBubbleBackground + ? DSColors.primaryNight + : DSColors.primaryLight, + sliderThumbColor: _isLightBubbleBackground + ? DSColors.neutralDarkRooftop + : DSColors.neutralLightSnow, + speedForegroundColor: _isLightBubbleBackground + ? DSColors.neutralDarkCity + : DSColors.neutralLightSnow, + speedBorderColor: _isLightBubbleBackground + ? _isDefaultBubbleColors + ? DSColors.neutralMediumSilver + : DSColors.neutralDarkCity + : _isDefaultBubbleColors + ? DSColors.disabledText + : DSColors.neutralLightSnow, + ), + ); + + Widget _buildLoading() => DSEndCallsRecordingContainer( + isLighBubbleBackground: _isLightBubbleBackground, + child: Row( + children: [ + const DSSpinnerLoading(), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: DSBodyText( + 'calls.preparing-record'.translate(), + color: _foregroundColor, + fontWeight: DSFontWeights.semiBold, + ), + ), + ], + ), + ); + + Widget _buildError() => DSEndCallsRecordingContainer( + isLighBubbleBackground: _isLightBubbleBackground, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DSBodyText( + 'calls.load-record'.translate(), + color: _foregroundColor, + fontWeight: DSFontWeights.semiBold, + ), + DSGhostButton( + onPressed: () => _streamController.add(true), + borderColor: _isLightBubbleBackground + ? DSColors.neutralMediumSilver + : DSColors.disabledText, + foregroundColor: _isLightBubbleBackground + ? DSColors.neutralDarkCity + : DSColors.neutralLightSnow, + trailingIcon: const Icon( + DSIcons.refresh_outline, + size: 24.0, + ), + autoSize: true, + ) + ], + ), + ); +} diff --git a/lib/src/widgets/chat/calls/ds_end_calls_recording_container.widget.dart b/lib/src/widgets/chat/calls/ds_end_calls_recording_container.widget.dart new file mode 100644 index 00000000..8aa66566 --- /dev/null +++ b/lib/src/widgets/chat/calls/ds_end_calls_recording_container.widget.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +import '../../../themes/colors/ds_colors.theme.dart'; + +class DSEndCallsRecordingContainer extends StatelessWidget { + final bool isLighBubbleBackground; + final Widget child; + + const DSEndCallsRecordingContainer({ + super.key, + required this.isLighBubbleBackground, + required this.child, + }); + + @override + Widget build(BuildContext context) => Container( + decoration: BoxDecoration( + color: isLighBubbleBackground + ? DSColors.neutralLightWhisper + : DSColors.neutralDarkDesk, + borderRadius: const BorderRadius.all( + Radius.circular( + 8.0, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: child, + ), + ); +} diff --git a/lib/src/widgets/utils/ds_card.widget.dart b/lib/src/widgets/utils/ds_card.widget.dart index 1faa9e49..f823f30b 100644 --- a/lib/src/widgets/utils/ds_card.widget.dart +++ b/lib/src/widgets/utils/ds_card.widget.dart @@ -6,6 +6,7 @@ import '../../enums/ds_align.enum.dart'; import '../../enums/ds_border_radius.enum.dart'; import '../../enums/ds_delivery_report_status.enum.dart'; import '../../enums/ds_ticket_message_type.enum.dart'; +import '../../models/ds_calls_media_message.model.dart'; import '../../models/ds_document_select.model.dart'; import '../../models/ds_media_link.model.dart'; import '../../models/ds_message_bubble_avatar_config.model.dart'; @@ -15,6 +16,7 @@ import '../../services/ds_file.service.dart'; import '../../utils/ds_message_content_type.util.dart'; import '../../utils/ds_utils.util.dart'; import '../chat/audio/ds_audio_message_bubble.widget.dart'; +import '../chat/calls/ds_end_calls_message_bubble.widget.dart'; import '../chat/ds_application_json_message_bubble.widget.dart'; import '../chat/ds_carrousel.widget.dart'; import '../chat/ds_contact_message_bubble.widget.dart'; @@ -49,6 +51,7 @@ class DSCard extends StatelessWidget { this.showRequestLocationButton = false, this.replyContent, this.isUploading = false, + this.onAsyncFetchSession, }) : style = style ?? DSMessageBubbleStyle(); final String type; @@ -66,6 +69,7 @@ class DSCard extends StatelessWidget { final bool showRequestLocationButton; final DSReplyContent? replyContent; final bool isUploading; + final Future Function(String)? onAsyncFetchSession; @override Widget build(BuildContext context) { @@ -167,6 +171,15 @@ class DSCard extends StatelessWidget { avatarConfig: avatarConfig, ); + case DSMessageContentType.callsMedia: + return DSEndCallsMessageBubble( + align: align, + borderRadius: borderRadius, + style: style, + callsMediaMessage: DSCallsMediaMessage.fromJson(content), + onAsyncFetchSession: onAsyncFetchSession, + ); + default: return DSUnsupportedContentMessageBubble( align: align, diff --git a/lib/src/widgets/utils/ds_group_card.widget.dart b/lib/src/widgets/utils/ds_group_card.widget.dart index 25d297e6..f8c01753 100644 --- a/lib/src/widgets/utils/ds_group_card.widget.dart +++ b/lib/src/widgets/utils/ds_group_card.widget.dart @@ -65,6 +65,7 @@ class DSGroupCard extends StatefulWidget { DSMessageBubbleStyle? style, bool Function(DSMessageItem, DSMessageItem)? compareMessages, ScrollController? scrollController, + this.onAsyncFetchSession, }) : compareMessages = compareMessages ?? _defaultCompareMessageFuntion, style = style ?? DSMessageBubbleStyle(), scrollController = scrollController ?? ScrollController(); @@ -82,6 +83,7 @@ class DSGroupCard extends StatefulWidget { final void Function()? onInfinitScroll; final bool shrinkWrap; final ScrollController scrollController; + final Future Function(String)? onAsyncFetchSession; @override State createState() => _DSGroupCardState(); @@ -264,6 +266,7 @@ class _DSGroupCardState extends State { messageId: message.id, customer: message.customer, isUploading: message.isUploading, + onAsyncFetchSession: widget.onAsyncFetchSession, ); final isLastMsg = msgCount == length; diff --git a/pubspec.yaml b/pubspec.yaml index 7f464bf7..3ae995c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: blip_ds description: Blip Design System for Flutter. -version: 0.1.5 +version: 0.1.6 homepage: https://github.com/takenet/blip-ds-flutter#readme repository: https://github.com/takenet/blip-ds-flutter @@ -16,7 +16,7 @@ dependencies: rxdart: ^0.27.4 flutter_spinkit: ^5.1.0 get: ^4.6.5 - open_filex: ^4.3.2 + open_filex: 4.3.2 path_provider: ^2.1.1 dio: ^5.2.1+1 url_launcher: ^6.1.5 diff --git a/sample/android/app/build.gradle b/sample/android/app/build.gradle index f4d361ea..40c52f3b 100644 --- a/sample/android/app/build.gradle +++ b/sample/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 ndkVersion flutter.ndkVersion compileOptions { diff --git a/sample/assets/ds_localization.json b/sample/assets/ds_localization.json new file mode 100644 index 00000000..73c0f364 --- /dev/null +++ b/sample/assets/ds_localization.json @@ -0,0 +1,24 @@ +{ + "pt_BR": { + "calls.answered": "Finalizada", + "calls.unanswered": "Não atendida", + "calls.voice-text": "Ligação", + "calls.preparing-record": "Preparando gravação...", + "calls.load-record": "Carregar gravação" + + }, + "en_US": { + "calls.answered": "Ended", + "calls.unanswered": "Not answered", + "calls.voice-text": "Phone call", + "calls.preparing-record": "Preparing recording...", + "calls.load-record": "Load recording" + }, + "es_LA": { + "calls.answered": "Finalizada", + "calls.unanswered": "No atendida", + "calls.voice-text": "Llamada", + "calls.preparing-record": "Grabación en curso...", + "calls.load-record": "Cargar grabación" + } + } \ No newline at end of file diff --git a/sample/lib/main.dart b/sample/lib/main.dart index 08e63c52..0ac5f30f 100644 --- a/sample/lib/main.dart +++ b/sample/lib/main.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; + import 'package:blip_ds/blip_ds.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/route_manager.dart'; import 'package:sample/widgets/showcase/sample_user_avatar.showcase.dart'; @@ -42,55 +45,77 @@ class SampleApp extends StatelessWidget { } } -class HomePage extends StatelessWidget { +class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + late final Future _translations; + + @override + void initState() { + super.initState(); + _translations = rootBundle.loadString('assets/ds_localization.json'); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: DSHeader( title: 'Blip Design System Showcase', ), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - children: [ - const SampleUserAvatarShowcase(), - SampleMessageBubbleShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleTextStyleShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleButtonShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleTypingShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleSwitchShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleDialogShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleToastShowcase(), - const Divider(color: DSColors.neutralDarkCity), - SampleRadioShowcase(), - const Divider(color: DSColors.neutralDarkCity), - SampleGroupCardShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleHeaderShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleBottomSheetShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleTicketMessage(), - const Divider(color: DSColors.neutralDarkCity), - const SampleCollectionShowcase(), - const Divider(color: DSColors.neutralDarkCity), - const SampleWeblinkShowcase(), - const Divider(color: DSColors.neutralDarkCity), - SampleInputShowcase(), - const Divider(color: DSColors.neutralDarkCity), - ], - ), - ), - ), + body: FutureBuilder( + future: _translations, + builder: (_, snapshot) { + if (snapshot.hasData) { + DSLocalizationService.setTranslations(jsonDecode(snapshot.data!)); + DSLocalizationService.setLocale(const Locale('pt', 'BR')); + } + + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + children: [ + const SampleUserAvatarShowcase(), + SampleMessageBubbleShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleTextStyleShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleButtonShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleTypingShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleSwitchShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleDialogShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleToastShowcase(), + const Divider(color: DSColors.neutralDarkCity), + SampleRadioShowcase(), + const Divider(color: DSColors.neutralDarkCity), + SampleGroupCardShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleHeaderShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleBottomSheetShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleTicketMessage(), + const Divider(color: DSColors.neutralDarkCity), + const SampleCollectionShowcase(), + const Divider(color: DSColors.neutralDarkCity), + const SampleWeblinkShowcase(), + const Divider(color: DSColors.neutralDarkCity), + SampleInputShowcase(), + const Divider(color: DSColors.neutralDarkCity), + ], + ), + ), + ); + }), ); } } diff --git a/sample/lib/widgets/showcase/sample_message_bubble.showcase.dart b/sample/lib/widgets/showcase/sample_message_bubble.showcase.dart index 76ff0196..786ebeab 100644 --- a/sample/lib/widgets/showcase/sample_message_bubble.showcase.dart +++ b/sample/lib/widgets/showcase/sample_message_bubble.showcase.dart @@ -17,7 +17,7 @@ class SampleMessageBubbleShowcase extends StatelessWidget { final RxString _sampleText = RxString(''); final String _sampleAudio = - 'https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3'; + 'https://filesamples.com/samples/video/webm/sample_640x360.webm'; final Map _sampleImages = { "extraSmall": 'https://cdn-icons-png.flaticon.com/128/6913/6913220.png', @@ -48,11 +48,87 @@ class SampleMessageBubbleShowcase extends StatelessWidget { SampleMessageBubbleShowcase({Key? key}) : super(key: key); + Future _onAsyncFetchSession(_) { + return Future.delayed( + const Duration(seconds: 2), + () => _sampleAudio, + ); + } + + Future _onAsyncFetchSessionError(_) { + return Future.delayed( + const Duration(seconds: 2), + () => throw UnimplementedError(), + ); + } + + final _callsMediaMessage = { + "sessionId": "346b4783-2c8e-4b08-a71f-01904a3c40f3", + "type": "voice", + "provider": "whatsapp", + "direction": "inbound", + "status": "completed", + "ticketId": "ticketId", + "identification": "5548999999999", + "callDuration": 60, + }; + + final _callsMediaMessageError = { + "sessionId": "346b4783-2c8e-4b08-a71f-01904a3c40f3", + "type": "voice", + "provider": "whatsapp", + "direction": "inbound", + "ticketId": "ticketId", + "identification": "5548999999999", + "callDuration": 0, + "status": "noanswer", + }; + @override Widget build(BuildContext context) { return Obx( () => Column( children: [ + DSEndCallsMessageBubble( + callsMediaMessage: DSCallsMediaMessage.fromJson(_callsMediaMessage), + onAsyncFetchSession: _onAsyncFetchSession, + align: DSAlign.right, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: DSEndCallsMessageBubble( + callsMediaMessage: + DSCallsMediaMessage.fromJson(_callsMediaMessage), + onAsyncFetchSession: _onAsyncFetchSessionError, + align: DSAlign.right, + ), + ), + DSEndCallsMessageBubble( + callsMediaMessage: + DSCallsMediaMessage.fromJson(_callsMediaMessageError), + onAsyncFetchSession: null, + align: DSAlign.right, + ), + DSEndCallsMessageBubble( + callsMediaMessage: DSCallsMediaMessage.fromJson(_callsMediaMessage), + onAsyncFetchSession: _onAsyncFetchSession, + align: DSAlign.left, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: DSEndCallsMessageBubble( + callsMediaMessage: + DSCallsMediaMessage.fromJson(_callsMediaMessage), + onAsyncFetchSession: _onAsyncFetchSessionError, + align: DSAlign.left, + ), + ), + DSEndCallsMessageBubble( + callsMediaMessage: + DSCallsMediaMessage.fromJson(_callsMediaMessageError), + onAsyncFetchSession: null, + align: DSAlign.left, + ), DSTextMessageBubble( text: 'Essa foto é linda', align: DSAlign.left, diff --git a/sample/pubspec.lock b/sample/pubspec.lock index b6377af1..354fd22f 100644 --- a/sample/pubspec.lock +++ b/sample/pubspec.lock @@ -31,7 +31,7 @@ packages: path: ".." relative: true source: path - version: "0.1.4" + version: "0.1.5" boolean_selector: dependency: transitive description: @@ -467,10 +467,10 @@ packages: dependency: transitive description: name: open_filex - sha256: "74e2280754cf8161e860746c3181db2c996d6c1909c7057b738ede4a469816b8" + sha256: "854aefd72dfd74219dc8c8d1767c34ec1eae64b8399a5be317bddb1ec2108915" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.3.2" package_info_plus: dependency: transitive description: diff --git a/sample/pubspec.yaml b/sample/pubspec.yaml index d0cd688d..a79aeb11 100644 --- a/sample/pubspec.yaml +++ b/sample/pubspec.yaml @@ -56,6 +56,7 @@ dev_dependencies: flutter: assets: - assets/messages.json + - assets/ds_localization.json # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in diff --git a/test/widgets/buttons/goldens/ds_pause_button/ds_pause_button.png b/test/widgets/buttons/goldens/ds_pause_button/ds_pause_button.png index a18b171c..e3cc1260 100644 Binary files a/test/widgets/buttons/goldens/ds_pause_button/ds_pause_button.png and b/test/widgets/buttons/goldens/ds_pause_button/ds_pause_button.png differ