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.
183 lines
5.7 KiB
Dart
183 lines
5.7 KiB
Dart
import 'package:fluffychat/config/app_config.dart';
|
|
import 'package:fluffychat/pangea/controllers/pangea_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_selection_overlay.dart';
|
|
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
|
import 'package:fluffychat/widgets/matrix.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
|
|
class OverlayMessageText extends StatefulWidget {
|
|
final PangeaMessageEvent pangeaMessageEvent;
|
|
final MessageOverlayController overlayController;
|
|
|
|
const OverlayMessageText({
|
|
super.key,
|
|
required this.pangeaMessageEvent,
|
|
required this.overlayController,
|
|
});
|
|
|
|
@override
|
|
OverlayMessageTextState createState() => OverlayMessageTextState();
|
|
}
|
|
|
|
class OverlayMessageTextState extends State<OverlayMessageText> {
|
|
final PangeaController pangeaController = MatrixState.pangeaController;
|
|
List<PangeaToken>? tokens;
|
|
|
|
@override
|
|
void initState() {
|
|
tokens = widget.pangeaMessageEvent.originalSent?.tokens;
|
|
if (widget.pangeaMessageEvent.originalSent != null && tokens == null) {
|
|
widget.pangeaMessageEvent.originalSent!
|
|
.tokensGlobal(context)
|
|
.then((tokens) {
|
|
// this isn't currently working because originalSent's _event is null
|
|
setState(() => this.tokens = tokens);
|
|
});
|
|
}
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final ownMessage = widget.pangeaMessageEvent.event.senderId ==
|
|
Matrix.of(context).client.userID;
|
|
|
|
final style = TextStyle(
|
|
color: ownMessage
|
|
? theme.colorScheme.onPrimary
|
|
: theme.colorScheme.onSurface,
|
|
height: 1.3,
|
|
fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor,
|
|
);
|
|
|
|
if (tokens == null || tokens!.isEmpty) {
|
|
return Text(
|
|
widget.pangeaMessageEvent.event.calcLocalizedBodyFallback(
|
|
MatrixLocals(L10n.of(context)!),
|
|
hideReply: true,
|
|
),
|
|
style: style,
|
|
);
|
|
}
|
|
|
|
// Convert the entire message into a list of characters
|
|
final Characters messageCharacters =
|
|
widget.pangeaMessageEvent.event.body.characters;
|
|
|
|
// When building token positions, use grapheme cluster indices
|
|
final List<TokenPosition> tokenPositions = [];
|
|
int globalIndex = 0;
|
|
|
|
for (int i = 0; i < tokens!.length; i++) {
|
|
final token = tokens![i];
|
|
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;
|
|
|
|
if (globalIndex < startIndex) {
|
|
tokenPositions.add(TokenPosition(start: globalIndex, end: startIndex));
|
|
}
|
|
|
|
tokenPositions.add(
|
|
TokenPosition(
|
|
start: startIndex,
|
|
end: endIndex,
|
|
tokenIndex: i,
|
|
token: token,
|
|
),
|
|
);
|
|
globalIndex = endIndex;
|
|
}
|
|
|
|
// debug prints for fixing words sticking together
|
|
// void printEscapedString(String input) {
|
|
// // Escaped string using Unicode escape sequences
|
|
// final String escapedString = input.replaceAllMapped(
|
|
// RegExp(r'[^\w\s]', unicode: true),
|
|
// (match) {
|
|
// final codeUnits = match.group(0)!.runes;
|
|
// String unicodeEscapes = '';
|
|
// for (final rune in codeUnits) {
|
|
// unicodeEscapes += '\\u{${rune.toRadixString(16)}}';
|
|
// }
|
|
// return unicodeEscapes;
|
|
// },
|
|
// );
|
|
// print("Escaped String: $escapedString");
|
|
|
|
// // Printing each character with its index
|
|
// int index = 0;
|
|
// for (final char in input.characters) {
|
|
// print("Index $index: $char");
|
|
// index++;
|
|
// }
|
|
// }
|
|
|
|
//TODO - take out of build function of every message
|
|
return RichText(
|
|
text: TextSpan(
|
|
children: tokenPositions.map((tokenPosition) {
|
|
final substring = messageCharacters
|
|
.skip(tokenPosition.start)
|
|
.take(tokenPosition.end - tokenPosition.start)
|
|
.toString();
|
|
|
|
if (tokenPosition.token != null) {
|
|
final isSelected =
|
|
widget.overlayController.isTokenSelected(tokenPosition.token!);
|
|
return TextSpan(
|
|
recognizer: TapGestureRecognizer()
|
|
..onTap = () {
|
|
debugPrint(
|
|
'tokenPosition.tokenIndex: ${tokenPosition.tokenIndex}',
|
|
);
|
|
widget.overlayController.onClickOverlayMessageToken(
|
|
tokenPosition.token!,
|
|
);
|
|
setState(() {});
|
|
},
|
|
text: substring,
|
|
style: style.merge(
|
|
TextStyle(
|
|
backgroundColor: isSelected
|
|
? Theme.of(context).brightness == Brightness.light
|
|
? Colors.black.withOpacity(0.4)
|
|
: Colors.white.withOpacity(0.4)
|
|
: Colors.transparent,
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
return TextSpan(
|
|
text: substring,
|
|
style: style,
|
|
);
|
|
}
|
|
}).toList(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TokenPosition {
|
|
final int start;
|
|
final int end;
|
|
final PangeaToken? token;
|
|
final int tokenIndex;
|
|
|
|
const TokenPosition({
|
|
required this.start,
|
|
required this.end,
|
|
this.token,
|
|
this.tokenIndex = -1,
|
|
});
|
|
}
|