diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 24041eab..53e1cebb 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -161,10 +161,8 @@ class MessageContent extends StatelessWidget { case MessageTypes.Audio: if (PlatformInfos.isMobile || PlatformInfos.isMacOS || - PlatformInfos.isWeb - // Disabled until https://github.com/bleonard252/just_audio_mpv/issues/3 - // is fixed - // || PlatformInfos.isLinux + PlatformInfos.isWeb || + PlatformInfos.isLinux ) { return AudioPlayerWidget( event, diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 2611af78..abf5e29f 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; @@ -15,6 +16,7 @@ import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart'; +import 'package:fluffychat/widgets/avatar_crop_dialog.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import '../../widgets/matrix.dart'; import 'settings_view.dart'; @@ -126,6 +128,8 @@ class SettingsController extends State { return; } MatrixFile file; + Uint8List bytes; + String name; if (PlatformInfos.isMobile) { final result = await ImagePicker().pickImage( source: action == AvatarAction.camera @@ -134,16 +138,25 @@ class SettingsController extends State { imageQuality: 50, ); if (result == null) return; - file = MatrixFile(bytes: await result.readAsBytes(), name: result.path); + bytes = await result.readAsBytes(); + name = result.path; } else { final result = await selectFiles(context, type: FileType.image); final pickedFile = result.firstOrNull; if (pickedFile == null) return; - file = MatrixFile( - bytes: await pickedFile.readAsBytes(), - name: pickedFile.name, - ); + bytes = await pickedFile.readAsBytes(); + name = pickedFile.name; } + final cropped = await showDialog( + context: context, + builder: (contect) => AvatarCropDialog(image: bytes), + ); + if (cropped == null) return; + bytes = cropped; + file = MatrixFile( + bytes: bytes, + name: name, + ); final success = await showFutureLoadingDialog( context: context, future: () => matrix.client.setAvatar(file), diff --git a/lib/widgets/avatar_crop_dialog.dart b/lib/widgets/avatar_crop_dialog.dart new file mode 100644 index 00000000..3122975a --- /dev/null +++ b/lib/widgets/avatar_crop_dialog.dart @@ -0,0 +1,70 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:crop_image/crop_image.dart'; +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:flutter/material.dart'; + +class AvatarCropDialog extends StatefulWidget { + final Uint8List image; + + const AvatarCropDialog({super.key, required this.image}); + + @override + AvatarCropDialogController createState() => AvatarCropDialogController(); +} + +class AvatarCropDialogController extends State { + final controller = CropController( + aspectRatio: 1, + defaultCrop: const Rect.fromLTWH(0.1, 0.1, 0.8, 0.8), + ); + + void onCancelAction() => Navigator.of(context).pop(); + + Future onCropAction() async { + final image = await controller.croppedBitmap(); + if (mounted) { + final data = await image.toByteData(format: ui.ImageByteFormat.png); + Navigator.of(context).pop(data?.buffer.asUint8List()); + } + } + + @override + Widget build(BuildContext context) => AvatarCropDialogView(this); +} + +class AvatarCropDialogView extends StatelessWidget { + final AvatarCropDialogController controller; + + const AvatarCropDialogView(this.controller, {super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(L10n.of(context).changeYourAvatar), + content: SizedBox( + width: 400, + height: 400, + child: CropImage( + controller: controller.controller, + image: Image.memory(controller.widget.image), + gridColor: Colors.white, + gridCornerSize: 20, + touchSize: 20, + alwaysShowThirdLines: true, + ), + ), + actions: [ + TextButton( + onPressed: controller.onCancelAction, + child: Text(L10n.of(context).cancel), + ), + TextButton( + onPressed: controller.onCropAction, + child: Text(L10n.of(context).ok), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index d5f13713..2bc5394a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -217,6 +217,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + crop_image: + dependency: "direct main" + description: + name: crop_image + sha256: "27cbce1685a595efee62caab81c98b49b636f765c1da86353f58f5b2bf2775d8" + url: "https://pub.dev" + source: hosted + version: "1.0.17" cross_file: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 15b7bcb3..b713c793 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: blurhash_dart: ^1.2.1 chewie: ^1.13.0 collection: ^1.18.0 + crop_image: ^1.0.17 cross_file: ^0.3.5 desktop_drop: ^0.7.0 desktop_notifications: ^0.6.3 @@ -50,9 +51,10 @@ dependencies: just_audio: ^0.10.5 just_audio_media_kit: ^2.1.0 latlong2: ^0.9.1 - matrix: ^6.2.0 linkify: ^5.0.0 + matrix: ^6.2.0 media_kit_libs_linux: ^1.2.1 + media_kit_libs_windows_video: ^1.0.11 mime: ^2.0.0 native_imaging: ^0.4.0 opus_caf_converter_dart: ^1.0.1 @@ -80,10 +82,9 @@ dependencies: url_launcher: ^6.3.2 video_compress: ^3.1.4 video_player: ^2.11.1 + video_player_media_kit: ^2.0.0 wakelock_plus: ^1.5.0 webrtc_interface: ^1.3.0 - media_kit_libs_windows_video: ^1.0.11 - video_player_media_kit: ^2.0.0 dev_dependencies: dart_code_linter: ^3.2.1