You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
8.0 KiB
Dart
229 lines
8.0 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
|
import 'package:test_sa/extensions/int_extensions.dart';
|
|
import 'package:test_sa/extensions/string_extensions.dart';
|
|
import 'package:test_sa/extensions/text_extensions.dart';
|
|
import 'package:test_sa/new_views/app_style/app_color.dart';
|
|
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
|
|
import 'package:test_sa/views/widgets/sound/sound_player.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
import '../model/copilot_message_model.dart';
|
|
|
|
class CopilotMessageBubble extends StatelessWidget {
|
|
final CopilotMessage message;
|
|
final bool showHeader;
|
|
|
|
const CopilotMessageBubble({Key? key, required this.message, this.showHeader = true}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isUser = message.role == CopilotRole.user;
|
|
return isUser ? _senderCard(context) : _recipientCard(context);
|
|
}
|
|
|
|
static const Widget _avatar = SizedBox(
|
|
height: 26,
|
|
width: 26,
|
|
child: DecoratedBox(decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.grey)),
|
|
);
|
|
|
|
String get _timeText => (message.timestamp?.toIso8601String() ?? DateTime.now().toIso8601String()).chatMsgTime;
|
|
|
|
Widget _senderCard(BuildContext context) {
|
|
final senderHeader = Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const InfoTextLabelWidget(label: "Me"),
|
|
8.width,
|
|
_avatar,
|
|
],
|
|
);
|
|
|
|
return Align(
|
|
alignment: Alignment.centerRight,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
if (showHeader) ...[senderHeader, 4.height] else 8.height,
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Flexible(
|
|
fit: FlexFit.loose,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(8),
|
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.75),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.assetGroupBackground(context),
|
|
border: Border.all(color: AppColor.borderColor(context)),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
if (message.localAudioPath != null)
|
|
ASoundPlayer(audio: message.localAudioPath!)
|
|
else
|
|
Text(
|
|
message.content,
|
|
style: AppTextStyles.bodyText2.copyWith(color: AppColor.textColor(context)),
|
|
),
|
|
Text(
|
|
_timeText,
|
|
style: AppTextStyles.bodyText2.copyWith(color: AppColor.textColor(context)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
(26 + 8).width,
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _recipientCard(BuildContext context) {
|
|
final showLoadingDots = message.isStreaming && message.content.isEmpty;
|
|
final recipientHeader = Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_avatar,
|
|
8.width,
|
|
const InfoTextLabelWidget(label: "Copilot"),
|
|
// Text(
|
|
// "Copilot",
|
|
// overflow: TextOverflow.ellipsis,
|
|
// maxLines: 1,
|
|
// style: AppTextStyles.bodyText.copyWith(color: AppColor.neutral50, fontWeight: FontWeight.w600),
|
|
// ),
|
|
],
|
|
);
|
|
return Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
if (showHeader) ...[recipientHeader, 4.height] else 8.height,
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
margin: const EdgeInsets.only(left: 26 + 8, right: 26 + 8),
|
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.75),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.background(context),
|
|
border: Border.all(color: AppColor.borderColor(context)),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
if (showLoadingDots)
|
|
const Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: SizedBox(
|
|
height: 16,
|
|
width: 16,
|
|
child: CircularProgressIndicator(strokeWidth: 2, color: AppColor.primary10),
|
|
),
|
|
)
|
|
else
|
|
_buildMessageContent(context),
|
|
// if (message.content.isNotEmpty && message.images.isNotEmpty) 8.height,
|
|
// if (message.content.isNotEmpty)
|
|
// Text(
|
|
// message.content,
|
|
// style: AppTextStyles.bodyText2.copyWith(
|
|
// color: message.isError ? Colors.red : AppColor.white10,
|
|
// ),
|
|
// ),
|
|
if (message.images.isNotEmpty) ...[
|
|
8.height,
|
|
SizedBox(
|
|
height: 90,
|
|
width: double.infinity,
|
|
child: ListView.separated(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: message.images.length,
|
|
separatorBuilder: (_, __) => 8.width,
|
|
itemBuilder: (context, index) {
|
|
final img = message.images[index];
|
|
return Image.memory(
|
|
base64Decode(img.base64),
|
|
width: 100,
|
|
height: 100,
|
|
fit: BoxFit.contain,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
widthFactor: 1,
|
|
child: Text(
|
|
_timeText,
|
|
style: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMessageContent(BuildContext context) {
|
|
var content = message.content;
|
|
|
|
content = content.replaceAllMapped(
|
|
RegExp(r'(\*\*[^*\n]+:\*\*)'),
|
|
(match) => '${match.group(1)} \n',
|
|
);
|
|
content = content.replaceAllMapped(
|
|
RegExp(r'(Link:)\s*', caseSensitive: false),
|
|
(match) => '${match.group(1)} \n',
|
|
);
|
|
|
|
return MarkdownBody(
|
|
data: content,
|
|
selectable: true,
|
|
onTapLink: (text, href, title) async {
|
|
if (href == null || href.isEmpty) return;
|
|
|
|
final uri = Uri.tryParse(href);
|
|
|
|
if (uri == null) return;
|
|
|
|
await launchUrl(
|
|
uri,
|
|
mode: LaunchMode.externalApplication,
|
|
);
|
|
},
|
|
styleSheet: MarkdownStyleSheet(
|
|
p: AppTextStyles.bodyText2.copyWith(
|
|
color: message.isError ? Colors.red : AppColor.textStyleColor(context),
|
|
),
|
|
strong: AppTextStyles.bodyText2.copyWith(
|
|
color: message.isError ? Colors.red : AppColor.textStyleColor(context),
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
listBullet: AppTextStyles.bodyText2.copyWith(
|
|
color: message.isError ? Colors.red : AppColor.textStyleColor(context),
|
|
),
|
|
a: AppTextStyles.bodyText2.copyWith(
|
|
color: AppColor.primary10,
|
|
decoration: TextDecoration.underline,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|