fluffychat merge

This commit is contained in:
ggurdin 2026-02-04 15:15:23 -05:00
commit b557c2047f
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
14 changed files with 627 additions and 76 deletions

View file

@ -3412,6 +3412,35 @@
}
},
"donate": "Donate",
"startedAPoll": "{username} started a poll.",
"@startedAPoll": {
"type": "String",
"placeholders": {
"username": {
"type": "String"
}
}
},
"poll": "Poll",
"startPoll": "Start poll",
"endPoll": "End poll",
"answersVisible": "Answers visible",
"answersHidden": "Answers hidden",
"pollQuestion": "Poll question",
"answerOption": "Answer option",
"addAnswerOption": "Add answer option",
"allowMultipleAnswers": "Allow multiple answers",
"pollHasBeenEnded": "Poll has been ended",
"countVotes": "{count} votes",
"@countVotes": {
"type": "int",
"placeholders": {
"count": {
"type": "int"
}
}
},
"answersWillBeVisibleWhenPollHasEnded": "Answers will be visible when poll has ended",
"ignore": "Block",
"ignoredUsers": "Blocked users",
"writeAMessageLangCodes": "Type in {l1} or {l2}...",

View file

@ -23,6 +23,7 @@ import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/chat_view.dart';
import 'package:fluffychat/pages/chat/event_info_dialog.dart';
import 'package:fluffychat/pages/chat/events/audio_player.dart';
import 'package:fluffychat/pages/chat/start_poll_bottom_sheet.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
import 'package:fluffychat/pangea/activity_sessions/activity_session_chat/activity_chat_controller.dart';
@ -70,6 +71,7 @@ import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_reques
import 'package:fluffychat/pangea/toolbar/message_practice/message_practice_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/message_selection_overlay.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance/tokens_util.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/error_reporter.dart';
import 'package:fluffychat/utils/file_selector.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
@ -1793,26 +1795,34 @@ class ChatController extends State<ChatPageWithRoom>
// Pangea#
}
void onAddPopupMenuButtonSelected(String choice) {
room.client.getConfig(); // Preload server file configuration.
void onAddPopupMenuButtonSelected(AddPopupMenuActions choice) {
room.client.getConfig();
if (choice == 'file') {
sendFileAction();
}
if (choice == 'image') {
sendFileAction(type: FileSelectorType.images);
}
if (choice == 'video') {
sendFileAction(type: FileSelectorType.videos);
}
if (choice == 'camera') {
openCameraAction();
}
if (choice == 'camera-video') {
openVideoCameraAction();
}
if (choice == 'location') {
sendLocationAction();
switch (choice) {
case AddPopupMenuActions.image:
sendFileAction(type: FileSelectorType.images);
return;
case AddPopupMenuActions.video:
sendFileAction(type: FileSelectorType.videos);
return;
case AddPopupMenuActions.file:
sendFileAction();
return;
case AddPopupMenuActions.poll:
showAdaptiveBottomSheet(
context: context,
builder: (context) => StartPollBottomSheet(room: room),
);
return;
case AddPopupMenuActions.photoCamera:
openCameraAction();
return;
case AddPopupMenuActions.videoCamera:
openVideoCameraAction();
return;
case AddPopupMenuActions.location:
sendLocationAction();
return;
}
}
@ -2521,3 +2531,13 @@ class ChatController extends State<ChatPageWithRoom>
);
}
}
enum AddPopupMenuActions {
image,
video,
file,
poll,
photoCamera,
videoCamera,
location,
}

View file

