bump matrix sdk to 0.40.0

This commit is contained in:
OfficialDakari 2025-05-10 12:42:34 +05:00
parent 442a5a5cdf
commit 9d7ed55d24
27 changed files with 358 additions and 64 deletions

View File

@ -1780,6 +1780,26 @@
"type": "String", "type": "String",
"placeholders": {} "placeholders": {}
}, },
"recoverMessage": "Recover message",
"@recoverMessage": {
"type": "String",
"placeholders": {}
},
"recoveredMessage": "Recovered message",
"@recoveredMessage": {
"type": "String",
"placeholders": {}
},
"errorRecoveringMessage": "An error has occured while recovering a message.",
"@errorRecoveringMessage": {
"type": "String",
"placeholders": {}
},
"errorRecoveringMessageNoAdmin": "This feature is available on Synapse homeservers only for adminstrators.",
"@errorRecoveringMessageNoAdmin": {
"type": "String",
"placeholders": {}
},
"requestPermission": "Request permission", "requestPermission": "Request permission",
"@requestPermission": { "@requestPermission": {
"type": "String", "type": "String",
@ -1800,6 +1820,11 @@
"type": "String", "type": "String",
"placeholders": {} "placeholders": {}
}, },
"retry": "Retry",
"@retry": {
"type": "String",
"placeholders": {}
},
"search": "Search", "search": "Search",
"@search": { "@search": {
"type": "String", "type": "String",
@ -2710,7 +2735,7 @@
"@openLinkInBrowser": {}, "@openLinkInBrowser": {},
"reportErrorDescription": "😭 Oh no. Something went wrong. If you want, you can report this bug to the developers.", "reportErrorDescription": "😭 Oh no. Something went wrong. If you want, you can report this bug to the developers.",
"@reportErrorDescription": {}, "@reportErrorDescription": {},
"report": "report", "report": "Report",
"@report": {}, "@report": {},
"signInWithPassword": "Sign in with password", "signInWithPassword": "Sign in with password",
"@signInWithPassword": {}, "@signInWithPassword": {},

View File

@ -1529,11 +1529,21 @@
"type": "String", "type": "String",
"placeholders": {} "placeholders": {}
}, },
"reportMessage": "Сообщить о сообщении", "reportMessage": "Пожаловаться",
"@reportMessage": { "@reportMessage": {
"type": "String", "type": "String",
"placeholders": {} "placeholders": {}
}, },
"retry": "Повторить",
"@retry": {
"type": "String",
"placeholders": {}
},
"recoverMessage": "Восстановить",
"@recoverMessage": {
"type": "String",
"placeholders": {}
},
"requestPermission": "Запросить разрешение", "requestPermission": "Запросить разрешение",
"@requestPermission": { "@requestPermission": {
"type": "String", "type": "String",
@ -2631,7 +2641,7 @@
"@makeAdminDescription": {}, "@makeAdminDescription": {},
"archiveRoomDescription": "Чат переместится в архив. Другим пользователям будет видно, что вы вышли из чата.", "archiveRoomDescription": "Чат переместится в архив. Другим пользователям будет видно, что вы вышли из чата.",
"@archiveRoomDescription": {}, "@archiveRoomDescription": {},
"hasKnocked": "🚪 {user} постучался", "hasKnocked": "🚪 {user} подал заявку на вступление",
"@hasKnocked": { "@hasKnocked": {
"placeholders": { "placeholders": {
"user": { "user": {
@ -2869,11 +2879,11 @@
"@customEmojisAndStickersBody": {}, "@customEmojisAndStickersBody": {},
"hideMemberChangesInPublicChatsBody": "Для улучшения читаемости не показывать на временной шкале входы и выходы из чата.", "hideMemberChangesInPublicChatsBody": "Для улучшения читаемости не показывать на временной шкале входы и выходы из чата.",
"@hideMemberChangesInPublicChatsBody": {}, "@hideMemberChangesInPublicChatsBody": {},
"knocking": "Стучаться", "knocking": "Подали заявку",
"@knocking": {}, "@knocking": {},
"accessAndVisibility": "Доступность и видимость", "accessAndVisibility": "Доступность и видимость",
"@accessAndVisibility": {}, "@accessAndVisibility": {},
"publicChatAddresses": "Адресы публичного чата", "publicChatAddresses": "Адреса публичного чата",
"@publicChatAddresses": {}, "@publicChatAddresses": {},
"accessAndVisibilityDescription": "Кому разрешено войти в этот чат и как этот чат может быть обнаружен.", "accessAndVisibilityDescription": "Кому разрешено войти в этот чат и как этот чат может быть обнаружен.",
"@accessAndVisibilityDescription": {}, "@accessAndVisibilityDescription": {},
@ -2881,7 +2891,7 @@
"@userRole": {}, "@userRole": {},
"noDatabaseEncryption": "Шифрование базы данных не поддерживается на этой платформе", "noDatabaseEncryption": "Шифрование базы данных не поддерживается на этой платформе",
"@noDatabaseEncryption": {}, "@noDatabaseEncryption": {},
"appLockDescription": "Заблокировать приложение когда не используется пин код", "appLockDescription": "Заблокировать приложение, когда не используется пин-код",
"@appLockDescription": {}, "@appLockDescription": {},
"calls": "Звонки", "calls": "Звонки",
"@calls": {}, "@calls": {},
@ -2889,7 +2899,7 @@
"@customEmojisAndStickers": {}, "@customEmojisAndStickers": {},
"hideRedactedMessages": "Скрыть редактированные сообщения", "hideRedactedMessages": "Скрыть редактированные сообщения",
"@hideRedactedMessages": {}, "@hideRedactedMessages": {},
"hideInvalidOrUnknownMessageFormats": "Скрыть неправильные или неизвестные форматы сообщения", "hideInvalidOrUnknownMessageFormats": "Скрыть неверные или неизвестные форматы сообщения",
"@hideInvalidOrUnknownMessageFormats": {}, "@hideInvalidOrUnknownMessageFormats": {},
"hideRedactedMessagesBody": "Если кто-то редактирует сообщение, оно будет скрыто в чате.", "hideRedactedMessagesBody": "Если кто-то редактирует сообщение, оно будет скрыто в чате.",
"@hideRedactedMessagesBody": {}, "@hideRedactedMessagesBody": {},
@ -2909,9 +2919,9 @@
} }
} }
}, },
"knock": "Постучаться", "knock": "Подать заявку",
"@knock": {}, "@knock": {},
"usersMustKnock": "Пользователи должны постучаться", "usersMustKnock": "По заявке на вступление",
"@usersMustKnock": {}, "@usersMustKnock": {},
"noOneCanJoin": "Никто не может присоединиться", "noOneCanJoin": "Никто не может присоединиться",
"@noOneCanJoin": {}, "@noOneCanJoin": {},
@ -2928,7 +2938,7 @@
}, },
"createNewAddress": "Создать новый адрес", "createNewAddress": "Создать новый адрес",
"@createNewAddress": {}, "@createNewAddress": {},
"minimumPowerLevel": "{level} является минимальным уровнем.", "minimumPowerLevel": "{level} является минимальным уровнем прав.",
"@minimumPowerLevel": { "@minimumPowerLevel": {
"type": "String", "type": "String",
"placeholders": { "placeholders": {
@ -2937,9 +2947,9 @@
} }
} }
}, },
"commandHint_ignore": "Игнорировать данный matrix ID", "commandHint_ignore": "Игнорировать данный Matrix ID",
"@commandHint_ignore": {}, "@commandHint_ignore": {},
"commandHint_unignore": "Не игнорировать данный matrix ID", "commandHint_unignore": "Перестать игнорировать данный Matrix ID",
"@commandHint_unignore": {}, "@commandHint_unignore": {},
"unreadChatsInApp": "{appname}: {unread} непрочитанные чаты", "unreadChatsInApp": "{appname}: {unread} непрочитанные чаты",
"@unreadChatsInApp": { "@unreadChatsInApp": {

View File

@ -1,6 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:fluffychat/pages/chat/recovered_event_dialog.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/synapse_admin_extension.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -271,6 +273,7 @@ class ChatController extends State<ChatPageWithRoom>
files: files, files: files,
room: room, room: room,
outerContext: context, outerContext: context,
replyEvent: replyEvent
), ),
); );
} }
@ -552,8 +555,10 @@ class ChatController extends State<ChatPageWithRoom>
files: files, files: files,
room: room, room: room,
outerContext: context, outerContext: context,
replyEvent: replyEvent,
), ),
); );
replyEvent = null;
} }
void sendImageFromClipBoard(Uint8List? image) async { void sendImageFromClipBoard(Uint8List? image) async {
@ -712,6 +717,38 @@ class ChatController extends State<ChatPageWithRoom>
}); });
} }
void recoverEventAction() async {
final mx = Matrix.of(context);
if (!await mx.client.isSynapseAdministrator()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).errorRecoveringMessageNoAdmin)),
);
return;
}
final event = selectedEvents.single;
await mx.client.reportEvent(roomId, event.eventId,
reason: "Extera (Next) Redacted Event Recover");
final reports = await mx.client.getEventReports();
final report = reports.firstWhere(
(rep) => rep['room_id'] == roomId && rep['event_id'] == event.eventId);
final recoveredEvent = await mx.client.getReportedEvent(report['id']);
if (recoveredEvent == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).errorRecoveringMessage)),
);
return;
}
Navigator.of(context).push(new MaterialPageRoute(
builder: (BuildContext ctx) {
return RecoveredEventDialog(event: recoveredEvent!, timeline: timeline!);
},
fullscreenDialog: true
));
}
void reportEventAction() async { void reportEventAction() async {
final event = selectedEvents.single; final event = selectedEvents.single;
final score = await showModalActionPopup<int>( final score = await showModalActionPopup<int>(
@ -757,9 +794,6 @@ class ChatController extends State<ChatPageWithRoom>
showEmojiPicker = false; showEmojiPicker = false;
selectedEvents.clear(); selectedEvents.clear();
}); });
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(L10n.of(context).contentHasBeenReported)),
);
} }
void deleteErrorEventsAction() async { void deleteErrorEventsAction() async {
@ -1087,20 +1121,18 @@ class ChatController extends State<ChatPageWithRoom>
} }
void onSelectMessage(Event event) { void onSelectMessage(Event event) {
if (!event.redacted) { if (selectedEvents.contains(event)) {
if (selectedEvents.contains(event)) { setState(
setState( () => selectedEvents.remove(event),
() => selectedEvents.remove(event), );
); } else {
} else { setState(
setState( () => selectedEvents.add(event),
() => selectedEvents.add(event),
);
}
selectedEvents.sort(
(a, b) => a.originServerTs.compareTo(b.originServerTs),
); );
} }
selectedEvents.sort(
(a, b) => a.originServerTs.compareTo(b.originServerTs),
);
} }
int? findChildIndexCallback(Key key, Map<String, int> thisEventsKeyMap) { int? findChildIndexCallback(Key key, Map<String, int> thisEventsKeyMap) {

View File

@ -43,6 +43,7 @@ class ChatEventList extends StatelessWidget {
// ListView's findChildIndexCallback // ListView's findChildIndexCallback
final thisEventsKeyMap = <String, int>{}; final thisEventsKeyMap = <String, int>{};
for (var i = 0; i < events.length; i++) { for (var i = 0; i < events.length; i++) {
//print('${events[i].roomId} ${events[i].senderId} ${events[i].body}');
thisEventsKeyMap[events[i].eventId] = i; thisEventsKeyMap[events[i].eventId] = i;
} }

View File

@ -28,7 +28,7 @@ import '../../utils/stream_extension.dart';
import 'chat_emoji_picker.dart'; import 'chat_emoji_picker.dart';
import 'chat_input_row.dart'; import 'chat_input_row.dart';
enum _EventContextAction { info, report } enum _EventContextAction { info, recover, report }
class ChatView extends StatelessWidget { class ChatView extends StatelessWidget {
final ChatController controller; final ChatController controller;
@ -81,6 +81,9 @@ class ChatView extends StatelessWidget {
case _EventContextAction.report: case _EventContextAction.report:
controller.reportEventAction(); controller.reportEventAction();
break; break;
case _EventContextAction.recover:
controller.recoverEventAction();
break;
} }
}, },
itemBuilder: (context) => [ itemBuilder: (context) => [
@ -95,6 +98,15 @@ class ChatView extends StatelessWidget {
], ],
), ),
), ),
if (controller.selectedEvents.single.redacted)
PopupMenuItem(
value: _EventContextAction.recover,
child: Row(mainAxisSize: MainAxisSize.min, children: [
const Icon(Icons.redo),
const SizedBox(width: 12),
Text(L10n.of(context).recoverMessage),
]),
),
if (controller.selectedEvents.single.status.isSent) if (controller.selectedEvents.single.status.isSent)
PopupMenuItem( PopupMenuItem(
value: _EventContextAction.report, value: _EventContextAction.report,

View File

@ -38,7 +38,7 @@ class Message extends StatelessWidget {
final bool animateIn; final bool animateIn;
final void Function()? resetAnimateIn; final void Function()? resetAnimateIn;
final bool wallpaperMode; final bool wallpaperMode;
final ScrollController scrollController; final ScrollController? scrollController;
final List<Color> colors; final List<Color> colors;
const Message( const Message(
@ -58,7 +58,7 @@ class Message extends StatelessWidget {
this.resetAnimateIn, this.resetAnimateIn,
this.wallpaperMode = false, this.wallpaperMode = false,
required this.onMention, required this.onMention,
required this.scrollController, this.scrollController,
required this.colors, required this.colors,
super.key, super.key,
}); });
@ -597,13 +597,13 @@ class Message extends StatelessWidget {
class BubbleBackground extends StatelessWidget { class BubbleBackground extends StatelessWidget {
const BubbleBackground({ const BubbleBackground({
super.key, super.key,
required this.scrollController,
required this.colors, required this.colors,
required this.ignore, required this.ignore,
required this.child, required this.child,
this.scrollController,
}); });
final ScrollController scrollController; final ScrollController? scrollController;
final List<Color> colors; final List<Color> colors;
final bool ignore; final bool ignore;
final Widget child; final Widget child;
@ -635,6 +635,7 @@ class BubblePainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (!(context.widget is ScrollView || context.widget is SingleChildScrollView)) return;
final scrollable = _scrollable ??= Scrollable.of(context); final scrollable = _scrollable ??= Scrollable.of(context);
final scrollableBox = scrollable.context.findRenderObject() as RenderBox; final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
final scrollableRect = Offset.zero & scrollableBox.size; final scrollableRect = Offset.zero & scrollableBox.size;

View File

@ -113,7 +113,7 @@ class MessageContent extends StatelessWidget {
case MessageTypes.Image: case MessageTypes.Image:
case MessageTypes.Sticker: case MessageTypes.Sticker:
if (event.redacted) continue textmessage; if (event.redacted) continue textmessage;
const maxSize = 256.0; final maxSize = event.messageType == MessageTypes.Image ? 512.0 : 256.0;
final w = event.content final w = event.content
.tryGetMap<String, Object?>('info') .tryGetMap<String, Object?>('info')
?.tryGet<int>('w'); ?.tryGet<int>('w');

View File

@ -1,3 +1,4 @@
import 'package:fluffychat/pages/chat/events/html_message.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:flutter_linkify/flutter_linkify.dart';
@ -79,7 +80,7 @@ class MessageDownloadContent extends StatelessWidget {
), ),
), ),
), ),
if (fileDescription != null) ...[ if (fileDescription != null && !event.isRichFileDescription) ...[
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16.0, horizontal: 16.0,
@ -103,6 +104,27 @@ class MessageDownloadContent extends StatelessWidget {
), ),
), ),
], ],
if (fileDescription != null && event.isRichFileDescription) ...[
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: HtmlMessage(
html: fileDescription,
textColor: textColor,
room: event.room,
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
linkStyle: TextStyle(
color: linkColor,
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
),
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
),
),
],
], ],
); );
} }

