Skip to content

Commit

Permalink
Message Action Menu (#1688)
Browse files Browse the repository at this point in the history
closes #1609 

Tap and hold a message, it will show the message action menu, where you
can copy, share, and report message


https://github.com/user-attachments/assets/08f12ee3-8c05-4bc2-8081-34023091414f
  • Loading branch information
beastoin authored Jan 16, 2025
2 parents c7aba49 + 2cbda09 commit 57d8f4f
Show file tree
Hide file tree
Showing 10 changed files with 343 additions and 104 deletions.
13 changes: 13 additions & 0 deletions app/lib/backend/http/api/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,16 @@ Future<List<ServerMessage>> sendVoiceMessageServer(List<File> files) async {
throw Exception('An error occurred uploadSample: $e');
}
}

Future reportMessageServer(String messageId) async {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/messages/$messageId/report',
headers: {},
method: 'POST',
body: '',
);
if (response == null) throw Exception('Failed to report message');
if (response.statusCode != 200) {
throw Exception('Failed to report message');
}
}
133 changes: 114 additions & 19 deletions app/lib/pages/chat/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:friend_private/backend/http/api/messages.dart';
import 'package:friend_private/backend/preferences.dart';
import 'package:friend_private/backend/schema/app.dart';
Expand All @@ -17,10 +18,14 @@ import 'package:friend_private/providers/conversation_provider.dart';
import 'package:friend_private/providers/message_provider.dart';
import 'package:friend_private/utils/analytics/mixpanel.dart';
import 'package:friend_private/widgets/dialog.dart';
import 'package:friend_private/widgets/extensions/string.dart';
import 'package:gradient_borders/gradient_borders.dart';
import 'package:provider/provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:uuid/uuid.dart';

import 'widgets/message_action_menu.dart';

class ChatPage extends StatefulWidget {
const ChatPage({
super.key,
Expand Down Expand Up @@ -224,25 +229,115 @@ class ChatPageState extends State<ChatPage> with AutomaticKeepAliveClientMixin {
? 200
: 170
: 0;
return Padding(
key: ValueKey(message.id),
padding:
EdgeInsets.only(bottom: bottomPadding, left: 18, right: 18, top: topPadding),
child: message.sender == MessageSender.ai
? AIMessage(
showTypingIndicator: provider.showTypingIndicator && chatIndex == 0,
message: message,
sendMessage: _sendMessageUtil,
displayOptions: provider.messages.length <= 1,
appSender: provider.messageSenderApp(message.appId),
updateConversation: (ServerConversation conversation) {
context.read<ConversationProvider>().updateConversation(conversation);
},
setMessageNps: (int value) {
provider.setMessageNps(message, value);
},
)
: HumanMessage(message: message),
return GestureDetector(
onLongPress: () {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (context) => MessageActionMenu(
message: message.text.decodeString,
onCopy: () async {
await Clipboard.setData(ClipboardData(text: message.text.decodeString));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Message copied to clipboard.',
style: TextStyle(
color: Color.fromARGB(255, 255, 255, 255),
fontSize: 12.0,
),
),
duration: Duration(milliseconds: 2000),
),
);
Navigator.pop(context);
},
onShare: () {
MixpanelManager()
.track('Chat Message Shared', properties: {'message': message.text});
Share.share(
'${message.text.decodeString}\n\nResponse from Omi. Get yours at https://omi.me',
subject: 'Chat with Omi',
);
Navigator.pop(context);
},
onReport: () {
if (message.sender == MessageSender.human) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'You cannot report your own messages.',
style: TextStyle(
color: Color.fromARGB(255, 255, 255, 255),
fontSize: 12.0,
),
),
duration: Duration(milliseconds: 2000),
),
);
return;
}
showDialog(
context: context,
builder: (context) {
return getDialog(
context,
() {
Navigator.of(context).pop();
},
() {
Navigator.of(context).pop();
Navigator.of(context).pop();
context.read<MessageProvider>().removeLocalMessage(message.id);
reportMessageServer(message.id);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Message reported successfully.',
style: TextStyle(
color: Color.fromARGB(255, 255, 255, 255),
fontSize: 12.0,
),
),
duration: Duration(milliseconds: 2000),
),
);
},
'Report Message',
'Are you sure you want to report this message?',
);
},
);
// Navigator.pop(context);
},
),
);
},
child: Padding(
key: ValueKey(message.id),
padding:
EdgeInsets.only(bottom: bottomPadding, left: 18, right: 18, top: topPadding),
child: message.sender == MessageSender.ai
? AIMessage(
showTypingIndicator: provider.showTypingIndicator && chatIndex == 0,
message: message,
sendMessage: _sendMessageUtil,
displayOptions: provider.messages.length <= 1,
appSender: provider.messageSenderApp(message.appId),
updateConversation: (ServerConversation conversation) {
context.read<ConversationProvider>().updateConversation(conversation);
},
setMessageNps: (int value) {
provider.setMessageNps(message, value);
},
)
: HumanMessage(message: message),
),
);
},
),
Expand Down
121 changes: 39 additions & 82 deletions app/lib/pages/chat/widgets/ai_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:auto_size_text/auto_size_text.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:friend_private/backend/http/api/conversations.dart';
import 'package:friend_private/backend/preferences.dart';
import 'package:friend_private/backend/schema/app.dart';
Expand All @@ -23,6 +22,8 @@ import 'package:friend_private/widgets/extensions/string.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';