@ -132,16 +132,15 @@
// alignment: Alignment.center,
// decoration: const BoxDecoration(),
// clipBehavior: Clip.hardEdge,
// child: PopupMenuButton<String>(
// child: PopupMenuButton<AddPopupMenuActions>(
// useRootNavigator: true,
// icon: const Icon(Icons.add_circle_outline),
// iconColor: theme.colorScheme.onPrimaryContainer,
// onSelected: controller.onAddPopupMenuButtonSelected,
// itemBuilder: (BuildContext context) =>
// <PopupMenuEntry<String>>[
// itemBuilder: (BuildContext context) => [
// if (PlatformInfos.isMobile)
// PopupMenuItem<String>(
// value: 'location',
// PopupMenuItem(
// value: AddPopupMenuActions.location,
// child: ListTile(
// leading: CircleAvatar(
// backgroundColor:
@ -154,8 +153,22 @@
// contentPadding: const EdgeInsets.all(0),
// ),
// ),
// PopupMenuItem<String>(
// value: 'image',
// PopupMenuItem(
// value: AddPopupMenuActions.poll,
// child: ListTile(
// leading: CircleAvatar(
// backgroundColor:
// theme.colorScheme.onPrimaryContainer,
// foregroundColor:
// theme.colorScheme.primaryContainer,
// child: const Icon(Icons.poll_outlined),
// ),
// title: Text(L10n.of(context).startPoll),
// contentPadding: const EdgeInsets.all(0),
// ),
// ),
// PopupMenuItem(
// value: AddPopupMenuActions.image,
// child: ListTile(
// leading: CircleAvatar(
// backgroundColor:
@ -168,8 +181,8 @@
// contentPadding: const EdgeInsets.all(0),
// ),
// ),
// PopupMenuItem<String>(
// value: 'video',
// PopupMenuItem(
// value: AddPopupMenuActions.video,
// child: ListTile(
// leading: CircleAvatar(
// backgroundColor:
@ -183,8 +196,8 @@
// contentPadding: const EdgeInsets.all(0),
// ),
// ),
// PopupMenuItem<String>(
// value: 'file',
// PopupMenuItem(
// value: AddPopupMenuActions.file,
// child: ListTile(
// leading: CircleAvatar(
// backgroundColor:
@ -217,8 +230,8 @@
// onSelected: controller.onAddPopupMenuButtonSelected,
// iconColor: theme.colorScheme.onPrimaryContainer,
// itemBuilder: (context) => [
// PopupMenuItem<String>(
// value: 'camera-video',
// PopupMenuItem(
// value: AddPopupMenuActions.videoCamera,
// child: ListTile(
// leading: CircleAvatar(
// backgroundColor:
@ -231,8 +244,8 @@
// contentPadding: const EdgeInsets.all(0),
// ),
// ),
// PopupMenuItem<String>(
// value: 'camera',
// PopupMenuItem(
// value: AddPopupMenuActions.photoCamera,
// child: ListTile(
// leading: CircleAvatar(
// backgroundColor:
@ -394,7 +407,7 @@
// onSelected: (mxid) => _popupMenuButtonSelected(mxid, context),
// itemBuilder: (BuildContext context) => clients
// .map(
// (client) => PopupMenuItem<String>(
// (client) => PopupMenuItem(
// value: client!.userID,
// child: FutureBuilder<Profile>(
// future: client.fetchOwnProfile(),

View file

