merge in sentry updates

pull/1077/head
Gabby Gurdin 2 years ago
commit c0f2d6ea39

@ -6,9 +6,7 @@ import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:matrix/matrix.dart';
import '../../config/themes.dart';
@ -27,6 +25,7 @@ class ChatInputRow extends StatelessWidget {
return const SizedBox.shrink();
}
const height = 48.0;
// #Pangea
return Column(
children: [
@ -103,163 +102,189 @@ class ChatInputRow extends StatelessWidget {
: const SizedBox.shrink(),
]
: <Widget>[
const SizedBox(width: 4),
KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.keyA,
},
onKeysPressed: () =>
controller.onAddPopupMenuButtonSelected('file'),
helpLabel: L10n.of(context)!.sendFile,
child: AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
height: height,
// #Pangea
// width:
// controller.sendController.text.isEmpty ? height : 0,
width: controller.sendController.text.isEmpty &&
controller.pangeaController.permissionsController
.showChatInputAddButton(controller.roomId)
? 56
: 0,
//Pangea#
alignment: Alignment.center,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: PopupMenuButton<String>(
icon: const Icon(Icons.add_outlined),
onSelected: controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
//#Pangea
if (controller.pangeaController.permissionsController
.canShareFile(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'file',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
child: Icon(Icons.attachment_outlined),
),
title: Text(L10n.of(context)!.sendFile),
contentPadding: const EdgeInsets.all(0),
// #Pangea
// const SizedBox(width: 4),
// KeyBoardShortcuts(
// keysToPress: {
// LogicalKeyboardKey.altLeft,
// LogicalKeyboardKey.keyA,
// },
// onKeysPressed: () =>
// controller.onAddPopupMenuButtonSelected('file'),
// helpLabel: L10n.of(context)!.sendFile,
// child:
// Pangea#
AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
height: height,
// #Pangea
// width:
// controller.sendController.text.isEmpty ? height : 0,
width: controller.sendController.text.isEmpty &&
controller.pangeaController.permissionsController
.showChatInputAddButton(controller.roomId)
? height
: 0,
// Pangea#
alignment: Alignment.center,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: PopupMenuButton<String>(
icon: const Icon(Icons.add_outlined),
onSelected: controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
//#Pangea
if (controller.pangeaController.permissionsController
.canShareFile(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'file',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
child: Icon(Icons.attachment_outlined),
),
title: Text(L10n.of(context)!.sendFile),
contentPadding: const EdgeInsets.all(0),
),
//#Pangea
if (controller.pangeaController.permissionsController
.canSharePhoto(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'image',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
child: Icon(Icons.image_outlined),
),
title: Text(L10n.of(context)!.sendImage),
contentPadding: const EdgeInsets.all(0),
),
//#Pangea
if (controller.pangeaController.permissionsController
.canSharePhoto(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'image',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
child: Icon(Icons.image_outlined),
),
title: Text(L10n.of(context)!.sendImage),
contentPadding: const EdgeInsets.all(0),
),
// #Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canSharePhoto(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'camera',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
child: Icon(Icons.camera_alt_outlined),
),
title: Text(L10n.of(context)!.openCamera),
contentPadding: const EdgeInsets.all(0),
),
//#Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canSharePhoto(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'camera',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
child: Icon(Icons.camera_alt_outlined),
),
title: Text(L10n.of(context)!.openCamera),
contentPadding: const EdgeInsets.all(0),
),
//#Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canShareVideo(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'camera-video',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
child: Icon(Icons.videocam_outlined),
),
title: Text(L10n.of(context)!.openVideoCamera),
contentPadding: const EdgeInsets.all(0),
),
//#Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canShareVideo(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'camera-video',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
child: Icon(Icons.videocam_outlined),
),
title: Text(L10n.of(context)!.openVideoCamera),
contentPadding: const EdgeInsets.all(0),
),
//#Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canShareLocation(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'location',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.brown,
foregroundColor: Colors.white,
child: Icon(Icons.gps_fixed_outlined),
),
title: Text(L10n.of(context)!.shareLocation),
contentPadding: const EdgeInsets.all(0),
),
if (controller.room
.getImagePacks(ImagePackUsage.sticker)
.isNotEmpty)
PopupMenuItem<String>(
value: 'sticker',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
child: Icon(Icons.emoji_emotions_outlined),
),
title: Text(L10n.of(context)!.sendSticker),
contentPadding: const EdgeInsets.all(0),
),
],
),
),
//#Pangea
// if (PlatformInfos.isMobile)
if (PlatformInfos.isMobile &&
controller.pangeaController.permissionsController
.canShareLocation(controller.roomId))
//Pangea#
PopupMenuItem<String>(
value: 'location',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.brown,
foregroundColor: Colors.white,
child: Icon(Icons.gps_fixed_outlined),
),
title: Text(L10n.of(context)!.shareLocation),
contentPadding: const EdgeInsets.all(0),
),
),
],
),
),
// #Pangea
// ),
// Pangea#
Container(
height: height,
width: height,
alignment: Alignment.center,
child: KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.keyE,
},
onKeysPressed: controller.emojiPickerAction,
helpLabel: L10n.of(context)!.emojis,
child: IconButton(
tooltip: L10n.of(context)!.emojis,
icon: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
},
child: Icon(
controller.showEmojiPicker
? Icons.keyboard
: Icons.add_reaction_outlined,
key: ValueKey(controller.showEmojiPicker),
),
child:
// #Pangea
// KeyBoardShortcuts(
// keysToPress: {
// LogicalKeyboardKey.altLeft,
// LogicalKeyboardKey.keyE,
// },
// onKeysPressed: controller.emojiPickerAction,
// helpLabel: L10n.of(context)!.emojis,
// child:
// Pangea#
IconButton(
tooltip: L10n.of(context)!.emojis,
icon: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
},
child: Icon(
controller.showEmojiPicker
? Icons.keyboard
: Icons.add_reaction_outlined,
key: ValueKey(controller.showEmojiPicker),
),
onPressed: controller.emojiPickerAction,
),
onPressed: controller.emojiPickerAction,
),
// #Pangea
// ),
// Pangea#
),
// #Pangea
// if (Matrix.of(context).isMultiAccount &&
@ -279,10 +304,7 @@ class ChatInputRow extends StatelessWidget {
room: controller.room,
minLines: 1,
maxLines: 8,
// #Pangea
// autofocus: !PlatformInfos.isMobile,
autofocus: false,
// Pangea#
autofocus: !PlatformInfos.isMobile,
keyboardType: TextInputType.multiline,
textInputAction: AppConfig.sendOnEnter == true &&
PlatformInfos.isMobile
@ -292,7 +314,7 @@ class ChatInputRow extends StatelessWidget {
// onSubmitted: controller.onInputBarSubmitted,
onSubmitted: (String value) =>
controller.onInputBarSubmitted(value, context),
// #Pangea
// Pangea#
onSubmitImage: controller.sendImageFromClipBoard,
focusNode: controller.inputFocus,
controller: controller.sendController,

@ -2,7 +2,6 @@ import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/enum/use_type.dart';
import 'package:fluffychat/pangea/models/language_model.dart';
import 'package:fluffychat/pangea/models/pangea_message_event.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/string_color.dart';
@ -154,10 +153,9 @@ class Message extends StatelessWidget {
}
// #Pangea
final PangeaMessageEvent? pangeaMessageEvent =
controller.getPangeaMessageEvent(event.eventId);
ToolbarDisplayController? toolbarController;
if (event.messageType == MessageTypes.Text ||
if (event.type == EventTypes.Message &&
event.messageType == MessageTypes.Text ||
event.messageType == MessageTypes.Notice) {
toolbarController = controller.getToolbarDisplayController(
event.eventId,
@ -368,7 +366,8 @@ class Message extends StatelessWidget {
borderRadius: borderRadius,
// #Pangea
selected: selected,
pangeaMessageEvent: pangeaMessageEvent,
pangeaMessageEvent:
toolbarController?.pangeaMessageEvent,
immersionMode: immersionMode,
toolbarController: toolbarController,
// Pangea#
@ -378,7 +377,9 @@ class Message extends StatelessWidget {
RelationshipTypes.edit,
) // #Pangea
||
(pangeaMessageEvent?.showUseType ??
(toolbarController
?.pangeaMessageEvent
.showUseType ??
false)
// Pangea#
)
@ -390,10 +391,12 @@ class Message extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
// #Pangea
if (pangeaMessageEvent
?.showUseType ??
if (toolbarController
?.pangeaMessageEvent
.showUseType ??
false) ...[
pangeaMessageEvent!.useType
toolbarController!
.pangeaMessageEvent.useType
.iconView(
context,
textColor.withAlpha(164),

@ -120,8 +120,20 @@ class MessageContent extends StatelessWidget {
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
final buttonTextColor = textColor;
switch (event.type) {
case EventTypes.Message:
// #Pangea
// case EventTypes.Message:
// Pangea#
case EventTypes.Encrypted:
// #Pangea
return _ButtonContent(
textColor: buttonTextColor,
onPressed: () {},
icon: '🔒',
label: L10n.of(context)!.encrypted,
fontSize: fontSize,
);
case EventTypes.Message:
// Pangea#
case EventTypes.Sticker:
switch (event.messageType) {
case MessageTypes.Image:
@ -208,14 +220,16 @@ class MessageContent extends StatelessWidget {
// else we fall through to the normal message rendering
continue textmessage;
case MessageTypes.BadEncrypted:
case EventTypes.Encrypted:
return _ButtonContent(
textColor: buttonTextColor,
onPressed: () => _verifyOrRequestKey(context),
icon: '🔒',
label: L10n.of(context)!.encrypted,
fontSize: fontSize,
);
// #Pangea
// case EventTypes.Encrypted:
// return _ButtonContent(
// textColor: buttonTextColor,
// onPressed: () => _verifyOrRequestKey(context),
// icon: '🔒',
// label: L10n.of(context)!.encrypted,
// fontSize: fontSize,
// );
// Pangea#
case MessageTypes.Location:
final geoUri =
Uri.tryParse(event.content.tryGet<String>('geo_uri')!);

@ -9,7 +9,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:matrix/matrix.dart';
import '../../utils/fluffy_share.dart';
@ -265,38 +264,40 @@ class ClientChooserButton extends StatelessWidget {
builder: (context, snapshot) => Stack(
alignment: Alignment.center,
children: [
...List.generate(
clientCount,
(index) => KeyBoardShortcuts(
keysToPress: _buildKeyboardShortcut(index + 1),
helpLabel: L10n.of(context)!.switchToAccount(index + 1),
onKeysPressed: () => _handleKeyboardShortcut(
matrix,
index,
context,
),
child: const SizedBox.shrink(),
),
),
KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.tab,
},
helpLabel: L10n.of(context)!.nextAccount,
onKeysPressed: () => _nextAccount(matrix, context),
child: const SizedBox.shrink(),
),
KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.tab,
},
helpLabel: L10n.of(context)!.previousAccount,
onKeysPressed: () => _previousAccount(matrix, context),
child: const SizedBox.shrink(),
),
// #Pangea
// ...List.generate(
// clientCount,
// (index) => KeyBoardShortcuts(
// keysToPress: _buildKeyboardShortcut(index + 1),
// helpLabel: L10n.of(context)!.switchToAccount(index + 1),
// onKeysPressed: () => _handleKeyboardShortcut(
// matrix,
// index,
// context,
// ),
// child: const SizedBox.shrink(),
// ),
// ),
// KeyBoardShortcuts(
// keysToPress: {
// LogicalKeyboardKey.controlLeft,
// LogicalKeyboardKey.tab,
// },
// helpLabel: L10n.of(context)!.nextAccount,
// onKeysPressed: () => _nextAccount(matrix, context),
// child: const SizedBox.shrink(),
// ),
// KeyBoardShortcuts(
// keysToPress: {
// LogicalKeyboardKey.controlLeft,
// LogicalKeyboardKey.shiftLeft,
// LogicalKeyboardKey.tab,
// },
// helpLabel: L10n.of(context)!.previousAccount,
// onKeysPressed: () => _previousAccount(matrix, context),
// child: const SizedBox.shrink(),
// ),
// Pangea#
PopupMenuButton<Object>(
onSelected: (o) => _clientSelected(o, context),
itemBuilder: _bundleMenuItems,

@ -233,6 +233,10 @@ class NewSpaceController extends State<NewSpace> {
GoogleAnalytics.createClass(room.name, room.classCode);
try {
await room.invite(BotName.byEnvironment);
await room.setPower(
BotName.byEnvironment,
ClassDefaultValues.powerLevelOfAdmin,
);
} catch (err) {
ErrorHandler.logError(
e: "Failed to invite pangea bot to space ${room.id}",

@ -218,6 +218,10 @@ class PangeaController {
final List<Room> spaces =
matrixState.client.rooms.where((room) => room.isSpace).toList();
for (final Room space in spaces) {
if (space.ownPowerLevel < ClassDefaultValues.powerLevelOfAdmin ||
!space.canInvite) {
continue;
}
List<User> participants;
try {
participants = await space.requestParticipants();
@ -228,7 +232,7 @@ class PangeaController {
continue;
}
final List<String> userIds = participants.map((user) => user.id).toList();
if (space.canInvite && !userIds.contains(BotName.byEnvironment)) {
if (!userIds.contains(BotName.byEnvironment)) {
try {
await space.invite(BotName.byEnvironment);
await space.setPower(
@ -240,6 +244,18 @@ class PangeaController {
e: "Failed to invite pangea bot to space ${space.id}",
);
}
} else if (space.getPowerLevelByUserId(BotName.byEnvironment) <
ClassDefaultValues.powerLevelOfAdmin) {
try {
await space.setPower(
BotName.byEnvironment,
ClassDefaultValues.powerLevelOfAdmin,
);
} catch (err) {
ErrorHandler.logError(
e: "Failed to reset power level for pangea bot in space ${space.id}",
);
}
}
}
}

@ -117,7 +117,12 @@ extension PangeaClient on Client {
// set description to let people know what the hell it is
Future<Room> getMyAnalyticsRoom(String langCode) async {
await roomsLoading;
// ensure room state events (room create,
// to check for analytics type) are loaded
for (final room in rooms) {
if (room.partial) await room.postLoad();
}
final Room? analyticsRoom = analyticsRoomLocal(langCode);
if (analyticsRoom != null) return analyticsRoom;

@ -514,7 +514,13 @@ extension PangeaRoom on Room {
return;
}
myAnalEvent.bulkUpdate(await _messageListForAllChildChats);
final updateMessages = await _messageListForAllChildChats;
updateMessages.removeWhere(
(element) => myAnalEvent.content.messages.any(
(e) => e.eventId == element.eventId,
),
);
myAnalEvent.bulkUpdate(updateMessages);
storageService?.save(migratedAnalyticsKey, true);
} catch (err, s) {

@ -14,6 +14,7 @@ import 'package:fluffychat/pangea/utils/bot_name.dart';
import 'package:fluffychat/pangea/widgets/chat/message_audio_card.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../widgets/matrix.dart';
import '../constants/language_keys.dart';
@ -270,32 +271,41 @@ class PangeaMessageEvent {
List<RepresentationEvent>? _representations;
List<RepresentationEvent> get representations {
if (_representations != null) return _representations!;
_representations = [];
if (_latestEdit.content[ModelKey.originalSent] != null) {
try {
_representations!.add(
RepresentationEvent(
content: PangeaRepresentation.fromJson(
_latestEdit.content[ModelKey.originalSent]
as Map<String, dynamic>,
),
tokens: _latestEdit.content[ModelKey.tokensSent] != null
? PangeaMessageTokens.fromJson(
_latestEdit.content[ModelKey.tokensSent]
as Map<String, dynamic>,
)
: null,
choreo: _latestEdit.content[ModelKey.choreoRecord] != null
? ChoreoRecord.fromJson(
_latestEdit.content[ModelKey.choreoRecord]
as Map<String, dynamic>,
)
: null,
timeline: timeline,
final RepresentationEvent sent = RepresentationEvent(
content: PangeaRepresentation.fromJson(
_latestEdit.content[ModelKey.originalSent] as Map<String, dynamic>,
),
tokens: _latestEdit.content[ModelKey.tokensSent] != null
? PangeaMessageTokens.fromJson(
_latestEdit.content[ModelKey.tokensSent]
as Map<String, dynamic>,
)
: null,
choreo: _latestEdit.content[ModelKey.choreoRecord] != null
? ChoreoRecord.fromJson(
_latestEdit.content[ModelKey.choreoRecord]
as Map<String, dynamic>,
)
: null,
timeline: timeline,
);
if (_latestEdit.content[ModelKey.choreoRecord] == null) {
Sentry.addBreadcrumb(
Breadcrumb(
message: "originalSent created without _event or _choreo",
data: {
"eventId": _latestEdit.eventId,
"room": _latestEdit.room.id,
"sender": _latestEdit.senderId,
},
),
);
}
_representations!.add(sent);
} catch (err, s) {
ErrorHandler.logError(
m: "error parsing originalSent",

@ -136,10 +136,10 @@ class RepresentationEvent {
if (_choreo != null) return _choreo;
if (_event == null) {
// debugger(when: kDebugMode);
ErrorHandler.logError(
m: '_event and _choreo both null',
s: StackTrace.current,
Sentry.addBreadcrumb(
Breadcrumb(
message: "_event and _choreo both null",
),
);
return null;
}

@ -139,9 +139,12 @@ class SignupPageController extends State<SignupPage> {
}
} catch (e) {
//#Pangea
ErrorHandler.logError(e: e);
//Pangea#
error = (e).toLocalizedString(context);
const cancelledString = "Exception: Request has been canceled";
if (e.toString() != cancelledString) {
ErrorHandler.logError(e: e);
error = (e).toLocalizedString(context);
}
// Pangea#
} finally {
if (mounted) {
setState(() => loading = false);

@ -6,11 +6,9 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/utils/download_chat.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:matrix/matrix.dart';
import 'matrix.dart';
@ -152,15 +150,17 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
return Stack(
alignment: Alignment.center,
children: [
KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.keyI,
},
helpLabel: L10n.of(context)!.chatDetails,
onKeysPressed: _showChatDetails,
child: const SizedBox.shrink(),
),
// #Pangea
// KeyBoardShortcuts(
// keysToPress: {
// LogicalKeyboardKey.controlLeft,
// LogicalKeyboardKey.keyI,
// },
// helpLabel: L10n.of(context)!.chatDetails,
// onKeysPressed: _showChatDetails,
// child: const SizedBox.shrink(),
// ),
// Pangea#
PopupMenuButton(
onSelected: (String choice) async {
switch (choice) {

@ -127,7 +127,7 @@ dependencies:
language_tool: ^2.1.1
matrix_homeserver_recommendations: ^0.3.0
open_file: ^3.3.2
purchases_flutter: ^5.6.0
purchases_flutter: ^6.25.0
sentry_flutter: ^7.4.0
shimmer: ^3.0.0
syncfusion_flutter_datepicker: ^23.2.7

Loading…
Cancel
Save