fluffychat merge

This commit is contained in:
ggurdin 2025-06-09 14:56:20 -04:00
commit e2acfb81ae
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
8 changed files with 176 additions and 14 deletions

View file

@ -696,6 +696,7 @@
}
}
},
"checkList": "Check list",
"countParticipants": "{count} participants",
"@countParticipants": {
"type": "String",

View file

@ -336,9 +336,10 @@ class ChatController extends State<ChatPageWithRoom>
);
}
KeyEventResult _shiftEnterKeyHandling(FocusNode node, KeyEvent evt) {
KeyEventResult _customEnterKeyHandling(FocusNode node, KeyEvent evt) {
if (!HardwareKeyboard.instance.isShiftPressed &&
evt.logicalKey.keyLabel == 'Enter') {
evt.logicalKey.keyLabel == 'Enter' &&
(AppConfig.sendOnEnter ?? !PlatformInfos.isMobile)) {
if (evt is KeyDownEvent) {
// #Pangea
// send();
@ -346,6 +347,36 @@ class ChatController extends State<ChatPageWithRoom>
// Pangea#
}
return KeyEventResult.handled;
} else if (evt.logicalKey.keyLabel == 'Enter' && evt is KeyDownEvent) {
final currentLineNum = sendController.text
.substring(
0,
sendController.selection.baseOffset,
)
.split('\n')
.length -
1;
final currentLine = sendController.text.split('\n')[currentLineNum];
for (final pattern in [
'- [ ] ',
'- [x] ',
'* [ ] ',
'* [x] ',
'- ',
'* ',
'+ ',
]) {
if (currentLine.startsWith(pattern)) {
if (currentLine == pattern) {
return KeyEventResult.ignored;
}
sendController.text += '\n$pattern';
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
} else {
return KeyEventResult.ignored;
}
@ -353,11 +384,7 @@ class ChatController extends State<ChatPageWithRoom>
@override
void initState() {
inputFocus = FocusNode(
onKeyEvent: (AppConfig.sendOnEnter ?? !PlatformInfos.isMobile)
? _shiftEnterKeyHandling
: null,
);
inputFocus = FocusNode(onKeyEvent: _customEnterKeyHandling);
scrollController.addListener(_updateScrollController);
inputFocus.addListener(_inputFocusListener);
@ -1748,6 +1775,14 @@ class ChatController extends State<ChatPageWithRoom>
if (choice == 'location') {
sendLocationAction();
}
if (choice == 'checklist') {
if (sendController.text.isEmpty) {
sendController.text = '- [ ] ';
} else {
sendController.text += '\n- [ ] ';
}
inputFocus.requestFocus();
}
}
unpinEvent(String eventId) async {

View file

@ -123,6 +123,18 @@ class ChatInputRow extends StatelessWidget {
onSelected: controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'checklist',
child: ListTile(
leading: CircleAvatar(
backgroundColor: theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.check_circle_outlined),
),
title: Text(L10n.of(context).checkList),
contentPadding: const EdgeInsets.all(0),
),
),
if (PlatformInfos.isMobile)
PopupMenuItem<String>(
value: 'location',

View file

@ -18,7 +18,9 @@ import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/utils/token_rendering_util.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/utils/event_checkbox_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import '../../../utils/url_launcher.dart';
@ -30,6 +32,9 @@ class HtmlMessage extends StatelessWidget {
final double fontSize;
final TextStyle linkStyle;
final void Function(LinkableElement) onOpen;
final String? eventId;
final Set<Event>? checkboxCheckedEvents;
// #Pangea
final MessageOverlayController? overlayController;
final PangeaMessageEvent? pangeaMessageEvent;
@ -52,6 +57,8 @@ class HtmlMessage extends StatelessWidget {
required this.linkStyle,
this.textColor = Colors.black,
required this.onOpen,
this.eventId,
this.checkboxCheckedEvents,
// #Pangea
this.overlayController,
required this.event,
@ -460,6 +467,24 @@ class HtmlMessage extends StatelessWidget {
if (!{'ol', 'ul'}.contains(node.parent?.localName)) {
continue block;
}
final eventId = this.eventId;
final isCheckbox = node.className == 'task-list-item';
final checkboxIndex = isCheckbox
? node.rootElement
.getElementsByClassName('task-list-item')
.indexOf(node) +
1
: null;
final checkedByReaction = !isCheckbox
? null
: checkboxCheckedEvents?.firstWhereOrNull(
(event) => event.checkedCheckboxId == checkboxIndex,
);
final staticallyChecked = !isCheckbox
? false
: node.children.first.attributes['checked'] == 'true';
return WidgetSpan(
child: Padding(
padding: EdgeInsets.only(left: fontSize),
@ -476,6 +501,42 @@ class HtmlMessage extends StatelessWidget {
text:
'${(node.parent?.nodes.whereType<dom.Element>().toList().indexOf(node) ?? 0) + (int.tryParse(node.parent?.attributes['start'] ?? '1') ?? 1)}. ',
),
if (node.className == 'task-list-item')
WidgetSpan(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: SizedBox.square(
dimension: fontSize,
child: Checkbox.adaptive(
checkColor: textColor,
side: BorderSide(color: textColor),
activeColor: textColor.withAlpha(64),
visualDensity: VisualDensity.compact,
value:
staticallyChecked || checkedByReaction != null,
onChanged: eventId == null ||
checkboxIndex == null ||
staticallyChecked ||
!room.canSendDefaultMessages ||
(checkedByReaction != null &&
checkedByReaction.senderId !=
room.client.userID)
? null
: (_) => showFutureLoadingDialog(
context: context,
future: () => checkedByReaction != null
? room.redactEvent(
checkedByReaction.eventId,
)
: room.checkCheckbox(
eventId,
checkboxIndex,
),
),
),
),
),
),
..._renderWithLineBreaks(
node.nodes,
context,
@ -761,7 +822,14 @@ class HtmlMessage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// #Pangea
// final element = parser.parse(html).body ?? dom.Element.html('');
// return Text.rich(
// _renderHtml(element, context),
// style: TextStyle(
// fontSize: fontSize,
// color: textColor,
// ),
// );
dom.Node parsed = parser.parse(html).body ?? dom.Element.html('');
if (tokens != null) {
parsed = _tokenizeHtml(parsed, html, List.from(tokens!));
@ -780,19 +848,13 @@ class HtmlMessage extends StatelessWidget {
},
child: Text.rich(
textScaler: TextScaler.noScaling,
// Pangea#
_renderHtml(
// #Pangea
// parser.parse(html).body ?? dom.Element.html(''),
parsed,
// Pangea#
context,
// #Pangea
TextStyle(
fontSize: fontSize,
color: textColor,
),
// Pangea#
),
style: TextStyle(
fontSize: fontSize,
@ -870,3 +932,7 @@ extension on String {
return colorValue == null ? null : Color(colorValue);
}
}
extension on dom.Element {
dom.Element get rootElement => parent?.rootElement ?? this;
}

View file

@ -19,6 +19,7 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_token_text.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_toolbar_selection_area.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import '../../../config/app_config.dart';
import '../../../utils/event_checkbox_extension.dart';
import '../../../utils/platform_infos.dart';
import '../../../utils/url_launcher.dart';
import 'audio_player.dart';
@ -291,6 +292,11 @@ class MessageContent extends StatelessWidget {
decorationColor: linkColor,
),
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
eventId: event.eventId,
checkboxCheckedEvents: event.aggregatedEvents(
timeline,
EventCheckboxRoomExtension.relationshipType,
),
// #Pangea
event: event,
overlayController: overlayController,

View file

@ -218,6 +218,21 @@ class PangeaChatInputRowState extends State<PangeaChatInputRow> {
onSelected: _controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'checklist',
child: ListTile(
leading: CircleAvatar(
backgroundColor:
theme.colorScheme.onPrimaryContainer,
foregroundColor:
theme.colorScheme.primaryContainer,
child:
const Icon(Icons.check_circle_outlined),
),
title: Text(L10n.of(context).checkList),
contentPadding: const EdgeInsets.all(0),
),
),
PopupMenuItem<String>(
value: 'file',
child: ListTile(

View file

@ -0,0 +1,27 @@
import 'package:matrix/matrix.dart';
extension EventCheckboxRoomExtension on Room {
static const String relationshipType = 'im.fluffychat.checkboxes';
Future<String?> checkCheckbox(
String eventId,
int checkboxId, {
String? txid,
}) =>
sendEvent(
{
'm.relates_to': {
'rel_type': relationshipType,
'event_id': eventId,
'checkbox_id': checkboxId,
},
},
type: EventTypes.Reaction,
txid: txid,
);
}
extension EventCheckboxExtension on Event {
int? get checkedCheckboxId => content
.tryGetMap<String, Object?>('m.relates_to')
?.tryGet<int>('checkbox_id');
}

View file

@ -207,4 +207,4 @@ dependency_overrides:
url: https://github.com/ThexXTURBOXx/flutter_web_auth_2.git
ref: 3.x-without-v1
path: flutter_web_auth_2
win32: 5.5.3
win32: 5.5.3