Merge pull request #1455 from GetStream/ISSUE-1453/FixingPositionOfReactionModal
This commit is contained in:
@@ -14,6 +14,10 @@
|
|||||||
|
|
||||||
- Updated dependencies to resolvable versions.
|
- Updated dependencies to resolvable versions.
|
||||||
|
|
||||||
|
🚀 Improved
|
||||||
|
-
|
||||||
|
- Improved draw of reaction options. [#1455](https://github.com/GetStream/stream-chat-flutter/pull/1455)
|
||||||
|
|
||||||
## 5.3.0
|
## 5.3.0
|
||||||
|
|
||||||
🔄 Changed
|
🔄 Changed
|
||||||
|
|||||||
+8
-115
@@ -2,6 +2,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart' hide ButtonStyle;
|
import 'package:flutter/material.dart' hide ButtonStyle;
|
||||||
import 'package:stream_chat_flutter/src/message_actions_modal/mam_widgets.dart';
|
import 'package:stream_chat_flutter/src/message_actions_modal/mam_widgets.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_align.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
/// {@template messageActionsModal}
|
/// {@template messageActionsModal}
|
||||||
@@ -97,34 +98,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
|
|
||||||
Widget _showMessageOptionsModal() {
|
Widget _showMessageOptionsModal() {
|
||||||
final mediaQueryData = MediaQuery.of(context);
|
final mediaQueryData = MediaQuery.of(context);
|
||||||
final size = mediaQueryData.size;
|
|
||||||
final user = StreamChat.of(context).currentUser;
|
final user = StreamChat.of(context).currentUser;
|
||||||
|
final orientation = mediaQueryData.orientation;
|
||||||
|
|
||||||
final roughMaxSize = size.width * 2 / 3;
|
final fontSize = widget.messageTheme.messageTextStyle?.fontSize;
|
||||||
var messageTextLength = widget.message.text!.length;
|
|
||||||
if (widget.message.quotedMessage != null) {
|
|
||||||
var quotedMessageLength =
|
|
||||||
(widget.message.quotedMessage!.text?.length ?? 0) + 40;
|
|
||||||
if (widget.message.quotedMessage!.attachments.isNotEmpty) {
|
|
||||||
quotedMessageLength += 40;
|
|
||||||
}
|
|
||||||
if (quotedMessageLength > messageTextLength) {
|
|
||||||
messageTextLength = quotedMessageLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final roughSentenceSize = messageTextLength *
|
|
||||||
(widget.messageTheme.messageTextStyle?.fontSize ?? 1) *
|
|
||||||
1.2;
|
|
||||||
final divFactor = widget.message.attachments.isNotEmpty
|
|
||||||
? 1
|
|
||||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
|
||||||
|
|
||||||
final streamChatThemeData = StreamChatTheme.of(context);
|
final streamChatThemeData = StreamChatTheme.of(context);
|
||||||
|
|
||||||
final numberOfReactions =
|
|
||||||
StreamChatConfiguration.of(context).reactionIcons.length;
|
|
||||||
final shiftFactor =
|
|
||||||
numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0;
|
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
final child = Center(
|
final child = Center(
|
||||||
@@ -142,11 +121,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment(
|
alignment: Alignment(
|
||||||
_calculateReactionsHorizontalAlignmentValue(
|
calculateReactionsHorizontalAlignment(
|
||||||
user,
|
user,
|
||||||
divFactor,
|
widget.message,
|
||||||
shiftFactor,
|
|
||||||
constraints,
|
constraints,
|
||||||
|
fontSize,
|
||||||
|
orientation,
|
||||||
),
|
),
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
@@ -156,7 +136,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 10),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
child: widget.messageWidget,
|
child: widget.messageWidget,
|
||||||
),
|
),
|
||||||
@@ -281,93 +261,6 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _calculateReactionsHorizontalAlignmentValue(
|
|
||||||
User? user,
|
|
||||||
num divFactor,
|
|
||||||
double shiftFactor,
|
|
||||||
BoxConstraints constraints,
|
|
||||||
) {
|
|
||||||
var result = 0.0;
|
|
||||||
var cont = true;
|
|
||||||
if (user?.id == widget.message.user?.id) {
|
|
||||||
if (divFactor >= 1.0) {
|
|
||||||
// This calculation is hacky and does not cover all bases!!!
|
|
||||||
// A better option is needed!
|
|
||||||
|
|
||||||
// Landscape calculations
|
|
||||||
if (constraints.maxWidth == 1350) {
|
|
||||||
// 12.7 iPad Pro
|
|
||||||
result = shiftFactor + 0.5;
|
|
||||||
cont = false;
|
|
||||||
} else if (constraints.maxWidth == 1178) {
|
|
||||||
// 11 inch iPad Pro
|
|
||||||
result = shiftFactor + 0.42;
|
|
||||||
cont = false;
|
|
||||||
} else if (constraints.maxWidth == 1164) {
|
|
||||||
// iPad Air 4
|
|
||||||
result = shiftFactor + 0.4;
|
|
||||||
cont = false;
|
|
||||||
} else if (constraints.maxWidth == 1117) {
|
|
||||||
// iPad Mini 6
|
|
||||||
result = shiftFactor + 0.37;
|
|
||||||
cont = false;
|
|
||||||
} else if (constraints.maxWidth == 1064) {
|
|
||||||
// iPad 9th gen
|
|
||||||
result = shiftFactor + 0.33;
|
|
||||||
cont = false;
|
|
||||||
} else if (constraints.maxWidth == 1008) {
|
|
||||||
// 9.7 inch iPad Pro
|
|
||||||
result = shiftFactor + 0.3;
|
|
||||||
cont = false;
|
|
||||||
} else if (constraints.maxWidth >= 200 && constraints.maxWidth <= 400) {
|
|
||||||
// Phone (?)
|
|
||||||
result = shiftFactor - 0.2;
|
|
||||||
cont = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cont) {
|
|
||||||
// Portrait calculations
|
|
||||||
if (constraints.maxWidth == 1008) {
|
|
||||||
// 12.7 iPad Pro
|
|
||||||
result = shiftFactor + 0.3;
|
|
||||||
} else if (constraints.maxWidth == 818) {
|
|
||||||
// 11 inch iPad Pro
|
|
||||||
result = shiftFactor + 0.07;
|
|
||||||
} else if (constraints.maxWidth == 804) {
|
|
||||||
// iPad Air 4
|
|
||||||
result = shiftFactor + 0.04;
|
|
||||||
} else if (constraints.maxWidth == 794) {
|
|
||||||
// iPad 9th gen
|
|
||||||
result = shiftFactor + 0.02;
|
|
||||||
} else if (constraints.maxWidth >= 752) {
|
|
||||||
// 9.7 inch iPad Pro
|
|
||||||
result = shiftFactor - 0.05;
|
|
||||||
} else if (constraints.maxWidth == 728) {
|
|
||||||
// iPad Mini 6
|
|
||||||
result = shiftFactor - 0.1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = 1.2 - divFactor;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (divFactor >= 1.0) {
|
|
||||||
result = shiftFactor + 0.2;
|
|
||||||
} else {
|
|
||||||
result = -(1.2 - divFactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure reactions don't get pushed past the edge of the screen.
|
|
||||||
//
|
|
||||||
// Hacky!!! Needs improvement!!!
|
|
||||||
if (result > 1) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InkWell _buildCustomAction(
|
InkWell _buildCustomAction(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
StreamMessageAction messageAction,
|
StreamMessageAction messageAction,
|
||||||
|
|||||||
+22
-39
@@ -2,6 +2,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_widget/reactions/reaction_bubble.dart';
|
import 'package:stream_chat_flutter/src/message_widget/reactions/reaction_bubble.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_align.dart';
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
/// {@template streamMessageReactionsModal}
|
/// {@template streamMessageReactionsModal}
|
||||||
@@ -39,35 +40,13 @@ class StreamMessageReactionsModal extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final size = MediaQuery.of(context).size;
|
|
||||||
final user = StreamChat.of(context).currentUser;
|
final user = StreamChat.of(context).currentUser;
|
||||||
final _userPermissions = StreamChannel.of(context).channel.ownCapabilities;
|
final _userPermissions = StreamChannel.of(context).channel.ownCapabilities;
|
||||||
|
final orientation = MediaQuery.of(context).orientation;
|
||||||
|
|
||||||
final hasReactionPermission =
|
final hasReactionPermission =
|
||||||
_userPermissions.contains(PermissionType.sendReaction);
|
_userPermissions.contains(PermissionType.sendReaction);
|
||||||
|
final fontSize = messageTheme.messageTextStyle?.fontSize;
|
||||||
final roughMaxSize = size.width * 2 / 3;
|
|
||||||
var messageTextLength = message.text!.length;
|
|
||||||
if (message.quotedMessage != null) {
|
|
||||||
var quotedMessageLength = message.quotedMessage!.text!.length + 40;
|
|
||||||
if (message.quotedMessage!.attachments.isNotEmpty) {
|
|
||||||
quotedMessageLength += 40;
|
|
||||||
}
|
|
||||||
if (quotedMessageLength > messageTextLength) {
|
|
||||||
messageTextLength = quotedMessageLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final roughSentenceSize = messageTextLength *
|
|
||||||
(messageTheme.messageTextStyle?.fontSize ?? 1) *
|
|
||||||
1.2;
|
|
||||||
final divFactor = message.attachments.isNotEmpty
|
|
||||||
? 1
|
|
||||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
|
||||||
|
|
||||||
final numberOfReactions =
|
|
||||||
StreamChatConfiguration.of(context).reactionIcons.length;
|
|
||||||
final shiftFactor =
|
|
||||||
numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0;
|
|
||||||
|
|
||||||
final child = Center(
|
final child = Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@@ -79,22 +58,26 @@ class StreamMessageReactionsModal extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if ((showReactions ?? hasReactionPermission) &&
|
if ((showReactions ?? hasReactionPermission) &&
|
||||||
(message.status == MessageSendingStatus.sent))
|
(message.status == MessageSendingStatus.sent))
|
||||||
Align(
|
LayoutBuilder(
|
||||||
alignment: Alignment(
|
builder: (context, constraints) {
|
||||||
user!.id == message.user!.id
|
return Align(
|
||||||
? (divFactor >= 1.0
|
alignment: Alignment(
|
||||||
? -0.2 - shiftFactor
|
calculateReactionsHorizontalAlignment(
|
||||||
: (1.2 - divFactor))
|
user,
|
||||||
: (divFactor >= 1.0
|
message,
|
||||||
? shiftFactor + 0.2
|
constraints,
|
||||||
: -(1.2 - divFactor)),
|
fontSize,
|
||||||
0,
|
orientation,
|
||||||
),
|
),
|
||||||
child: StreamReactionPicker(
|
0,
|
||||||
message: message,
|
),
|
||||||
),
|
child: StreamReactionPicker(
|
||||||
|
message: message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 10),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
child: messageWidget,
|
child: messageWidget,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
/// This method calculates the align that the modal of reactions should have.
|
||||||
|
/// This is an approximation based on the size of the message and the
|
||||||
|
/// available space in the screen.
|
||||||
|
double calculateReactionsHorizontalAlignment(
|
||||||
|
User? user,
|
||||||
|
Message message,
|
||||||
|
BoxConstraints constraints,
|
||||||
|
double? fontSize,
|
||||||
|
Orientation orientation,
|
||||||
|
) {
|
||||||
|
final maxWidth = constraints.maxWidth;
|
||||||
|
|
||||||
|
final roughSentenceSize = message.roughMessageSize(fontSize);
|
||||||
|
final hasAttachments = message.attachments.isNotEmpty;
|
||||||
|
final isReply = message.quotedMessageId != null;
|
||||||
|
final isAttachment = hasAttachments && !isReply;
|
||||||
|
|
||||||
|
// divFactor is the percentage of the available space that the message takes.
|
||||||
|
// When the divFactor is bigger than 0.5 that means that the messages is
|
||||||
|
// bigger than 50% of the available space and the modal should have an offset
|
||||||
|
// in the direction that the message grows. When the divFactor is smaller
|
||||||
|
// than 0.5 then the offset should be to he side opposite of the message
|
||||||
|
// growth.
|
||||||
|
// In resume, when divFactor > 0.5 then result > 0, when divFactor < 0.5
|
||||||
|
// then result < 0.
|
||||||
|
var divFactor = 0.5;
|
||||||
|
|
||||||
|
// When in portrait, attachments normally take 75% of the screen, when in
|
||||||
|
// landscape, attachments normally take 50% of the screen.
|
||||||
|
if (isAttachment) {
|
||||||
|
if (orientation == Orientation.portrait) {
|
||||||
|
divFactor = 0.75;
|
||||||
|
} else {
|
||||||
|
divFactor = 0.5;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
divFactor = roughSentenceSize == 0 ? 0.5 : (roughSentenceSize / maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
final signal = user?.id == message.user?.id ? 1 : -1;
|
||||||
|
final result = signal * (1 - divFactor * 2.0);
|
||||||
|
|
||||||
|
return _capResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure reactions don't get pushed past the edge of the screen.
|
||||||
|
//
|
||||||
|
// This happens if divFactor is really big. When this happens, we can simply
|
||||||
|
// move the model all the way to the end of screen.
|
||||||
|
double _capResult(double result) {
|
||||||
|
if (result > 1.0) {
|
||||||
|
return 1;
|
||||||
|
} else if (result < -1.0) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:diacritic/diacritic.dart';
|
import 'package:diacritic/diacritic.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@@ -12,6 +14,16 @@ extension StringExtension on String {
|
|||||||
String capitalize() =>
|
String capitalize() =>
|
||||||
isNotEmpty ? '${this[0].toUpperCase()}${substring(1).toLowerCase()}' : '';
|
isNotEmpty ? '${this[0].toUpperCase()}${substring(1).toLowerCase()}' : '';
|
||||||
|
|
||||||
|
/// Returns the biggest line of a text.
|
||||||
|
String biggestLine() {
|
||||||
|
if (contains('\n')) {
|
||||||
|
return split('\n')
|
||||||
|
.reduce((curr, next) => curr.length > next.length ? curr : next);
|
||||||
|
} else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether the string contains only emoji's or not.
|
/// Returns whether the string contains only emoji's or not.
|
||||||
///
|
///
|
||||||
/// Emojis guidelines
|
/// Emojis guidelines
|
||||||
@@ -353,6 +365,33 @@ extension MessageX on Message {
|
|||||||
return copyWith(text: messageTextToRender);
|
return copyWith(text: messageTextToRender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an approximation of message size
|
||||||
|
double roughMessageSize(double? fontSize) {
|
||||||
|
var messageTextLength = min(text!.biggestLine().length, 65);
|
||||||
|
|
||||||
|
if (quotedMessage != null) {
|
||||||
|
var quotedMessageLength =
|
||||||
|
(min(quotedMessage!.text?.biggestLine().length ?? 0, 65)) + 8;
|
||||||
|
|
||||||
|
if (quotedMessage!.attachments.isNotEmpty) {
|
||||||
|
quotedMessageLength += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quotedMessageLength > messageTextLength * 1.2) {
|
||||||
|
messageTextLength = quotedMessageLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quoted message have a smaller font, so it is necessary to reduce the
|
||||||
|
// size of the multiplier to count for the smaller font.
|
||||||
|
var multiplier = 0.55;
|
||||||
|
if (quotedMessage != null) {
|
||||||
|
multiplier = 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageTextLength * (fontSize ?? 1) * multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
/// It returns the message with the translated text if available locally
|
/// It returns the message with the translated text if available locally
|
||||||
Message translate(String language) =>
|
Message translate(String language) =>
|
||||||
copyWith(text: i18n?['${language}_text'] ?? text);
|
copyWith(text: i18n?['${language}_text'] ?? text);
|
||||||
|
|||||||
Reference in New Issue
Block a user