@ -130,6 +130,7 @@ class Message extends StatelessWidget {
EventTypes.Sticker,
EventTypes.Encrypted,
EventTypes.CallInvite,
PollEventContent.startType,
}.contains(event.type)) {
if (event.type.startsWith('m.call.')) {
return const SizedBox.shrink();

View file

@ -7,6 +7,7 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/events/poll.dart';
import 'package:fluffychat/pages/chat/events/video_player.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
@ -285,27 +286,11 @@ class MessageContent extends StatelessWidget {
textmessage:
default:
if (event.redacted) {
return FutureBuilder<User?>(
future: event.redactedBecause?.fetchSenderUser(),
builder: (context, snapshot) {
final reason =
event.redactedBecause?.content.tryGet<String>('reason');
final redactedBy = snapshot.data?.calcDisplayname() ??
event.redactedBecause?.senderId.localpart ??
L10n.of(context).user;
return _ButtonContent(
label: reason == null
? L10n.of(context).redactedBy(redactedBy)
: L10n.of(context).redactedByBecause(
redactedBy,
reason,
),
icon: '🗑️',
textColor: buttonTextColor.withAlpha(128),
onPressed: () => onInfoTab!(event),
fontSize: fontSize,
);
},
return RedactionWidget(
event: event,
buttonTextColor: buttonTextColor,
onInfoTab: onInfoTab,
fontSize: fontSize,
);
}
var html = AppSettings.renderHtml.value && event.isRichMessage
@ -362,6 +347,21 @@ class MessageContent extends StatelessWidget {
),
);
}
case PollEventContent.startType:
if (event.redacted) {
return RedactionWidget(
event: event,
buttonTextColor: buttonTextColor,
onInfoTab: onInfoTab,
fontSize: fontSize,
);
}
return PollWidget(
event: event,
timeline: timeline,
textColor: textColor,
linkColor: linkColor,
);
case EventTypes.CallInvite:
return FutureBuilder<User?>(
future: event.fetchSenderUser(),
@ -399,6 +399,46 @@ class MessageContent extends StatelessWidget {
}
}
class RedactionWidget extends StatelessWidget {
const RedactionWidget({
super.key,
required this.event,
required this.buttonTextColor,
required this.onInfoTab,
required this.fontSize,
});
final Event event;
final Color buttonTextColor;
final void Function(Event p1)? onInfoTab;
final double fontSize;
@override
Widget build(BuildContext context) {
return FutureBuilder<User?>(
future: event.redactedBecause?.fetchSenderUser(),
builder: (context, snapshot) {
final reason = event.redactedBecause?.content.tryGet<String>('reason');
final redactedBy = snapshot.data?.calcDisplayname() ??
event.redactedBecause?.senderId.localpart ??
L10n.of(context).user;
return _ButtonContent(
label: reason == null
? L10n.of(context).redactedBy(redactedBy)
: L10n.of(context).redactedByBecause(
redactedBy,
reason,
),
icon: '🗑️',
textColor: buttonTextColor.withAlpha(128),
onPressed: () => onInfoTab!(event),
fontSize: fontSize,
);
},
);
}
}
class _ButtonContent extends StatelessWidget {
final void Function() onPressed;
final String label;

View file

@ -0,0 +1,233 @@
import 'package:flutter/material.dart';
import 'package:async/async.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart' hide Result;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/url_launcher.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
class PollWidget extends StatelessWidget {
final Event event;
final Timeline timeline;
final Color textColor;
final Color linkColor;
const PollWidget({
required this.event,
required this.timeline,
required this.textColor,
required this.linkColor,
super.key,
});
void _endPoll(BuildContext context) => showFutureLoadingDialog(
context: context,
future: () => event.endPoll(),
);
void _toggleVote(
BuildContext context,
String answerId,
int maxSelection,
) async {
final userId = event.room.client.userID!;
final answerIds = event.getPollResponses(timeline)[userId] ?? {};
if (!answerIds.remove(answerId)) {
answerIds.add(answerId);
if (answerIds.length > maxSelection) {
answerIds.clear();
answerIds.add(answerId);
}
}
showFutureLoadingDialog(
context: context,
future: () => event.answerPoll(answerIds.toList()),
);
}
@override
Widget build(BuildContext context) {
final eventContentResult = Result(() => event.parsedPollEventContent);
final eventContent = eventContentResult.asValue?.value;
if (eventContent == null) {
Logs().w('Invalid poll event', eventContentResult.error);
return const Text('Unable to parse poll event...');
}
final responses = event.getPollResponses(timeline);
final pollHasBeenEnded = event.getPollHasBeenEnded(timeline);
final canVote = event.room.canSendEvent(PollEventContent.responseType) &&
!pollHasBeenEnded;
final maxPolls = responses.length;
final answersVisible =
eventContent.pollStartContent.kind == PollKind.disclosed ||
pollHasBeenEnded;
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Linkify(
text: eventContent.pollStartContent.question.mText,
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
),
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
),
),
Divider(color: linkColor.withAlpha(64)),
...eventContent.pollStartContent.answers.map(
(answer) {
final votedUserIds = responses.entries
.where((entry) => entry.value.contains(answer.id))
.map((entry) => entry.key)
.toSet();
return Material(
color: Colors.transparent,
clipBehavior: Clip.hardEdge,
child: CheckboxListTile.adaptive(
value: responses[event.room.client.userID!]
?.contains(answer.id) ??
false,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
checkboxScaleFactor: 1.5,
checkboxShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
),
onChanged: !canVote
? null
: (_) => _toggleVote(
context,
answer.id,
eventContent.pollStartContent.maxSelections,
),
title: Text(
answer.mText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: textColor,
fontSize: AppConfig.messageFontSize *
AppSettings.fontSizeFactor.value,
),
),
subtitle: answersVisible
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Text(
L10n.of(context)
.countVotes(votedUserIds.length),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: linkColor,
fontSize:
12 * AppSettings.fontSizeFactor.value,
),
),
const SizedBox(width: 2),
...votedUserIds.map((userId) {
final user = event.room
.getState(EventTypes.RoomMember, userId)
?.asUser(event.room);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 2.0,
),
child: Avatar(
mxContent: user?.avatarUrl,
name: user?.calcDisplayname() ??
userId.localpart,
size: 12 *
AppSettings.fontSizeFactor.value,
),
);
}),
const SizedBox(width: 2),
],
),
),
LinearProgressIndicator(
color: linkColor,
backgroundColor: linkColor.withAlpha(128),
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
value: maxPolls == 0
? 0
: votedUserIds.length / maxPolls,
),
],
)
: null,
),
);
},
),
if (!pollHasBeenEnded && event.senderId == event.room.client.userID)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: OutlinedButton(
onPressed: () => _endPoll(context),
style: OutlinedButton.styleFrom(
foregroundColor: linkColor,
side: BorderSide(color: linkColor.withAlpha(64)),
),
child: Text(L10n.of(context).endPoll),
),
)
else if (!answersVisible)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
L10n.of(context).answersWillBeVisibleWhenPollHasEnded,
style: TextStyle(
color: linkColor,
fontSize: 12 * AppSettings.fontSizeFactor.value,
fontStyle: FontStyle.italic,
),
),
)
else if (pollHasBeenEnded)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
L10n.of(context).pollHasBeenEnded,
style: TextStyle(
color: linkColor,
fontSize: 12 * AppSettings.fontSizeFactor.value,
fontStyle: FontStyle.italic,
),
),
),
],
),
);
}
}

