feat: use the same widget to render hidden tokens in messages and chat list item subtitles (#1356)
This commit is contained in:
parent
0203aaf209
commit
d53067583d
4 changed files with 323 additions and 291 deletions
|
|
@ -5,7 +5,6 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
|||
import 'package:fluffychat/utils/room_status_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
|
@ -307,71 +306,78 @@ class ChatListItem extends StatelessWidget {
|
|||
maxLines: 1,
|
||||
softWrap: false,
|
||||
)
|
||||
: FutureBuilder(
|
||||
key: ValueKey(
|
||||
'${lastEvent?.eventId}_${lastEvent?.type}',
|
||||
),
|
||||
// #Pangea
|
||||
future: room.lastEvent != null
|
||||
? GetChatListItemSubtitle().getSubtitle(
|
||||
L10n.of(context),
|
||||
room.lastEvent,
|
||||
MatrixState.pangeaController,
|
||||
)
|
||||
: Future.value(
|
||||
L10n.of(context).emptyChat,
|
||||
),
|
||||
// future: needLastEventSender
|
||||
// ? lastEvent.calcLocalizedBody(
|
||||
// MatrixLocals(L10n.of(context)),
|
||||
// hideReply: true,
|
||||
// hideEdit: true,
|
||||
// plaintextBody: true,
|
||||
// removeMarkdown: true,
|
||||
// withSenderNamePrefix:
|
||||
// (!isDirectChat ||
|
||||
// directChatMatrixId !=
|
||||
// room.lastEvent?.senderId),
|
||||
// )
|
||||
// : null,
|
||||
// #Pangea
|
||||
: room.lastEvent != null
|
||||
? ChatListItemSubtitle(
|
||||
event: room.lastEvent,
|
||||
style: TextStyle(
|
||||
fontWeight:
|
||||
unread || room.hasNewMessages
|
||||
? FontWeight.bold
|
||||
: null,
|
||||
color:
|
||||
theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
// Pangea#
|
||||
initialData:
|
||||
lastEvent?.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
hideReply: true,
|
||||
hideEdit: true,
|
||||
plaintextBody: true,
|
||||
removeMarkdown: true,
|
||||
withSenderNamePrefix: (!isDirectChat ||
|
||||
directChatMatrixId !=
|
||||
room.lastEvent?.senderId),
|
||||
),
|
||||
builder: (context, snapshot) => Text(
|
||||
room.membership == Membership.invite
|
||||
? isDirectChat
|
||||
? L10n.of(context).invitePrivateChat
|
||||
// #Pangea
|
||||
// : L10n.of(context).inviteGroupChat
|
||||
: L10n.of(context).inviteChat
|
||||
// Pangea#
|
||||
: snapshot.data ??
|
||||
L10n.of(context).emptyChat,
|
||||
softWrap: false,
|
||||
maxLines:
|
||||
room.notificationCount >= 1 ? 2 : 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: unread || room.hasNewMessages
|
||||
? FontWeight.bold
|
||||
: FutureBuilder(
|
||||
key: ValueKey(
|
||||
'${lastEvent?.eventId}_${lastEvent?.type}',
|
||||
),
|
||||
future: needLastEventSender
|
||||
? lastEvent.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
hideReply: true,
|
||||
hideEdit: true,
|
||||
plaintextBody: true,
|
||||
removeMarkdown: true,
|
||||
withSenderNamePrefix:
|
||||
(!isDirectChat ||
|
||||
directChatMatrixId !=
|
||||
room.lastEvent
|
||||
?.senderId),
|
||||
)
|
||||
: null,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
decoration:
|
||||
room.lastEvent?.redacted == true
|
||||
? TextDecoration.lineThrough
|
||||
: null,
|
||||
initialData:
|
||||
lastEvent?.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
hideReply: true,
|
||||
hideEdit: true,
|
||||
plaintextBody: true,
|
||||
removeMarkdown: true,
|
||||
withSenderNamePrefix: (!isDirectChat ||
|
||||
directChatMatrixId !=
|
||||
room.lastEvent?.senderId),
|
||||
),
|
||||
builder: (context, snapshot) => Text(
|
||||
room.membership == Membership.invite
|
||||
? isDirectChat
|
||||
? L10n.of(context)
|
||||
.invitePrivateChat
|
||||
// #Pangea
|
||||
// : L10n.of(context).inviteGroupChat
|
||||
: L10n.of(context).inviteChat
|
||||
// Pangea#
|
||||
: snapshot.data ??
|
||||
L10n.of(context).emptyChat,
|
||||
softWrap: false,
|
||||
maxLines:
|
||||
room.notificationCount >= 1 ? 2 : 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight:
|
||||
unread || room.hasNewMessages
|
||||
? FontWeight.bold
|
||||
: null,
|
||||
color: theme
|
||||
.colorScheme.onSurfaceVariant,
|
||||
decoration:
|
||||
room.lastEvent?.redacted == true
|
||||
? TextDecoration.lineThrough
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
AnimatedContainer(
|
||||
|
|
|
|||
|
|
@ -1,151 +1,119 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_token_text.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
|
||||
class GetChatListItemSubtitle {
|
||||
final List<String> hideContentKeys = [
|
||||
ModelKey.transcription,
|
||||
];
|
||||
class ChatListItemSubtitle extends StatelessWidget {
|
||||
final Event? event;
|
||||
final TextStyle style;
|
||||
|
||||
String _constructTokens(
|
||||
List<PangeaToken> tokens,
|
||||
List<PangeaToken> hiddenTokens,
|
||||
) {
|
||||
String result = "";
|
||||
int currentPosition = 0;
|
||||
for (final token in tokens) {
|
||||
if (token.text.offset > currentPosition) {
|
||||
result += " " * (token.text.offset - currentPosition);
|
||||
currentPosition = token.text.offset;
|
||||
}
|
||||
const ChatListItemSubtitle({
|
||||
super.key,
|
||||
required this.event,
|
||||
required this.style,
|
||||
});
|
||||
|
||||
if (hiddenTokens.contains(token)) {
|
||||
result += "_" * token.text.length;
|
||||
} else {
|
||||
result += token.text.content;
|
||||
}
|
||||
currentPosition += token.text.length;
|
||||
}
|
||||
return result;
|
||||
bool _showPangeaContent(Event event) {
|
||||
return MatrixState.pangeaController.languageController.languagesSet &&
|
||||
!event.redacted &&
|
||||
event.type == EventTypes.Message &&
|
||||
event.messageType == MessageTypes.Text;
|
||||
}
|
||||
|
||||
Future<String> getSubtitle(
|
||||
L10n l10n,
|
||||
Event? event,
|
||||
PangeaController pangeaController,
|
||||
Future<MessageEventAndTokens> _getPangeaMessageEvent(
|
||||
final Event event,
|
||||
) async {
|
||||
if (event == null) return l10n.emptyChat;
|
||||
try {
|
||||
if (!pangeaController.languageController.languagesSet ||
|
||||
event.redacted ||
|
||||
event.type != EventTypes.Message ||
|
||||
event.messageType != MessageTypes.Text) {
|
||||
return event.calcLocalizedBody(
|
||||
MatrixLocals(l10n),
|
||||
final Timeline timeline = event.room.timeline != null
|
||||
? event.room.timeline!
|
||||
: await event.room.getTimeline();
|
||||
|
||||
final pangeaMessageEvent = PangeaMessageEvent(
|
||||
event: event,
|
||||
timeline: timeline,
|
||||
ownMessage: event.senderId == event.room.client.userID,
|
||||
);
|
||||
|
||||
final tokens =
|
||||
await pangeaMessageEvent.messageDisplayRepresentation?.tokensGlobal(
|
||||
event.senderId,
|
||||
event.originServerTs,
|
||||
);
|
||||
|
||||
return MessageEventAndTokens(
|
||||
event: pangeaMessageEvent,
|
||||
tokens: tokens,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (event == null) return Text(L10n.of(context).emptyChat, style: style);
|
||||
if (!_showPangeaContent(event!)) {
|
||||
return FutureBuilder(
|
||||
future: event!.calcLocalizedBody(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
hideReply: true,
|
||||
hideEdit: true,
|
||||
plaintextBody: true,
|
||||
removeMarkdown: true,
|
||||
withSenderNamePrefix: !event.room.isDirectChat ||
|
||||
event.room.directChatMatrixID != event.room.lastEvent?.senderId,
|
||||
);
|
||||
}
|
||||
|
||||
String? eventContextId = event.eventId;
|
||||
if (!event.eventId.isValidMatrixId || event.eventId.sigil != '\$') {
|
||||
eventContextId = null;
|
||||
}
|
||||
|
||||
final Timeline timeline = event.room.timeline != null &&
|
||||
event.room.timeline!.chunk.eventsMap.containsKey(eventContextId)
|
||||
? event.room.timeline!
|
||||
: await event.room.getTimeline(eventContextId: eventContextId);
|
||||
|
||||
final PangeaMessageEvent pangeaMessageEvent = PangeaMessageEvent(
|
||||
event: event,
|
||||
timeline: timeline,
|
||||
ownMessage: event.senderId == event.room.client.userID,
|
||||
);
|
||||
|
||||
final l2Code = pangeaController.languageController.activeL2Code();
|
||||
if (l2Code == null || l2Code == LanguageKeys.unknownLanguage) {
|
||||
return event.body;
|
||||
}
|
||||
|
||||
final String? text =
|
||||
pangeaMessageEvent.messageDisplayRepresentation?.text;
|
||||
|
||||
final tokens =
|
||||
await pangeaMessageEvent.messageDisplayRepresentation?.tokensGlobal(
|
||||
event.senderId,
|
||||
event.originServerTs,
|
||||
);
|
||||
|
||||
if (tokens != null) {
|
||||
final analyticsEntry = pangeaController.getAnalytics.perMessage.get(
|
||||
tokens,
|
||||
pangeaMessageEvent,
|
||||
);
|
||||
|
||||
if (analyticsEntry?.nextActivity?.activityType ==
|
||||
ActivityTypeEnum.hiddenWordListening) {
|
||||
try {
|
||||
return _constructTokens(
|
||||
tokens,
|
||||
analyticsEntry!.nextActivity!.tokens,
|
||||
);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
data: {
|
||||
"tokens": tokens,
|
||||
"analyticsEntry": analyticsEntry,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final i18n = MatrixLocals(l10n);
|
||||
|
||||
if (text == null || event.room.lastEvent == null) {
|
||||
return l10n.emptyChat;
|
||||
}
|
||||
|
||||
if (!event.room.isDirectChat ||
|
||||
event.room.directChatMatrixID != event.room.lastEvent!.senderId) {
|
||||
final senderNameOrYou = event.senderId == event.room.client.userID
|
||||
? i18n.you
|
||||
: event.room
|
||||
.getParticipants()
|
||||
.firstWhereOrNull((u) => u.id != event.room.client.userID)
|
||||
?.calcDisplayname(i18n: i18n) ??
|
||||
event.room.lastEvent!.senderId;
|
||||
|
||||
return "$senderNameOrYou: $text";
|
||||
}
|
||||
|
||||
return text;
|
||||
} catch (e, s) {
|
||||
// debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"event": event.toJson(),
|
||||
withSenderNamePrefix: !event!.room.isDirectChat ||
|
||||
event!.room.directChatMatrixID != event!.room.lastEvent?.senderId,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
snapshot.hasData && snapshot.data != null
|
||||
? snapshot.data!
|
||||
: L10n.of(context).emptyChat,
|
||||
style: style,
|
||||
);
|
||||
},
|
||||
);
|
||||
return event.body;
|
||||
}
|
||||
|
||||
return FutureBuilder(
|
||||
future: _getPangeaMessageEvent(event!),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final messageEventAndTokens = snapshot.data as MessageEventAndTokens;
|
||||
final pangeaMessageEvent = messageEventAndTokens.event;
|
||||
final tokens = messageEventAndTokens.tokens;
|
||||
|
||||
final analyticsEntry = tokens != null
|
||||
? MatrixState.pangeaController.getAnalytics.perMessage.get(
|
||||
tokens,
|
||||
pangeaMessageEvent,
|
||||
)
|
||||
: null;
|
||||
|
||||
return MessageTextWidget(
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
style: style,
|
||||
messageAnalyticsEntry: analyticsEntry,
|
||||
isSelected: null,
|
||||
onClick: null,
|
||||
);
|
||||
}
|
||||
|
||||
return Text(
|
||||
L10n.of(context).emptyChat,
|
||||
style: style,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageEventAndTokens {
|
||||
final PangeaMessageEvent event;
|
||||
final List<PangeaToken>? tokens;
|
||||
|
||||
MessageEventAndTokens({
|
||||
required this.event,
|
||||
required this.tokens,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
63
lib/pangea/utils/message_text_util.dart
Normal file
63
lib/pangea/utils/message_text_util.dart
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import 'package:fluffychat/pangea/controllers/message_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/message_token_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MessageTextUtil {
|
||||
static List<TokenPosition> getTokenPositions(
|
||||
PangeaMessageEvent pangeaMessageEvent, {
|
||||
MessageAnalyticsEntry? messageAnalyticsEntry,
|
||||
bool Function(PangeaToken)? isSelected,
|
||||
}) {
|
||||
// Convert the entire message into a list of characters
|
||||
final Characters messageCharacters =
|
||||
pangeaMessageEvent.messageDisplayText.characters;
|
||||
|
||||
// When building token positions, use grapheme cluster indices
|
||||
final List<TokenPosition> tokenPositions = [];
|
||||
int globalIndex = 0;
|
||||
|
||||
for (final token
|
||||
in pangeaMessageEvent.messageDisplayRepresentation!.tokens!) {
|
||||
final start = token.start;
|
||||
final end = token.end;
|
||||
|
||||
// Calculate the number of grapheme clusters up to the start and end positions
|
||||
final int startIndex = messageCharacters.take(start).length;
|
||||
final int endIndex = messageCharacters.take(end).length;
|
||||
|
||||
final hideContent =
|
||||
messageAnalyticsEntry?.isTokenInHiddenWordActivity(token) ?? false;
|
||||
|
||||
final hasHiddenContent =
|
||||
messageAnalyticsEntry?.hasHiddenWordActivity ?? false;
|
||||
|
||||
if (globalIndex < startIndex) {
|
||||
tokenPositions.add(
|
||||
TokenPosition(
|
||||
start: globalIndex,
|
||||
end: startIndex,
|
||||
hideContent: false,
|
||||
highlight: (isSelected?.call(token) ?? false) && !hasHiddenContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
tokenPositions.add(
|
||||
TokenPosition(
|
||||
start: startIndex,
|
||||
end: endIndex,
|
||||
token: token,
|
||||
hideContent: hideContent,
|
||||
highlight: (isSelected?.call(token) ?? false) &&
|
||||
!hideContent &&
|
||||
!hasHiddenContent,
|
||||
),
|
||||
);
|
||||
globalIndex = endIndex;
|
||||
}
|
||||
|
||||
return tokenPositions;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:fluffychat/pangea/controllers/message_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/message_text_util.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -47,110 +48,18 @@ class MessageTokenText extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
// Convert the entire message into a list of characters
|
||||
final Characters messageCharacters =
|
||||
_pangeaMessageEvent.messageDisplayText.characters;
|
||||
|
||||
// When building token positions, use grapheme cluster indices
|
||||
final List<TokenPosition> tokenPositions = [];
|
||||
int globalIndex = 0;
|
||||
|
||||
for (final token
|
||||
in _pangeaMessageEvent.messageDisplayRepresentation!.tokens!) {
|
||||
final start = token.start;
|
||||
final end = token.end;
|
||||
|
||||
// Calculate the number of grapheme clusters up to the start and end positions
|
||||
final int startIndex = messageCharacters.take(start).length;
|
||||
final int endIndex = messageCharacters.take(end).length;
|
||||
|
||||
final hideContent =
|
||||
messageAnalyticsEntry?.isTokenInHiddenWordActivity(token) ?? false;
|
||||
|
||||
final hasHiddenContent =
|
||||
messageAnalyticsEntry?.hasHiddenWordActivity ?? false;
|
||||
|
||||
if (globalIndex < startIndex) {
|
||||
tokenPositions.add(
|
||||
TokenPosition(
|
||||
start: globalIndex,
|
||||
end: startIndex,
|
||||
hideContent: false,
|
||||
highlight: (_isSelected?.call(token) ?? false) && !hasHiddenContent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
tokenPositions.add(
|
||||
TokenPosition(
|
||||
start: startIndex,
|
||||
end: endIndex,
|
||||
token: token,
|
||||
hideContent: hideContent,
|
||||
highlight: (_isSelected?.call(token) ?? false) &&
|
||||
!hideContent &&
|
||||
!hasHiddenContent,
|
||||
),
|
||||
);
|
||||
globalIndex = endIndex;
|
||||
}
|
||||
|
||||
void callOnClick(TokenPosition tokenPosition) {
|
||||
_onClick != null && tokenPosition.token != null
|
||||
? _onClick!(tokenPosition.token!)
|
||||
: null;
|
||||
}
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
children:
|
||||
tokenPositions.mapIndexed((int i, TokenPosition tokenPosition) {
|
||||
final substring = messageCharacters
|
||||
.skip(tokenPosition.start)
|
||||
.take(tokenPosition.end - tokenPosition.start)
|
||||
.toString();
|
||||
|
||||
if (tokenPosition.token != null) {
|
||||
if (tokenPosition.hideContent) {
|
||||
return WidgetSpan(
|
||||
child: GestureDetector(
|
||||
onTap: () => callOnClick(tokenPosition),
|
||||
child: HiddenText(text: substring, style: _style),
|
||||
),
|
||||
);
|
||||
}
|
||||
return TextSpan(
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => callOnClick(tokenPosition),
|
||||
text: substring,
|
||||
style: _style.merge(
|
||||
TextStyle(
|
||||
backgroundColor: tokenPosition.highlight
|
||||
? Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(0.4)
|
||||
: Colors.white.withOpacity(0.4)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if ((i > 0 || i < tokenPositions.length - 1) &&
|
||||
tokenPositions[i + 1].hideContent &&
|
||||
tokenPositions[i - 1].hideContent) {
|
||||
return WidgetSpan(
|
||||
child: GestureDetector(
|
||||
onTap: () => callOnClick(tokenPosition),
|
||||
child: HiddenText(text: substring, style: _style),
|
||||
),
|
||||
);
|
||||
}
|
||||
return TextSpan(
|
||||
text: substring,
|
||||
style: _style,
|
||||
);
|
||||
}
|
||||
}).toList(),
|
||||
),
|
||||
return MessageTextWidget(
|
||||
pangeaMessageEvent: _pangeaMessageEvent,
|
||||
style: _style,
|
||||
messageAnalyticsEntry: messageAnalyticsEntry,
|
||||
isSelected: _isSelected,
|
||||
onClick: callOnClick,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -213,3 +122,89 @@ class HiddenText extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageTextWidget extends StatelessWidget {
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
final TextStyle style;
|
||||
final MessageAnalyticsEntry? messageAnalyticsEntry;
|
||||
final bool Function(PangeaToken)? isSelected;
|
||||
final void Function(TokenPosition tokenPosition)? onClick;
|
||||
|
||||
const MessageTextWidget({
|
||||
super.key,
|
||||
required this.pangeaMessageEvent,
|
||||
required this.style,
|
||||
this.messageAnalyticsEntry,
|
||||
this.isSelected,
|
||||
this.onClick,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Characters messageCharacters =
|
||||
pangeaMessageEvent.messageDisplayText.characters;
|
||||
|
||||
final tokenPositions = MessageTextUtil.getTokenPositions(
|
||||
pangeaMessageEvent,
|
||||
messageAnalyticsEntry: messageAnalyticsEntry,
|
||||
isSelected: isSelected,
|
||||
);
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
children:
|
||||
tokenPositions.mapIndexed((int i, TokenPosition tokenPosition) {
|
||||
final substring = messageCharacters
|
||||
.skip(tokenPosition.start)
|
||||
.take(tokenPosition.end - tokenPosition.start)
|
||||
.toString();
|
||||
|
||||
if (tokenPosition.token != null) {
|
||||
if (tokenPosition.hideContent) {
|
||||
return WidgetSpan(
|
||||
child: GestureDetector(
|
||||
onTap: onClick != null
|
||||
? () => onClick?.call(tokenPosition)
|
||||
: null,
|
||||
child: HiddenText(text: substring, style: style),
|
||||
),
|
||||
);
|
||||
}
|
||||
return TextSpan(
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap =
|
||||
onClick != null ? () => onClick?.call(tokenPosition) : null,
|
||||
text: substring,
|
||||
style: style.merge(
|
||||
TextStyle(
|
||||
backgroundColor: tokenPosition.highlight
|
||||
? Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(0.4)
|
||||
: Colors.white.withOpacity(0.4)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if ((i > 0 || i < tokenPositions.length - 1) &&
|
||||
tokenPositions[i + 1].hideContent &&
|
||||
tokenPositions[i - 1].hideContent) {
|
||||
return WidgetSpan(
|
||||
child: GestureDetector(
|
||||
onTap: onClick != null
|
||||
? () => onClick?.call(tokenPosition)
|
||||
: null,
|
||||
child: HiddenText(text: substring, style: style),
|
||||
),
|
||||
);
|
||||
}
|
||||
return TextSpan(
|
||||
text: substring,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue