bump matrix sdk to 0.40.0
This commit is contained in:
parent
442a5a5cdf
commit
9d7ed55d24
|
|
@ -1780,6 +1780,26 @@
|
|||
"type": "String",
|
||||
"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": {
|
||||
"type": "String",
|
||||
|
|
@ -1800,6 +1820,11 @@
|
|||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"retry": "Retry",
|
||||
"@retry": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"search": "Search",
|
||||
"@search": {
|
||||
"type": "String",
|
||||
|
|
@ -2710,7 +2735,7 @@
|
|||
"@openLinkInBrowser": {},
|
||||
"reportErrorDescription": "😭 Oh no. Something went wrong. If you want, you can report this bug to the developers.",
|
||||
"@reportErrorDescription": {},
|
||||
"report": "report",
|
||||
"report": "Report",
|
||||
"@report": {},
|
||||
"signInWithPassword": "Sign in with password",
|
||||
"@signInWithPassword": {},
|
||||
|
|
|
|||
|
|
@ -1529,11 +1529,21 @@
|
|||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"reportMessage": "Сообщить о сообщении",
|
||||
"reportMessage": "Пожаловаться",
|
||||
"@reportMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"retry": "Повторить",
|
||||
"@retry": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"recoverMessage": "Восстановить",
|
||||
"@recoverMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"requestPermission": "Запросить разрешение",
|
||||
"@requestPermission": {
|
||||
"type": "String",
|
||||
|
|
@ -2631,7 +2641,7 @@
|
|||
"@makeAdminDescription": {},
|
||||
"archiveRoomDescription": "Чат переместится в архив. Другим пользователям будет видно, что вы вышли из чата.",
|
||||
"@archiveRoomDescription": {},
|
||||
"hasKnocked": "🚪 {user} постучался",
|
||||
"hasKnocked": "🚪 {user} подал заявку на вступление",
|
||||
"@hasKnocked": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
|
|
@ -2869,11 +2879,11 @@
|
|||
"@customEmojisAndStickersBody": {},
|
||||
"hideMemberChangesInPublicChatsBody": "Для улучшения читаемости не показывать на временной шкале входы и выходы из чата.",
|
||||
"@hideMemberChangesInPublicChatsBody": {},
|
||||
"knocking": "Стучаться",
|
||||
"knocking": "Подали заявку",
|
||||
"@knocking": {},
|
||||
"accessAndVisibility": "Доступность и видимость",
|
||||
"@accessAndVisibility": {},
|
||||
"publicChatAddresses": "Адресы публичного чата",
|
||||
"publicChatAddresses": "Адреса публичного чата",
|
||||
"@publicChatAddresses": {},
|
||||
"accessAndVisibilityDescription": "Кому разрешено войти в этот чат и как этот чат может быть обнаружен.",
|
||||
"@accessAndVisibilityDescription": {},
|
||||
|
|
@ -2881,7 +2891,7 @@
|
|||
"@userRole": {},
|
||||
"noDatabaseEncryption": "Шифрование базы данных не поддерживается на этой платформе",
|
||||
"@noDatabaseEncryption": {},
|
||||
"appLockDescription": "Заблокировать приложение когда не используется пин код",
|
||||
"appLockDescription": "Заблокировать приложение, когда не используется пин-код",
|
||||
"@appLockDescription": {},
|
||||
"calls": "Звонки",
|
||||
"@calls": {},
|
||||
|
|
@ -2889,7 +2899,7 @@
|
|||
"@customEmojisAndStickers": {},
|
||||
"hideRedactedMessages": "Скрыть редактированные сообщения",
|
||||
"@hideRedactedMessages": {},
|
||||
"hideInvalidOrUnknownMessageFormats": "Скрыть неправильные или неизвестные форматы сообщения",
|
||||
"hideInvalidOrUnknownMessageFormats": "Скрыть неверные или неизвестные форматы сообщения",
|
||||
"@hideInvalidOrUnknownMessageFormats": {},
|
||||
"hideRedactedMessagesBody": "Если кто-то редактирует сообщение, оно будет скрыто в чате.",
|
||||
"@hideRedactedMessagesBody": {},
|
||||
|
|
@ -2909,9 +2919,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"knock": "Постучаться",
|
||||
"knock": "Подать заявку",
|
||||
"@knock": {},
|
||||
"usersMustKnock": "Пользователи должны постучаться",
|
||||
"usersMustKnock": "По заявке на вступление",
|
||||
"@usersMustKnock": {},
|
||||
"noOneCanJoin": "Никто не может присоединиться",
|
||||
"@noOneCanJoin": {},
|
||||
|
|
@ -2928,7 +2938,7 @@
|
|||
},
|
||||
"createNewAddress": "Создать новый адрес",
|
||||
"@createNewAddress": {},
|
||||
"minimumPowerLevel": "{level} является минимальным уровнем.",
|
||||
"minimumPowerLevel": "{level} является минимальным уровнем прав.",
|
||||
"@minimumPowerLevel": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
|
|
@ -2937,9 +2947,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"commandHint_ignore": "Игнорировать данный matrix ID",
|
||||
"commandHint_ignore": "Игнорировать данный Matrix ID",
|
||||
"@commandHint_ignore": {},
|
||||
"commandHint_unignore": "Не игнорировать данный matrix ID",
|
||||
"commandHint_unignore": "Перестать игнорировать данный Matrix ID",
|
||||
"@commandHint_unignore": {},
|
||||
"unreadChatsInApp": "{appname}: {unread} непрочитанные чаты",
|
||||
"@unreadChatsInApp": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import 'dart:async';
|
||||
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/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -271,6 +273,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
files: files,
|
||||
room: room,
|
||||
outerContext: context,
|
||||
replyEvent: replyEvent
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -552,8 +555,10 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
files: files,
|
||||
room: room,
|
||||
outerContext: context,
|
||||
replyEvent: replyEvent,
|
||||
),
|
||||
);
|
||||
replyEvent = null;
|
||||
}
|
||||
|
||||
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 {
|
||||
final event = selectedEvents.single;
|
||||
final score = await showModalActionPopup<int>(
|
||||
|
|
@ -757,9 +794,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
showEmojiPicker = false;
|
||||
selectedEvents.clear();
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context).contentHasBeenReported)),
|
||||
);
|
||||
}
|
||||
|
||||
void deleteErrorEventsAction() async {
|
||||
|
|
@ -1087,20 +1121,18 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
void onSelectMessage(Event event) {
|
||||
if (!event.redacted) {
|
||||
if (selectedEvents.contains(event)) {
|
||||
setState(
|
||||
() => selectedEvents.remove(event),
|
||||
);
|
||||
} else {
|
||||
setState(
|
||||
() => selectedEvents.add(event),
|
||||
);
|
||||
}
|
||||
selectedEvents.sort(
|
||||
(a, b) => a.originServerTs.compareTo(b.originServerTs),
|
||||
if (selectedEvents.contains(event)) {
|
||||
setState(
|
||||
() => selectedEvents.remove(event),
|
||||
);
|
||||
} else {
|
||||
setState(
|
||||
() => selectedEvents.add(event),
|
||||
);
|
||||
}
|
||||
selectedEvents.sort(
|
||||
(a, b) => a.originServerTs.compareTo(b.originServerTs),
|
||||
);
|
||||
}
|
||||
|
||||
int? findChildIndexCallback(Key key, Map<String, int> thisEventsKeyMap) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class ChatEventList extends StatelessWidget {
|
|||
];
|
||||
|
||||
final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0;
|
||||
|
||||
|
||||
final events = timeline.events.filterByVisibleInGui();
|
||||
final animateInEventIndex = controller.animateInEventIndex;
|
||||
|
||||
|
|
@ -43,6 +43,7 @@ class ChatEventList extends StatelessWidget {
|
|||
// ListView's findChildIndexCallback
|
||||
final thisEventsKeyMap = <String, int>{};
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
//print('${events[i].roomId} ${events[i].senderId} ${events[i].body}');
|
||||
thisEventsKeyMap[events[i].eventId] = i;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import '../../utils/stream_extension.dart';
|
|||
import 'chat_emoji_picker.dart';
|
||||
import 'chat_input_row.dart';
|
||||
|
||||
enum _EventContextAction { info, report }
|
||||
enum _EventContextAction { info, recover, report }
|
||||
|
||||
class ChatView extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
|
@ -81,6 +81,9 @@ class ChatView extends StatelessWidget {
|
|||
case _EventContextAction.report:
|
||||
controller.reportEventAction();
|
||||
break;
|
||||
case _EventContextAction.recover:
|
||||
controller.recoverEventAction();
|
||||
break;
|
||||
}
|
||||
},
|
||||
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)
|
||||
PopupMenuItem(
|
||||
value: _EventContextAction.report,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class Message extends StatelessWidget {
|
|||
final bool animateIn;
|
||||
final void Function()? resetAnimateIn;
|
||||
final bool wallpaperMode;
|
||||
final ScrollController scrollController;
|
||||
final ScrollController? scrollController;
|
||||
final List<Color> colors;
|
||||
|
||||
const Message(
|
||||
|
|
@ -58,7 +58,7 @@ class Message extends StatelessWidget {
|
|||
this.resetAnimateIn,
|
||||
this.wallpaperMode = false,
|
||||
required this.onMention,
|
||||
required this.scrollController,
|
||||
this.scrollController,
|
||||
required this.colors,
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -597,13 +597,13 @@ class Message extends StatelessWidget {
|
|||
class BubbleBackground extends StatelessWidget {
|
||||
const BubbleBackground({
|
||||
super.key,
|
||||
required this.scrollController,
|
||||
required this.colors,
|
||||
required this.ignore,
|
||||
required this.child,
|
||||
this.scrollController,
|
||||
});
|
||||
|
||||
final ScrollController scrollController;
|
||||
final ScrollController? scrollController;
|
||||
final List<Color> colors;
|
||||
final bool ignore;
|
||||
final Widget child;
|
||||
|
|
@ -635,6 +635,7 @@ class BubblePainter extends CustomPainter {
|
|||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (!(context.widget is ScrollView || context.widget is SingleChildScrollView)) return;
|
||||
final scrollable = _scrollable ??= Scrollable.of(context);
|
||||
final scrollableBox = scrollable.context.findRenderObject() as RenderBox;
|
||||
final scrollableRect = Offset.zero & scrollableBox.size;
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class MessageContent extends StatelessWidget {
|
|||
case MessageTypes.Image:
|
||||
case MessageTypes.Sticker:
|
||||
if (event.redacted) continue textmessage;
|
||||
const maxSize = 256.0;
|
||||
final maxSize = event.messageType == MessageTypes.Image ? 512.0 : 256.0;
|
||||
final w = event.content
|
||||
.tryGetMap<String, Object?>('info')
|
||||
?.tryGet<int>('w');
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fluffychat/pages/chat/events/html_message.dart';
|
||||
import 'package:flutter/material.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: const EdgeInsets.symmetric(
|
||||
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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
final userSearch = userMatch[1]!.toLowerCase();
|
||||
for (final user in room.getParticipants()) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/cupertino.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/dialog_text_field.dart';
|
||||
import '../../utils/resize_video.dart';
|
||||
import 'package:matrix/src/utils/markdown.dart';
|
||||
import 'package:html_unescape/html_unescape.dart';
|
||||
|
||||
class SendFileDialog extends StatefulWidget {
|
||||
final Room room;
|
||||
final List<XFile> files;
|
||||
final BuildContext outerContext;
|
||||
final Event? replyEvent;
|
||||
|
||||
const SendFileDialog({
|
||||
required this.room,
|
||||
required this.files,
|
||||
required this.outerContext,
|
||||
this.replyEvent,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -97,13 +102,40 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
}
|
||||
|
||||
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 {
|
||||
await widget.room.sendFileEvent(
|
||||
file,
|
||||
thumbnail: thumbnail,
|
||||
shrinkImageMaxDimension: compress ? 1600 : null,
|
||||
extraContent: label.isEmpty ? null : {'body': label},
|
||||
extraContent: extraContent,
|
||||
);
|
||||
} on MatrixException catch (e) {
|
||||
final retryAfterMs = e.retryAfterMs;
|
||||
|
|
@ -128,12 +160,13 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
file,
|
||||
thumbnail: thumbnail,
|
||||
shrinkImageMaxDimension: compress ? 1600 : null,
|
||||
extraContent: label.isEmpty ? null : {'body': label},
|
||||
extraContent: extraContent,
|
||||
);
|
||||
}
|
||||
}
|
||||
scaffoldMessenger.clearSnackBars();
|
||||
} catch (e) {
|
||||
print('error: ${e.toString()}');
|
||||
scaffoldMessenger.clearSnackBars();
|
||||
final theme = Theme.of(context);
|
||||
scaffoldMessenger.showSnackBar(
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class SendLocationDialogState extends State<SendLocationDialog> {
|
|||
}
|
||||
|
||||
Future<void> requestLocation() async {
|
||||
error = null;
|
||||
if (!(await Geolocator.isLocationServiceEnabled())) {
|
||||
setState(() => disabled = true);
|
||||
return;
|
||||
|
|
@ -58,15 +59,15 @@ class SendLocationDialogState extends State<SendLocationDialog> {
|
|||
try {
|
||||
position = await Geolocator.getCurrentPosition(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
timeLimit: Duration(seconds: 30),
|
||||
accuracy: LocationAccuracy.high,
|
||||
timeLimit: Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
} on TimeoutException {
|
||||
position = await Geolocator.getCurrentPosition(
|
||||
locationSettings: const LocationSettings(
|
||||
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,
|
||||
child: Text(L10n.of(context).cancel),
|
||||
),
|
||||
if (error != null)
|
||||
AdaptiveDialogAction(onPressed: requestLocation, child: Text(L10n.of(context).retry)),
|
||||
if (position != null)
|
||||
AdaptiveDialogAction(
|
||||
onPressed: isSending ? null : sendAction,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class NewGroupController extends State<NewGroup> {
|
|||
|
||||
bool publicGroup = false;
|
||||
bool groupCanBeFound = false;
|
||||
bool enableEncryption = false;
|
||||
|
||||
Uint8List? avatar;
|
||||
|
||||
|
|
@ -49,6 +50,8 @@ class NewGroupController extends State<NewGroup> {
|
|||
|
||||
void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b);
|
||||
|
||||
void setEnableEncryption(bool b) => setState(() => enableEncryption = b);
|
||||
|
||||
void selectPhoto() async {
|
||||
final photo = await selectFiles(
|
||||
context,
|
||||
|
|
@ -68,9 +71,7 @@ class NewGroupController extends State<NewGroup> {
|
|||
final roomId = await Matrix.of(context).client.createGroupChat(
|
||||
visibility:
|
||||
groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private,
|
||||
preset: publicGroup
|
||||
? sdk.CreateRoomPreset.publicChat
|
||||
: sdk.CreateRoomPreset.privateChat,
|
||||
enableEncryption: enableEncryption,
|
||||
groupName: nameController.text.isNotEmpty ? nameController.text : null,
|
||||
initialState: [
|
||||
if (avatar != null)
|
||||
|
|
@ -78,8 +79,16 @@ class NewGroupController extends State<NewGroup> {
|
|||
type: sdk.EventTypes.RoomAvatar,
|
||||
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;
|
||||
context.go('/rooms/$roomId/invite');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,8 +134,10 @@ class NewGroupView extends StatelessWidget {
|
|||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
value: !controller.publicGroup,
|
||||
onChanged: null,
|
||||
value: controller.enableEncryption,
|
||||
onChanged: controller.loading
|
||||
? null
|
||||
: controller.setEnableEncryption,
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ class SettingsStyleView extends StatelessWidget {
|
|||
vertical: 8,
|
||||
),
|
||||
child: Text(
|
||||
'What is Extera Next?',
|
||||
'погнали в роблокс?',
|
||||
style: TextStyle(
|
||||
color: theme.onBubbleColor,
|
||||
fontSize: AppConfig.messageFontSize *
|
||||
|
|
@ -259,7 +259,7 @@ class SettingsStyleView extends StatelessWidget {
|
|||
vertical: 8,
|
||||
),
|
||||
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(
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontSize: AppConfig.messageFontSize *
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import 'package:fluffychat/config/setting_keys.dart';
|
|||
import 'package:fluffychat/utils/custom_http_client.dart';
|
||||
import 'package:fluffychat/utils/custom_image_resizer.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 'matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart';
|
||||
|
||||
|
|
@ -119,7 +118,7 @@ abstract class ClientManager {
|
|||
},
|
||||
logLevel: kReleaseMode ? Level.warning : Level.verbose,
|
||||
databaseBuilder: flutterMatrixSdkDatabaseBuilder,
|
||||
legacyDatabaseBuilder: FlutterHiveCollectionsDatabase.databaseBuilder,
|
||||
//legacyDatabaseBuilder: FlutterHiveCollectionsDatabase.databaseBuilder,
|
||||
supportedLoginTypes: {
|
||||
AuthenticationTypes.password,
|
||||
AuthenticationTypes.sso,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ extension DateTimeExtension on DateTime {
|
|||
|
||||
/// Returns a simple time String.
|
||||
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('h:mm a', L10n.of(context).localeName).format(this);
|
||||
|
||||
|
|
|
|||
|
|
@ -55,9 +55,5 @@ extension IsStateExtension on Event {
|
|||
content.tryGet<String>('membership') == 'ban' ||
|
||||
stateKey != senderId);
|
||||
|
||||
bool get isState => !{
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.Encrypted,
|
||||
}.contains(type);
|
||||
bool get isState => this.stateKey != null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import 'package:universal_html/html.dart' as html;
|
|||
|
||||
import 'package:fluffychat/config/app_config.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 'cipher.dart';
|
||||
|
||||
|
|
@ -55,7 +54,9 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
|
|||
Logs().e('Unable to send error notification', e, s);
|
||||
}
|
||||
|
||||
return FlutterHiveCollectionsDatabase.databaseBuilder(client);
|
||||
database = await _constructDatabase(client);
|
||||
await database.open();
|
||||
return database;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ class MatrixLocals extends MatrixLocalizations {
|
|||
|
||||
MatrixLocals(this.l10n);
|
||||
|
||||
@override
|
||||
String voiceMessage(String senderName, Duration? duration) {
|
||||
return l10n.voiceMessage;
|
||||
}
|
||||
|
||||
@override
|
||||
String acceptedTheInvitation(String targetName) {
|
||||
return l10n.acceptedTheInvitation(targetName);
|
||||
|
|
|
|||
|
|
@ -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'];
|
||||
}
|
||||
}
|
||||
|
|
@ -53,11 +53,11 @@ static void my_application_activate(GApplication* application) {
|
|||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
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_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "FluffyChat");
|
||||
gtk_window_set_title(window, "Extera Next");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 864, 720);
|
||||
|
|
|
|||
|
|
@ -1159,10 +1159,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: matrix
|
||||
sha256: "829f225f74b2a8c83b6b1f960004ecf742215d9ee9d8cc9e0f1974ff8e281f0f"
|
||||
sha256: "7d15fdbc760be7e40c58bb65e03baa8241b1e31db2bc67dab61883aabc083a85"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.39.1"
|
||||
version: "0.40.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ dependencies:
|
|||
latlong2: ^0.9.1
|
||||
linkify: ^5.0.0
|
||||
material: ^1.0.0+2
|
||||
matrix: ^0.39.1
|
||||
matrix: 0.40.0
|
||||
mime: ^1.0.6
|
||||
native_imaging: ^0.2.0
|
||||
opus_caf_converter_dart: ^1.0.1
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
|||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.CreateAndShow(L"FluffyChat", origin, size)) {
|
||||
if (!window.CreateAndShow(L"Extera Next", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
|
|
|||
Loading…
Reference in New Issue