View file

@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
class StartPollBottomSheet extends StatefulWidget {
final Room room;
const StartPollBottomSheet({required this.room, super.key});
@override
State<StartPollBottomSheet> createState() => _StartPollBottomSheetState();
}
class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
final TextEditingController _bodyController = TextEditingController();
bool _allowMultipleAnswers = false;
final List<TextEditingController> _answers = [
TextEditingController(),
TextEditingController(),
];
PollKind _pollKind = PollKind.disclosed;
bool _canCreate = false;
bool isLoading = false;
String? _txid;
void _createPoll() async {
try {
var id = 0;
_txid ??= widget.room.client.generateUniqueTransactionId();
await widget.room.startPoll(
question: _bodyController.text.trim(),
answers: _answers
.map(
(answerController) => PollAnswer(
id: (++id).toString(),
mText: answerController.text.trim(),
),
)
.toList(),
kind: _pollKind,
maxSelections: _allowMultipleAnswers ? _answers.length : 1,
txid: _txid,
);
Navigator.of(context).pop();
} catch (e, s) {
Logs().w('Unable to create poll', e, s);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toLocalizedString(context))),
);
}
}
void _updateCanCreate([_]) {
final newCanCreate = _bodyController.text.trim().isNotEmpty &&
!_answers.any((controller) => controller.text.trim().isEmpty);
if (_canCreate != newCanCreate) {
setState(() {
_canCreate = newCanCreate;
});
}
}
@override
Widget build(BuildContext context) {
const maxAnswers = 10;
return Scaffold(
appBar: AppBar(
leading: CloseButton(
onPressed: Navigator.of(context).pop,
),
title: Text(L10n.of(context).startPoll),
),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
children: [
SegmentedButton<PollKind>(
selected: {_pollKind},
multiSelectionEnabled: false,
onSelectionChanged: (pollKind) => setState(() {
_pollKind = pollKind.first;
}),
segments: [
ButtonSegment(
value: PollKind.disclosed,
label: Text(
L10n.of(context).answersVisible,
),
),
ButtonSegment(
value: PollKind.undisclosed,
label: Text(
L10n.of(context).answersHidden,
),
),
],
),
const SizedBox(height: 32),
TextField(
controller: _bodyController,
minLines: 1,
maxLines: 4,
maxLength: 512,
onChanged: _updateCanCreate,
decoration: InputDecoration(
hintText: L10n.of(context).pollQuestion,
counter: const SizedBox.shrink(),
),
),
const Divider(height: 32),
..._answers.map(
(answerController) => Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: TextField(
controller: answerController,
onChanged: _updateCanCreate,
maxLength: 64,
decoration: InputDecoration(
counter: const SizedBox.shrink(),
hintText: L10n.of(context).answerOption,
suffixIcon: _answers.length == 2
? null
: IconButton(
icon: const Icon(Icons.cancel_outlined),
onPressed: () => setState(() {
_answers.remove(answerController..dispose());
}),
),
),
),
),
),
Align(
alignment: Alignment.centerLeft,
child: TextButton.icon(
icon: const Icon(Icons.add_outlined),
onPressed: _answers.length < maxAnswers
? () => setState(() {
_answers.add(TextEditingController());
})
: null,
label: Text(L10n.of(context).addAnswerOption),
),
),
ListTile(
contentPadding: EdgeInsets.zero,
leading: Switch.adaptive(
value: _allowMultipleAnswers,
onChanged: (allow) => setState(() {
_allowMultipleAnswers = allow;
}),
),
title: Text(L10n.of(context).allowMultipleAnswers),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: !isLoading && _canCreate ? _createPoll : null,
child: isLoading
? const LinearProgressIndicator()
: Text(L10n.of(context).startPoll),
),
],
),
);
}
}