View File

@ -152,7 +152,7 @@ class InputBar extends StatelessWidget {
} }
} }
} }
final userMatch = RegExp(r'(?:\s|^)@([-\w]+)$').firstMatch(searchText); final userMatch = RegExp(r'(?:\s|^)@([^ \[\]]+)$', unicode: true).firstMatch(searchText);
if (userMatch != null) { if (userMatch != null) {
final userSearch = userMatch[1]!.toLowerCase(); final userSearch = userMatch[1]!.toLowerCase();
for (final user in room.getParticipants()) { for (final user in room.getParticipants()) {

View File

@ -0,0 +1,67 @@
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/events/message.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class RecoveredEventDialog extends StatefulWidget {
final Event event;
final Timeline timeline;
const RecoveredEventDialog({
required this.event,
required this.timeline,
super.key,
});
@override
RecoveredEventDialogState createState() =>
RecoveredEventDialogState(event, timeline);
}
class RecoveredEventDialogState extends State<RecoveredEventDialog> {
final Event event;
final Timeline timeline;
RecoveredEventDialogState(this.event, this.timeline);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colors = [
theme.secondaryBubbleColor,
theme.bubbleColor,
];
final message = Message(
event,
colors: colors,
onInfoTab: (Event ev) => {},
onMention: () => {},
onSelect: (Event ev) => {},
onSwipe: () => {},
scrollToEventId: (String p0) => {},
timeline: timeline,
animateIn: false,
displayReadMarker: false,
highlightMarker: false,
longPressSelect: false,
selected: false,
wallpaperMode: false,
);
return Scaffold(
appBar: AppBar(
title: Text(L10n.of(context).recoveredMessage)
),
body: Container(
child: Column(
children: [
message,
],
),
),
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -15,16 +16,20 @@ import 'package:fluffychat/utils/size_string.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/dialog_text_field.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/dialog_text_field.dart';
import '../../utils/resize_video.dart'; import '../../utils/resize_video.dart';
import 'package:matrix/src/utils/markdown.dart';
import 'package:html_unescape/html_unescape.dart';
class SendFileDialog extends StatefulWidget { class SendFileDialog extends StatefulWidget {
final Room room; final Room room;
final List<XFile> files; final List<XFile> files;
final BuildContext outerContext; final BuildContext outerContext;
final Event? replyEvent;
const SendFileDialog({ const SendFileDialog({
required this.room, required this.room,
required this.files, required this.files,
required this.outerContext, required this.outerContext,
this.replyEvent,
super.key, super.key,
}); });
@ -97,13 +102,40 @@ class SendFileDialogState extends State<SendFileDialog> {
} }
final label = _labelTextController.text.trim(); final label = _labelTextController.text.trim();
final extraContent = <String, dynamic>{};
if (label.isNotEmpty) {
extraContent['body'] = label;
final html = markdown(
label,
getEmotePacks: () => widget.room.getImagePacksFlat(ImagePackUsage.emoticon),
getMention: widget.room.getMention,
convertLinebreaks: Matrix.of(context).client.convertLinebreaksInFormatting,
);
// if the decoded html is the same as the body, there is no need in sending a formatted message
if (HtmlUnescape()
.convert(html.replaceAll(RegExp(r'<br />\n?'), '\n')) !=
label) {
extraContent['format'] = 'org.matrix.custom.html';
extraContent['formatted_body'] = html;
}
}
if (widget.replyEvent != null) {
extraContent['m.relates_to'] = {
'm.in_reply_to': {
'event_id': widget.replyEvent!.eventId,
},
};
}
try { try {
await widget.room.sendFileEvent( await widget.room.sendFileEvent(
file, file,
thumbnail: thumbnail, thumbnail: thumbnail,
shrinkImageMaxDimension: compress ? 1600 : null, shrinkImageMaxDimension: compress ? 1600 : null,
extraContent: label.isEmpty ? null : {'body': label}, extraContent: extraContent,
); );
} on MatrixException catch (e) { } on MatrixException catch (e) {
final retryAfterMs = e.retryAfterMs; final retryAfterMs = e.retryAfterMs;
@ -128,12 +160,13 @@ class SendFileDialogState extends State<SendFileDialog> {
file, file,
thumbnail: thumbnail, thumbnail: thumbnail,
shrinkImageMaxDimension: compress ? 1600 : null, shrinkImageMaxDimension: compress ? 1600 : null,
extraContent: label.isEmpty ? null : {'body': label}, extraContent: extraContent,
); );
} }
} }
scaffoldMessenger.clearSnackBars(); scaffoldMessenger.clearSnackBars();
} catch (e) { } catch (e) {
print('error: ${e.toString()}');
scaffoldMessenger.clearSnackBars(); scaffoldMessenger.clearSnackBars();
final theme = Theme.of(context); final theme = Theme.of(context);
scaffoldMessenger.showSnackBar( scaffoldMessenger.showSnackBar(

View File

@ -37,6 +37,7 @@ class SendLocationDialogState extends State<SendLocationDialog> {
} }
Future<void> requestLocation() async { Future<void> requestLocation() async {
error = null;
if (!(await Geolocator.isLocationServiceEnabled())) { if (!(await Geolocator.isLocationServiceEnabled())) {
setState(() => disabled = true); setState(() => disabled = true);
return; return;
@ -58,15 +59,15 @@ class SendLocationDialogState extends State<SendLocationDialog> {
try { try {
position = await Geolocator.getCurrentPosition( position = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings( locationSettings: const LocationSettings(
accuracy: LocationAccuracy.best, accuracy: LocationAccuracy.high,
timeLimit: Duration(seconds: 30), timeLimit: Duration(seconds: 5),
), ),
); );
} on TimeoutException { } on TimeoutException {
position = await Geolocator.getCurrentPosition( position = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings( locationSettings: const LocationSettings(
accuracy: LocationAccuracy.medium, accuracy: LocationAccuracy.medium,
timeLimit: Duration(seconds: 30), timeLimit: Duration(seconds: 5),
), ),
); );
} }
@ -123,6 +124,8 @@ class SendLocationDialogState extends State<SendLocationDialog> {
onPressed: Navigator.of(context, rootNavigator: false).pop, onPressed: Navigator.of(context, rootNavigator: false).pop,
child: Text(L10n.of(context).cancel), child: Text(L10n.of(context).cancel),
), ),
if (error != null)
AdaptiveDialogAction(onPressed: requestLocation, child: Text(L10n.of(context).retry)),
if (position != null) if (position != null)
AdaptiveDialogAction( AdaptiveDialogAction(
onPressed: isSending ? null : sendAction, onPressed: isSending ? null : sendAction,

View File

@ -27,6 +27,7 @@ class NewGroupController extends State<NewGroup> {
bool publicGroup = false; bool publicGroup = false;
bool groupCanBeFound = false; bool groupCanBeFound = false;
bool enableEncryption = false;
Uint8List? avatar; Uint8List? avatar;
@ -49,6 +50,8 @@ class NewGroupController extends State<NewGroup> {
void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b); void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b);
void setEnableEncryption(bool b) => setState(() => enableEncryption = b);
void selectPhoto() async { void selectPhoto() async {
final photo = await selectFiles( final photo = await selectFiles(
context, context,
@ -68,9 +71,7 @@ class NewGroupController extends State<NewGroup> {
final roomId = await Matrix.of(context).client.createGroupChat( final roomId = await Matrix.of(context).client.createGroupChat(
visibility: visibility:
groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private, groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private,
preset: publicGroup enableEncryption: enableEncryption,
? sdk.CreateRoomPreset.publicChat
: sdk.CreateRoomPreset.privateChat,
groupName: nameController.text.isNotEmpty ? nameController.text : null, groupName: nameController.text.isNotEmpty ? nameController.text : null,
initialState: [ initialState: [
if (avatar != null) if (avatar != null)
@ -78,8 +79,16 @@ class NewGroupController extends State<NewGroup> {
type: sdk.EventTypes.RoomAvatar, type: sdk.EventTypes.RoomAvatar,
content: {'url': avatarUrl.toString()}, content: {'url': avatarUrl.toString()},
), ),
sdk.StateEvent(
type: sdk.EventTypes.RoomJoinRules,
content: {'join_rule': publicGroup ? 'public' : 'invite'}
)
], ],
); );
final room = Matrix.of(context).client.getRoomById(roomId);
// if (room != null) {
// room.setJoinRules(publicGroup ? JoinRules.public : JoinRules.invite);
// }
if (!mounted) return; if (!mounted) return;
context.go('/rooms/$roomId/invite'); context.go('/rooms/$roomId/invite');
} }

View File

@ -134,8 +134,10 @@ class NewGroupView extends StatelessWidget {
color: theme.colorScheme.onSurface, color: theme.colorScheme.onSurface,
), ),
), ),
value: !controller.publicGroup, value: controller.enableEncryption,
onChanged: null, onChanged: controller.loading
? null
: controller.setEnableEncryption,
), ),
), ),
AnimatedSize( AnimatedSize(

View File

@ -226,7 +226,7 @@ class SettingsStyleView extends StatelessWidget {
vertical: 8, vertical: 8,
), ),
child: Text( child: Text(
'What is Extera Next?', 'погнали в роблокс?',
style: TextStyle( style: TextStyle(
color: theme.onBubbleColor, color: theme.onBubbleColor,
fontSize: AppConfig.messageFontSize * fontSize: AppConfig.messageFontSize *
@ -259,7 +259,7 @@ class SettingsStyleView extends StatelessWidget {
vertical: 8, vertical: 8,
), ),
child: Text( child: Text(
'It is another attempt at making Extera, this time it is a fork of FluffyChat, that eliminates a lot of cons of Cinny-based (Web) Extera, like: slow chats loading, laggy animations.', 'Го, в toilet tower defense',
style: TextStyle( style: TextStyle(
color: theme.colorScheme.onSurface, color: theme.colorScheme.onSurface,
fontSize: AppConfig.messageFontSize * fontSize: AppConfig.messageFontSize *

View File

@ -18,7 +18,6 @@ import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/utils/custom_http_client.dart'; import 'package:fluffychat/utils/custom_http_client.dart';
import 'package:fluffychat/utils/custom_image_resizer.dart'; import 'package:fluffychat/utils/custom_image_resizer.dart';
import 'package:fluffychat/utils/init_with_restore.dart'; import 'package:fluffychat/utils/init_with_restore.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart'; import 'matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart';
@ -119,7 +118,7 @@ abstract class ClientManager {
}, },
logLevel: kReleaseMode ? Level.warning : Level.verbose, logLevel: kReleaseMode ? Level.warning : Level.verbose,
databaseBuilder: flutterMatrixSdkDatabaseBuilder, databaseBuilder: flutterMatrixSdkDatabaseBuilder,
legacyDatabaseBuilder: FlutterHiveCollectionsDatabase.databaseBuilder, //legacyDatabaseBuilder: FlutterHiveCollectionsDatabase.databaseBuilder,
supportedLoginTypes: { supportedLoginTypes: {
AuthenticationTypes.password, AuthenticationTypes.password,
AuthenticationTypes.sso, AuthenticationTypes.sso,

View File

@ -28,7 +28,8 @@ extension DateTimeExtension on DateTime {
/// Returns a simple time String. /// Returns a simple time String.
String localizedTimeOfDay(BuildContext context) => String localizedTimeOfDay(BuildContext context) =>
L10n.of(context).alwaysUse24HourFormat == 'true' (MediaQuery.alwaysUse24HourFormatOf(context) ||
L10n.of(context).alwaysUse24HourFormat == 'true')
? DateFormat('HH:mm', L10n.of(context).localeName).format(this) ? DateFormat('HH:mm', L10n.of(context).localeName).format(this)
: DateFormat('h:mm a', L10n.of(context).localeName).format(this); : DateFormat('h:mm a', L10n.of(context).localeName).format(this);

View File

@ -55,9 +55,5 @@ extension IsStateExtension on Event {
content.tryGet<String>('membership') == 'ban' || content.tryGet<String>('membership') == 'ban' ||
stateKey != senderId); stateKey != senderId);
bool get isState => !{ bool get isState => this.stateKey != null;
EventTypes.Message,
EventTypes.Sticker,
EventTypes.Encrypted,
}.contains(type);
} }

View File

@ -11,7 +11,6 @@ import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'cipher.dart'; import 'cipher.dart';
@ -55,7 +54,9 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
Logs().e('Unable to send error notification', e, s); Logs().e('Unable to send error notification', e, s);
} }
return FlutterHiveCollectionsDatabase.databaseBuilder(client); database = await _constructDatabase(client);
await database.open();
return database;
} }
} }

View File

@ -7,6 +7,11 @@ class MatrixLocals extends MatrixLocalizations {
MatrixLocals(this.l10n); MatrixLocals(this.l10n);
@override
String voiceMessage(String senderName, Duration? duration) {
return l10n.voiceMessage;
}
@override @override
String acceptedTheInvitation(String targetName) { String acceptedTheInvitation(String targetName) {
return l10n.acceptedTheInvitation(targetName); return l10n.acceptedTheInvitation(targetName);

View File

@ -0,0 +1,75 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:matrix/matrix.dart' as matrix;
extension SynapseAdmin on matrix.Client {
Future<List<dynamic>> getEventReports({int from = 0, int limit = 10}) async {
final requestUri = Uri(
path: '/_synapse/admin/v1/event_reports',
query: 'from=$from&limit=$limit&order_by=received_ts&dir=b'
);
if (baseUri == null) return [];
print(baseUri!.resolveUri(requestUri).toString());
final request = Request('GET', baseUri!.resolveUri(requestUri));
request.headers['authorization'] = 'Bearer $accessToken';
final response = await httpClient.send(request);
final responseBody = await response.stream.toBytes();
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
final responseString = utf8.decode(responseBody);
final json = jsonDecode(responseString);
return json['event_reports'];
}
Future<matrix.Event?> getReportedEvent(int id) async {
final requestUri = Uri(
path: '/_synapse/admin/v1/event_reports/$id',
);
if (baseUri == null) return null;
final request = Request('GET', baseUri!.resolveUri(requestUri));
request.headers['authorization'] = 'Bearer $accessToken';
final response = await httpClient.send(request);
final responseBody = await response.stream.toBytes();
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
final responseString = utf8.decode(responseBody);
final json = jsonDecode(responseString);
final room = getRoomById(json['room_id']);
if (room == null) return null;
print('Event content: ${jsonEncode(json['event_json'])}');
return new matrix.Event(
content: json['event_json']['content'],
type: json['event_json']['type'],
eventId: json['event_id'],
senderId: json['sender'],
originServerTs: DateTime.fromMillisecondsSinceEpoch(json['event_json']['origin_server_ts']),
room: room,
);
}
Future<bool> isSynapseAdministrator() async {
print('Checking if I am admin...');
print('User ID: $userID');
if (userID == null) return false;
final requestUri = Uri(
path: '/_synapse/admin/v1/users/$userID/admin',
);
print('Base URL: ${baseUri.toString()}');
if (baseUri == null) return false;
final request = Request('GET', baseUri!.resolveUri(requestUri));
request.headers['authorization'] = 'Bearer $accessToken';
final response = await httpClient.send(request);
final responseBody = await response.stream.toBytes();
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
final responseString = utf8.decode(responseBody);
final json = jsonDecode(responseString);
print('Response from endpoint: $responseString');
return json['admin'];
}
}

View File

@ -53,11 +53,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) { if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "FluffyChat"); gtk_header_bar_set_title(header_bar, "Extera Next");
gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else { } else {
gtk_window_set_title(window, "FluffyChat"); gtk_window_set_title(window, "Extera Next");
} }
gtk_window_set_default_size(window, 864, 720); gtk_window_set_default_size(window, 864, 720);

View File

@ -1159,10 +1159,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: matrix name: matrix
sha256: "829f225f74b2a8c83b6b1f960004ecf742215d9ee9d8cc9e0f1974ff8e281f0f" sha256: "7d15fdbc760be7e40c58bb65e03baa8241b1e31db2bc67dab61883aabc083a85"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.39.1" version: "0.40.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:

View File

@ -62,7 +62,7 @@ dependencies:
latlong2: ^0.9.1 latlong2: ^0.9.1
linkify: ^5.0.0 linkify: ^5.0.0
material: ^1.0.0+2 material: ^1.0.0+2
matrix: ^0.39.1 matrix: 0.40.0
mime: ^1.0.6 mime: ^1.0.6
native_imaging: ^0.2.0 native_imaging: ^0.2.0
opus_caf_converter_dart: ^1.0.1 opus_caf_converter_dart: ^1.0.1

View File

@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"FluffyChat", origin, size)) { if (!window.CreateAndShow(L"Extera Next", origin, size)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);