fluffychat merge
This commit is contained in:
commit
e2acfb81ae
8 changed files with 176 additions and 14 deletions
|
|
@ -696,6 +696,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"checkList": "Check list",
|
||||||
"countParticipants": "{count} participants",
|
"countParticipants": "{count} participants",
|
||||||
"@countParticipants": {
|
"@countParticipants": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|
|
||||||
|
|
@ -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 &&
|
if (!HardwareKeyboard.instance.isShiftPressed &&
|
||||||
evt.logicalKey.keyLabel == 'Enter') {
|
evt.logicalKey.keyLabel == 'Enter' &&
|
||||||
|
(AppConfig.sendOnEnter ?? !PlatformInfos.isMobile)) {
|
||||||
if (evt is KeyDownEvent) {
|
if (evt is KeyDownEvent) {
|
||||||
// #Pangea
|
// #Pangea
|
||||||
// send();
|
// send();
|
||||||
|
|
@ -346,6 +347,36 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
// Pangea#
|
// Pangea#
|
||||||
}
|
}
|
||||||
return KeyEventResult.handled;
|
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 {
|
} else {
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
@ -353,11 +384,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
inputFocus = FocusNode(
|
inputFocus = FocusNode(onKeyEvent: _customEnterKeyHandling);
|
||||||
onKeyEvent: (AppConfig.sendOnEnter ?? !PlatformInfos.isMobile)
|
|
||||||
? _shiftEnterKeyHandling
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
scrollController.addListener(_updateScrollController);
|
scrollController.addListener(_updateScrollController);
|
||||||
inputFocus.addListener(_inputFocusListener);
|
inputFocus.addListener(_inputFocusListener);
|
||||||
|
|
@ -1748,6 +1775,14 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
if (choice == 'location') {
|
if (choice == 'location') {
|
||||||
sendLocationAction();
|
sendLocationAction();
|
||||||
}
|
}
|
||||||
|
if (choice == 'checklist') {
|
||||||
|
if (sendController.text.isEmpty) {
|
||||||
|
sendController.text = '- [ ] ';
|
||||||
|
} else {
|
||||||
|
sendController.text += '\n- [ ] ';
|
||||||
|
}
|
||||||
|
inputFocus.requestFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unpinEvent(String eventId) async {
|
unpinEvent(String eventId) async {
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,18 @@ class ChatInputRow extends StatelessWidget {
|
||||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
<PopupMenuEntry<String>>[
|
<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)
|
if (PlatformInfos.isMobile)
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
value: 'location',
|
value: 'location',
|
||||||
|
|
|
||||||
|
|
@ -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/enums/reading_assistance_mode_enum.dart';
|
||||||
import 'package:fluffychat/pangea/toolbar/utils/token_rendering_util.dart';
|
import 'package:fluffychat/pangea/toolbar/utils/token_rendering_util.dart';
|
||||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.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/avatar.dart';
|
||||||
|
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||||
import '../../../utils/url_launcher.dart';
|
import '../../../utils/url_launcher.dart';
|
||||||
|
|
@ -30,6 +32,9 @@ class HtmlMessage extends StatelessWidget {
|
||||||
final double fontSize;
|
final double fontSize;
|
||||||
final TextStyle linkStyle;
|
final TextStyle linkStyle;
|
||||||
final void Function(LinkableElement) onOpen;
|
final void Function(LinkableElement) onOpen;
|
||||||
|
final String? eventId;
|
||||||
|
final Set<Event>? checkboxCheckedEvents;
|
||||||
|
|
||||||
// #Pangea
|
// #Pangea
|
||||||
final MessageOverlayController? overlayController;
|
final MessageOverlayController? overlayController;
|
||||||
final PangeaMessageEvent? pangeaMessageEvent;
|
final PangeaMessageEvent? pangeaMessageEvent;
|
||||||
|
|
@ -52,6 +57,8 @@ class HtmlMessage extends StatelessWidget {
|
||||||
required this.linkStyle,
|
required this.linkStyle,
|
||||||
this.textColor = Colors.black,
|
this.textColor = Colors.black,
|
||||||
required this.onOpen,
|
required this.onOpen,
|
||||||
|
this.eventId,
|
||||||
|
this.checkboxCheckedEvents,
|
||||||
// #Pangea
|
// #Pangea
|
||||||
this.overlayController,
|
this.overlayController,
|
||||||
required this.event,
|
required this.event,
|
||||||
|
|
@ -460,6 +467,24 @@ class HtmlMessage extends StatelessWidget {
|
||||||
if (!{'ol', 'ul'}.contains(node.parent?.localName)) {
|
if (!{'ol', 'ul'}.contains(node.parent?.localName)) {
|
||||||
continue block;
|
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(
|
return WidgetSpan(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(left: fontSize),
|
padding: EdgeInsets.only(left: fontSize),
|
||||||
|
|
@ -476,6 +501,42 @@ class HtmlMessage extends StatelessWidget {
|
||||||
text:
|
text:
|
||||||
'${(node.parent?.nodes.whereType<dom.Element>().toList().indexOf(node) ?? 0) + (int.tryParse(node.parent?.attributes['start'] ?? '1') ?? 1)}. ',
|
'${(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(
|
..._renderWithLineBreaks(
|
||||||
node.nodes,
|
node.nodes,
|
||||||
context,
|
context,
|
||||||
|
|
@ -761,7 +822,14 @@ class HtmlMessage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// #Pangea
|
// #Pangea
|
||||||
|
// final element = parser.parse(html).body ?? dom.Element.html('');
|
||||||
// return Text.rich(
|
// return Text.rich(
|
||||||
|
// _renderHtml(element, context),
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: fontSize,
|
||||||
|
// color: textColor,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
dom.Node parsed = parser.parse(html).body ?? dom.Element.html('');
|
dom.Node parsed = parser.parse(html).body ?? dom.Element.html('');
|
||||||
if (tokens != null) {
|
if (tokens != null) {
|
||||||
parsed = _tokenizeHtml(parsed, html, List.from(tokens!));
|
parsed = _tokenizeHtml(parsed, html, List.from(tokens!));
|
||||||
|
|
@ -780,19 +848,13 @@ class HtmlMessage extends StatelessWidget {
|
||||||
},
|
},
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
textScaler: TextScaler.noScaling,
|
textScaler: TextScaler.noScaling,
|
||||||
// Pangea#
|
|
||||||
_renderHtml(
|
_renderHtml(
|
||||||
// #Pangea
|
|
||||||
// parser.parse(html).body ?? dom.Element.html(''),
|
|
||||||
parsed,
|
parsed,
|
||||||
// Pangea#
|
|
||||||
context,
|
context,
|
||||||
// #Pangea
|
|
||||||
TextStyle(
|
TextStyle(
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
),
|
),
|
||||||
// Pangea#
|
|
||||||
),
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
|
|
@ -870,3 +932,7 @@ extension on String {
|
||||||
return colorValue == null ? null : Color(colorValue);
|
return colorValue == null ? null : Color(colorValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension on dom.Element {
|
||||||
|
dom.Element get rootElement => parent?.rootElement ?? this;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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/pangea/toolbar/widgets/message_toolbar_selection_area.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||||
import '../../../config/app_config.dart';
|
import '../../../config/app_config.dart';
|
||||||
|
import '../../../utils/event_checkbox_extension.dart';
|
||||||
import '../../../utils/platform_infos.dart';
|
import '../../../utils/platform_infos.dart';
|
||||||
import '../../../utils/url_launcher.dart';
|
import '../../../utils/url_launcher.dart';
|
||||||
import 'audio_player.dart';
|
import 'audio_player.dart';
|
||||||
|
|
@ -291,6 +292,11 @@ class MessageContent extends StatelessWidget {
|
||||||
decorationColor: linkColor,
|
decorationColor: linkColor,
|
||||||
),
|
),
|
||||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||||
|
eventId: event.eventId,
|
||||||
|
checkboxCheckedEvents: event.aggregatedEvents(
|
||||||
|
timeline,
|
||||||
|
EventCheckboxRoomExtension.relationshipType,
|
||||||
|
),
|
||||||
// #Pangea
|
// #Pangea
|
||||||
event: event,
|
event: event,
|
||||||
overlayController: overlayController,
|
overlayController: overlayController,
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,21 @@ class PangeaChatInputRowState extends State<PangeaChatInputRow> {
|
||||||
onSelected: _controller.onAddPopupMenuButtonSelected,
|
onSelected: _controller.onAddPopupMenuButtonSelected,
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
<PopupMenuEntry<String>>[
|
<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>(
|
PopupMenuItem<String>(
|
||||||
value: 'file',
|
value: 'file',
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
|
|
||||||
27
lib/utils/event_checkbox_extension.dart
Normal file
27
lib/utils/event_checkbox_extension.dart
Normal 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');
|
||||||
|
}
|
||||||
|
|
@ -207,4 +207,4 @@ dependency_overrides:
|
||||||
url: https://github.com/ThexXTURBOXx/flutter_web_auth_2.git
|
url: https://github.com/ThexXTURBOXx/flutter_web_auth_2.git
|
||||||
ref: 3.x-without-v1
|
ref: 3.x-without-v1
|
||||||
path: flutter_web_auth_2
|
path: flutter_web_auth_2
|
||||||
win32: 5.5.3
|
win32: 5.5.3
|
||||||
Loading…
Add table
Reference in a new issue