View file

@ -1,3 +1,8 @@
import 'package:flutter/material.dart' hide Visibility;
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/chat_settings/pages/pangea_chat_access_settings.dart';
import 'package:fluffychat/pangea/extensions/join_rule_extension.dart';
@ -7,9 +12,6 @@ import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart' hide Visibility;
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
class ChatAccessSettings extends StatefulWidget {
final String roomId;

View file

@ -81,15 +81,29 @@ class PangeaChatInputRow extends StatelessWidget {
alignment: Alignment.center,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: PopupMenuButton<String>(
child: PopupMenuButton<AddPopupMenuActions>(
useRootNavigator: true,
icon: const Icon(Icons.add_outlined),
onSelected: controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
<PopupMenuEntry<AddPopupMenuActions>>[
PopupMenuItem(
value: AddPopupMenuActions.poll,
child: ListTile(
leading: CircleAvatar(
backgroundColor:
theme.colorScheme.onPrimaryContainer,
foregroundColor:
theme.colorScheme.primaryContainer,
child: const Icon(Icons.poll_outlined),
),
title: Text(L10n.of(context).startPoll),
contentPadding: const EdgeInsets.all(0),
),
),
if (!isBotDM)
PopupMenuItem<String>(
value: 'file',
PopupMenuItem<AddPopupMenuActions>(
value: AddPopupMenuActions.file,
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.green,
@ -100,8 +114,8 @@ class PangeaChatInputRow extends StatelessWidget {
contentPadding: const EdgeInsets.all(0),
),
),
PopupMenuItem<String>(
value: 'image',
PopupMenuItem<AddPopupMenuActions>(
value: AddPopupMenuActions.image,
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.blue,
@ -112,9 +126,24 @@ class PangeaChatInputRow extends StatelessWidget {
contentPadding: const EdgeInsets.all(0),
),
),
if (!isBotDM)
PopupMenuItem(
value: AddPopupMenuActions.image,
child: ListTile(
leading: CircleAvatar(
backgroundColor:
theme.colorScheme.onPrimaryContainer,
foregroundColor:
theme.colorScheme.primaryContainer,
child: const Icon(Icons.photo_outlined),
),
title: Text(L10n.of(context).sendImage),
contentPadding: const EdgeInsets.all(0),
),
),
if (PlatformInfos.isMobile)
PopupMenuItem<String>(
value: 'camera',
PopupMenuItem<AddPopupMenuActions>(
value: AddPopupMenuActions.photoCamera,
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.purple,
@ -127,8 +156,8 @@ class PangeaChatInputRow extends StatelessWidget {
),
if (!isBotDM)
if (PlatformInfos.isMobile)
PopupMenuItem<String>(
value: 'camera-video',
PopupMenuItem<AddPopupMenuActions>(
value: AddPopupMenuActions.videoCamera,
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.red,
@ -143,8 +172,8 @@ class PangeaChatInputRow extends StatelessWidget {
),
if (!isBotDM)
if (PlatformInfos.isMobile)
PopupMenuItem<String>(
value: 'location',
PopupMenuItem<AddPopupMenuActions>(
value: AddPopupMenuActions.location,
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.brown,

View file

@ -25,8 +25,8 @@ extension IsStateExtension on Event {
// always filter out edit and reaction relationships
!{RelationshipTypes.edit, RelationshipTypes.reaction}
.contains(relationshipType) &&
// always filter out m.key.* events
!type.startsWith('m.key.verification.') &&
// always filter out m.key.* and other known but unimportant events
!isKnownHiddenStates &&
// event types to hide: redaction and reaction events
// if a reaction has been redacted we also want it to be hidden in the timeline
!{EventTypes.Reaction, EventTypes.Redaction}.contains(type) &&
@ -57,6 +57,12 @@ extension IsStateExtension on Event {
EventTypes.RoomTombstone,
}.contains(type);
bool get isKnownHiddenStates =>
{
PollEventContent.responseType,
}.contains(type) ||
type.startsWith('m.key.verification.');
// #Pangea
bool get isVisibleInPangeaGui {
if (!room.showActivityChatUI) {
@ -82,6 +88,7 @@ extension IsStateExtension on Event {
EventTypes.RoomMember,
EventTypes.RoomTombstone,
EventTypes.CallInvite,
PollEventContent.startType,
PangeaEventTypes.activityPlan,
PangeaEventTypes.activityRole,
};

View file

@ -366,4 +366,10 @@ class MatrixLocals extends MatrixLocalizations {
@override
String get refreshingLastEvent => l10n.loadingPleaseWait;
@override
String startedAPoll(String senderName) => '$senderName started a poll';
@override
String get pollHasBeenEnded => l10n.pollHasBeenEnded;
}

View file

@ -180,7 +180,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f
audio_session: eaca2512cf2b39212d724f35d11f46180ad3a33e
desktop_drop: 248706031734554504f939cab1ad4c5fbc9c9c72
desktop_drop: 10a3e6a7fa9dbe350541f2574092fecfa345a07b
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
dynamic_color: cb7c2a300ee67ed3bd96c3e852df3af0300bf610
emoji_picker_flutter: 51ca408e289d84d1e460016b2a28721ec754fcf7

View file

@ -1508,7 +1508,7 @@ packages:
path: "/Users/ggurdin/pangea/matrix-dart-sdk"
relative: false
source: path
version: "3.0.0"
version: "3.0.2"
meta:
dependency: transitive
description:

View file

@ -64,7 +64,7 @@ dependencies:
# matrix: #^3.0.1
# git:
# url: https://github.com/famedly/matrix-dart-sdk.git
# ref: krille/refactor-update-user-device-keys
# ref: main
matrix:
path: /Users/ggurdin/pangea/matrix-dart-sdk
# Pangea#