import 'markdown_message_widget.dart';

class AIMessage extends StatefulWidget {
final bool showTypingIndicator;
final ServerMessage message;
Expand Down Expand Up @@ -150,32 +151,6 @@ Widget buildMessageWidget(
}
}

Widget _getMarkdownWidget(BuildContext context, String content) {
var style = TextStyle(color: Colors.grey.shade300, fontSize: 15, height: 1.3);
return MarkdownBody(
shrinkWrap: true,
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)).copyWith(
a: style,
p: style,
blockquote: style.copyWith(
backgroundColor: Colors.transparent,
color: Colors.black,
),
blockquoteDecoration: BoxDecoration(
color: Colors.grey.shade800,
borderRadius: BorderRadius.circular(4),
),
code: style.copyWith(
backgroundColor: Colors.transparent,
decoration: TextDecoration.none,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
data: content,
);
}

Widget _getNpsWidget(BuildContext context, ServerMessage message, Function(int) setMessageNps) {
if (!message.askForNps) return const SizedBox();

Expand Down Expand Up @@ -217,24 +192,18 @@ class InitialMessageWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: [
SelectionArea(
child: showTypingIndicator
? const Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 4),
TypingIndicator(),
Spacer(),
],
)
: _getMarkdownWidget(context, messageText)
// AutoSizeText(
// messageText,
// style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.w500, color: Colors.grey.shade300),
// ),
),
showTypingIndicator
? const Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 4),
TypingIndicator(),
Spacer(),
],
)
: getMarkdownWidget(context, messageText),
const SizedBox(height: 8),
const SizedBox(height: 8),
InitialOptionWidget(optionText: 'What did I do yesterday?', sendMessage: sendMessage),
Expand Down Expand Up @@ -270,20 +239,18 @@ class DaySummaryWidget extends StatelessWidget {
),
),
const SizedBox(height: 16),
SelectionArea(
child: showTypingIndicator
? const Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 4),
TypingIndicator(),
Spacer(),
],
)
: daySummaryMessagesList(messageText),
),
showTypingIndicator
? const Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 4),
TypingIndicator(),
Spacer(),
],
)
: daySummaryMessagesList(messageText),
],
);
}
Expand Down Expand Up @@ -430,11 +397,8 @@ class NormalMessageWidget extends StatelessWidget {
),
)
: const SizedBox.shrink(),
SelectionArea(
child: messageText.isEmpty ? const SizedBox.shrink() : _getMarkdownWidget(context, messageText),
),
messageText.isEmpty ? const SizedBox.shrink() : getMarkdownWidget(context, messageText),
_getNpsWidget(context, message, setMessageNps),
if (!showTypingIndicator) CopyButton(messageText: messageText),
],
);
}
Expand Down Expand Up @@ -488,25 +452,18 @@ class _MemoriesMessageWidgetState extends State<MemoriesMessageWidget> {
),
),
),
SelectionArea(
child: widget.showTypingIndicator
? const Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 4),
TypingIndicator(),
Spacer(),
],
)
: _getMarkdownWidget(context, widget.messageText)
// AutoSizeText(
// widget.messageText,
// style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.w500, color: Colors.grey.shade300),
// ),
),
CopyButton(messageText: widget.messageText),
widget.showTypingIndicator
? const Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(width: 4),
TypingIndicator(),
Spacer(),
],
)
: getMarkdownWidget(context, widget.messageText),
const SizedBox(height: 16),
for (var data in widget.messageMemories.indexed) ...[
Padding(
Expand Down
28 changes: 28 additions & 0 deletions app/lib/pages/chat/widgets/markdown_message_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';

Widget getMarkdownWidget(BuildContext context, String content) {
var style = TextStyle(color: Colors.grey.shade300, fontSize: 15, height: 1.3);
return MarkdownBody(
shrinkWrap: true,
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)).copyWith(
a: style,
p: style,
blockquote: style.copyWith(
backgroundColor: Colors.transparent,
color: Colors.black,
),
blockquoteDecoration: BoxDecoration(
color: Colors.grey.shade800,
borderRadius: BorderRadius.circular(4),
),
code: style.copyWith(
backgroundColor: Colors.transparent,
decoration: TextDecoration.none,
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
data: content,
);
}
Loading

0 comments on commit 57d8f4f

Please sign in to comment.