Compare commits
7 commits
main
...
chat-impro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3069cd93ab | ||
|
|
f9b7e2a4bd | ||
|
|
0d8b42b100 | ||
|
|
bc0bb11f14 | ||
|
|
0f9fbe0703 | ||
|
|
f89e43a93e | ||
|
|
1125c06823 |
2 changed files with 148 additions and 78 deletions
|
|
@ -112,7 +112,6 @@ class IRCMessage {
|
|||
/// Returns an [InlineSpan] list that corresponds to the badges, username, words, and emotes of the given [IRCMessage].
|
||||
List<InlineSpan> generateSpan(
|
||||
BuildContext context, {
|
||||
TextStyle? style,
|
||||
required ChatAssetsStore assetsStore,
|
||||
required double badgeScale,
|
||||
required double emoteScale,
|
||||
|
|
@ -123,6 +122,7 @@ class IRCMessage {
|
|||
bool useReadableColors = false,
|
||||
TimestampType timestamp = TimestampType.disabled,
|
||||
}) {
|
||||
final style = DefaultTextStyle.of(context).style;
|
||||
final isLightTheme = Theme.of(context).brightness == Brightness.light;
|
||||
|
||||
final emoteToObject = assetsStore.emoteToObject;
|
||||
|
|
@ -146,7 +146,7 @@ class IRCMessage {
|
|||
span.add(
|
||||
TextSpan(
|
||||
text: '${DateFormat.Hm().format(parsedTime)} ',
|
||||
style: style?.copyWith(color: style.color?.withOpacity(0.5)),
|
||||
style: style.copyWith(color: style.color?.withOpacity(0.5)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -155,7 +155,7 @@ class IRCMessage {
|
|||
span.add(
|
||||
TextSpan(
|
||||
text: '${DateFormat('h:mm').format(parsedTime)} ',
|
||||
style: style?.copyWith(color: style.color?.withOpacity(0.5)),
|
||||
style: style.copyWith(color: style.color?.withOpacity(0.5)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -328,7 +328,7 @@ class IRCMessage {
|
|||
text: user != null ? getReadableName(displayName, user!) : displayName,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()..onTap = onTapName,
|
||||
),
|
||||
|
|
@ -339,7 +339,7 @@ class IRCMessage {
|
|||
|
||||
// Italicize the text if it was called with an IRC Action (e.g., "/me").
|
||||
final textStyle =
|
||||
action == true ? const TextStyle(fontStyle: FontStyle.italic) : style;
|
||||
style.copyWith(fontStyle: action == true ? FontStyle.italic : null);
|
||||
|
||||
if (!showMessage) {
|
||||
span.add(const TextSpan(text: ' <message deleted>'));
|
||||
|
|
@ -390,7 +390,7 @@ class IRCMessage {
|
|||
if (nextWordIsEmoji)
|
||||
Text(
|
||||
words[index],
|
||||
style: textStyle?.copyWith(fontSize: emoteSize - 5),
|
||||
style: textStyle.copyWith(fontSize: emoteSize - 5),
|
||||
),
|
||||
...emoteStack.reversed.map(
|
||||
(emote) => FrostyCachedNetworkImage(
|
||||
|
|
@ -423,7 +423,7 @@ class IRCMessage {
|
|||
if (nextWordIsEmoji)
|
||||
Text(
|
||||
emoji,
|
||||
style: textStyle?.copyWith(fontSize: 40),
|
||||
style: textStyle.copyWith(fontSize: 40),
|
||||
),
|
||||
...emoteStack.reversed.map(
|
||||
(emote) => FrostyCachedNetworkImage(
|
||||
|
|
@ -455,6 +455,7 @@ class IRCMessage {
|
|||
localSpan.add(const TextSpan(text: ' '));
|
||||
localSpan.add(
|
||||
_createTextSpan(
|
||||
context,
|
||||
text: words[index],
|
||||
style: textStyle,
|
||||
launchExternal: launchExternal,
|
||||
|
|
@ -478,14 +479,18 @@ class IRCMessage {
|
|||
} else {
|
||||
if (regexEmoji.hasMatch(word)) {
|
||||
localSpan.add(
|
||||
_createEmojiSpan(
|
||||
emoji: word,
|
||||
style: textStyle?.copyWith(fontSize: emoteSize - 5),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Text(
|
||||
word,
|
||||
style: textStyle.copyWith(fontSize: emoteSize - 5),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
localSpan.add(
|
||||
_createTextSpan(
|
||||
context,
|
||||
text: word,
|
||||
style: textStyle,
|
||||
launchExternal: launchExternal,
|
||||
|
|
@ -507,19 +512,6 @@ class IRCMessage {
|
|||
return span;
|
||||
}
|
||||
|
||||
static WidgetSpan _createEmojiSpan({
|
||||
required String emoji,
|
||||
TextStyle? style,
|
||||
}) {
|
||||
return WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Text(
|
||||
emoji,
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _createBadgeWidget({
|
||||
required ChatBadge badge,
|
||||
double? size,
|
||||
|
|
@ -562,27 +554,36 @@ class IRCMessage {
|
|||
}) {
|
||||
return WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: InkWell(
|
||||
onTap: () => _showAssetDetailsBottomSheet(
|
||||
context,
|
||||
leading: _createBadgeWidget(
|
||||
child: Stack(
|
||||
children: [
|
||||
_createBadgeWidget(
|
||||
badge: badge,
|
||||
size: size,
|
||||
backgroundColor: backgroundColor,
|
||||
isSvg: isSvg,
|
||||
size: 56,
|
||||
),
|
||||
url: badge.url,
|
||||
title: badge.name,
|
||||
subtitle: Text(badge.type.toString()),
|
||||
launchExternal: launchExternal,
|
||||
showCopyName: false,
|
||||
),
|
||||
child: _createBadgeWidget(
|
||||
badge: badge,
|
||||
size: size,
|
||||
backgroundColor: backgroundColor,
|
||||
isSvg: isSvg,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => _showAssetDetailsBottomSheet(
|
||||
context,
|
||||
leading: _createBadgeWidget(
|
||||
badge: badge,
|
||||
backgroundColor: backgroundColor,
|
||||
isSvg: isSvg,
|
||||
size: 56,
|
||||
),
|
||||
url: badge.url,
|
||||
title: badge.name,
|
||||
subtitle: Text(badge.type.toString()),
|
||||
launchExternal: launchExternal,
|
||||
showCopyName: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -596,44 +597,76 @@ class IRCMessage {
|
|||
}) {
|
||||
return WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: InkWell(
|
||||
onTap: () => showEmoteDetailsBottomSheet(
|
||||
context,
|
||||
emote: emote,
|
||||
launchExternal: launchExternal,
|
||||
),
|
||||
child: FrostyCachedNetworkImage(
|
||||
imageUrl: emote.url,
|
||||
height: height,
|
||||
width: width,
|
||||
useFade: false,
|
||||
placeholder: (context, url) => const SizedBox(),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
FrostyCachedNetworkImage(
|
||||
imageUrl: emote.url,
|
||||
height: height,
|
||||
width: width,
|
||||
useFade: false,
|
||||
placeholder: (context, url) => const SizedBox(),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => showEmoteDetailsBottomSheet(
|
||||
context,
|
||||
emote: emote,
|
||||
launchExternal: launchExternal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static TextSpan _createTextSpan({
|
||||
static InlineSpan _createTextSpan(
|
||||
BuildContext context, {
|
||||
required String text,
|
||||
required bool launchExternal,
|
||||
TextStyle? style,
|
||||
required TextStyle style,
|
||||
Function(String)? onTapPingedUser,
|
||||
}) {
|
||||
if (text.startsWith('@')) {
|
||||
return TextSpan(
|
||||
text: text,
|
||||
style: style?.copyWith(fontWeight: FontWeight.bold),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
if (onTapPingedUser != null) {
|
||||
onTapPingedUser(text.substring(1).split(',')[0]);
|
||||
}
|
||||
},
|
||||
final isLight = Theme.of(context).brightness == Brightness.light;
|
||||
final mention = text.split(',')[0];
|
||||
|
||||
final mentionTextWidget = Text(
|
||||
mention,
|
||||
style: style.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
const borderRadius = BorderRadius.all(Radius.circular(4));
|
||||
return WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: onTapPingedUser != null
|
||||
? Material(
|
||||
borderRadius: borderRadius,
|
||||
child: InkWell(
|
||||
borderRadius: borderRadius,
|
||||
onTap: () => onTapPingedUser(mention.substring(1)),
|
||||
child: Ink(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color:
|
||||
isLight ? Colors.grey.shade200 : Colors.grey.shade900,
|
||||
),
|
||||
child: mentionTextWidget,
|
||||
),
|
||||
),
|
||||
)
|
||||
: mentionTextWidget,
|
||||
);
|
||||
} else if (RegExp(r'https?:\/\/').hasMatch(text)) {
|
||||
return TextSpan(
|
||||
text: text,
|
||||
style: style?.copyWith(
|
||||
style: style.copyWith(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Colors.blue,
|
||||
|
|
|
|||
|
|
@ -47,9 +47,6 @@ class ChatMessage extends StatelessWidget {
|
|||
BuildContext context, {
|
||||
required String nickname,
|
||||
}) {
|
||||
// Ignore if the message is a recent message in the modal bottom sheet.
|
||||
if (isModal) return;
|
||||
|
||||
final twitchApi = context.read<TwitchApi>();
|
||||
final authStore = context.read<AuthStore>();
|
||||
twitchApi
|
||||
|
|
@ -79,7 +76,7 @@ class ChatMessage extends StatelessWidget {
|
|||
chatStore.updateNotification('Message copied');
|
||||
}
|
||||
|
||||
void onLongPressMessage(BuildContext context, TextStyle defaultTextStyle) {
|
||||
void onLongPressMessage(BuildContext context) {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
if (ircMessage.command != Command.privateMessage &&
|
||||
|
|
@ -107,7 +104,7 @@ class ChatMessage extends StatelessWidget {
|
|||
launchExternal: chatStore.settings.launchUrlExternal,
|
||||
timestamp: chatStore.settings.timestampType,
|
||||
),
|
||||
style: defaultTextStyle,
|
||||
style: DefaultTextStyle.of(context).style,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -160,9 +157,10 @@ class ChatMessage extends StatelessWidget {
|
|||
children: ircMessage.generateSpan(
|
||||
context,
|
||||
onTapName: () => onTapName(context),
|
||||
onTapPingedUser: (nickname) =>
|
||||
onTapPingedUser(context, nickname: nickname),
|
||||
style: defaultTextStyle,
|
||||
onTapPingedUser: isModal
|
||||
? null
|
||||
: (nickname) =>
|
||||
onTapPingedUser(context, nickname: nickname),
|
||||
assetsStore: chatStore.assetsStore,
|
||||
emoteScale: chatStore.settings.emoteScale,
|
||||
badgeScale: chatStore.settings.badgeScale,
|
||||
|
|
@ -179,6 +177,7 @@ class ChatMessage extends StatelessWidget {
|
|||
|
||||
Widget? messageHeaderIcon;
|
||||
Widget? messageHeader;
|
||||
const mentionBorderRadius = BorderRadius.all(Radius.circular(4));
|
||||
if (replyUser != null && replyBody != null) {
|
||||
messageHeaderIcon = Icon(
|
||||
Icons.chat_rounded,
|
||||
|
|
@ -197,8 +196,48 @@ class ChatMessage extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
child: Text(
|
||||
'Replying to @$replyUser: $replyBody',
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: 'Replying to ',
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Material(
|
||||
borderRadius: mentionBorderRadius,
|
||||
child: InkWell(
|
||||
borderRadius: mentionBorderRadius,
|
||||
onTap: () =>
|
||||
onTapPingedUser(context, nickname: replyUser),
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Colors.grey.shade200
|
||||
: Colors.grey.shade900,
|
||||
),
|
||||
child: Text(
|
||||
'@$replyUser',
|
||||
style: defaultTextStyle.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: messageHeaderTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: ': $replyBody',
|
||||
),
|
||||
],
|
||||
),
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
|
@ -292,7 +331,6 @@ class ChatMessage extends StatelessWidget {
|
|||
children: ircMessage.generateSpan(
|
||||
context,
|
||||
onTapName: () => onTapName(context),
|
||||
style: defaultTextStyle,
|
||||
assetsStore: chatStore.assetsStore,
|
||||
emoteScale: chatStore.settings.emoteScale,
|
||||
badgeScale: chatStore.settings.badgeScale,
|
||||
|
|
@ -385,7 +423,6 @@ class ChatMessage extends StatelessWidget {
|
|||
children: ircMessage.generateSpan(
|
||||
context,
|
||||
onTapName: () => onTapName(context),
|
||||
style: defaultTextStyle,
|
||||
assetsStore: chatStore.assetsStore,
|
||||
emoteScale: chatStore.settings.emoteScale,
|
||||
badgeScale: chatStore.settings.badgeScale,
|
||||
|
|
@ -447,7 +484,7 @@ class ChatMessage extends StatelessWidget {
|
|||
chatStore.assetsStore.showEmoteMenu = false;
|
||||
}
|
||||
},
|
||||
onLongPress: () => onLongPressMessage(context, defaultTextStyle),
|
||||
onLongPress: () => onLongPressMessage(context),
|
||||
child: coloredMessage,
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue