diff --git a/.changes/2060-spanish-and-arabic.md b/.changes/2060-spanish-and-arabic.md new file mode 100644 index 000000000000..4d6ee2ea0605 --- /dev/null +++ b/.changes/2060-spanish-and-arabic.md @@ -0,0 +1 @@ +- Acter is now available in Spanish (thanks to Sandra) and in Arabic (thanks, Omar!) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index e207c55f3164..f8026e538063 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -49,7 +49,7 @@ jobs: run: ls -ltas packages/rust_sdk/lib/ - name: Build Dart SDK - run: flutter pub global run dartdoc --output ../../../docs/api/main/dart-sdk/ + run: flutter pub global run dartdoc --output ../../docs/api/main/dart-sdk/ working-directory: packages/rust_sdk - name: Upload artifact diff --git a/.github/workflows/deploy-nightly.yml b/.github/workflows/deploy-nightly.yml index 796774537507..f245c325afdd 100644 --- a/.github/workflows/deploy-nightly.yml +++ b/.github/workflows/deploy-nightly.yml @@ -8,6 +8,9 @@ on: schedule: - cron: 0 1 * * 0-3,5-6 +permissions: + contents: write + jobs: run_checker: runs-on: ubuntu-latest @@ -277,7 +280,7 @@ jobs: ###### #### ## ## ## ####### ######## ## ####### ######## ######## #### ###### ## ## publish: - environment: nightly + environment: release runs-on: ubuntu-latest name: Publish if: ${{ github.event.schedule }} @@ -288,7 +291,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - token: ${{ secrets.PAT }} + token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/download-artifact@v3 with: diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index 94690f19af35..b1accb0d0d59 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -4,6 +4,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.target || 'all' }} cancel-in-progress: true +permissions: + contents: write + on: workflow_dispatch: inputs: @@ -66,9 +69,8 @@ jobs: tag: v${{ inputs.new_version || steps.version.outputs.version }} version: ${{ inputs.new_version || steps.version.outputs.version }} build_num: ${{ steps.build_num.outputs.build_num }} - prev_tag: ${{ inputs.prev_tag || 'release-latest' }} targets: ${{ inputs.target || 'all' }} - release_title: ${{ inputs.custom_title || 'Release'}} + release_title_prefix: ${{ inputs.custom_title || 'Release'}} steps: - id: version # the suffix 0 allows us to provide up to 9 more hotfixes on the same day @@ -83,7 +85,7 @@ jobs: with: build_num: ${{ needs.tags.outputs.build_num }} version: ${{ needs.tags.outputs.version }} - release_title: ${{ needs.tags.outputs.release_title }} ${{ needs.tags.outputs.tag }} + release_title: "${{ needs.tags.outputs.release_title_prefix }} ${{ needs.tags.outputs.tag }}" release_tag: ${{ needs.tags.outputs.tag }} targets: ${{ needs.tags.outputs.targets }} release: true @@ -354,7 +356,7 @@ jobs: ###### #### ## ## ## ####### ######## ## ####### ######## ######## #### ###### ## ## publish: - environment: nightly + environment: release runs-on: ubuntu-latest name: Publish # if: ${{ github.event.schedule }} @@ -365,7 +367,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - token: ${{ secrets.PAT }} + token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/download-artifact@v3 with: @@ -413,7 +415,7 @@ jobs: # publish this full release now draft: true generate_release_notes: false - name: ${{ needs.tags.outputs.release_title }} ${{ needs.tags.outputs.tag }} + name: "${{ needs.tags.outputs.release_title_prefix }} ${{ needs.tags.outputs.tag }}" tag_name: ${{ needs.tags.outputs.tag }} body_path: CHANGELOG.md prerelease: false diff --git a/app/lib/common/actions/close_room.dart b/app/lib/common/actions/close_room.dart index da24196bdd6e..7a280816cc32 100644 --- a/app/lib/common/actions/close_room.dart +++ b/app/lib/common/actions/close_room.dart @@ -4,7 +4,6 @@ import 'package:acter/common/providers/sdk_provider.dart'; import 'package:acter/common/providers/space_providers.dart'; import 'package:acter/common/toolkit/buttons/danger_action_button.dart'; import 'package:acter/common/utils/routes.dart'; - import 'package:acter/common/widgets/room/room_profile_header.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; diff --git a/app/lib/common/actions/report_content.dart b/app/lib/common/actions/report_content.dart index 4254a01e4bf5..784fb3809b0b 100644 --- a/app/lib/common/actions/report_content.dart +++ b/app/lib/common/actions/report_content.dart @@ -1,7 +1,6 @@ import 'package:acter/common/providers/chat_providers.dart'; import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/providers/space_providers.dart'; - import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:acter/common/widgets/default_dialog.dart'; import 'package:acter/common/widgets/input_text_field.dart'; diff --git a/app/lib/common/providers/common_providers.dart b/app/lib/common/providers/common_providers.dart index 6427fad5e0f7..851242f836e7 100644 --- a/app/lib/common/providers/common_providers.dart +++ b/app/lib/common/providers/common_providers.dart @@ -10,7 +10,7 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::common::providers'); +final _log = Logger('a3::common::common_providers'); // Loading Providers final loadingProvider = StateProvider((ref) => false); diff --git a/app/lib/common/providers/notifiers/chat_notifiers.dart b/app/lib/common/providers/notifiers/chat_notifiers.dart index 394290dbb2ed..3980c822ec25 100644 --- a/app/lib/common/providers/notifiers/chat_notifiers.dart +++ b/app/lib/common/providers/notifiers/chat_notifiers.dart @@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::common::chat'); +final _log = Logger('a3::common::chat_notifiers'); class AsyncConvoNotifier extends FamilyAsyncNotifier { late Stream _listener; diff --git a/app/lib/common/providers/notifiers/notification_settings_notifier.dart b/app/lib/common/providers/notifiers/notification_settings_notifier.dart index b56bb9b6bf4b..9b678cf064b5 100644 --- a/app/lib/common/providers/notifiers/notification_settings_notifier.dart +++ b/app/lib/common/providers/notifiers/notification_settings_notifier.dart @@ -5,7 +5,7 @@ import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:logging/logging.dart'; import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::common::notification_settings'); +final _log = Logger('a3::common::notification_settings_notifier'); class AsyncNotificationSettingsNotifier extends AsyncNotifier { diff --git a/app/lib/common/providers/notifiers/reactions_notifiers.dart b/app/lib/common/providers/notifiers/reactions_notifiers.dart index 44c8156c8e62..11e6d9000a54 100644 --- a/app/lib/common/providers/notifiers/reactions_notifiers.dart +++ b/app/lib/common/providers/notifiers/reactions_notifiers.dart @@ -4,7 +4,7 @@ import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:logging/logging.dart'; import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::common::reactions'); +final _log = Logger('a3::common::reactions_notifiers'); class ReactionManagerNotifier extends FamilyNotifier { diff --git a/app/lib/common/providers/notifiers/room_notifiers.dart b/app/lib/common/providers/notifiers/room_notifiers.dart index 8628bc2e619a..a2143d35ef74 100644 --- a/app/lib/common/providers/notifiers/room_notifiers.dart +++ b/app/lib/common/providers/notifiers/room_notifiers.dart @@ -5,10 +5,10 @@ import 'package:acter/features/home/providers/client_providers.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; -import 'package:riverpod/riverpod.dart'; import 'package:logging/logging.dart'; +import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::common::room'); +final _log = Logger('a3::common::room_notifiers'); class AsyncMaybeRoomNotifier extends FamilyAsyncNotifier { late Stream _listener; diff --git a/app/lib/common/providers/notifiers/space_notifiers.dart b/app/lib/common/providers/notifiers/space_notifiers.dart index 151584940e11..56aa9ee4cfb7 100644 --- a/app/lib/common/providers/notifiers/space_notifiers.dart +++ b/app/lib/common/providers/notifiers/space_notifiers.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:acter/features/home/providers/client_providers.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; -import 'package:riverpod/riverpod.dart'; import 'package:logging/logging.dart'; +import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::common::space'); +final _log = Logger('a3::common::space_notifiers'); class AsyncMaybeSpaceNotifier extends FamilyAsyncNotifier { late Stream _listener; diff --git a/app/lib/common/utils/utils.dart b/app/lib/common/utils/utils.dart index 644a494af46c..5e8560479a53 100644 --- a/app/lib/common/utils/utils.dart +++ b/app/lib/common/utils/utils.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; + import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/utils/routes.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; @@ -13,9 +14,9 @@ import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/app/lib/common/widgets/chat/edit_room_description_sheet.dart b/app/lib/common/widgets/chat/edit_room_description_sheet.dart index ec0220707b37..2be3680842ef 100644 --- a/app/lib/common/widgets/chat/edit_room_description_sheet.dart +++ b/app/lib/common/widgets/chat/edit_room_description_sheet.dart @@ -3,11 +3,11 @@ import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::chat::room_description_edit_sheet'); +final _log = Logger('a3::common::chat::room_description'); void showEditRoomDescriptionBottomSheet({ required BuildContext context, diff --git a/app/lib/common/widgets/download_button.dart b/app/lib/common/widgets/download_button.dart deleted file mode 100644 index 6ef4c7a839ed..000000000000 --- a/app/lib/common/widgets/download_button.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:io'; -import 'package:path/path.dart'; - -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:flutter_easyloading/flutter_easyloading.dart'; - -Future downloadFile(BuildContext context, File file) async { - final lang = L10n.of(context); - final filename = basename(file.path); - String? outputFile = await FilePicker.platform.saveFile( - dialogTitle: lang.downloadFileDialogTitle, - fileName: filename, - ); - - if (outputFile != null) { - await file.copy(outputFile); - EasyLoading.showToast(lang.downloadFileSuccess(outputFile)); - } -} - -class DownloadButton extends StatelessWidget { - final File file; - - const DownloadButton({super.key, required this.file}); - - @override - Widget build(BuildContext context) { - if (Platform.isAndroid) { - // Saving unfortunately crashes on Android at the moment - // FIXME: https://github.com/acterglobal/a3/issues/1803 - return const SizedBox.shrink(); - } - return IconButton( - onPressed: () => downloadFile(context, file), - icon: const Icon(Icons.download_rounded), - ); - } -} diff --git a/app/lib/common/widgets/event/event_selector_drawer.dart b/app/lib/common/widgets/event/event_selector_drawer.dart index d140b4e2ed06..854c4883d337 100644 --- a/app/lib/common/widgets/event/event_selector_drawer.dart +++ b/app/lib/common/widgets/event/event_selector_drawer.dart @@ -3,7 +3,11 @@ import 'package:acter/features/events/widgets/event_item.dart'; import 'package:acter/features/events/widgets/skeletons/event_list_skeleton_widget.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::common::cal_event::select_drawer'); const Key selectEventDrawerKey = Key('event-widgets-select-event-drawer'); @@ -56,28 +60,28 @@ Future selectEventDrawer({ : ListView.builder( padding: const EdgeInsets.all(8), itemCount: eventsList.length, - itemBuilder: (context, index) { - final event = eventsList[index]; - return EventItem( - event: event, - isShowRsvp: false, - onTapEventItem: (event) { - Navigator.pop(context, event); - }, - ); - }, + itemBuilder: (context, index) => EventItem( + event: eventsList[index], + isShowRsvp: false, + onTapEventItem: (event) { + Navigator.pop(context, event); + }, + ), ), ), ], ); }, error: (error, stack) { + _log.severe('Failed to load all cal events', error, stack); return Center( - child: Text('Failed to load: $error'), + child: Text(L10n.of(context).failedToLoadEventsDueTo(error)), ); }, - loading: () => - const SizedBox(height: 500, child: EventListSkeleton()), + loading: () => const SizedBox( + height: 500, + child: EventListSkeleton(), + ), ); }, ), diff --git a/app/lib/common/widgets/image_dialog.dart b/app/lib/common/widgets/image_dialog.dart index be4985c12a18..9dd4ddd99963 100644 --- a/app/lib/common/widgets/image_dialog.dart +++ b/app/lib/common/widgets/image_dialog.dart @@ -1,10 +1,8 @@ import 'dart:io'; -import 'package:acter/common/themes/app_theme.dart'; -import 'package:acter/common/widgets/download_button.dart'; +import 'package:acter/features/files/widgets/share_file_button.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:share_plus/share_plus.dart'; import 'package:zoom_hover_pinch_image/zoom_hover_pinch_image.dart'; class ImageDialog extends ConsumerWidget { @@ -19,7 +17,6 @@ class ImageDialog extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final canShare = !isDesktop; return Dialog( insetPadding: EdgeInsets.zero, child: Scaffold( @@ -32,14 +29,7 @@ class ImageDialog extends ConsumerWidget { overflow: TextOverflow.ellipsis, ), actions: [ - if (canShare) - IconButton( - onPressed: () { - Share.shareXFiles([XFile(imageFile.path)]); - }, - icon: const Icon(Icons.share), - ), - DownloadButton(file: imageFile), + ShareFileButton(file: imageFile), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close), diff --git a/app/lib/common/widgets/room/select_room_drawer.dart b/app/lib/common/widgets/room/select_room_drawer.dart index 6b55dceb705a..6789735a2eb5 100644 --- a/app/lib/common/widgets/room/select_room_drawer.dart +++ b/app/lib/common/widgets/room/select_room_drawer.dart @@ -1,14 +1,16 @@ import 'package:acter/common/providers/chat_providers.dart'; import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/providers/space_providers.dart'; - import 'package:acter/common/widgets/room/brief_room_list_entry.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:path/path.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; + +final _log = Logger('a3::common::room::select_drawer'); // ChildRoomType configures the sub child type of the `Spaces` enum RoomType { @@ -143,14 +145,14 @@ class _SelectRoomDrawerState extends ConsumerState { //Show space list based on the search term Widget searchedRoomsList(BuildContext context) { - final searchedrooms = ref.watch( + final searched = ref.watch( switch (widget.roomType) { RoomType.space => searchedSpacesProvider, RoomType.groupChat => roomSearchedChatsProvider, }, ); - return searchedrooms.when( + return searched.when( data: (rooms) { if (rooms.isEmpty) { return Center( @@ -164,7 +166,12 @@ class _SelectRoomDrawerState extends ConsumerState { heightFactor: 10, child: CircularProgressIndicator(), ), - error: (e, s) => Center(child: Text(L10n.of(context).searchingFailed(e))), + error: (e, s) { + _log.severe('Failed to search space or convo', e, s); + return Center( + child: Text(L10n.of(context).searchingFailed(e)), + ); + }, ); } diff --git a/app/lib/common/widgets/spaces/has_space_permission.dart b/app/lib/common/widgets/spaces/has_space_permission.dart index f99b38c03087..a8f29140c15e 100644 --- a/app/lib/common/widgets/spaces/has_space_permission.dart +++ b/app/lib/common/widgets/spaces/has_space_permission.dart @@ -1,6 +1,9 @@ import 'package:acter/common/providers/room_providers.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::common::spaces::has_permission'); class HasSpacePermission extends ConsumerWidget { final String spaceId; @@ -22,7 +25,10 @@ class HasSpacePermission extends ConsumerWidget { return ref.watch(roomMembershipProvider(spaceId)).when( data: (membership) => membership?.canString(permission) == true ? child : otherwise, - error: (e, s) => otherwise, + error: (e, s) { + _log.severe('Failed to load membership', e, s); + return otherwise; + }, loading: () => otherwise, ); } diff --git a/app/lib/common/widgets/spaces/select_space_form_field.dart b/app/lib/common/widgets/spaces/select_space_form_field.dart index 3fa49b8963b5..a3f2bee7a873 100644 --- a/app/lib/common/widgets/spaces/select_space_form_field.dart +++ b/app/lib/common/widgets/spaces/select_space_form_field.dart @@ -5,8 +5,11 @@ import 'package:acter_avatar/acter_avatar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::common::spaces::select_form_field'); + class SelectSpaceFormField extends ConsumerWidget { static Key openKey = const Key('select-space-form-field-open'); @@ -103,7 +106,10 @@ class SelectSpaceFormField extends ConsumerWidget { useCompatView ? selectSpace(context, ref) : null, ) : Text(currentSelectedSpace!), - error: (e, s) => Text(L10n.of(context).errorLoading(e)), + error: (e, s) { + _log.severe('Failed to load the details of selected space', e, s); + return Text(L10n.of(context).loadingFailed(e)); + }, loading: () => Skeletonizer( child: Chip( avatar: ActerAvatar( diff --git a/app/lib/common/widgets/spaces/space_info.dart b/app/lib/common/widgets/spaces/space_info.dart index 0c97d60aea11..a168e8018b2e 100644 --- a/app/lib/common/widgets/spaces/space_info.dart +++ b/app/lib/common/widgets/spaces/space_info.dart @@ -3,10 +3,13 @@ import 'package:acter/common/widgets/visibility/visibility_chip.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::common::spaces::info'); + final isActerSpaceForSpace = FutureProvider.autoDispose.family((ref, space) async { return await space.isActerSpace(); @@ -33,7 +36,10 @@ class SpaceInfo extends ConsumerWidget { ], ); }, - error: (e, s) => Text(L10n.of(context).error(e)), + error: (e, s) { + _log.severe('Failed to load space', e, s); + return Text(L10n.of(context).loadingFailed(e)); + }, loading: () => skeletonUI(), ); } diff --git a/app/lib/common/widgets/user_builder.dart b/app/lib/common/widgets/user_builder.dart index ddfe7db4c1a3..80101575ffd5 100644 --- a/app/lib/common/widgets/user_builder.dart +++ b/app/lib/common/widgets/user_builder.dart @@ -2,16 +2,15 @@ import 'dart:typed_data'; import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/themes/colors/color_scheme.dart'; - import 'package:acter_avatar/acter_avatar.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:skeletonizer/skeletonizer.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; +import 'package:skeletonizer/skeletonizer.dart'; final _log = Logger('a3::common::user'); diff --git a/app/lib/common/widgets/video_dialog.dart b/app/lib/common/widgets/video_dialog.dart index 4a45b5f97bf2..ef59a5e6ee1d 100644 --- a/app/lib/common/widgets/video_dialog.dart +++ b/app/lib/common/widgets/video_dialog.dart @@ -1,9 +1,7 @@ import 'dart:io'; -import 'package:acter/common/themes/app_theme.dart'; import 'package:acter/common/widgets/acter_video_player.dart'; -import 'package:acter/common/widgets/download_button.dart'; +import 'package:acter/features/files/widgets/share_file_button.dart'; import 'package:flutter/material.dart'; -import 'package:share_plus/share_plus.dart'; class VideoDialog extends StatelessWidget { final String title; @@ -17,7 +15,6 @@ class VideoDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final canShare = !isDesktop; return Dialog( insetPadding: EdgeInsets.zero, child: Container( @@ -37,14 +34,7 @@ class VideoDialog extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - if (canShare) - IconButton( - onPressed: () { - Share.shareXFiles([XFile(videoFile.path)]); - }, - icon: const Icon(Icons.share), - ), - DownloadButton(file: videoFile), + ShareFileButton(file: videoFile), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close), diff --git a/app/lib/common/widgets/visibility/visibility_chip.dart b/app/lib/common/widgets/visibility/visibility_chip.dart index ded3913a308c..ec62a2c34d95 100644 --- a/app/lib/common/widgets/visibility/visibility_chip.dart +++ b/app/lib/common/widgets/visibility/visibility_chip.dart @@ -2,9 +2,12 @@ import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/utils/utils.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; + +final _log = Logger('a3::common::visibility::chip'); class VisibilityChip extends ConsumerWidget { final String roomId; @@ -18,9 +21,12 @@ class VisibilityChip extends ConsumerWidget { data: (visibility) { return renderSpaceChip(context, visibility); }, - error: (error, st) => Chip( - label: Text(L10n.of(context).loadingFailed(error)), - ), + error: (error, st) { + _log.severe('Failed to load room visibility', error, st); + return Chip( + label: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => renderLoading(), ); } diff --git a/app/lib/features/activities/widgets/invitation_card.dart b/app/lib/features/activities/widgets/invitation_card.dart index 37a3db527320..e5ad545da390 100644 --- a/app/lib/features/activities/widgets/invitation_card.dart +++ b/app/lib/features/activities/widgets/invitation_card.dart @@ -1,5 +1,4 @@ import 'package:acter/common/providers/room_providers.dart'; - import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:acter/features/activities/providers/invitations_providers.dart'; import 'package:acter/features/home/providers/client_providers.dart'; diff --git a/app/lib/features/attachments/providers/notifiers/attachments_notifiers.dart b/app/lib/features/attachments/providers/notifiers/attachments_notifiers.dart index 4990ec8e707f..b60e285677cd 100644 --- a/app/lib/features/attachments/providers/notifiers/attachments_notifiers.dart +++ b/app/lib/features/attachments/providers/notifiers/attachments_notifiers.dart @@ -7,7 +7,7 @@ import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::common::attachments'); +final _log = Logger('a3::attachments::notifiers'); class AttachmentsManagerNotifier extends AutoDisposeFamilyAsyncNotifier< AttachmentsManager, Future> { diff --git a/app/lib/features/attachments/widgets/attachment_item.dart b/app/lib/features/attachments/widgets/attachment_item.dart index 312849c23ea8..0ed4aed13bb9 100644 --- a/app/lib/features/attachments/widgets/attachment_item.dart +++ b/app/lib/features/attachments/widgets/attachment_item.dart @@ -1,19 +1,17 @@ import 'package:acter/common/actions/redact_content.dart'; import 'package:acter/common/models/attachment_media_state/attachment_media_state.dart'; import 'package:acter/common/models/types.dart'; -import 'package:acter/common/themes/app_theme.dart'; import 'package:acter/common/themes/colors/color_scheme.dart'; import 'package:acter/common/utils/utils.dart'; -import 'package:acter/common/widgets/download_button.dart'; import 'package:acter/common/widgets/image_dialog.dart'; import 'package:acter/common/widgets/video_dialog.dart'; import 'package:acter/features/attachments/providers/attachment_providers.dart'; +import 'package:acter/features/files/actions/file_share.dart'; import 'package:acter/features/pins/actions/attachment_leading_icon.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart' show Attachment; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:share_plus/share_plus.dart'; // Attachment item UI class AttachmentItem extends ConsumerWidget { @@ -182,11 +180,7 @@ class AttachmentItem extends ConsumerWidget { } // If attachment is downloaded and file or others else { - if (isDesktop) { - downloadFile(context, mediaState.mediaFile!); - } else { - Share.shareXFiles([XFile(mediaState.mediaFile!.path)]); - } + openFileShareDialog(context: context, file: mediaState.mediaFile!); } } } diff --git a/app/lib/features/attachments/widgets/attachment_section.dart b/app/lib/features/attachments/widgets/attachment_section.dart index 84e0466def1d..ef00f3eaad56 100644 --- a/app/lib/features/attachments/widgets/attachment_section.dart +++ b/app/lib/features/attachments/widgets/attachment_section.dart @@ -10,8 +10,11 @@ import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::attachments::section'); + class AttachmentSectionWidget extends ConsumerWidget { static const attachmentsKey = Key('attachments'); static const redactBtnKey = Key('attachments-redact-btn'); @@ -31,7 +34,10 @@ class AttachmentSectionWidget extends ConsumerWidget { attachmentManager: manager, key: attachmentsKey, ), - error: (e, st) => onError(context, e), + error: (e, st) { + _log.severe('Failed to load attachment manager', e, st); + return onError(context, e); + }, loading: () => loading(context), ); } @@ -70,7 +76,10 @@ class FoundAttachmentSectionWidget extends ConsumerWidget { return attachments.when( data: (list) => attachmentData(list, context, ref), - error: (err, st) => Text(L10n.of(context).errorLoadingAttachments(err)), + error: (err, st) { + _log.severe('Failed to load attachments', err, st); + return Text(L10n.of(context).errorLoadingAttachments(err)); + }, loading: () => const Skeletonizer( child: Wrap( spacing: 5.0, diff --git a/app/lib/features/attachments/widgets/views/file_view.dart b/app/lib/features/attachments/widgets/views/file_view.dart index 9e055dc5fee6..0f8157b701be 100644 --- a/app/lib/features/attachments/widgets/views/file_view.dart +++ b/app/lib/features/attachments/widgets/views/file_view.dart @@ -1,12 +1,10 @@ import 'package:acter/common/models/attachment_media_state/attachment_media_state.dart'; -import 'package:acter/common/themes/app_theme.dart'; -import 'package:acter/common/widgets/download_button.dart'; import 'package:acter/features/attachments/providers/attachment_providers.dart'; +import 'package:acter/features/files/actions/file_share.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart' show Attachment; import 'package:flutter/material.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:share_plus/share_plus.dart'; class FileView extends ConsumerWidget { final Attachment attachment; @@ -47,11 +45,7 @@ class FileView extends ConsumerWidget { return InkWell( onTap: () async { if (mediaState.mediaFile != null) { - if (isDesktop) { - downloadFile(context, mediaState.mediaFile!); - } else { - Share.shareXFiles([XFile(mediaState.mediaFile!.path)]); - } + openFileShareDialog(context: context, file: mediaState.mediaFile!); } else { ref .read(attachmentMediaStateProvider(attachment).notifier) @@ -97,11 +91,10 @@ class FileView extends ConsumerWidget { return InkWell( onTap: openView! ? () async { - if (isDesktop) { - downloadFile(context, mediaState.mediaFile!); - } else { - Share.shareXFiles([XFile(mediaState.mediaFile!.path)]); - } + openFileShareDialog( + context: context, + file: mediaState.mediaFile!, + ); } : null, child: ClipRRect( diff --git a/app/lib/features/auth/pages/forgot_password.dart b/app/lib/features/auth/pages/forgot_password.dart index 4034e9d45001..b87305321ae3 100644 --- a/app/lib/features/auth/pages/forgot_password.dart +++ b/app/lib/features/auth/pages/forgot_password.dart @@ -14,7 +14,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::auth::passwordReset'); +final _log = Logger('a3::auth::forgot_password'); class ForgotPassword extends ConsumerStatefulWidget { static Key passwordKey = const Key('pw-reset-password-field'); diff --git a/app/lib/features/auth/pages/register_page.dart b/app/lib/features/auth/pages/register_page.dart index 475f6c782411..8e73a0191272 100644 --- a/app/lib/features/auth/pages/register_page.dart +++ b/app/lib/features/auth/pages/register_page.dart @@ -1,7 +1,6 @@ import 'package:acter/common/providers/network_provider.dart'; import 'package:acter/common/themes/colors/color_scheme.dart'; import 'package:acter/common/toolkit/buttons/inline_text_button.dart'; - import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:acter/common/utils/constants.dart'; import 'package:acter/common/utils/routes.dart'; @@ -19,7 +18,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; -final _log = Logger('Register'); +final _log = Logger('a3::auth::register'); Future tryRedeem(SuperInvites superInvites, String token) async { // try to redeem the token in a fire-and-forget-manner diff --git a/app/lib/features/auth/providers/notifiers/auth_notifier.dart b/app/lib/features/auth/providers/notifiers/auth_notifier.dart index b84278c15485..8fae4381464f 100644 --- a/app/lib/features/auth/providers/notifiers/auth_notifier.dart +++ b/app/lib/features/auth/providers/notifiers/auth_notifier.dart @@ -7,7 +7,7 @@ import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::onboarding::auth'); +final _log = Logger('a3::auth::notifier'); class AuthStateNotifier extends StateNotifier { final Ref ref; diff --git a/app/lib/features/bug_report/pages/bug_report_page.dart b/app/lib/features/bug_report/pages/bug_report_page.dart index 91e39f6a17b2..38ec4481f65f 100644 --- a/app/lib/features/bug_report/pages/bug_report_page.dart +++ b/app/lib/features/bug_report/pages/bug_report_page.dart @@ -1,15 +1,16 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; + +import 'package:acter/common/providers/common_providers.dart'; import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; +import 'package:acter/common/utils/utils.dart'; import 'package:acter/config/env.g.dart'; import 'package:acter/config/setup.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:acter/common/providers/common_providers.dart'; -import 'package:acter/common/utils/utils.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; diff --git a/app/lib/features/chat/pages/room_page.dart b/app/lib/features/chat/pages/room_page.dart index f836e35bc6ef..36480702e13a 100644 --- a/app/lib/features/chat/pages/room_page.dart +++ b/app/lib/features/chat/pages/room_page.dart @@ -28,9 +28,12 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; +import 'package:logging/logging.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::chat::room'); + class RoomPage extends ConsumerWidget { static const roomPageKey = Key('chat-room-page'); final String roomId; @@ -75,9 +78,10 @@ class RoomPage extends ConsumerWidget { ); }, skipLoadingOnReload: false, - error: (error, stackTrace) => Text( - L10n.of(context).errorLoadingMembersCount(error), - ), + error: (e, s) { + _log.severe('Failed to load active members', e, s); + return Text(L10n.of(context).errorLoadingMembersCount(e)); + }, loading: () => Skeletonizer( child: Text(L10n.of(context).membersCount(100)), ), diff --git a/app/lib/features/chat/pages/room_profile_page.dart b/app/lib/features/chat/pages/room_profile_page.dart index 51a27d84718d..94bea2fb8479 100644 --- a/app/lib/features/chat/pages/room_profile_page.dart +++ b/app/lib/features/chat/pages/room_profile_page.dart @@ -5,9 +5,9 @@ import 'package:acter/common/providers/space_providers.dart'; import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:acter/common/utils/routes.dart'; import 'package:acter/common/utils/utils.dart'; +import 'package:acter/common/widgets/default_dialog.dart'; import 'package:acter/common/widgets/edit_plain_description_sheet.dart'; import 'package:acter/common/widgets/edit_title_sheet.dart'; -import 'package:acter/common/widgets/default_dialog.dart'; import 'package:acter/common/widgets/visibility/visibility_chip.dart'; import 'package:acter/features/chat/widgets/member_list.dart'; import 'package:acter/features/chat/widgets/room_avatar.dart'; @@ -26,7 +26,7 @@ import 'package:settings_ui/settings_ui.dart'; import 'package:share_plus/share_plus.dart'; import 'package:skeletonizer/skeletonizer.dart'; -final _log = Logger('a3::chat::room_profile_page'); +final _log = Logger('a3::chat::room_profile'); class RoomProfilePage extends ConsumerStatefulWidget { final String roomId; @@ -272,11 +272,7 @@ class _RoomProfilePageState extends ConsumerState { ); } - Widget _actions( - BuildContext context, - Convo? convo, - bool isDirectChat, - ) { + Widget _actions(BuildContext context, Convo? convo, bool isDirectChat) { final convoLoader = ref.watch(chatProvider(widget.roomId)); final myMembership = ref.watch(roomMembershipProvider(widget.roomId)); @@ -294,15 +290,18 @@ class _RoomProfilePageState extends ConsumerState { onTap: () async => await conv?.setBookmarked(!isBookmarked), ); }, - error: (e, st) => Skeletonizer( - child: IconButton.filled( - icon: const Icon( - Icons.bookmark_add_outlined, - size: 20, + error: (e, st) { + _log.severe('Failed to load convo', e, st); + return Skeletonizer( + child: IconButton.filled( + icon: const Icon( + Icons.bookmark_add_outlined, + size: 20, + ), + onPressed: () {}, ), - onPressed: () {}, - ), - ), + ); + }, loading: () => ActionItemSkeleton( iconData: Icons.bookmark_add_outlined, actionName: L10n.of(context).bookmark, @@ -325,7 +324,10 @@ class _RoomProfilePageState extends ConsumerState { onTap: () => _handleInvite(membership), ); }, - error: (e, st) => Text(L10n.of(context).errorLoadingTileDueTo(e)), + error: (e, st) { + _log.severe('Failed to load room membership', e, st); + return Text(L10n.of(context).errorLoadingTileDueTo(e)); + }, loading: () => ActionItemSkeleton( iconData: Atlas.user_plus_thin, actionName: L10n.of(context).invite, @@ -468,8 +470,10 @@ class _RoomProfilePageState extends ConsumerState { loading: () => Skeletonizer( child: Text(L10n.of(context).membersCount(0)), ), - error: (error, stackTrace) => - Text(L10n.of(context).errorLoadingMembersCount(error)), + error: (e, st) { + _log.severe('Failed to load room members', e, st); + return Text(L10n.of(context).errorLoadingMembersCount(e)); + }, ), MemberList(roomId: widget.roomId), ], diff --git a/app/lib/features/chat/providers/notifiers/chat_room_notifier.dart b/app/lib/features/chat/providers/notifiers/chat_room_notifier.dart index c360dc3f2570..0145f323dda6 100644 --- a/app/lib/features/chat/providers/notifiers/chat_room_notifier.dart +++ b/app/lib/features/chat/providers/notifiers/chat_room_notifier.dart @@ -10,8 +10,8 @@ import 'package:acter/features/chat/utils.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart' as types; -import 'package:riverpod/riverpod.dart'; import 'package:logging/logging.dart'; +import 'package:riverpod/riverpod.dart'; final _log = Logger('a3::chat::room_notifier'); @@ -231,8 +231,11 @@ class ChatRoomNotifier extends StateNotifier { try { roomMsg = await timeline.getMessage(originalId); } catch (error, stack) { - _log.severe('Failing to load reference $replyId (from $originalId)', - error, stack,); + _log.severe( + 'Failing to load reference $replyId (from $originalId)', + error, + stack, + ); return; } diff --git a/app/lib/features/chat/providers/notifiers/media_chat_notifier.dart b/app/lib/features/chat/providers/notifiers/media_chat_notifier.dart index 082474df7b1e..e15cb8ede05b 100644 --- a/app/lib/features/chat/providers/notifiers/media_chat_notifier.dart +++ b/app/lib/features/chat/providers/notifiers/media_chat_notifier.dart @@ -6,10 +6,10 @@ import 'package:acter/features/chat/models/media_chat_state/media_chat_state.dar import 'package:acter/features/chat/providers/chat_providers.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:riverpod/riverpod.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:riverpod/riverpod.dart'; final _log = Logger('a3::chat::media_chat_notifier'); diff --git a/app/lib/features/chat/widgets/chats_list.dart b/app/lib/features/chat/widgets/chats_list.dart index 9487cf63800e..bafa2e716b62 100644 --- a/app/lib/features/chat/widgets/chats_list.dart +++ b/app/lib/features/chat/widgets/chats_list.dart @@ -6,11 +6,14 @@ import 'package:acter/common/widgets/empty_state_widget.dart'; import 'package:acter/features/chat/providers/chat_providers.dart'; import 'package:acter/features/chat/providers/room_list_filter_provider.dart'; import 'package:acter/features/home/providers/client_providers.dart'; +import 'package:diffutil_dart/diffutil.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:diffutil_dart/diffutil.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::chat::chats_list'); class ChatsList extends ConsumerStatefulWidget { final Function(String)? onSelected; @@ -48,14 +51,15 @@ class _ChatsListConsumerState extends ConsumerState { child: CircularProgressIndicator(), ), ), - error: (e, s) => SliverToBoxAdapter( - child: Center( - heightFactor: 10, - child: Text( - '${L10n.of(context).searchingFailed}: $e', + error: (e, s) { + _log.severe('Failed to filter convos', e, s); + return SliverToBoxAdapter( + child: Center( + heightFactor: 10, + child: Text(L10n.of(context).searchingFailed(e)), ), - ), - ), + ); + }, skipLoadingOnReload: true, ); } diff --git a/app/lib/features/chat/widgets/create_chat.dart b/app/lib/features/chat/widgets/create_chat.dart index 9507c6841c96..8e61da6cd1f2 100644 --- a/app/lib/features/chat/widgets/create_chat.dart +++ b/app/lib/features/chat/widgets/create_chat.dart @@ -1,23 +1,23 @@ import 'dart:io'; import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; -import 'package:acter/features/chat/actions/create_chat.dart'; -import 'package:acter/common/widgets/user_builder.dart'; -import 'package:acter/features/invite_members/providers/invite_providers.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:acter/common/providers/space_providers.dart'; import 'package:acter/common/utils/routes.dart'; import 'package:acter/common/utils/utils.dart'; import 'package:acter/common/widgets/input_text_field.dart'; import 'package:acter/common/widgets/spaces/select_space_form_field.dart'; +import 'package:acter/common/widgets/user_builder.dart'; +import 'package:acter/features/chat/actions/create_chat.dart'; import 'package:acter/features/chat/providers/create_chat_providers.dart'; import 'package:acter/features/home/providers/client_providers.dart'; +import 'package:acter/features/invite_members/providers/invite_providers.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart' as ffi; import 'package:atlas_icons/atlas_icons.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; @@ -378,7 +378,10 @@ class _CreateChatWidgetConsumerState extends ConsumerState<_CreateChatWidget> { onUp: _onUp, ), ), - error: (e, st) => Text(L10n.of(context).errorLoadingUsers(e)), + error: (e, st) { + _log.severe('Failed to search users', e, st); + return Text(L10n.of(context).errorLoadingUsers(e)); + }, loading: () => const Center( heightFactor: 5, child: CircularProgressIndicator(), @@ -716,7 +719,10 @@ class _UserWidget extends ConsumerWidget { ), ); }, - error: (e, st) => Text(L10n.of(context).errorLoadingAvatar(e)), + error: (e, st) { + _log.severe('Failed to load binary data of avatar', e, st); + return Text(L10n.of(context).errorLoadingAvatar(e)); + }, loading: () => Skeletonizer( child: ActerAvatar( options: AvatarOptions.DM( diff --git a/app/lib/features/chat/widgets/custom_input.dart b/app/lib/features/chat/widgets/custom_input.dart index b9269726c20f..88cc8eb73ab8 100644 --- a/app/lib/features/chat/widgets/custom_input.dart +++ b/app/lib/features/chat/widgets/custom_input.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:acter/common/models/types.dart'; import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/themes/app_theme.dart'; - import 'package:acter/common/widgets/emoji_picker_widget.dart'; import 'package:acter/common/widgets/frost_effect.dart'; import 'package:acter/features/attachments/actions/select_attachment.dart'; diff --git a/app/lib/features/chat/widgets/file_message_builder.dart b/app/lib/features/chat/widgets/file_message_builder.dart index 316a37dec992..5554f310e95d 100644 --- a/app/lib/features/chat/widgets/file_message_builder.dart +++ b/app/lib/features/chat/widgets/file_message_builder.dart @@ -1,14 +1,12 @@ import 'package:acter/common/models/types.dart'; -import 'package:acter/common/themes/app_theme.dart'; -import 'package:acter/common/widgets/download_button.dart'; import 'package:acter/features/chat/models/media_chat_state/media_chat_state.dart'; import 'package:acter/features/chat/providers/chat_providers.dart'; +import 'package:acter/features/files/actions/file_share.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:share_plus/share_plus.dart'; class FileMessageBuilder extends ConsumerWidget { final types.FileMessage message; @@ -31,11 +29,7 @@ class FileMessageBuilder extends ConsumerWidget { return InkWell( onTap: () async { if (mediaState.mediaFile != null) { - if (isDesktop) { - downloadFile(context, mediaState.mediaFile!); - } else { - Share.shareXFiles([XFile(mediaState.mediaFile!.path)]); - } + openFileShareDialog(context: context, file: mediaState.mediaFile!); } else { await ref .read(mediaChatStateProvider(messageInfo).notifier) diff --git a/app/lib/features/chat/widgets/member_list.dart b/app/lib/features/chat/widgets/member_list.dart index 977da52f315a..9142da8acb38 100644 --- a/app/lib/features/chat/widgets/member_list.dart +++ b/app/lib/features/chat/widgets/member_list.dart @@ -2,8 +2,11 @@ import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/features/chat/widgets/skeletons/members_list_skeleton_widget.dart'; import 'package:acter/features/member/widgets/member_list_entry.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::chat::member_list'); class MemberList extends ConsumerWidget { final String roomId; @@ -21,9 +24,7 @@ class MemberList extends ConsumerWidget { data: (members) { if (members.isEmpty) { return Center( - child: Text( - L10n.of(context).noMembersFound, - ), + child: Text(L10n.of(context).noMembersFound), ); } return ListView.builder( @@ -42,9 +43,12 @@ class MemberList extends ConsumerWidget { }, ); }, - error: (error, stack) => Center( - child: Text(L10n.of(context).loadingFailed(error)), - ), + error: (error, stack) { + _log.severe('Failed to load room members', error, stack); + return Center( + child: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => const MembersListSkeleton(), ); } diff --git a/app/lib/features/chat/widgets/mention_profile_builder.dart b/app/lib/features/chat/widgets/mention_profile_builder.dart index adef6abd8d27..fd5e4c771348 100644 --- a/app/lib/features/chat/widgets/mention_profile_builder.dart +++ b/app/lib/features/chat/widgets/mention_profile_builder.dart @@ -5,9 +5,12 @@ import 'package:acter/features/home/providers/client_providers.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:acter_trigger_auto_complete/acter_trigger_autocomplete.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; + +final _log = Logger('a3::chat::mention_profile_builder'); class MentionProfileBuilder extends ConsumerWidget { final BuildContext context; @@ -23,7 +26,7 @@ class MentionProfileBuilder extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final client = ref.watch(alwaysClientProvider); final userId = client.userId().toString(); - var memberIds = ref.watch(membersIdsProvider((roomQuery.roomId))); + var memberIds = ref.watch(membersIdsProvider(roomQuery.roomId)); return memberIds.when( loading: () => Skeletonizer( child: SizedBox( @@ -31,7 +34,10 @@ class MentionProfileBuilder extends ConsumerWidget { child: Card(child: ListView()), ), ), - error: (error, st) => ErrorWidget(L10n.of(context).failedToLoad(error)), + error: (error, st) { + _log.severe('Failed to load room members', error, st); + return ErrorWidget(L10n.of(context).loadingFailed(error)); + }, data: (data) { final users = data.fold>({}, (map, uId) { if (uId != userId) { diff --git a/app/lib/features/chat/widgets/message_actions.dart b/app/lib/features/chat/widgets/message_actions.dart index c7b3305aba03..34e6ec5a38a9 100644 --- a/app/lib/features/chat/widgets/message_actions.dart +++ b/app/lib/features/chat/widgets/message_actions.dart @@ -1,10 +1,9 @@ +import 'package:acter/common/actions/report_content.dart'; import 'package:acter/common/providers/chat_providers.dart'; import 'package:acter/common/providers/common_providers.dart'; import 'package:acter/common/providers/room_providers.dart'; - import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:acter/common/widgets/default_dialog.dart'; -import 'package:acter/common/actions/report_content.dart'; import 'package:acter/features/chat/providers/chat_providers.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; diff --git a/app/lib/features/chat/widgets/room_avatar.dart b/app/lib/features/chat/widgets/room_avatar.dart index 4d4533c228e0..93e6ad5f8457 100644 --- a/app/lib/features/chat/widgets/room_avatar.dart +++ b/app/lib/features/chat/widgets/room_avatar.dart @@ -3,10 +3,13 @@ import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/features/home/providers/client_providers.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::chat::room_avatar'); + class RoomAvatar extends ConsumerWidget { final String roomId; final double avatarSize; @@ -107,8 +110,10 @@ class RoomAvatar extends ConsumerWidget { } }, skipLoadingOnReload: false, - error: (error, stackTrace) => - errorAvatar(L10n.of(context).loadingMembersCountFailed(error)), + error: (e, st) { + _log.severe('Failed to load room members', e, st); + return errorAvatar(L10n.of(context).loadingMembersCountFailed(e)); + }, loading: () => loadingAvatar(), ); } diff --git a/app/lib/features/comments/widgets/comments_list.dart b/app/lib/features/comments/widgets/comments_list.dart index 0ad2c3ea4c6e..38b382fa8e31 100644 --- a/app/lib/features/comments/widgets/comments_list.dart +++ b/app/lib/features/comments/widgets/comments_list.dart @@ -4,8 +4,11 @@ import 'package:acter/features/comments/widgets/comment.dart'; import 'package:acter/features/comments/widgets/create_comment.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::comments::list'); class CommentsList extends ConsumerStatefulWidget { final CommentsManager manager; @@ -32,7 +35,10 @@ class _CommentsListState extends ConsumerState { return commentListUI(context, manager); } }, - error: (e, st) => onError(context, e), + error: (e, st) { + _log.severe('Failed to load comments', e, st); + return onError(context, e); + }, loading: () => loading(context), ); } diff --git a/app/lib/features/comments/widgets/comments_section.dart b/app/lib/features/comments/widgets/comments_section.dart index a69f539740ff..eaaa9e534611 100644 --- a/app/lib/features/comments/widgets/comments_section.dart +++ b/app/lib/features/comments/widgets/comments_section.dart @@ -5,8 +5,11 @@ import 'package:acter/features/settings/providers/settings_providers.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::comments::section'); class CommentsSection extends ConsumerWidget { final Future manager; @@ -24,7 +27,10 @@ class CommentsSection extends ConsumerWidget { } return ref.watch(commentsManagerProvider(manager)).when( data: (manager) => found(context, manager), - error: (e, st) => onError(context, e), + error: (e, st) { + _log.severe('Failed to load comment manager', e, st); + return onError(context, e); + }, loading: () => loading(context), ); } diff --git a/app/lib/features/events/pages/create_event_page.dart b/app/lib/features/events/pages/create_event_page.dart index 2f7d38d12d41..1a7f83866eab 100644 --- a/app/lib/features/events/pages/create_event_page.dart +++ b/app/lib/features/events/pages/create_event_page.dart @@ -16,7 +16,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::event::createOrEdit'); +final _log = Logger('a3::cal_event::createOrEdit'); const createEditEventKey = Key('create-edit-event'); diff --git a/app/lib/features/events/pages/event_details_page.dart b/app/lib/features/events/pages/event_details_page.dart index bac992c3f720..fb9eb48be7ea 100644 --- a/app/lib/features/events/pages/event_details_page.dart +++ b/app/lib/features/events/pages/event_details_page.dart @@ -1,8 +1,9 @@ +import 'dart:io'; + import 'package:acter/common/actions/redact_content.dart'; import 'package:acter/common/actions/report_content.dart'; import 'package:acter/common/providers/common_providers.dart'; import 'package:acter/common/providers/room_providers.dart'; -import 'package:acter/common/themes/app_theme.dart'; import 'package:acter/common/utils/utils.dart'; import 'package:acter/features/events/actions/get_event_type.dart'; import 'package:acter/features/events/widgets/change_date_sheet.dart'; @@ -19,6 +20,7 @@ import 'package:acter/features/events/utils/events_utils.dart'; import 'package:acter/features/events/widgets/event_date_widget.dart'; import 'package:acter/features/events/widgets/participants_list.dart'; import 'package:acter/features/events/widgets/skeletons/event_details_skeleton_widget.dart'; +import 'package:acter/features/files/actions/file_share.dart'; import 'package:acter/features/home/providers/client_providers.dart'; import 'package:acter/features/home/widgets/space_chip.dart'; import 'package:acter/features/space/widgets/member_avatar.dart'; @@ -26,7 +28,6 @@ import 'package:acter_avatar/acter_avatar.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:atlas_icons/atlas_icons.dart'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -35,9 +36,9 @@ import 'package:jiffy/jiffy.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' show join; import 'package:path_provider/path_provider.dart'; -import 'package:share_plus/share_plus.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; -final _log = Logger('a3::event::details'); +final _log = Logger('a3::cal_event::details'); class EventDetailPage extends ConsumerStatefulWidget { final String calendarId; @@ -68,8 +69,10 @@ class _EventDetailPageConsumerState extends ConsumerState { ], ); }, - error: (error, stackTrace) => - Text(L10n.of(context).errorLoadingEventDueTo(error)), + error: (e, st) { + _log.severe('Failed to load cal event', e, st); + return Text(L10n.of(context).errorLoadingEventDueTo(e)); + }, loading: () => const EventDetailsSkeleton(), ), ); @@ -419,51 +422,29 @@ class _EventDetailPageConsumerState extends ConsumerState { } Widget _buildShareAction(CalendarEvent calendarEvent) { - return PopupMenuButton( - icon: const Icon(Icons.share), - itemBuilder: (context) => [ - PopupMenuItem( - onTap: () => onShareEvent(calendarEvent), - child: Row( - children: [ - const Icon(Icons.share), - const SizedBox(width: 10), - Text(L10n.of(context).shareIcal), - ], - ), - ), - ], + return IconButton( + icon: PhosphorIcon(PhosphorIcons.shareFat()), + onPressed: () => onShareEvent(calendarEvent), ); } Future onShareEvent(CalendarEvent event) async { try { final filename = event.title().replaceAll(RegExp(r'[^A-Za-z0-9_-]'), '_'); - - if (isDesktop) { - String? outputFile = await FilePicker.platform.saveFile( - dialogTitle: 'Please select where to store the file', - fileName: '$filename.ics', - ); - - if (outputFile != null) { - // User canceled the picker - event.icalForSharing(outputFile); - EasyLoading.showToast('File saved to $outputFile'); - } - return; - } - final tempDir = await getTemporaryDirectory(); final icalPath = join(tempDir.path, '$filename.ics'); event.icalForSharing(icalPath); - await Share.shareXFiles([ - XFile( - icalPath, + if (context.mounted) { + await openFileShareDialog( + // ignore: use_build_context_synchronously + context: context, + // ignore: use_build_context_synchronously + header: Text(L10n.of(context).shareIcal), + file: File(icalPath), mimeType: 'text/calendar', - ), - ]); + ); + } } catch (error, stack) { _log.severe('Creating iCal Share Event failed:', error, stack); // ignore: use_build_context_synchronously diff --git a/app/lib/features/events/pages/event_list_page.dart b/app/lib/features/events/pages/event_list_page.dart index ff3968b20539..89187a2f6436 100644 --- a/app/lib/features/events/pages/event_list_page.dart +++ b/app/lib/features/events/pages/event_list_page.dart @@ -1,4 +1,5 @@ import 'dart:math'; + import 'package:acter/common/providers/space_providers.dart'; import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:acter/common/utils/routes.dart'; @@ -11,10 +12,13 @@ import 'package:acter/features/events/widgets/event_item.dart'; import 'package:acter/features/events/widgets/skeletons/event_list_skeleton_widget.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::cal_event::list'); class EventListPage extends ConsumerStatefulWidget { final String? spaceId; @@ -76,15 +80,17 @@ class _EventListPageState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - ActerSearchWidget( - searchTextController: searchTextController, - ), + ActerSearchWidget(searchTextController: searchTextController), filterChipsButtons(), Expanded( child: eventList.when( data: (events) => _buildEventList(events), - error: (error, stack) => - Center(child: Text(L10n.of(context).loadingFailed(error))), + error: (e, st) { + _log.severe('Failed to search events in space', e, st); + return Center( + child: Text(L10n.of(context).searchingFailed(e)), + ); + }, loading: () => const EventListSkeleton(), ), ), diff --git a/app/lib/features/events/widgets/event_item.dart b/app/lib/features/events/widgets/event_item.dart index cb844bf7b844..b4b0da1640c1 100644 --- a/app/lib/features/events/widgets/event_item.dart +++ b/app/lib/features/events/widgets/event_item.dart @@ -8,9 +8,12 @@ import 'package:acter/features/events/widgets/event_date_widget.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart' show CalendarEvent; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::cal_event::event_item'); class EventItem extends StatelessWidget { final CalendarEvent event; @@ -115,12 +118,15 @@ class EventItem extends StatelessWidget { ? rsvpStatusWidget : const SizedBox.shrink(); }, - error: (e, st) => Chip( - label: Text( - L10n.of(context).errorLoadingRsvpStatus(e), - softWrap: true, - ), - ), + error: (e, st) { + _log.severe('Failed to load RSVP status', e, st); + return Chip( + label: Text( + L10n.of(context).errorLoadingRsvpStatus(e), + softWrap: true, + ), + ); + }, loading: () => Chip( label: Text(L10n.of(context).loadingRsvpStatus), ), @@ -133,12 +139,12 @@ class EventItem extends StatelessWidget { if (status != null) { switch (status) { case 'yes': - return Icon( + return Icon( Icons.check_circle, color: Theme.of(context).colorScheme.secondary, ); case 'no': - return Icon( + return Icon( Icons.cancel, color: Theme.of(context).colorScheme.error, ); diff --git a/app/lib/features/files/actions/download_file.dart b/app/lib/features/files/actions/download_file.dart new file mode 100644 index 000000000000..0a245a62d2d9 --- /dev/null +++ b/app/lib/features/files/actions/download_file.dart @@ -0,0 +1,24 @@ +import 'dart:io'; +import 'package:path/path.dart'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; + +Future downloadFile(BuildContext context, File file) async { + final lang = L10n.of(context); + final filename = basename(file.path); + String? outputFile = await FilePicker.platform.saveFile( + dialogTitle: lang.downloadFileDialogTitle, + fileName: filename, + ); + + if (outputFile == null) { + return false; + } + + await file.copy(outputFile); + EasyLoading.showToast(lang.downloadFileSuccess(outputFile)); + return true; +} diff --git a/app/lib/features/files/actions/file_share.dart b/app/lib/features/files/actions/file_share.dart new file mode 100644 index 000000000000..e2427fdd95e2 --- /dev/null +++ b/app/lib/features/files/actions/file_share.dart @@ -0,0 +1,109 @@ +import 'dart:io'; + +import 'package:acter/features/files/actions/download_file.dart'; +import 'package:open_filex/open_filex.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:share_plus/share_plus.dart'; + +Future openFileShareDialog({ + required BuildContext context, + required File file, + Widget? header, + String? mimeType, + List? beforeOptions, + List? afterOptions, +}) async { + await showModalBottomSheet( + showDragHandle: true, + useSafeArea: true, + context: context, + isDismissible: true, + constraints: const BoxConstraints(maxHeight: 300), + builder: (context) => _FileOptionsDialog( + file: file, + header: header, + beforeOptions: beforeOptions, + afterOptions: afterOptions, + mimeType: mimeType, + ), + ); +} + +class _FileOptionsDialog extends StatelessWidget { + final File file; + final String? mimeType; + final Widget? header; + final List? beforeOptions; + final List? afterOptions; + + const _FileOptionsDialog({ + required this.file, + this.header, + this.afterOptions, + this.beforeOptions, + this.mimeType, + }); + + @override + Widget build(BuildContext context) { + final lang = L10n.of(context); + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + constraints: const BoxConstraints( + maxWidth: 600, + minWidth: 300, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + header ?? const SizedBox.shrink(), + if (header != null) const SizedBox(height: 16.0), + if (beforeOptions?.isNotEmpty == true) ...beforeOptions!, + TextButton.icon( + onPressed: () async { + final result = await OpenFilex.open(file.absolute.path); + if (result.type == ResultType.done) { + // done, close this dialog + if (context.mounted) { + Navigator.pop(context); + } + } + }, + label: Text(lang.openFile), + icon: PhosphorIcon(PhosphorIcons.fileArrowUp()), + ), + TextButton.icon( + onPressed: () async { + final result = await Share.shareXFiles( + [XFile(file.path, mimeType: mimeType)],); + if (result.status == ShareResultStatus.success) { + // done, close this dialog + if (context.mounted) { + Navigator.pop(context); + } + } + }, + label: Text(lang.shareFile), + icon: PhosphorIcon(PhosphorIcons.shareNetwork()), + ), + if (!Platform.isAndroid) // crashes on Android for some reason ... + TextButton.icon( + onPressed: () async { + if (await downloadFile(context, file)) { + // done, close this dialog + if (context.mounted) { + Navigator.pop(context); + } + } + }, + label: Text(lang.saveFileAs), + icon: PhosphorIcon(PhosphorIcons.downloadSimple()), + ), + if (afterOptions?.isNotEmpty == true) ...afterOptions!, + ], + ), + ); + } +} diff --git a/app/lib/features/files/widgets/share_file_button.dart b/app/lib/features/files/widgets/share_file_button.dart new file mode 100644 index 000000000000..bbf32c02bc1f --- /dev/null +++ b/app/lib/features/files/widgets/share_file_button.dart @@ -0,0 +1,18 @@ +import 'dart:io'; +import 'package:acter/features/files/actions/file_share.dart'; +import 'package:flutter/material.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +class ShareFileButton extends StatelessWidget { + final File file; + + const ShareFileButton({super.key, required this.file}); + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () => openFileShareDialog(context: context, file: file), + icon: PhosphorIcon(PhosphorIcons.shareFat()), + ); + } +} diff --git a/app/lib/features/home/pages/home_shell.dart b/app/lib/features/home/pages/home_shell.dart index 9106217ab214..a9692a584b31 100644 --- a/app/lib/features/home/pages/home_shell.dart +++ b/app/lib/features/home/pages/home_shell.dart @@ -83,7 +83,7 @@ class HomeShellState extends ConsumerState { // shake is possible in only actual mobile devices if (await isRealPhone()) { detector = ShakeDetector.autoStart( - shakeThresholdGravity: 3.0, + shakeThresholdGravity: 30.0, onShake: () { openBugReport(context); }, diff --git a/app/lib/features/home/widgets/my_events.dart b/app/lib/features/home/widgets/my_events.dart index 3390b0ca250d..0ae8cfb8d2de 100644 --- a/app/lib/features/home/widgets/my_events.dart +++ b/app/lib/features/home/widgets/my_events.dart @@ -5,9 +5,12 @@ import 'package:acter/features/events/widgets/event_item.dart'; import 'package:acter/features/events/widgets/skeletons/event_list_skeleton_widget.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::home::my_events'); class MyEventsSection extends ConsumerWidget { final int? limit; @@ -44,9 +47,10 @@ class MyEventsSection extends ConsumerWidget { ) : const SizedBox.shrink(); }, - error: (error, stackTrace) => Text( - L10n.of(context).loadingEventsFailed(error), - ), + error: (error, stackTrace) { + _log.severe('Failed to load cal events', error, stackTrace); + return Text(L10n.of(context).loadingEventsFailed(error)); + }, loading: () => const EventListSkeleton(), ); } diff --git a/app/lib/features/home/widgets/my_tasks.dart b/app/lib/features/home/widgets/my_tasks.dart index 5d33b61ce7e5..823e6c204334 100644 --- a/app/lib/features/home/widgets/my_tasks.dart +++ b/app/lib/features/home/widgets/my_tasks.dart @@ -7,6 +7,9 @@ import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::home::my_tasks'); class MyTasksSection extends ConsumerWidget { final int limit; @@ -26,8 +29,10 @@ class MyTasksSection extends ConsumerWidget { ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - separatorBuilder: (context, index) => - const Divider(color: Colors.white24, indent: 30), + separatorBuilder: (context, index) => const Divider( + color: Colors.white24, + indent: 30, + ), itemCount: tasks.length, itemBuilder: (context, index) { return TaskItem( @@ -42,7 +47,10 @@ class MyTasksSection extends ConsumerWidget { ), ], ), - error: (error, stack) => Text(L10n.of(context).loadingTasksFailed(error)), + error: (error, stack) { + _log.severe('Failed to load open tasks', error, stack); + return Text(L10n.of(context).loadingTasksFailed(error)); + }, loading: () => Text(L10n.of(context).loading), ); } diff --git a/app/lib/features/home/widgets/space_chip.dart b/app/lib/features/home/widgets/space_chip.dart index c8bec38528e5..04347a31ca0b 100644 --- a/app/lib/features/home/widgets/space_chip.dart +++ b/app/lib/features/home/widgets/space_chip.dart @@ -3,9 +3,12 @@ import 'package:acter/common/toolkit/buttons/inline_text_button.dart'; import 'package:acter/router/utils.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; + +final _log = Logger('a3::home::space_chip'); class SpaceChip extends ConsumerWidget { final SpaceItem? space; @@ -34,9 +37,12 @@ class SpaceChip extends ConsumerWidget { data: (space) { return renderSpace(context, space); }, - error: (error, st) => Chip( - label: Text(L10n.of(context).loadingFailed(error)), - ), + error: (error, st) { + _log.severe('Failed to load brief of space', error, st); + return Chip( + label: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => renderLoading(spaceId!), ); } @@ -48,9 +54,7 @@ class SpaceChip extends ConsumerWidget { child: Chip( avatar: ActerAvatar( options: AvatarOptions( - AvatarInfo( - uniqueId: spaceId, - ), + AvatarInfo(uniqueId: spaceId), size: 24, ), ), diff --git a/app/lib/features/invite_members/pages/invite_individual_users.dart b/app/lib/features/invite_members/pages/invite_individual_users.dart index af9d9959927f..51f5cca0819c 100644 --- a/app/lib/features/invite_members/pages/invite_individual_users.dart +++ b/app/lib/features/invite_members/pages/invite_individual_users.dart @@ -1,15 +1,18 @@ import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/widgets/empty_state_widget.dart'; import 'package:acter/common/widgets/user_builder.dart'; +import 'package:acter/features/invite_members/providers/invite_providers.dart'; import 'package:acter/features/invite_members/widgets/direct_invite.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:acter/features/invite_members/providers/invite_providers.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::invite::individual_users'); + class InviteIndividualUsers extends ConsumerWidget { final String roomId; @@ -172,8 +175,10 @@ class InviteIndividualUsers extends ConsumerWidget { userId: data[index].userId().toString(), roomId: roomId, ), - error: (err, stackTrace) => - Text(L10n.of(context).error(err)), + error: (err, st) { + _log.severe('Failed to search users', err, st); + return Text(L10n.of(context).searchingFailed(err)); + }, loading: () => Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), child: Skeletonizer( diff --git a/app/lib/features/invite_members/pages/invite_space_members.dart b/app/lib/features/invite_members/pages/invite_space_members.dart index 269e4a6adae1..7ca1325b37c5 100644 --- a/app/lib/features/invite_members/pages/invite_space_members.dart +++ b/app/lib/features/invite_members/pages/invite_space_members.dart @@ -5,12 +5,12 @@ import 'package:acter/features/invite_members/widgets/space_member_invite_card.d import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; -final _log = Logger('a3::invite::invite_space_members'); +final _log = Logger('a3::invite::space_members'); class InviteSpaceMembers extends ConsumerStatefulWidget { final String roomId; @@ -106,9 +106,12 @@ class _InviteSpaceMembersConsumerState ref.watch(otherSpacesForInviteMembersProvider(widget.roomId)); return otherSpaces.when( data: _buildOtherSpaceData, - error: (error, stack) => ListTile( - title: Text(error.toString()), - ), + error: (error, stack) { + _log.severe('Failed to load other spaces', error, stack); + return ListTile( + title: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => _buildSkeletonizerLoading(), ); } diff --git a/app/lib/features/invite_members/providers/invite_providers.dart b/app/lib/features/invite_members/providers/invite_providers.dart index 299342161fc5..5b654cf4c6cd 100644 --- a/app/lib/features/invite_members/providers/invite_providers.dart +++ b/app/lib/features/invite_members/providers/invite_providers.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::common::invite_provider'); +final _log = Logger('a3::invite::providers'); final userNameRegExp = RegExp( r'@\S+:\S+.\S+$', @@ -66,8 +66,11 @@ final suggestedUsersProvider = FutureProvider.family, String>( MemoryImage? avatar; if (user.hasAvatar()) { try { - avatar = await user.getAvatar(null).then((val) => - MemoryImage(Uint8List.fromList(val.data()!.asTypedList())),); + avatar = await user.getAvatar(null).then( + (val) => MemoryImage( + Uint8List.fromList(val.data()!.asTypedList()), + ), + ); } catch (e, s) { _log.severe('failure fetching avatar', e, s); } diff --git a/app/lib/features/invite_members/widgets/invite_code_ui.dart b/app/lib/features/invite_members/widgets/invite_code_ui.dart index 61b60de23672..27195dcf5178 100644 --- a/app/lib/features/invite_members/widgets/invite_code_ui.dart +++ b/app/lib/features/invite_members/widgets/invite_code_ui.dart @@ -15,7 +15,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::invite_members::invite_code'); +final _log = Logger('a3::invite::invite_code'); class InviteCodeUI extends ConsumerStatefulWidget { final String roomId; diff --git a/app/lib/features/member/widgets/member_info_drawer.dart b/app/lib/features/member/widgets/member_info_drawer.dart index c076f771052a..504f4a0f9816 100644 --- a/app/lib/features/member/widgets/member_info_drawer.dart +++ b/app/lib/features/member/widgets/member_info_drawer.dart @@ -17,8 +17,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::member::member_info_drawer'); + class _MemberInfoDrawerInner extends ConsumerWidget { final Member member; final String memberId; @@ -218,15 +221,18 @@ class _MemberInfoDrawerInner extends ConsumerWidget { } return menu; }, - error: (e, s) => [ - _roomTitle(context, ref), - MenuItemWidget( - iconData: Atlas.triangle_exclamation_thin, - title: L10n.of(context).errorLoading(e), - withMenu: false, - onTap: () {}, - ), - ], + error: (e, s) { + _log.severe('Failed to load room membership', e, s); + return [ + _roomTitle(context, ref), + MenuItemWidget( + iconData: Atlas.triangle_exclamation_thin, + title: L10n.of(context).loadingFailed(e), + withMenu: false, + onTap: () {}, + ), + ]; + }, loading: () => [ _roomTitle(context, ref), Skeletonizer( @@ -314,10 +320,13 @@ class MemberInfoDrawer extends ConsumerWidget { memberId: memberId, isShowActions: isShowActions, ), - error: (e, s) => Padding( - padding: const EdgeInsets.all(20.0), - child: Text(L10n.of(context).errorLoadingProfile(e)), - ), + error: (e, s) { + _log.severe('Failed to load room member', e, s); + return Padding( + padding: const EdgeInsets.all(20.0), + child: Text(L10n.of(context).errorLoadingProfile(e)), + ); + }, loading: () => const MemberInfoSkeleton(), ); } diff --git a/app/lib/features/news/pages/add_news_page.dart b/app/lib/features/news/pages/add_news_page.dart index d88b6242b6e3..a1d306d94842 100644 --- a/app/lib/features/news/pages/add_news_page.dart +++ b/app/lib/features/news/pages/add_news_page.dart @@ -27,8 +27,11 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; import 'package:mime/mime.dart'; +final _log = Logger('a3::news::add_page'); + const addNewsKey = Key('add-news'); class AddNewsPage extends ConsumerStatefulWidget { @@ -227,11 +230,16 @@ class AddNewsState extends ConsumerState { ), ); }, - loading: () => - const SizedBox(width: 300, child: EventItemSkeleton()), - error: (e, s) => Center( - child: Text(L10n.of(context).failedToLoadEvent(e)), + loading: () => const SizedBox( + width: 300, + child: EventItemSkeleton(), ), + error: (e, s) { + _log.severe('Failed to load cal event', e, s); + return Center( + child: Text(L10n.of(context).failedToLoadEvent(e)), + ); + }, ), ], ), @@ -345,9 +353,8 @@ class AddNewsState extends ConsumerState { return; } - String displayMsg = L10n.of(context).slidePosting; // Show loading message - EasyLoading.show(status: displayMsg); + EasyLoading.show(status: L10n.of(context).slidePosting); try { final space = await ref.read(spaceProvider(spaceId).future); NewsEntryDraft draft = space.newsDraft(); @@ -467,7 +474,7 @@ class AddNewsState extends ConsumerState { return; } EasyLoading.showError( - '$displayMsg ${L10n.of(context).failed}: \n $err', + L10n.of(context).error(err), duration: const Duration(seconds: 3), ); } diff --git a/app/lib/features/news/widgets/news_item.dart b/app/lib/features/news/widgets/news_item.dart index 97d44ea9e64e..2cbf696ef1fb 100644 --- a/app/lib/features/news/widgets/news_item.dart +++ b/app/lib/features/news/widgets/news_item.dart @@ -4,9 +4,9 @@ import 'package:acter/features/events/widgets/event_item.dart'; import 'package:acter/features/events/widgets/skeletons/event_item_skeleton_widget.dart'; import 'package:acter/features/news/model/news_references_model.dart'; import 'package:acter/features/news/widgets/news_item_slide/video_slide.dart'; -import 'package:acter/features/news/widgets/news_side_bar.dart'; import 'package:acter/features/news/widgets/news_item_slide/image_slide.dart'; import 'package:acter/features/news/widgets/news_item_slide/text_slide.dart'; +import 'package:acter/features/news/widgets/news_side_bar.dart'; import 'package:acter/router/utils.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; @@ -14,8 +14,11 @@ import 'package:carousel_indicator/carousel_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::news::news_item'); + class NewsItem extends ConsumerStatefulWidget { final Client client; final NewsEntry news; @@ -104,7 +107,10 @@ class _NewsItemState extends ConsumerState { padding: const EdgeInsets.all(16), child: space.when( data: (space) => Text(space.avatarInfo.displayName ?? roomId), - error: (e, st) => Text(L10n.of(context).errorLoadingSpace(e)), + error: (e, st) { + _log.severe('Failed to load brief of space', e, st); + return Text(L10n.of(context).errorLoadingSpace(e)); + }, loading: () => Skeletonizer( child: Text(roomId), ), @@ -180,8 +186,12 @@ class _NewsItemState extends ConsumerState { ); }, loading: () => const EventItemSkeleton(), - error: (e, s) => - Center(child: Text(L10n.of(context).failedToLoadEvent(e))), + error: (e, s) { + _log.severe('Failed to load cal event', e, s); + return Center( + child: Text(L10n.of(context).failedToLoadEvent(e)), + ); + }, ); } else { return Card( diff --git a/app/lib/features/news/widgets/news_item_slide/image_slide.dart b/app/lib/features/news/widgets/news_item_slide/image_slide.dart index ed998413c913..03fa3b2e2f05 100644 --- a/app/lib/features/news/widgets/news_item_slide/image_slide.dart +++ b/app/lib/features/news/widgets/news_item_slide/image_slide.dart @@ -1,8 +1,12 @@ import 'dart:typed_data'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; + import 'package:acter/features/news/model/keys.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::news::widget::image_slide'); class ImageSlide extends StatelessWidget { final NewsSlide slide; @@ -22,6 +26,11 @@ class ImageSlide extends StatelessWidget { future: slide.sourceBinary(null), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { + _log.severe( + 'Failed to load image of slide', + snapshot.error, + snapshot.stackTrace, + ); return Center( child: Text(L10n.of(context).errorLoadingImage(snapshot.error!)), ); diff --git a/app/lib/features/news/widgets/news_item_slide/video_slide.dart b/app/lib/features/news/widgets/news_item_slide/video_slide.dart index 81563f859a2a..e92709358206 100644 --- a/app/lib/features/news/widgets/news_item_slide/video_slide.dart +++ b/app/lib/features/news/widgets/news_item_slide/video_slide.dart @@ -5,9 +5,12 @@ import 'package:acter/features/news/model/keys.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; +final _log = Logger('a3::news::widget::video_slide'); + class VideoSlide extends StatelessWidget { final NewsSlide slide; final Color bgColor; @@ -43,8 +46,13 @@ class VideoSlide extends StatelessWidget { future: getNewsVideo(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { + _log.severe( + 'Failed to load video of slide', + snapshot.error, + snapshot.stackTrace, + ); return Center( - child: Text(L10n.of(context).errorLoading(snapshot.error!)), + child: Text(L10n.of(context).loadingFailed(snapshot.error!)), ); } diff --git a/app/lib/features/news/widgets/news_side_bar.dart b/app/lib/features/news/widgets/news_side_bar.dart index d38f60b1b9b7..596baab669ee 100644 --- a/app/lib/features/news/widgets/news_side_bar.dart +++ b/app/lib/features/news/widgets/news_side_bar.dart @@ -100,7 +100,7 @@ class NewsSideBar extends ConsumerWidget { ), ), error: (e, st) { - _log.severe('Error loading space', e, st); + _log.severe('Failed to load brief of space', e, st); return ActerAvatar( options: AvatarOptions( AvatarInfo( diff --git a/app/lib/features/news/widgets/news_widget.dart b/app/lib/features/news/widgets/news_widget.dart index 9c5d4c4ad153..2d96bb1dafb2 100644 --- a/app/lib/features/news/widgets/news_widget.dart +++ b/app/lib/features/news/widgets/news_widget.dart @@ -6,9 +6,12 @@ import 'package:acter/features/home/providers/client_providers.dart'; import 'package:acter/features/news/providers/news_providers.dart'; import 'package:acter/features/news/widgets/news_item.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::news::news_widget'); class NewsWidget extends ConsumerStatefulWidget { const NewsWidget({super.key}); @@ -76,7 +79,10 @@ class _NewsWidgetState extends ConsumerState { ); }, error: (error, stackTrace) { - return Center(child: Text(L10n.of(context).couldNotFetchNews)); + _log.severe('Failed to load news list', error, stackTrace); + return Center( + child: Text(L10n.of(context).couldNotFetchNews), + ); }, loading: () => const Center( child: SizedBox( diff --git a/app/lib/features/pins/pages/pin_page.dart b/app/lib/features/pins/pages/pin_page.dart index 85a5ed996f95..63d8a86306bf 100644 --- a/app/lib/features/pins/pages/pin_page.dart +++ b/app/lib/features/pins/pages/pin_page.dart @@ -15,8 +15,11 @@ import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::pins::pin_page'); + class PinPage extends ConsumerWidget { static const pinPageKey = Key('pin-page'); static const actionMenuKey = Key('pin-action-menu'); @@ -195,11 +198,16 @@ class PinPage extends ConsumerWidget { ); }, loading: () => SliverAppBar( - title: Skeletonizer(child: Text(L10n.of(context).loadingPin)), - ), - error: (err, st) => SliverAppBar( - title: Text(L10n.of(context).errorLoadingPin(err)), + title: Skeletonizer( + child: Text(L10n.of(context).loadingPin), + ), ), + error: (err, st) { + _log.severe('Failed to load pin', err, st); + return SliverAppBar( + title: Text(L10n.of(context).errorLoadingPin(err)), + ); + }, ), SliverToBoxAdapter( child: pin.when( @@ -213,7 +221,10 @@ class PinPage extends ConsumerWidget { CommentsSection(manager: acterPin.comments()), ], ), - error: (err, st) => Text(L10n.of(context).errorLoadingPin(err)), + error: (err, st) { + _log.severe('Failed to load pin', err, st); + return Text(L10n.of(context).errorLoadingPin(err)); + }, loading: () => const Skeletonizer( child: Card(), ), diff --git a/app/lib/features/pins/pages/pins_list_page.dart b/app/lib/features/pins/pages/pins_list_page.dart index 6c5ac0bf12c5..dcdd28620449 100644 --- a/app/lib/features/pins/pages/pins_list_page.dart +++ b/app/lib/features/pins/pages/pins_list_page.dart @@ -1,4 +1,5 @@ import 'dart:math'; + import 'package:acter/common/providers/space_providers.dart'; import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:acter/common/utils/routes.dart'; @@ -11,10 +12,13 @@ import 'package:acter/features/pins/widgets/pin_list_item.dart'; import 'package:acter/features/pins/widgets/pin_list_skeleton.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::pins::list'); class PinsListPage extends ConsumerStatefulWidget { final String? spaceId; @@ -86,8 +90,12 @@ class _AllPinsPageConsumerState extends ConsumerState { Expanded( child: pinList.when( data: (pins) => _buildPinsList(pins), - error: (error, stack) => - Center(child: Text(L10n.of(context).loadingFailed(error))), + error: (error, stack) { + _log.severe('Failed to load pins', error, stack); + return Center( + child: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => const PinListSkeleton(), ), ), diff --git a/app/lib/features/pins/widgets/pin_list_item.dart b/app/lib/features/pins/widgets/pin_list_item.dart index b5ef57bb001e..1ab13bd25452 100644 --- a/app/lib/features/pins/widgets/pin_list_item.dart +++ b/app/lib/features/pins/widgets/pin_list_item.dart @@ -10,8 +10,11 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_matrix_html/flutter_html.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::pins::pin_item'); + class PinListItemById extends ConsumerWidget { final String pinId; final bool showSpace; @@ -31,7 +34,10 @@ class PinListItemById extends ConsumerWidget { pin: acterPin, showSpace: showSpace, ), - error: (err, st) => Text(L10n.of(context).errorLoading(err)), + error: (err, st) { + _log.severe('Failed to load pin', err, st); + return Text(L10n.of(context).errorLoadingPin(err)); + }, loading: () => const Skeletonizer( child: SizedBox( height: 100, diff --git a/app/lib/features/public_room_search/widgets/public_room_item.dart b/app/lib/features/public_room_search/widgets/public_room_item.dart index f8c76e566520..86b6f3426d08 100644 --- a/app/lib/features/public_room_search/widgets/public_room_item.dart +++ b/app/lib/features/public_room_search/widgets/public_room_item.dart @@ -3,10 +3,10 @@ import 'package:acter/features/public_room_search/providers/public_space_info_pr import 'package:acter/features/public_room_search/types.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter/material.dart'; -import 'package:logging/logging.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; final _log = Logger('a3::public_room_search::public_room_item'); @@ -26,14 +26,12 @@ class _JoinBtn extends ConsumerWidget { data: (data) => data == null ? noMember(context) : alreadyMember(context), error: (error, st) { - _log.severe('loading membership info failed', error, st); + _log.severe('Failed to load room membership', error, st); return Text(L10n.of(context).loadingFailed(error)); }, loading: () => Skeletonizer( child: OutlinedButton( - onPressed: () => onSelected( - item, - ), + onPressed: () => onSelected(item), child: Text(L10n.of(context).requestToJoin), ), ), @@ -42,9 +40,7 @@ class _JoinBtn extends ConsumerWidget { Widget alreadyMember(BuildContext context) { return OutlinedButton( - onPressed: () => onSelected( - item, - ), + onPressed: () => onSelected(item), child: Text(L10n.of(context).member), ); } @@ -52,16 +48,12 @@ class _JoinBtn extends ConsumerWidget { Widget noMember(BuildContext context) { if (item.joinRuleStr() == 'Public') { return OutlinedButton( - onPressed: () => onSelected( - item, - ), + onPressed: () => onSelected(item), child: Text(L10n.of(context).join), ); } else { return OutlinedButton( - onPressed: () => onSelected( - item, - ), + onPressed: () => onSelected(item), child: Text(L10n.of(context).requestToJoin), ); } @@ -93,17 +85,13 @@ class PublicRoomItem extends ConsumerWidget { child: Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: ListTile( - onTap: () => onSelected( - item, - ), + onTap: () => onSelected(item), leading: profileInfo.when( data: (profile) => ActerAvatar( - options: AvatarOptions( - profile, - ), + options: AvatarOptions(profile), ), error: (e, s) { - _log.severe('loading failed', e, s); + _log.severe('Failed to load avatar info', e, s); return fallbackAvatar(); }, loading: fallbackAvatar, @@ -128,8 +116,10 @@ class PublicRoomItem extends ConsumerWidget { if (topic != null) Flexible( child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), child: Text( topic, style: Theme.of(context).textTheme.labelMedium, diff --git a/app/lib/features/room/actions/join_room.dart b/app/lib/features/room/actions/join_room.dart index de3c183d63bb..b830ca8be932 100644 --- a/app/lib/features/room/actions/join_room.dart +++ b/app/lib/features/room/actions/join_room.dart @@ -35,7 +35,7 @@ Future joinRoom( return null; } EasyLoading.showError( - '$displayMsg ${L10n.of(context).failed}: \n $err"', + L10n.of(context).error(err), duration: const Duration(seconds: 3), ); return null; diff --git a/app/lib/features/room/widgets/notifications_settings_tile.dart b/app/lib/features/room/widgets/notifications_settings_tile.dart index bb8fa8602108..8ce80e1b2311 100644 --- a/app/lib/features/room/widgets/notifications_settings_tile.dart +++ b/app/lib/features/room/widgets/notifications_settings_tile.dart @@ -7,7 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:settings_ui/settings_ui.dart'; -final _log = Logger('a3::room::notification_settings_tile'); +final _log = Logger('a3::room::notification_settings'); String? notifToText(BuildContext context, String curNotifStatus) { if (curNotifStatus == 'muted') { diff --git a/app/lib/features/search/widgets/pins_builder.dart b/app/lib/features/search/widgets/pins_builder.dart index e83d7d01c70b..19a6613cacc0 100644 --- a/app/lib/features/search/widgets/pins_builder.dart +++ b/app/lib/features/search/widgets/pins_builder.dart @@ -1,9 +1,12 @@ import 'package:acter/common/utils/routes.dart'; import 'package:acter/features/search/providers/pins.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::search::pins_builder'); class PinsBuilder extends ConsumerWidget { final bool popBeforeRoute; @@ -17,7 +20,10 @@ class PinsBuilder extends ConsumerWidget { final foundPins = ref.watch(pinsFoundProvider); return foundPins.when( loading: () => Text(L10n.of(context).loading), - error: (e, st) => Text(L10n.of(context).error(e)), + error: (e, st) { + _log.severe('Failed to search pins', e, st); + return Text(L10n.of(context).searchingFailed(e)); + }, data: (data) { final Widget body; if (data.isEmpty) { diff --git a/app/lib/features/search/widgets/quick_actions_builder.dart b/app/lib/features/search/widgets/quick_actions_builder.dart index e3ef0a5b421c..2c826885dedb 100644 --- a/app/lib/features/search/widgets/quick_actions_builder.dart +++ b/app/lib/features/search/widgets/quick_actions_builder.dart @@ -9,10 +9,10 @@ import 'package:acter/features/spaces/model/keys.dart'; import 'package:acter/features/tasks/sheets/create_update_task_list.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; final _log = Logger('a3::search::quick_actions_builder'); diff --git a/app/lib/features/search/widgets/spaces_builder.dart b/app/lib/features/search/widgets/spaces_builder.dart index 6a73b13db000..f104f998df7c 100644 --- a/app/lib/features/search/widgets/spaces_builder.dart +++ b/app/lib/features/search/widgets/spaces_builder.dart @@ -4,11 +4,14 @@ import 'package:acter/features/search/providers/search.dart'; import 'package:acter/features/search/providers/spaces.dart'; import 'package:acter/router/utils.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::search::spaces_builder'); + class SpacesBuilder extends ConsumerWidget { final bool popBeforeRoute; @@ -22,10 +25,13 @@ class SpacesBuilder extends ConsumerWidget { final foundSpaces = ref.watch(spacesFoundProvider); return foundSpaces.when( loading: () => renderLoading(context), - error: (e, st) => inBox( - context, - Text(L10n.of(context).error(e)), - ), + error: (e, st) { + _log.severe('Failed to search spaces', e, st); + return inBox( + context, + Text(L10n.of(context).searchingFailed(e)), + ); + }, data: (data) { if (data.isEmpty) { return renderEmpty(context, ref); diff --git a/app/lib/features/settings/model/language_model.dart b/app/lib/features/settings/model/language_model.dart index 6a11cef87134..fd774e3c390b 100644 --- a/app/lib/features/settings/model/language_model.dart +++ b/app/lib/features/settings/model/language_model.dart @@ -9,8 +9,12 @@ class LanguageModel { factory LanguageModel.fromCode(String? locale) { switch (locale) { + case 'ar': + return const LanguageModel.arabic(); case 'de': return const LanguageModel.german(); + case 'es': + return const LanguageModel.spanish(); case 'pl': return const LanguageModel.polish(); case 'fr': @@ -38,11 +42,21 @@ class LanguageModel { : languageName = 'Polski', languageCode = 'pl'; + const LanguageModel.spanish() + : languageName = 'Espanol', + languageCode = 'es'; + + const LanguageModel.arabic() + : languageName = 'اَلْعَرَبِيَّةُ', + languageCode = 'ar'; + static const allLanguagesList = [ - // we show them in ehm... alphabetical order + // we show them in ehm... alphabetical order of the name in their own language LanguageModel.german(), LanguageModel.english(), + LanguageModel.spanish(), LanguageModel.french(), LanguageModel.polish(), + LanguageModel.arabic(), ]; } diff --git a/app/lib/features/settings/pages/blocked_users.dart b/app/lib/features/settings/pages/blocked_users.dart index c4b60331bd1c..971df563f935 100644 --- a/app/lib/features/settings/pages/blocked_users.dart +++ b/app/lib/features/settings/pages/blocked_users.dart @@ -119,8 +119,9 @@ class BlockedUsersPage extends ConsumerWidget { child: Text(L10n.of(context).hereYouCanSeeAllUsersYouBlocked), ), error: (error, stack) { + _log.severe('Failed to load the ignored users', error, stack); return Center( - child: Text(L10n.of(context).failedToLoad(error)), + child: Text(L10n.of(context).loadingFailed(error)), ); }, loading: () => const Center( diff --git a/app/lib/features/settings/pages/change_password.dart b/app/lib/features/settings/pages/change_password.dart index 2bc43c981522..9a2cbbebc64c 100644 --- a/app/lib/features/settings/pages/change_password.dart +++ b/app/lib/features/settings/pages/change_password.dart @@ -6,11 +6,11 @@ import 'package:acter/features/settings/pages/settings_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::room::change_password'); +final _log = Logger('a3::settings::change_password'); class ChangePasswordPage extends ConsumerStatefulWidget { const ChangePasswordPage({super.key}); diff --git a/app/lib/features/settings/pages/chat_settings_page.dart b/app/lib/features/settings/pages/chat_settings_page.dart index 13a0abdffeb6..21337609f361 100644 --- a/app/lib/features/settings/pages/chat_settings_page.dart +++ b/app/lib/features/settings/pages/chat_settings_page.dart @@ -7,9 +7,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:settings_ui/settings_ui.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:logging/logging.dart'; final _log = Logger('a3::settings::chat_settings'); @@ -47,11 +47,12 @@ class ChatSettingsPage extends ConsumerWidget { } }, ), - error: (error, stack) => SettingsTile.navigation( - title: Text( - L10n.of(context).failed, - ), - ), + error: (error, stack) { + _log.severe('Failed to load user app settings', error, stack); + return SettingsTile.navigation( + title: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => SettingsTile.switchTile( title: Skeletonizer( child: Text(L10n.of(context).chatSettingsAutoDownload), @@ -66,10 +67,7 @@ class ChatSettingsPage extends ConsumerWidget { ); } - AbstractSettingsTile _typingNotice( - BuildContext context, - WidgetRef ref, - ) { + AbstractSettingsTile _typingNotice(BuildContext context, WidgetRef ref) { return ref.watch(userAppSettingsProvider).when( data: (settings) => SettingsTile.switchTile( title: Text(L10n.of(context).chatSettingsTyping), @@ -98,11 +96,12 @@ class ChatSettingsPage extends ConsumerWidget { } }, ), - error: (error, stack) => SettingsTile.navigation( - title: Text( - L10n.of(context).failed, - ), - ), + error: (error, stack) { + _log.severe('Failed to load user app settings', error, stack); + return SettingsTile.navigation( + title: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => SettingsTile.switchTile( title: Skeletonizer( child: Text(L10n.of(context).chatSettingsTyping), diff --git a/app/lib/features/settings/pages/email_addresses.dart b/app/lib/features/settings/pages/email_addresses.dart index 488bf2ff40de..d8c647048f78 100644 --- a/app/lib/features/settings/pages/email_addresses.dart +++ b/app/lib/features/settings/pages/email_addresses.dart @@ -9,6 +9,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::settings::email_addresses'); class AddEmailAddr extends StatefulWidget { const AddEmailAddr({super.key}); @@ -92,15 +95,14 @@ class EmailAddressesPage extends ConsumerWidget { ), IconButton( onPressed: () => addEmailAddress(context, ref), - icon: const Icon( - Atlas.plus_circle_thin, - ), + icon: const Icon(Atlas.plus_circle_thin), ), ], ), body: emailAddresses.when( data: (addresses) => buildAddresses(context, addresses), error: (error, stack) { + _log.severe('Failed to load email addresses', error, stack); return Center( child: Text(L10n.of(context).errorLoadingEmailAddresses(error)), ); diff --git a/app/lib/features/settings/pages/notifications_page.dart b/app/lib/features/settings/pages/notifications_page.dart index dad30e7c81af..07e8193032ed 100644 --- a/app/lib/features/settings/pages/notifications_page.dart +++ b/app/lib/features/settings/pages/notifications_page.dart @@ -1,7 +1,6 @@ import 'package:acter/config/notifications/init.dart'; import 'package:acter_notifify/util.dart'; import 'package:acter/common/toolkit/buttons/danger_action_button.dart'; - import 'package:acter/common/toolkit/buttons/primary_action_button.dart'; import 'package:acter/common/utils/utils.dart'; import 'package:acter/common/widgets/with_sidebar.dart'; @@ -261,11 +260,14 @@ class NotificationsSettingsPage extends ConsumerWidget { .map((item) => _pusherTile(context, ref, item)) .toList(); }, - error: (e, s) => [ - SettingsTile( - title: Text(L10n.of(context).failedToLoadPushTargets(e)), - ), - ], + error: (e, s) { + _log.severe('Failed to load pushers', e, s); + return [ + SettingsTile( + title: Text(L10n.of(context).failedToLoadPushTargets(e)), + ), + ]; + }, loading: () => [ SettingsTile( title: Text(L10n.of(context).loadingTargets), diff --git a/app/lib/features/settings/pages/sessions_page.dart b/app/lib/features/settings/pages/sessions_page.dart index 4d0d3852f1b0..7fc67f3be824 100644 --- a/app/lib/features/settings/pages/sessions_page.dart +++ b/app/lib/features/settings/pages/sessions_page.dart @@ -8,6 +8,9 @@ import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::settings::sessions'); class SessionsPage extends ConsumerWidget { const SessionsPage({super.key}); @@ -36,6 +39,7 @@ class SessionsPage extends ConsumerWidget { body: allSessions.when( data: (sessions) => buildSessions(context, sessions), error: (error, stack) { + _log.severe('Failed to load unknown sessions', error, stack); return Center( child: Text(L10n.of(context).couldNotLoadAllSessions), ); diff --git a/app/lib/features/settings/widgets/app_notifications_settings_tile.dart b/app/lib/features/settings/widgets/app_notifications_settings_tile.dart index 7d7f7e3eca50..6f938a692346 100644 --- a/app/lib/features/settings/widgets/app_notifications_settings_tile.dart +++ b/app/lib/features/settings/widgets/app_notifications_settings_tile.dart @@ -2,9 +2,12 @@ import 'package:acter/common/providers/common_providers.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:settings_ui/settings_ui.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::settings::app_notifications'); + class _AppNotificationSettingsTile extends ConsumerWidget { final String title; final String appKey; @@ -22,9 +25,16 @@ class _AppNotificationSettingsTile extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return ref.watch(appContentNotificationSetting(appKey)).when( data: (v) => innerBuild(context, ref, v), - error: (error, st) => SettingsTile( - title: Text('${L10n.of(context).error}: $error'), - ), + error: (error, st) { + _log.severe( + 'Fetching of app content notification setting failed', + error, + st, + ); + return SettingsTile( + title: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => Skeletonizer( child: SettingsTile.switchTile( initialValue: true, diff --git a/app/lib/features/settings/widgets/labs_notifications_settings_tile.dart b/app/lib/features/settings/widgets/labs_notifications_settings_tile.dart index 54508d505e33..52022a1e5467 100644 --- a/app/lib/features/settings/widgets/labs_notifications_settings_tile.dart +++ b/app/lib/features/settings/widgets/labs_notifications_settings_tile.dart @@ -12,7 +12,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:settings_ui/settings_ui.dart'; -final _log = Logger('a3::settings::labs_notifications_settings_tile'); +final _log = Logger('a3::settings::labs_notifications'); final isOnSupportedPlatform = Platform.isAndroid || Platform.isIOS; diff --git a/app/lib/features/space/actions/set_space_title.dart b/app/lib/features/space/actions/set_space_title.dart index 5680e20469b1..969a9e82d6f5 100644 --- a/app/lib/features/space/actions/set_space_title.dart +++ b/app/lib/features/space/actions/set_space_title.dart @@ -3,11 +3,11 @@ import 'package:acter/common/providers/space_providers.dart'; import 'package:acter/common/widgets/edit_title_sheet.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::space::set_space_title'); +final _log = Logger('a3::space::actions::set_space_title'); void showEditSpaceNameBottomSheet({ required BuildContext context, diff --git a/app/lib/features/space/dialogs/suggested_rooms.dart b/app/lib/features/space/dialogs/suggested_rooms.dart index 1e8d6180c5f0..ad23d43d3e72 100644 --- a/app/lib/features/space/dialogs/suggested_rooms.dart +++ b/app/lib/features/space/dialogs/suggested_rooms.dart @@ -8,8 +8,8 @@ import 'package:acter/features/space/actions/has_seen_suggested.dart'; import 'package:acter/features/space/providers/suggested_provider.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; final _log = Logger('a3::spaces::suggested_rooms'); diff --git a/app/lib/features/space/pages/members_page.dart b/app/lib/features/space/pages/members_page.dart index 4bd789460b08..b3b39d3edeb3 100644 --- a/app/lib/features/space/pages/members_page.dart +++ b/app/lib/features/space/pages/members_page.dart @@ -1,12 +1,16 @@ import 'dart:math'; + import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/providers/space_providers.dart'; import 'package:acter/common/utils/routes.dart'; import 'package:acter/features/member/widgets/member_list_entry.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::space::members_page'); class SpaceMembersPage extends ConsumerWidget { final String spaceIdOrAlias; @@ -52,9 +56,7 @@ class SpaceMembersPage extends ConsumerWidget { if (members.isEmpty) { return SliverToBoxAdapter( child: Center( - child: Text( - L10n.of(context).noMembersFound, - ), + child: Text(L10n.of(context).noMembersFound), ), ); } @@ -72,11 +74,14 @@ class SpaceMembersPage extends ConsumerWidget { }, ); }, - error: (error, stack) => SliverToBoxAdapter( - child: Center( - child: Text(L10n.of(context).loadingFailed(error)), - ), - ), + error: (error, stack) { + _log.severe('Failed to load space members', error, stack); + return SliverToBoxAdapter( + child: Center( + child: Text(L10n.of(context).loadingFailed(error)), + ), + ); + }, loading: () => SliverToBoxAdapter( child: Center( child: Text(L10n.of(context).loading), diff --git a/app/lib/features/space/pages/space_details_page.dart b/app/lib/features/space/pages/space_details_page.dart index 38dcafd102aa..cf97629e3378 100644 --- a/app/lib/features/space/pages/space_details_page.dart +++ b/app/lib/features/space/pages/space_details_page.dart @@ -18,8 +18,11 @@ import 'package:acter/features/space/widgets/space_toolbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +final _log = Logger('a3::space::space_details'); + class SpaceDetailsPage extends ConsumerStatefulWidget { static const headerKey = Key('space-menus-header'); final String spaceId; @@ -146,7 +149,10 @@ class _SpaceDetailsPageState extends ConsumerState { }, ); }, - error: (error, stack) => Text(L10n.of(context).loadingFailed(error)), + error: (error, stack) { + _log.severe('Failed to load tabs in space', error, stack); + return Text(L10n.of(context).loadingFailed(error)); + }, loading: () => const SpaceDetailsSkeletons(), ); } diff --git a/app/lib/features/space/pages/sub_spaces_page.dart b/app/lib/features/space/pages/sub_spaces_page.dart index b4a932e3555a..eb4fb4f2e4e6 100644 --- a/app/lib/features/space/pages/sub_spaces_page.dart +++ b/app/lib/features/space/pages/sub_spaces_page.dart @@ -12,6 +12,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::space::sub_spaces'); class SubSpacesPage extends ConsumerWidget { static const moreOptionKey = Key('related-spaces-more-actions'); @@ -115,9 +118,12 @@ class SubSpacesPage extends ConsumerWidget { return const SizedBox.shrink(); } }, - error: (error, stack) => Center( - child: Text(L10n.of(context).loadingFailed(error)), - ), + error: (error, stack) { + _log.severe('Failed to load the related spaces', error, stack); + return Center( + child: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => const SizedBox.shrink(), ), ], @@ -139,9 +145,12 @@ class SubSpacesPage extends ConsumerWidget { canLinkSpace, ); }, - error: (error, stack) => Center( - child: Text(L10n.of(context).loadingFailed(error)), - ), + error: (error, stack) { + _log.severe('Failed to load the related spaces', error, stack); + return Center( + child: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => Center( child: Text(L10n.of(context).loading), ), diff --git a/app/lib/features/space/providers/suggested_provider.dart b/app/lib/features/space/providers/suggested_provider.dart index 4721d2cd7750..166178478500 100644 --- a/app/lib/features/space/providers/suggested_provider.dart +++ b/app/lib/features/space/providers/suggested_provider.dart @@ -1,8 +1,8 @@ import 'package:acter/common/providers/room_providers.dart'; import 'package:acter/common/providers/space_providers.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; -import 'package:riverpod/riverpod.dart'; import 'package:logging/logging.dart'; +import 'package:riverpod/riverpod.dart'; final _log = Logger('a3::space::providers::suggested'); diff --git a/app/lib/features/space/settings/pages/apps_settings_page.dart b/app/lib/features/space/settings/pages/apps_settings_page.dart index a92c24ea6a1c..f66bcd80dad3 100644 --- a/app/lib/features/space/settings/pages/apps_settings_page.dart +++ b/app/lib/features/space/settings/pages/apps_settings_page.dart @@ -342,9 +342,12 @@ class SpaceAppsSettingsPage extends ConsumerWidget { ); }, loading: () => const Center(child: Text('loading')), - error: (e, s) => Center( - child: Text('Error loading app settings: $e'), - ), + error: (e, s) { + _log.severe('Failed to load space settings', e, s); + return Center( + child: Text(L10n.of(context).loadingFailed(e)), + ); + }, ), ); } diff --git a/app/lib/features/space/settings/pages/visibility_accessibility_page.dart b/app/lib/features/space/settings/pages/visibility_accessibility_page.dart index 723e355b64b5..b186f194b28c 100644 --- a/app/lib/features/space/settings/pages/visibility_accessibility_page.dart +++ b/app/lib/features/space/settings/pages/visibility_accessibility_page.dart @@ -3,18 +3,18 @@ import 'package:acter/common/providers/sdk_provider.dart'; import 'package:acter/common/providers/space_providers.dart'; import 'package:acter/common/utils/utils.dart'; import 'package:acter/common/widgets/spaces/has_space_permission.dart'; -import 'package:acter/common/widgets/visibility/room_visibilty_type.dart'; import 'package:acter/common/widgets/spaces/space_selector_drawer.dart'; +import 'package:acter/common/widgets/visibility/room_visibilty_type.dart'; import 'package:acter_avatar/acter_avatar.dart'; import 'package:atlas_icons/atlas_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; -final _log = Logger('a3::space::settings::visibility_accessibility_settings'); +final _log = Logger('a3::space::settings::visibility_accessibility'); class VisibilityAccessibilityPage extends ConsumerStatefulWidget { final String roomId; @@ -109,9 +109,12 @@ class _VisibilityAccessibilityPageState }, ); }, - error: (e, st) => const RoomVisibilityType( - selectedVisibilityEnum: RoomVisibility.Private, - ), + error: (e, st) { + _log.severe('Failed to load room visibility', e, st); + return const RoomVisibilityType( + selectedVisibilityEnum: RoomVisibility.Private, + ); + }, loading: () => const Skeletonizer( child: RoomVisibilityType( selectedVisibilityEnum: RoomVisibility.Private, @@ -150,7 +153,7 @@ class _VisibilityAccessibilityPageState }, ), error: (error, stack) { - _log.severe('Loading Space Info failed', error, stack); + _log.severe('Failed to load the allowed rooms', error, stack); return _spaceItemCard( 'Loading Space Info failed', subtitle: Text('$error'), @@ -214,7 +217,7 @@ class _VisibilityAccessibilityPageState return ref.watch(briefSpaceItemProvider(spaceId)).when( data: (d) => _spaceFoundUI(d, canEdit), error: (error, stack) { - _log.severe('Loading Space Info failed', error, stack); + _log.severe('Failed to load brief of space', error, stack); return _spaceItemCard( spaceId, subtitle: Text('Loading Space Info failed: $error'), diff --git a/app/lib/features/space/sheets/link_room_sheet.dart b/app/lib/features/space/sheets/link_room_sheet.dart index df0d990eefa2..8edf591e0388 100644 --- a/app/lib/features/space/sheets/link_room_sheet.dart +++ b/app/lib/features/space/sheets/link_room_sheet.dart @@ -14,6 +14,9 @@ import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::space::link_room_sheet'); // ChildRoomType configures the sub child type of the `Spaces` enum ChildRoomType { @@ -138,7 +141,10 @@ class _LinkRoomPageConsumerState extends ConsumerState { ], ); }, - error: (e, s) => errorUI(L10n.of(context).error(e)), + error: (e, s) { + _log.severe('Failed to load the details of selected space', e, s); + return errorUI(L10n.of(context).loadingFailed(e)); + }, loading: () => Container(), ), ); @@ -178,7 +184,10 @@ class _LinkRoomPageConsumerState extends ConsumerState { data: (chats) => chats.isEmpty ? Text(L10n.of(context).noChatsFoundMatchingYourSearchTerm) : chatListUI(chats), - error: (e, s) => errorUI(L10n.of(context).searchingFailed(e)), + error: (e, s) { + _log.severe('Failed to search chats', e, s); + return errorUI(L10n.of(context).searchingFailed(e)); + }, loading: () => loadingUI(), ); } @@ -233,7 +242,10 @@ class _LinkRoomPageConsumerState extends ConsumerState { return spaceListUI(spaces); }, loading: () => loadingUI(), - error: (e, s) => errorUI(L10n.of(context).searchingFailed(e)), + error: (e, s) { + _log.severe('Failed to search spaces', e, s); + return errorUI(L10n.of(context).searchingFailed(e)); + }, ); } diff --git a/app/lib/features/space/widgets/related/chats_helpers.dart b/app/lib/features/space/widgets/related/chats_helpers.dart index 36b3bf9ce12e..76e93b31ec05 100644 --- a/app/lib/features/space/widgets/related/chats_helpers.dart +++ b/app/lib/features/space/widgets/related/chats_helpers.dart @@ -3,10 +3,13 @@ import 'package:acter/common/widgets/chat/convo_card.dart'; import 'package:acter/common/widgets/chat/convo_hierarchy_card.dart'; import 'package:acter/router/utils.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::space::related::chats_helpers'); + Widget chatsListUI(WidgetRef ref, List chats, int chatsLimit) { return ListView.builder( shrinkWrap: true, @@ -54,10 +57,16 @@ Widget renderFurther( }, ); }, - error: (e, s) => - Card(child: Text(L10n.of(context).errorLoadingRelatedChats(e))), + error: (e, s) { + _log.severe('Failed to load the related chats', e, s); + return Card( + child: Text(L10n.of(context).errorLoadingRelatedChats(e)), + ); + }, loading: () => Skeletonizer( - child: Card(child: Text(L10n.of(context).loadingOtherChats)), + child: Card( + child: Text(L10n.of(context).loadingOtherChats), + ), ), ); } diff --git a/app/lib/features/space/widgets/related/spaces_helpers.dart b/app/lib/features/space/widgets/related/spaces_helpers.dart index 5487633031d5..62964b595b9d 100644 --- a/app/lib/features/space/widgets/related/spaces_helpers.dart +++ b/app/lib/features/space/widgets/related/spaces_helpers.dart @@ -9,7 +9,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; -final _log = Logger('a3::space::widget::related::spaces_helpers'); +final _log = Logger('a3::space::related::spaces_helpers'); List? _renderKnownSubspaces( BuildContext context, @@ -92,15 +92,12 @@ Widget renderMoreSubspaces( }, ); }, - error: (error, s) { - _log.severe( - 'Loading subspaces from remote failed $spaceIdOrAlias', - error, - s, - ); + error: (e, s) { + _log.severe('Failed to load the related subspaces', e, s); return Card( - child: - ListTile(title: Text(L10n.of(context).loadingSpacesFailed(error))), + child: ListTile( + title: Text(L10n.of(context).loadingSpacesFailed(e)), + ), ); }, loading: () => const Skeletonizer( diff --git a/app/lib/features/space/widgets/space_header_profile.dart b/app/lib/features/space/widgets/space_header_profile.dart index a2f52e14df5e..e8179cddeb54 100644 --- a/app/lib/features/space/widgets/space_header_profile.dart +++ b/app/lib/features/space/widgets/space_header_profile.dart @@ -9,8 +9,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::space::space_header_profile'); + class SpaceHeaderProfile extends ConsumerWidget { static const headerKey = Key('space-header'); @@ -126,9 +129,10 @@ class SpaceHeaderProfile extends ConsumerWidget { ), ); }, - error: (error, stack) => Text( - L10n.of(context).loadingMembersFailed(error), - ), + error: (error, stack) { + _log.severe('Failed to load members in space', error, stack); + return Text(L10n.of(context).loadingMembersFailed(error)); + }, loading: () => const Skeletonizer( child: Wrap( direction: Axis.horizontal, diff --git a/app/lib/features/space/widgets/space_sections/about_section.dart b/app/lib/features/space/widgets/space_sections/about_section.dart index 159e49d61652..cb36a809beb9 100644 --- a/app/lib/features/space/widgets/space_sections/about_section.dart +++ b/app/lib/features/space/widgets/space_sections/about_section.dart @@ -3,8 +3,11 @@ import 'package:acter/features/space/actions/set_space_topic.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::space::sections::about'); + class AboutSection extends ConsumerWidget { final String spaceId; @@ -60,9 +63,10 @@ class AboutSection extends ConsumerWidget { ), ); }, - error: (error, stack) => Text( - L10n.of(context).loadingFailed(error), - ), + error: (error, stack) { + _log.severe('Failed to load space', error, stack); + return Text(L10n.of(context).loadingFailed(error)); + }, loading: () => Skeletonizer( child: Text(L10n.of(context).loading), ), diff --git a/app/lib/features/space/widgets/space_sections/chats_section.dart b/app/lib/features/space/widgets/space_sections/chats_section.dart index 9bee2206e686..e0a5b87e151f 100644 --- a/app/lib/features/space/widgets/space_sections/chats_section.dart +++ b/app/lib/features/space/widgets/space_sections/chats_section.dart @@ -4,11 +4,14 @@ import 'package:acter/features/space/widgets/related/chats_helpers.dart'; import 'package:acter/features/space/widgets/related/util.dart'; import 'package:acter/features/space/widgets/space_sections/section_header.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; import 'package:skeletonizer/skeletonizer.dart'; +final _log = Logger('a3::space::sections::chats'); + class ChatsSection extends ConsumerWidget { final String spaceId; final int limit; @@ -28,8 +31,12 @@ class ChatsSection extends ConsumerWidget { ref, spaceRelationsOverview.knownChats, ), - error: (error, stack) => - Center(child: Text(L10n.of(context).loadingFailed(error))), + error: (error, stack) { + _log.severe('Failed to load the related spaces', error, stack); + return Center( + child: Text(L10n.of(context).loadingSpacesFailed(error)), + ); + }, loading: () => Skeletonizer( child: Center( child: Text(L10n.of(context).loading), diff --git a/app/lib/features/space/widgets/space_sections/events_section.dart b/app/lib/features/space/widgets/space_sections/events_section.dart index cd58e7c5fd18..3e8b12c0d33e 100644 --- a/app/lib/features/space/widgets/space_sections/events_section.dart +++ b/app/lib/features/space/widgets/space_sections/events_section.dart @@ -4,9 +4,12 @@ import 'package:acter/features/events/widgets/event_item.dart'; import 'package:acter/features/space/widgets/space_sections/section_header.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::space::sections::cal_events'); class EventsSection extends ConsumerWidget { final String spaceId; @@ -21,14 +24,16 @@ class EventsSection extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final eventsList = ref.watch( - eventListSearchFilterProvider( - (spaceId: spaceId, searchText: ''), - ), + eventListSearchFilterProvider((spaceId: spaceId, searchText: '')), ); return eventsList.when( data: (events) => buildEventsSectionUI(context, events), - error: (error, stack) => - Center(child: Text(L10n.of(context).loadingFailed(error))), + error: (e, s) { + _log.severe('Failed to search cal events in space', e, s); + return Center( + child: Text(L10n.of(context).searchingFailed(e)), + ); + }, loading: () => Center( child: Text(L10n.of(context).loading), ), diff --git a/app/lib/features/space/widgets/space_sections/members_section.dart b/app/lib/features/space/widgets/space_sections/members_section.dart index 99b2f423752d..4d5e11c5ce2b 100644 --- a/app/lib/features/space/widgets/space_sections/members_section.dart +++ b/app/lib/features/space/widgets/space_sections/members_section.dart @@ -3,9 +3,12 @@ import 'package:acter/common/utils/routes.dart'; import 'package:acter/features/member/widgets/member_list_entry.dart'; import 'package:acter/features/space/widgets/space_sections/section_header.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::space::sections::members'); class MembersSection extends ConsumerWidget { final String spaceId; @@ -22,8 +25,12 @@ class MembersSection extends ConsumerWidget { final membersList = ref.watch(membersIdsProvider(spaceId)); return membersList.when( data: (members) => buildMembersSectionUI(context, members), - error: (error, stack) => - Center(child: Text(L10n.of(context).loadingFailed(error))), + error: (error, stack) { + _log.severe('Failed to load members in space', error, stack); + return Center( + child: Text(L10n.of(context).loadingMembersFailed(error)), + ); + }, loading: () => Center( child: Text(L10n.of(context).loading), ), diff --git a/app/lib/features/space/widgets/space_sections/pins_section.dart b/app/lib/features/space/widgets/space_sections/pins_section.dart index 1284140f14ab..7c5f7368f79e 100644 --- a/app/lib/features/space/widgets/space_sections/pins_section.dart +++ b/app/lib/features/space/widgets/space_sections/pins_section.dart @@ -4,9 +4,12 @@ import 'package:acter/features/pins/widgets/pin_list_item.dart'; import 'package:acter/features/space/widgets/space_sections/section_header.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::space::sections::pins'); class PinsSection extends ConsumerWidget { final String spaceId; @@ -23,8 +26,12 @@ class PinsSection extends ConsumerWidget { final pinList = ref.watch(pinListProvider(spaceId)); return pinList.when( data: (pins) => buildPinsSectionUI(context, pins), - error: (error, stack) => - Center(child: Text(L10n.of(context).loadingFailed(error))), + error: (error, stack) { + _log.severe('Failed to load pins in space', error, stack); + return Center( + child: Text(L10n.of(context).loadingFailed(error)), + ); + }, loading: () => Center( child: Text(L10n.of(context).loading), ), diff --git a/app/lib/features/space/widgets/space_sections/spaces_section.dart b/app/lib/features/space/widgets/space_sections/spaces_section.dart index f55cef616c6a..2790521b9925 100644 --- a/app/lib/features/space/widgets/space_sections/spaces_section.dart +++ b/app/lib/features/space/widgets/space_sections/spaces_section.dart @@ -8,6 +8,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::space::sections::spaces'); class SpacesSection extends ConsumerWidget { final String spaceId; @@ -28,8 +31,12 @@ class SpacesSection extends ConsumerWidget { ref, spaceRelationsOverview.knownSubspaces, ), - error: (error, stack) => - Center(child: Text(L10n.of(context).loadingFailed(error))), + error: (error, stack) { + _log.severe('Failed to load the related spaces', error, stack); + return Center( + child: Text(L10n.of(context).loadingSpacesFailed(error)), + ); + }, loading: () => Center( child: Text(L10n.of(context).loading), ), diff --git a/app/lib/features/space/widgets/space_sections/tasks_section.dart b/app/lib/features/space/widgets/space_sections/tasks_section.dart index 8f6ce82998e2..2cfbdb536c78 100644 --- a/app/lib/features/space/widgets/space_sections/tasks_section.dart +++ b/app/lib/features/space/widgets/space_sections/tasks_section.dart @@ -6,6 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::space::sections::tasks'); class TasksSection extends ConsumerWidget { final String spaceId; @@ -22,8 +25,12 @@ class TasksSection extends ConsumerWidget { final taskList = ref.watch(taskListProvider(spaceId)); return taskList.when( data: (tasks) => buildTasksSectionUI(context, tasks), - error: (error, stack) => - Center(child: Text(L10n.of(context).loadingFailed(error))), + error: (error, stack) { + _log.severe('Failed to load tasks in space', error, stack); + return Center( + child: Text(L10n.of(context).loadingTasksFailed(error)), + ); + }, loading: () => Center( child: Text(L10n.of(context).loading), ), diff --git a/app/lib/features/super_invites/dialogs/redeem_dialog.dart b/app/lib/features/super_invites/dialogs/redeem_dialog.dart index c7e07c95c75b..533b6e8efd0a 100644 --- a/app/lib/features/super_invites/dialogs/redeem_dialog.dart +++ b/app/lib/features/super_invites/dialogs/redeem_dialog.dart @@ -12,7 +12,7 @@ import 'package:skeletonizer/skeletonizer.dart'; const redeemConfirmKey = Key('super-invite-redeem-confirm-btn'); const redeemInfoKey = Key('super-invites-redeem-info'); -final _log = Logger('a3::super_invites::redeem_dialog'); +final _log = Logger('a3::super_invites::redeem'); class _ShowRedeemTokenDialog extends ConsumerWidget { final String token; @@ -33,13 +33,9 @@ class _ShowRedeemTokenDialog extends ConsumerWidget { children: [ info.when( data: (info) => renderInfo(context, ref, info), - error: (error, stackTrace) { - _log.severe( - 'Loading super invite failed: $token', - error, - stackTrace, - ); - final errorStr = error.toString(); + error: (e, s) { + _log.severe('Failed to load the super invite: $token', e, s); + final errorStr = e.toString(); if (errorStr.contains('error: [404]')) { // Server doesn't yet support previewing return Text( @@ -48,20 +44,16 @@ class _ShowRedeemTokenDialog extends ConsumerWidget { } if (errorStr.contains('error: [403]')) { // 403 means we can't use that anymore - return Text( - L10n.of(context).superInvitesDeleted(token), - ); + return Text(L10n.of(context).superInvitesDeleted(token)); } - return Text(L10n.of(context).loadingFailed(error)); + return Text(L10n.of(context).loadingFailed(e)); }, loading: () => Skeletonizer( child: Card( child: ListTile( leading: ActerAvatar( options: const AvatarOptions.DM( - AvatarInfo( - uniqueId: 'nothing', - ), + AvatarInfo(uniqueId: 'nothing'), size: 18, ), ), @@ -101,9 +93,7 @@ class _ShowRedeemTokenDialog extends ConsumerWidget { displayName != null ? '$displayName ($userId)' : userId, ), ), - subtitle: Text( - L10n.of(context).superInvitedTo(info.roomsCount()), - ), + subtitle: Text(L10n.of(context).superInvitedTo(info.roomsCount())), leading: ActerAvatar( options: AvatarOptions.DM( AvatarInfo( diff --git a/app/lib/features/super_invites/pages/super_invites.dart b/app/lib/features/super_invites/pages/super_invites.dart index b56350907cbd..6a8d141acf81 100644 --- a/app/lib/features/super_invites/pages/super_invites.dart +++ b/app/lib/features/super_invites/pages/super_invites.dart @@ -8,6 +8,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::super_invites::list'); class SuperInvitesPage extends ConsumerWidget { static Key createNewToken = const Key('super-invites-create'); @@ -29,7 +32,7 @@ class SuperInvitesPage extends ConsumerWidget { icon: const Icon(Atlas.arrows_rotating_right_thin), iconSize: 28, color: Theme.of(context).colorScheme.surface, - onPressed: () async { + onPressed: () { ref.invalidate(superInvitesTokensProvider); }, ), @@ -38,7 +41,7 @@ class SuperInvitesPage extends ConsumerWidget { icon: const Icon(Atlas.plus_circle_thin), iconSize: 28, color: Theme.of(context).colorScheme.surface, - onPressed: () async { + onPressed: () { context.pushNamed(Routes.actionCreateSuperInvite.name); }, ), @@ -98,12 +101,11 @@ class SuperInvitesPage extends ConsumerWidget { ), ), ), - error: (error, stack) { + error: (e, s) { + _log.severe('Failed to load the super invite tokens', e, s); return SliverToBoxAdapter( child: Center( - child: Text( - L10n.of(context).failedToLoadInviteCodes(error), - ), + child: Text(L10n.of(context).failedToLoadInviteCodes(e)), ), ); }, diff --git a/app/lib/features/tasks/pages/task_item_detail_page.dart b/app/lib/features/tasks/pages/task_item_detail_page.dart index d22bf0c5a16f..d54233516331 100644 --- a/app/lib/features/tasks/pages/task_item_detail_page.dart +++ b/app/lib/features/tasks/pages/task_item_detail_page.dart @@ -21,7 +21,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::tasks::task_item_details_page'); +final _log = Logger('a3::tasks::task_item_details'); class TaskItemDetailPage extends ConsumerWidget { final String taskListId; @@ -131,8 +131,15 @@ class TaskItemDetailPage extends ConsumerWidget { ), ], ), - error: (e, s) => AppBar(title: Text(L10n.of(context).failedToLoad(e))), - loading: () => AppBar(title: Text(L10n.of(context).loading)), + error: (e, s) { + _log.severe('Failed to load task', e, s); + return AppBar( + title: Text(L10n.of(context).loadingFailed(e)), + ); + }, + loading: () => AppBar( + title: Text(L10n.of(context).loading), + ), ); } @@ -177,7 +184,10 @@ class TaskItemDetailPage extends ConsumerWidget { ) { return task.when( data: (data) => taskData(context, data, ref), - error: (e, s) => Text(L10n.of(context).failedToLoad(e)), + error: (e, s) { + _log.severe('Failed to load task', e, s); + return Text(L10n.of(context).loadingFailed(e)); + }, loading: () => const TaskItemDetailPageSkeleton(), ); } diff --git a/app/lib/features/tasks/pages/task_list_details_page.dart b/app/lib/features/tasks/pages/task_list_details_page.dart index 4bd6dbc82891..b71da73f44a3 100644 --- a/app/lib/features/tasks/pages/task_list_details_page.dart +++ b/app/lib/features/tasks/pages/task_list_details_page.dart @@ -1,8 +1,8 @@ import 'package:acter/common/actions/redact_content.dart'; +import 'package:acter/common/actions/report_content.dart'; import 'package:acter/common/widgets/edit_html_description_sheet.dart'; import 'package:acter/common/widgets/edit_title_sheet.dart'; import 'package:acter/common/widgets/render_html.dart'; -import 'package:acter/common/actions/report_content.dart'; import 'package:acter/features/attachments/widgets/attachment_section.dart'; import 'package:acter/features/comments/widgets/comments_section.dart'; import 'package:acter/features/tasks/providers/tasklists_providers.dart'; @@ -14,7 +14,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -final _log = Logger('a3::tasks::task_list_details_page'); +final _log = Logger('a3::tasks::tasklist_details'); class TaskListDetailPage extends ConsumerStatefulWidget { static const pageKey = Key('task-list-details-page'); @@ -92,7 +92,12 @@ class _TaskListPageState extends ConsumerState { ), ], ), - error: (e, s) => AppBar(title: Text(L10n.of(context).failedToLoad(e))), + error: (e, s) { + _log.severe('Failed to load tasklist', e, s); + return AppBar( + title: Text(L10n.of(context).loadingFailed(e)), + ); + }, loading: () => AppBar( title: Text(L10n.of(context).loading), ), @@ -128,7 +133,10 @@ class _TaskListPageState extends ConsumerState { final taskList = ref.watch(taskListItemProvider(widget.taskListId)); return taskList.when( data: (data) => _buildTaskListData(data), - error: (e, s) => Text(L10n.of(context).failedToLoad(e)), + error: (e, s) { + _log.severe('Failed to load tasklist', e, s); + return Text(L10n.of(context).loadingFailed(e)); + }, loading: () => Text(L10n.of(context).loading), ); } diff --git a/app/lib/features/tasks/pages/tasks_list_page.dart b/app/lib/features/tasks/pages/tasks_list_page.dart index 671418e59a85..095e5fab8ff6 100644 --- a/app/lib/features/tasks/pages/tasks_list_page.dart +++ b/app/lib/features/tasks/pages/tasks_list_page.dart @@ -14,6 +14,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::tasks::tasklist'); class TasksListPage extends ConsumerStatefulWidget { static const scrollView = Key('space-task-lists'); @@ -108,9 +111,12 @@ class _TasksListPageConsumerState extends ConsumerState { Expanded( child: tasksList.when( data: (tasks) => _buildTasksList(tasks), - error: (error, stack) => Center( - child: Text(L10n.of(context).loadingFailed(error)), - ), + error: (e, s) { + _log.severe('Failed to search tasklists in space', e, s); + return Center( + child: Text(L10n.of(context).searchingFailed(e)), + ); + }, loading: () => const TasksListSkeleton(), ), ), diff --git a/app/lib/features/tasks/providers/notifiers.dart b/app/lib/features/tasks/providers/notifiers.dart index 5845ff71e8bd..f3b60a1117c8 100644 --- a/app/lib/features/tasks/providers/notifiers.dart +++ b/app/lib/features/tasks/providers/notifiers.dart @@ -6,7 +6,7 @@ import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:logging/logging.dart'; import 'package:riverpod/riverpod.dart'; -final _log = Logger('a3::tasks::providers'); +final _log = Logger('a3::tasks::notifiers'); //List of task items based on the specified task list class TaskItemsListNotifier diff --git a/app/lib/features/tasks/widgets/task_item.dart b/app/lib/features/tasks/widgets/task_item.dart index 1eab00bc1bf7..4e093915e197 100644 --- a/app/lib/features/tasks/widgets/task_item.dart +++ b/app/lib/features/tasks/widgets/task_item.dart @@ -59,13 +59,13 @@ class TaskItem extends ConsumerWidget { trailing: trailing(ref, task), ), error: (error, stack) { - _log.severe('failed to load task', error, stack); + _log.severe('Failed to load task', error, stack); return ListTile( - title: Text('Loading of task failed: $error'), + title: Text(L10n.of(context).loadingFailed(error)), ); }, - loading: () => const ListTile( - title: Text('loading'), + loading: () => ListTile( + title: Text(L10n.of(context).loading), ), ); } @@ -127,7 +127,10 @@ class TaskItem extends ConsumerWidget { ), ], ), - error: (e, s) => Text(L10n.of(context).loadingFailed(e)), + error: (e, s) { + _log.severe('Failed to load task', e, s); + return Text(L10n.of(context).loadingFailed(e)); + }, loading: () => Skeletonizer( child: Text(L10n.of(context).loading), ), diff --git a/app/lib/features/tasks/widgets/task_items_list_widget.dart b/app/lib/features/tasks/widgets/task_items_list_widget.dart index 444f45479792..548eb795c8e5 100644 --- a/app/lib/features/tasks/widgets/task_items_list_widget.dart +++ b/app/lib/features/tasks/widgets/task_items_list_widget.dart @@ -10,6 +10,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('a3::tasks::list'); class TaskItemsListWidget extends ConsumerWidget { final TaskList taskList; @@ -28,7 +31,10 @@ class TaskItemsListWidget extends ConsumerWidget { final tasks = ref.watch(taskItemsListProvider(taskList)); return tasks.when( data: (overview) => taskData(context, overview), - error: (error, stack) => Text(L10n.of(context).errorLoadingTasks(error)), + error: (error, stack) { + _log.severe('Failed to load tasklist', error, stack); + return Text(L10n.of(context).errorLoadingTasks(error)); + }, loading: () => const TaskItemsSkeleton(), ); } diff --git a/app/lib/features/tasks/widgets/task_list_item_card.dart b/app/lib/features/tasks/widgets/task_list_item_card.dart index 59cdfa5d1352..d1e567f7ce7d 100644 --- a/app/lib/features/tasks/widgets/task_list_item_card.dart +++ b/app/lib/features/tasks/widgets/task_list_item_card.dart @@ -4,6 +4,7 @@ import 'package:acter/features/tasks/providers/tasklists_providers.dart'; import 'package:acter/features/tasks/widgets/task_items_list_widget.dart'; import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; @@ -47,13 +48,13 @@ class TaskListItemCard extends ConsumerWidget { ), ), error: (error, stack) { - _log.severe('failed to load tasklist', error, stack); + _log.severe('Failed to load tasklist', error, stack); return Card( - child: Text('Loading of tasklist failed: $error'), + child: Text(L10n.of(context).errorLoadingTasks(error)), ); }, - loading: () => const Card( - child: Text('loading'), + loading: () => Card( + child: Text(L10n.of(context).loading), ), ); } diff --git a/app/lib/l10n/app_ar.arb b/app/lib/l10n/app_ar.arb index 5bd66c7dcf90..ba7c3532ae11 100644 --- a/app/lib/l10n/app_ar.arb +++ b/app/lib/l10n/app_ar.arb @@ -855,16 +855,12 @@ "@errorSubmittingComment": {}, "errorUpdatingEvent": "خطأ في تحديث الحدث: {error}", "@errorUpdatingEvent": {}, - "failedToLoad": "فشل في التحميل: {error}", - "@failedToLoad": {}, "encryptionBackupDisableActionKeepIt": "لا، احتفظ بها", "@encryptionBackupDisableActionKeepIt": {}, "encryptionBackupDisableActionDestroyIt": "نعم، أتلفه", "@encryptionBackupDisableActionDestroyIt": {}, "encryptionBackupRecover": "استرجاع النسخة الاحتياطية للتشفير", "@encryptionBackupRecover": {}, - "errorLoading": "خطأ في التحميل {error}", - "@errorLoading": {}, "eventDescriptionsData": "بيانات شرح الحدث", "@eventDescriptionsData": {}, "eventTitleData": "بيانات عنوان الحدث", @@ -1172,5 +1168,1116 @@ "errorUpdatingDescription": "خطأ في تحديث الوصف: {error}", "@errorUpdatingDescription": {}, "formatMustBe": "يجب أن تكون الصيغة @user:server.tld", - "@formatMustBe": {} + "@formatMustBe": {}, + "closingRoomTitleDescription": "عند إغلاق هذه الغرفة، سنقوم بما يلي :\n\n - طرد كل من لديه مستوى إذن أقل من الإذن الذي لديك \n - إزالتها كفرع من الفضاءات الأم (حيث لديك الأذونات للقيام بذلك),\n - ضبط قاعدة الدعوة إلى \"خاص'\n - سوف تغادر الغرفة.\n\nلا يمكن التراجع فيه. هل أنت متأكد من أنك تريد الإغلاق؟", + "@closingRoomTitleDescription": {}, + "noTasksListAvailableDescription": "شارك المهام ذات الأولوية مع مجتمعك وقم بإدارتها، مثل أي قائمة مهام، حتى يتم إطلاع الجميع على آخر المستجدات.", + "@noTasksListAvailableDescription": {}, + "suggestedRoomsSubtitle": "نقترح عليك أيضاً الانضمام إلى", + "@suggestedRoomsSubtitle": {}, + "invitingSpaceMembersProgress": "دعوة عضو فضاء{count} / {total}", + "@invitingSpaceMembersProgress": {}, + "closingRoomDoneBut": "تم الإغلاق وتمّت مغادرتك. ولكن لم تتمكن من إزالة {skipped} المستخدمين الآخرين وإزالتها كفرع من {skippedParents} الفضاءات بسبب عدم وجود إذن. قد لا يزال بإمكان الآخرين الوصول إليها.", + "@closingRoomDoneBut": {}, + "analyticsDescription2": "هذه بالطبع مجهولة المصدر ولا تحتوي على أي معلومات خاصة", + "@analyticsDescription2": {}, + "sendCrashReportsInfo": "شارك تعقّب الأعطال تلقائياً عبر نظام Sentry مع فريق Acter", + "@sendCrashReportsInfo": {}, + "memberDescriptionsData": "بيانات وصف العضو", + "@memberDescriptionsData": {}, + "noIStay": "لا، سأبقى", + "@noIStay": {}, + "introPageDescription2ndLine": "تواصل مع زملائك النشطاء، وتبادل الأفكار وتعاون معهم لإحداث تغيير هادف.", + "@introPageDescription2ndLine": {}, + "shareFailed": "المشاركة فشلت: {error}", + "@shareFailed": {}, + "joinSpaceTutorialTitle": "الانضمام إلى الفضاء الموجود", + "@joinSpaceTutorialTitle": {}, + "creatingSpace": "إنشاء فضاء", + "@creatingSpace": {}, + "defaultModes": "الإعدادات التلقائية", + "@defaultModes": {}, + "encryptedSpace": "فضاء مشفر", + "@encryptedSpace": {}, + "loginSuccess": "تم تسجيل الدخول بنجاح", + "@loginSuccess": {}, + "newPassword": "كلمة المرور الجديدة", + "@newPassword": {}, + "personalSettings": "الإعدادات الخاصة", + "@personalSettings": {}, + "suggestedUsers": "المستخدمين المقترحين", + "@suggestedUsers": {}, + "emailAddress": "عنوان البريد الإلكتروني", + "@emailAddress": {}, + "noMatchingTasksListFound": "لم يتم العثور على قائمة المهام المطابقة", + "@noMatchingTasksListFound": {}, + "introPageDescriptionHl": " مجتمع صنّاع التغيير.", + "@introPageDescriptionHl": {}, + "linkExistingSpace": "ربط الفضاء الموجود", + "@linkExistingSpace": {}, + "manageBudgetsCooperatively": "إدارة الميزانيات بشكل جماعي", + "@manageBudgetsCooperatively": {}, + "reportThisMessage": "الإبلاغ عن هذه الرسالة", + "@reportThisMessage": {}, + "theParentSpace": "الفضاء الأم", + "@theParentSpace": {}, + "updatePowerLevel": "تحديث مستوى الإذن", + "@updatePowerLevel": {}, + "addImageSlide": "أضف شريحة صورة", + "@addImageSlide": {}, + "changingNotificationMode": "تغيير وضع الإشعارات…", + "@changingNotificationMode": {}, + "peopleGoing": "{count} أشخاص مغادرون", + "@peopleGoing": {}, + "pushTargetDetails": "تفاصيل الهدف المدفوع", + "@pushTargetDetails": {}, + "rageShakeTargetUrl": "رابط الهدف Rageshake", + "@rageShakeTargetUrl": {}, + "searchPublicDirectory": "البحث في الدليل العام", + "@searchPublicDirectory": {}, + "sendingEmailFailed": "فشل الإرسال: {error}", + "@sendingEmailFailed": {}, + "verificationRequestWaitingFor": "في انتظار {sender}…", + "@verificationRequestWaitingFor": { + "placeholders": { + "sender": { + "example": "@kyra:acter.global", + "type": "String" + } + } + }, + "dueSuccess": "تم تغيير الأجل بنجاح", + "@dueSuccess": {}, + "loadingOtherChats": "جاري تحميل المحادثات الأخرى", + "@loadingOtherChats": {}, + "loadingCommentsList": "جاري تحميل قائمة التعليقات", + "@loadingCommentsList": {}, + "resettingPassword": "إعادة ضبط كلمة المرور الخاصة بك", + "@resettingPassword": {}, + "errorSyncing": "خطأ في المزامنة: {error}", + "@errorSyncing": {}, + "failedToReject": "لم ينجح الرفض", + "@failedToReject": {}, + "copyToClipboard": "نسخ للحافظة", + "@copyToClipboard": {}, + "createSpaceTutorialTitle": "إنشاء فضاء جديد", + "@createSpaceTutorialTitle": {}, + "protectPrivacyTitle": "حماية خصوصيتك", + "@protectPrivacyTitle": {}, + "endDateRequired": "تاريخ الانتهاء ضروري!", + "@endDateRequired": {}, + "startGroupDM": "ابدأ إدارة مجموعة دي ام DM", + "@startGroupDM": {}, + "invitingSpaceMembersLoading": "دعوة أعضاء الفضاء", + "@invitingSpaceMembersLoading": {}, + "encryptionBackupNoBackupAction": "تفعيل النسخ الاحتياطي", + "@encryptionBackupNoBackupAction": {}, + "forgotPassword": "هل نسيت كلمة المرور؟", + "@forgotPassword": {}, + "reportSent": "تم إرسال التقرير!", + "@reportSent": {}, + "selectLanguage": "اختر اللغة", + "@selectLanguage": {}, + "unblockingUserProgress": "إلغاء حظر المستخدم", + "@unblockingUserProgress": {}, + "unverifiedSessions": "دورات لم يتم التحقق منها", + "@unverifiedSessions": {}, + "loadingVideo": "جارٍ تحميل الفيديو", + "@loadingVideo": {}, + "avatarUploading": "تحميل الصورة الرمزية لملف التعريف", + "@avatarUploading": {}, + "shareInviteCode": "شارك رمز الدعوة", + "@shareInviteCode": {}, + "changingYourPassword": "تغيير كلمة المرور الخاصة بك", + "@changingYourPassword": {}, + "reportTaskItem": "إبلاغ عن بند المهام", + "@reportTaskItem": {}, + "dueDate": "تاريخ الأجل", + "@dueDate": {}, + "encryptionBackupRecoverInputHint": "مفتاح الاسترجاع", + "@encryptionBackupRecoverInputHint": {}, + "joinActer": "انضم إلىActer", + "@joinActer": {}, + "labsAppFeatures": "خصائص التطبيق", + "@labsAppFeatures": {}, + "settingsKeyBackUpTitle": "مفتاح النسخ الاحتياطي", + "@settingsKeyBackUpTitle": {}, + "yesLeave": "نعم، مغادرة", + "@yesLeave": {}, + "eventUpdate": "تحديث الفعالية", + "@eventUpdate": {}, + "loadingChat": "جارٍ تحميل المحادثة…", + "@loadingChat": {}, + "chatInvitedUserId": "{userId} دعا", + "@chatInvitedUserId": {}, + "selectSpace": "اختر فضاءً", + "@selectSpace": {}, + "encryptionBackupProvideKeyAction": "توفير مفتاح", + "@encryptionBackupProvideKeyAction": {}, + "foundUsers": "المستخدمون الذين تم إيجادهم", + "@foundUsers": {}, + "noThanks": "لا، شكراً", + "@noThanks": {}, + "notificationsOverwrites": "إلغاء الإشعارات", + "@notificationsOverwrites": {}, + "pollsAndSurveys": "استبيانات واستطلاعات الرأي", + "@pollsAndSurveys": {}, + "resetPassword": "إعادة ضبط كلمة المرور", + "@resetPassword": {}, + "roomNotFound": "لم يتم العثور على المحادثة", + "@roomNotFound": {}, + "sasGotIt": "فهمت", + "@sasGotIt": {}, + "debugInfo": "معلومات التصحيح", + "@debugInfo": {}, + "debugLevel": "مستوى التصحيح", + "@debugLevel": {}, + "httpProxy": "بروكسي HTTP", + "@httpProxy": {}, + "mobilePushNotifications": "الإشعارات المدفوعة عبر الهاتف المحمول", + "@mobilePushNotifications": {}, + "newEncryptedMessage": "رسالة جديدة مشفرة", + "@newEncryptedMessage": {}, + "myDashboard": "لوحة المتابعة الخاصة بي", + "@myDashboard": {}, + "newChat": "محادثة جديدة", + "@newChat": {}, + "newMessage": "رسالة جديدة", + "@newMessage": {}, + "notificationStatusSubmitted": "حالة الإشعار تم إرسالها", + "@notificationStatusSubmitted": {}, + "noTopicFound": "لم يتم العثور على أي موضوع", + "@noTopicFound": {}, + "notYetSupported": "غير مدعوم بعد", + "@notYetSupported": {}, + "notificationsUnmuted": "تم إلغاء كتم الإشعارات", + "@notificationsUnmuted": {}, + "notificationTargets": "أهداف الإشعارات", + "@notificationTargets": {}, + "notVisible": "غير ظاهرة", + "@notVisible": {}, + "postponeN": "تأجيل {days} أيام", + "@postponeN": {}, + "postingTaskList": "نشر قائمة المهام", + "@postingTaskList": {}, + "powerLevel": "مستوى الإذن", + "@powerLevel": {}, + "sasIncomingReqNotifTitle": "طلب التحقق", + "@sasIncomingReqNotifTitle": {}, + "saveChanges": "حفظ التغييرات", + "@saveChanges": {}, + "savingCode": "رمز الحفظ", + "@savingCode": {}, + "searchTermFieldHint": "البحث عن...", + "@searchTermFieldHint": {}, + "seenBy": "تمت مشاهدته من قبل", + "@seenBy": {}, + "spaceConfiguration": "تهيئة الفضاء", + "@spaceConfiguration": {}, + "typingUser1": "{user} يقوم بالكتابة...", + "@typingUser1": {}, + "updatingDisplayName": "تحديث اسم العرض", + "@updatingDisplayName": {}, + "updatingProfileImage": "تحديث صورة ملف التعريف", + "@updatingProfileImage": {}, + "usedTimes": "استُخدمت {count} مرات", + "@usedTimes": {}, + "usersYouBlocked": "المستخدمون الذين قمت بحظرهم", + "@usersYouBlocked": {}, + "updatingDue": "التحديث المستحق", + "@updatingDue": {}, + "updatingEvent": "تحديث الفعالية", + "@updatingEvent": {}, + "updatingRSVP": "تحديث RSVP", + "@updatingRSVP": {}, + "updatingSpace": "تحديث الفضاء", + "@updatingSpace": {}, + "uploadAvatar": "تحميل الصورة الرمزية", + "@uploadAvatar": {}, + "verifyOtherSession": "التحقق من دورة أخرى", + "@verifyOtherSession": {}, + "verifySession": "التحقق من الدورة", + "@verifySession": {}, + "deactivateAccount": "إلغاء تفعيل الحساب", + "@deactivateAccount": {}, + "deletingCode": "حذف الرمز", + "@deletingCode": {}, + "dueToday": "الموعد المحدد اليوم", + "@dueToday": {}, + "forgotYourPassword": "هل نسيت كلمة المرور؟", + "@forgotYourPassword": {}, + "editInviteCode": "تعديل رمز الدعوة", + "@editInviteCode": {}, + "createInviteCode": "إنشاء رمز الدعوة", + "@createInviteCode": {}, + "pleaseSelectValidEndDate": "الرجاء تحديد تاريخ انتهاء الصلاحية", + "@pleaseSelectValidEndDate": {}, + "powerLevelSubmitted": "تحديث مستوى الإذن لـ {module} المرسلة", + "@powerLevelSubmitted": {}, + "optionalParentSpace": "فضاء الأم الاختياري", + "@optionalParentSpace": {}, + "redeeming": "استرداد {token}", + "@redeeming": {}, + "analyticsDescription1": "من خلال مشاركة بيانات الأعطال وتقارير الأخطاء معنا.", + "@analyticsDescription1": {}, + "sendCrashReportsTitle": "إرسال تقارير الأعطال والأخطاء", + "@sendCrashReportsTitle": {}, + "analyticsTitle": "ساعدنا على مساعدتك", + "@analyticsTitle": {}, + "closingRoomRemovingFromParents": "الإغلاق قيد التنفيذ. إزالة الغرفة من الغرفة الأم {currentParent} / {totalParents}", + "@closingRoomRemovingFromParents": {}, + "closingRoomRemovingMembers": "الإغلاق قيد التنفيذ. طرد عضو {kicked} / {total}", + "@closingRoomRemovingMembers": {}, + "createDefaultChat": "إنشاء غرفة محادثة افتراضية كذلك", + "@createDefaultChat": {}, + "closingRoomMatrixMsg": "تم إغلاق الغرفة", + "@closingRoomMatrixMsg": {}, + "closingRoomTitle": "أغلق هذه الغرفة", + "@closingRoomTitle": {}, + "closingRoomFailed": "الإغلاق فشل: {error}", + "@closingRoomFailed": {}, + "dangerZone": "منطقة خطرة", + "@dangerZone": {}, + "defaultNotification": "تلقائي{type}", + "@defaultNotification": {}, + "deleteAttachment": "حذف المرفق", + "@deleteAttachment": {}, + "deleteCode": "حذف الرمز", + "@deleteCode": {}, + "deleteTarget": "حذف الهدف", + "@deleteTarget": {}, + "deviceId": "معرّف الجهاز", + "@deviceId": {}, + "deviceName": "اسم الجهاز", + "@deviceName": {}, + "displayName": "اسم العرض", + "@displayName": {}, + "due": "الأجل: {date}", + "@due": {}, + "editDetails": "تعديل التفاصيل", + "@editDetails": {}, + "editMessage": "تعديل الرسالة", + "@editMessage": {}, + "editProfile": "تعديل الملف الشخصي", + "@editProfile": {}, + "editSpace": "تعديل الفضاء", + "@editSpace": {}, + "encryptionBackupEnabling": "تمكين النسخ الاحتياطي", + "@encryptionBackupEnabling": {}, + "encryptionBackupResetting": "إعادة ضبط النسخ الاحتياطي", + "@encryptionBackupResetting": {}, + "encryptionBackupResettingSuccess": "تمت إعادة الضبط بنجاح", + "@encryptionBackupResettingSuccess": {}, + "encryptionBackupRecoverRecoveringSuccess": "نجاح عملية الاسترجاع", + "@encryptionBackupRecoverRecoveringSuccess": {}, + "encryptionBackupRecoverRecoveringImportFailed": "فشلت عملية الاستيراد", + "@encryptionBackupRecoverRecoveringImportFailed": {}, + "encryptionBackupKeyBackup": "مفتاح النسخ الاحتياطي", + "@encryptionBackupKeyBackup": {}, + "error": "خطأ {error}", + "@error": {}, + "eventName": "اسم الحدث", + "@eventName": {}, + "loginAgain": "تسجيل الدخول مجدداً", + "@loginAgain": {}, + "linkToChat": "ربط بالمحادثة", + "@linkToChat": {}, + "loadingFailed": "فشل التحميل: {error}", + "@loadingFailed": {}, + "inviteCode": "رمز الدعوة", + "@inviteCode": {}, + "joinRoom": "انضم إلى المحادثة", + "@joinRoom": {}, + "kickProgress": "إزالة المستخدم", + "@kickProgress": {}, + "kickSuccess": "تمت إزالة المستخدم", + "@kickSuccess": {}, + "kickUser": "حذف مستخدم", + "@kickUser": {}, + "leaveRoom": "مغادرة المحادثة", + "@leaveRoom": {}, + "leaveSpace": "ترك الفضاء", + "@leaveSpace": {}, + "leavingSpace": "مغادرة الفضاء", + "@leavingSpace": {}, + "leavingRoom": "مغادرة المحادثة", + "@leavingRoom": {}, + "logIn": "تسجيل الدخول", + "@logIn": {}, + "markedAsDone": "تم وضع علامة تم إنجازه", + "@markedAsDone": {}, + "memberTitleData": "بيانات عنوان العضو", + "@memberTitleData": {}, + "noChatsFound": "لم يتم العثور على أي محادثات", + "@noChatsFound": {}, + "noConnectedSpaces": "لا توجد فضاءات متصلة", + "@noConnectedSpaces": {}, + "noDisplayName": "لا يوجد اسم عرض", + "@noDisplayName": {}, + "noDueDate": "لا يوجد تاريخ محدد", + "@noDueDate": {}, + "noParticipantsGoing": "لا يوجد مشاركين راحلين", + "@noParticipantsGoing": {}, + "noSpacesFound": "لم يتم العثور على أي فضاءت", + "@noSpacesFound": {}, + "moreRooms": "+{count} غرف إضافية", + "@moreRooms": {}, + "logSettings": "إعدادات التسجيل", + "@logSettings": {}, + "newUpdate": "تحديث جديد", + "@newUpdate": {}, + "noOverwrite": "عدم الكتابة فوق", + "@noOverwrite": {}, + "pleaseSelectSpace": "الرجاء اختيار فضاء", + "@pleaseSelectSpace": {}, + "pushTargetDeleted": "تم حذف الهدف المدفوع", + "@pushTargetDeleted": {}, + "rageShakeAppName": "اسم التطبيق Rageshake", + "@rageShakeAppName": {}, + "notGoing": "عدم المغادرة", + "@notGoing": {}, + "parentSpace": "الفضاء الأم", + "@parentSpace": {}, + "parentSpaces": "الفضاءات الأم", + "@parentSpaces": {}, + "passwordResetTitle": "إعادة ضبط كلمة المرور", + "@passwordResetTitle": {}, + "pinName": "دبس الاسم", + "@pinName": {}, + "playbackSpeed": "سرعة إعادة التشغيل", + "@playbackSpeed": {}, + "pleaseWait": "الرجاء الانتظار…", + "@pleaseWait": {}, + "privacyPolicy": "اتفاقية الخصوصية", + "@privacyPolicy": {}, + "quickSelect": "اختيار سريع:", + "@quickSelect": {}, + "redeemingFailed": "فشل الاسترداد: {error}", + "@redeemingFailed": {}, + "replyTo": "الردّ على {name}", + "@replyTo": {}, + "reportThisEvent": "الإبلاغ عن هذا الحدث", + "@reportThisEvent": {}, + "reportThisPost": "الإبلاغ عن هذا المنشور", + "@reportThisPost": {}, + "reportSendingFailed": "فشل إرسال التقرير", + "@reportSendingFailed": {}, + "requestToJoin": "طلب الانضمام", + "@requestToJoin": {}, + "searchingFailed": "فشل البحث {error}", + "@searchingFailed": {}, + "seeOpenTasks": "انظر المهام المفتوحة", + "@seeOpenTasks": {}, + "selectParentSpace": "اختر الفضاء الأم", + "@selectParentSpace": {}, + "reasonHint": "سبب اختياري", + "@reasonHint": {}, + "removePin": "إزالة الدبوس", + "@removePin": {}, + "reportPin": "الإبلاغ عن الدبوس", + "@reportPin": {}, + "searchChats": "بحث عن المحادثات", + "@searchChats": {}, + "searchSpace": "البحث عن فضاء", + "@searchSpace": {}, + "searchSpaces": "البحث عن الفضاءات", + "@searchSpaces": {}, + "selectDue": "اختر تاريخ الاستحقاق", + "@selectDue": {}, + "sendingAttachment": "إرسال المرفق", + "@sendingAttachment": {}, + "sendingReport": "إرسال التقرير", + "@sendingReport": {}, + "sentAnImage": "تم إرسال صورة.", + "@sentAnImage": {}, + "sessionTokenName": "اسم توكن (Token) الدورة", + "@sessionTokenName": {}, + "setDebugLevel": "ضبط مستوى التصحيح", + "@setDebugLevel": {}, + "setHttpProxy": "ضبط بروكسي HTTP", + "@setHttpProxy": {}, + "securityAndPrivacy": "الأمن والخصوصية", + "@securityAndPrivacy": {}, + "spaceNotificationOverwrite": "استبدال إشعار الفضاء", + "@spaceNotificationOverwrite": {}, + "spacesAndChats": "الفضاءات و المحادثات", + "@spacesAndChats": {}, + "superInvitedBy": "{user} يدعوك", + "@superInvitedBy": {}, + "taskListName": "اسم قائمة المهام", + "@taskListName": {}, + "termsOfService": "بنود الخدمة", + "@termsOfService": {}, + "createNewPin": "إنشاء دبوس جديد", + "@createNewPin": {}, + "suggestedRoomsTitle": "مقترح للانضمام", + "@suggestedRoomsTitle": {}, + "addSuggested": "تم تحديده كمقترح", + "@addSuggested": {}, + "sendingEmail": "إرسال بريد إلكتروني", + "@sendingEmail": {}, + "shareIcal": "مشاركة iCal", + "@shareIcal": {}, + "signUp": "التسجيل", + "@signUp": {}, + "slidePosting": "نشر الشرائح", + "@slidePosting": {}, + "spaceName": "اسم الفضاء", + "@spaceName": {}, + "spaceNotifications": "إشعارات الفضاء", + "@spaceNotifications": {}, + "startDM": "ابدأ دي ام DM", + "@startDM": {}, + "submittingComment": "إرسال التعليق", + "@submittingComment": {}, + "superInvitations": "دعوات مميزة", + "@superInvitations": {}, + "superInvites": "رموز الدعوة", + "@superInvites": {}, + "createComment": "خلق تعليق", + "@createComment": {}, + "theSelectedRooms": "المحادثات المختارة", + "@theSelectedRooms": {}, + "tryToJoin": "حاول الانضمام", + "@tryToJoin": {}, + "createNewSpace": "إنشاء فضاء جديد", + "@createNewSpace": {}, + "thirdParty": "طرف ثالث", + "@thirdParty": {}, + "typeName": "اكتب الاسم", + "@typeName": {}, + "unblockingUser": "إلغاء حظر المستخدم", + "@unblockingUser": {}, + "unblockTitle": "إلغاء حظر {userId}", + "@unblockTitle": {}, + "unblockUser": "إلغاء حظر مستخدم", + "@unblockUser": {}, + "unreadMarkerFeatureTitle": "العلامات غير المقروءة", + "@unreadMarkerFeatureTitle": {}, + "unknownRoom": "محادثة غير معروفة", + "@unknownRoom": {}, + "verificationSasDoNotMatch": "لا تتطابق", + "@verificationSasDoNotMatch": {}, + "verifyThisSession": "تحقق من هذه الدورة", + "@verifyThisSession": {}, + "yesPleaseUpdate": "نعم، الرجاء التحديث", + "@yesPleaseUpdate": {}, + "yourActiveDevices": "أجهزتك النشطة", + "@yourActiveDevices": {}, + "addTextSlide": "أضف شريحة نصية", + "@addTextSlide": {}, + "addVideoSlide": "أضف شريحة فيديو", + "@addVideoSlide": {}, + "verificationSasMatch": "تتطابق", + "@verificationSasMatch": {}, + "verificationScanEmojiTitle": "لا يمكن المسح الضوئي", + "@verificationScanEmojiTitle": {}, + "welcomeBack": "أهلاً بعودتك", + "@welcomeBack": {}, + "welcomeTo": "مرحباً بك في ", + "@welcomeTo": {}, + "yourPassword": "كلمة المرور الخاصة بك", + "@yourPassword": {}, + "adding": "إضافة {email}", + "@adding": {}, + "acterApp": "تطبيق Acter", + "@acterApp": {}, + "creatingPin": "إنشاء دبوس…", + "@creatingPin": {}, + "dueTomorrow": "الموعد المحدد غداً", + "@dueTomorrow": {}, + "endDate": "تاريخ الانتهاء", + "@endDate": {}, + "endTime": "نهاية الوقت", + "@endTime": {}, + "emailAddresses": "عناوين البريد الإلكتروني", + "@emailAddresses": {}, + "eventCreate": "إنشاء حدث", + "@eventCreate": {}, + "eventEdit": "تعديل حدث", + "@eventEdit": {}, + "eventRemove": "إزالة حدث", + "@eventRemove": {}, + "eventReport": "إبلاغ عن حدث", + "@eventReport": {}, + "eventShare": "مشاركة الفعالية", + "@eventShare": {}, + "loadingRsvpStatus": "جاري تحميل حالة rsvp", + "@loadingRsvpStatus": {}, + "loadingFirstSync": "جاري تحميل المزامنة الأولى", + "@loadingFirstSync": {}, + "pinCreatedSuccessfully": "تم إنشاء الدبوس بنجاح", + "@pinCreatedSuccessfully": {}, + "encryptedDMChat": "محادثة دي ام DM المشفرة", + "@encryptedDMChat": {}, + "loadingPin": "جارٍ تحميل الدبوس", + "@loadingPin": {}, + "loadingRoom": "جارٍ تحميل المحادثة", + "@loadingRoom": {}, + "loadingTargets": "جارٍ تحميل الأهداف", + "@loadingTargets": {}, + "loadingImage": "جارٍ تحميل الصورة", + "@loadingImage": {}, + "encryptedChatMessageInfoTitle": "رسالة مقفلة", + "@encryptedChatMessageInfoTitle": {}, + "chatMessageDeleted": "تم حذف الرسالة", + "@chatMessageDeleted": {}, + "chatJoinedDisplayName": "{name} انضم", + "@chatJoinedDisplayName": {}, + "chatJoinedUserId": "{userId} انضم", + "@chatJoinedUserId": {}, + "chatYouJoined": "انضممت", + "@chatYouJoined": {}, + "chatYouInvited": "لقد قمت بدعوة", + "@chatYouInvited": {}, + "chatInvitedDisplayName": "{name} دعا", + "@chatInvitedDisplayName": {}, + "introPageDescriptionPre": "Acter أكثر من مجرد تطبيق.\nإنها", + "@introPageDescriptionPre": {}, + "changedPushNotificationSettingsSuccessfully": "تم تغيير إعدادات الإشعارات المدفوعة بنجاح", + "@changedPushNotificationSettingsSuccessfully": {}, + "seeAllMyEvents": "شاهد {count} أحداث الخاصة بي", + "@seeAllMyEvents": {}, + "chatInvitationAcceptedDisplayName": "{name} قبل الدعوة", + "@chatInvitationAcceptedDisplayName": {}, + "chatInvitationAcceptedUserId": "{userId} قبل الدعوة", + "@chatInvitationAcceptedUserId": {}, + "removeThisPin": "أزل هذا الدبوس", + "@removeThisPin": {}, + "removeThisPost": "أزل هذا المنشور", + "@removeThisPost": {}, + "reportThisPin": "أبلغ عن هذا الدبوس", + "@reportThisPin": {}, + "resettingPasswordFailed": "إعادة الضبط فشلت: {error}", + "@resettingPasswordFailed": {}, + "resettingPasswordSuccessful": "تمت إعادة تعيين كلمة المرور بنجاح.", + "@resettingPasswordSuccessful": {}, + "startDateRequired": "تاريخ البدء ضروري!", + "@startDateRequired": {}, + "startTimeRequired": "وقت البدء ضروري!", + "@startTimeRequired": {}, + "endTimeRequired": "وقت النهاية ضروري!", + "@endTimeRequired": {}, + "selectCustomDate": "اختر تاريخاً معيناً", + "@selectCustomDate": {}, + "joinExistingSpace": "انضم إلى الفضاء الموجود", + "@joinExistingSpace": {}, + "dmChat": "محادثة دي ام DM", + "@dmChat": {}, + "removingContent": "إزالة محتوى", + "@removingContent": {}, + "removingAttachment": "إزالة المرفق", + "@removingAttachment": {}, + "reportThis": "الإبلاغ عن هذا", + "@reportThis": {}, + "sharedSuccessfully": "تمت المشاركة بنجاح", + "@sharedSuccessfully": {}, + "searchUser": "البحث عن مستخدم", + "@searchUser": {}, + "bugReportTitle": "أبلغ عن مشكلة", + "@bugReportTitle": {}, + "emptyDescription": "الرجاء إدخال الوصف", + "@emptyDescription": {}, + "includeLog": "قم بإدراج السجلات الحالية", + "@includeLog": {}, + "nukeLocalData": "نوك للبيانات المحلية", + "@nukeLocalData": {}, + "sharingRoom": "مشاركة هذه المحادثة…", + "@sharingRoom": {}, + "commentEmptyStateTitle": "لم يتم العثور على أي تعليقات.", + "@commentEmptyStateTitle": {}, + "acterUsername": "اسم المستخدم Acter الخاص بك", + "@acterUsername": {}, + "copyToClip": "نسخ إلى الحافظة", + "@copyToClip": {}, + "avatarAddTitle": "إضافة صورة رمزية للمستخدم", + "@avatarAddTitle": {}, + "inviteSpaceMembersTitle": "دعوة أعضاء الفضاء", + "@inviteSpaceMembersTitle": {}, + "inviteIndividualUsersTitle": "دعوة المستخدمين الأفراد", + "@inviteIndividualUsersTitle": {}, + "generateInviteCode": "إنشاء رمز الدعوة", + "@generateInviteCode": {}, + "noUserFoundTitle": "لم يتم العثور على مستخدمين", + "@noUserFoundTitle": {}, + "membersInvited": "{count} أعضاء تمت دعوتهم", + "@membersInvited": {}, + "visibilityAndAccessibility": "وضوح الرؤية وسهولة الوصول", + "@visibilityAndAccessibility": {}, + "spaceWithAccess": "فضاء مع إمكانية الدخول", + "@spaceWithAccess": {}, + "changePasswordDescription": "تغيير كلمة المرور الخاصة بك", + "@changePasswordDescription": {}, + "passwordChangedSuccessfully": "تم تغيير كلمة المرور بنجاح", + "@passwordChangedSuccessfully": {}, + "addMoreDetails": "أضف المزيد من التفاصيل", + "@addMoreDetails": {}, + "changePassword": "تغيير كلمة المرور", + "@changePassword": {}, + "oldPassword": "كلمة المرور القديمة", + "@oldPassword": {}, + "noEventAvailableDescription": "أنشئ فعالية جديدة واجلب مجتمعك معًا.", + "@noEventAvailableDescription": {}, + "failedToUploadAvatar": "فشل تحميل الصورة الرمزية: {error}", + "@failedToUploadAvatar": {}, + "noTasksListAvailableYet": "لا توجد قائمة مهام متاحة بعد", + "@noTasksListAvailableYet": {}, + "noMatchingEventsFound": "لم يتم العثور على أحداث مماثلة", + "@noMatchingEventsFound": {}, + "deleteTaskList": "حذف قائمة المهام", + "@deleteTaskList": {}, + "deleteTaskItem": "حذف بند المهام", + "@deleteTaskItem": {}, + "reportTaskList": "إبلاغ عن قائمة المهام", + "@reportTaskList": {}, + "noEventsFound": "لم يتم العثور على أي فعاليات", + "@noEventsFound": {}, + "myUpcomingEvents": "فعالياتي القادمة", + "@myUpcomingEvents": {}, + "from": "من", + "@from": {}, + "gallery": "معرض الصور", + "@gallery": {}, + "general": "عام", + "@general": {}, + "notifications": "الإشعارات", + "@notifications": {}, + "polls": "الاستطلاعات", + "@polls": {}, + "retry": "أعد المحاولة", + "@retry": {}, + "roomId": "معرف المحادثة", + "@roomId": {}, + "rsvp": "RSVP", + "@rsvp": {}, + "space": "فضاء", + "@space": {}, + "going": "الانتقال", + "@going": {}, + "image": "صورة", + "@image": {}, + "info": "معلومات", + "@info": {}, + "invited": "دعا", + "@invited": {}, + "labs": "المختبرات", + "@labs": {}, + "moderator": "المشرف", + "@moderator": {}, + "more": "المزيد", + "@more": {}, + "muted": "مكتوم", + "@muted": {}, + "name": "الاسم", + "@name": {}, + "ok": "موافق", + "@ok": {}, + "postpone": "تأجيل", + "@postpone": {}, + "sasVerified": "تم التحقق!", + "@sasVerified": {}, + "save": "حفظ", + "@save": {}, + "search": "بحث", + "@search": {}, + "title": "العنوان", + "@title": {}, + "to": "إلى", + "@to": {}, + "today": "اليوم", + "@today": {}, + "token": "توكن (token)", + "@token": {}, + "tomorrow": "غداً", + "@tomorrow": {}, + "username": "اسم المستخدم", + "@username": {}, + "selectPicture": "اختر صورة", + "@selectPicture": {}, + "selectVideo": "اختر فيديو", + "@selectVideo": {}, + "selectDate": "اختر تاريخاً", + "@selectDate": {}, + "selectTime": "اختر توقيت", + "@selectTime": {}, + "sendDM": "أرسل دي ام (DM)", + "@sendDM": {}, + "showMore": "عرض المزيد", + "@showMore": {}, + "showLess": "عرض الأقل", + "@showLess": {}, + "recommendedSpace": "الفضاء المُوصى به", + "@recommendedSpace": {}, + "joinSpace": "الانضمام لفضاء", + "@joinSpace": {}, + "invitingLoading": "دعوة {userId}", + "@invitingLoading": {}, + "invitations": "الدعوات", + "@invitations": {}, + "invite": "ادعُ", + "@invite": {}, + "updateName": "تحديث الاسم", + "@updateName": {}, + "updateDescription": "تحديث الوصف", + "@updateDescription": {}, + "editName": "تعديل الاسم", + "@editName": {}, + "editDescription": "تعديل الوصف", + "@editDescription": {}, + "eventParticipants": "المشاركون في الفعالية", + "@eventParticipants": {}, + "upcomingEvents": "الفعاليات المقبلة", + "@upcomingEvents": {}, + "closeSpace": "إغلاق الفضاء", + "@closeSpace": {}, + "closeChat": "إغلاق المحادثة", + "@closeChat": {}, + "closingRoom": "جاري الغلق", + "@closingRoom": {}, + "closingRoomDone": "تم الغلق بنجاح.", + "@closingRoomDone": {}, + "defaultChatName": "{name} المحادثة", + "@defaultChatName": {}, + "custom": "خصِّص", + "@custom": {}, + "deactivate": "إلغاء التفعيل", + "@deactivate": {}, + "decline": "رفض", + "@decline": {}, + "delete": "حذف", + "@delete": {}, + "denied": "تم النفي", + "@denied": {}, + "description": "الوصف", + "@description": {}, + "dms": "دي ام (DMs)", + "@dms": {}, + "edit": "تعديل", + "@edit": {}, + "edited": "تم تعديلها", + "@edited": {}, + "encrypted": "مشفرة", + "@encrypted": {}, + "encryptionBackupRecoverAction": "استرجاع", + "@encryptionBackupRecoverAction": {}, + "encryptionBackupRecoverRecovering": "استرداد", + "@encryptionBackupRecoverRecovering": {}, + "events": "الفعاليات", + "@events": {}, + "failed": "فشلت", + "@failed": {}, + "file": "ملف", + "@file": {}, + "join": "انضم", + "@join": {}, + "joined": "إنضم", + "@joined": {}, + "language": "اللغة", + "@language": {}, + "leave": "غادر", + "@leave": {}, + "licenses": "التراخيص", + "@licenses": {}, + "link": "الرابط", + "@link": {}, + "links": "الروابط", + "@links": {}, + "loading": "جارٍ التحميل", + "@loading": {}, + "location": "الموقع", + "@location": {}, + "logOut": "تسجيل الخروج", + "@logOut": {}, + "manage": "إدارة", + "@manage": {}, + "maybe": "ربما", + "@maybe": {}, + "member": "عضو", + "@member": {}, + "members": "الأعضاء", + "@members": {}, + "message": "رسالة", + "@message": {}, + "next": "التالي", + "@next": {}, + "no": "لا", + "@no": {}, + "noChatsStillSyncing": "جاري المزامنة...", + "@noChatsStillSyncing": {}, + "past": "السابق", + "@past": {}, + "pending": "قيد الانتظار", + "@pending": {}, + "pins": "الدبابيس", + "@pins": {}, + "play": "شغل", + "@play": {}, + "preview": "لمحة عامة", + "@preview": {}, + "private": "خاص", + "@private": {}, + "profile": "ملف التعريف", + "@profile": {}, + "pushKey": "مفتاح الضغط", + "@pushKey": {}, + "notes": "الملاحظات", + "@notes": {}, + "okay": "موافق", + "@okay": {}, + "on": "على", + "@on": {}, + "optional": "اختياري", + "@optional": {}, + "or": " أو ", + "@or": {}, + "overview": "لمحة عامة", + "@overview": {}, + "parents": "الأم", + "@parents": {}, + "password": "كلمة المرور", + "@password": {}, + "reason": "السبب", + "@reason": {}, + "reasonLabel": "السبب", + "@reasonLabel": {}, + "redeem": "استرداد", + "@redeem": {}, + "register": "التسجيل", + "@register": {}, + "regular": "منتظم", + "@regular": {}, + "remove": "إزالة", + "@remove": {}, + "reply": "ردّ", + "@reply": {}, + "report": "تقرير", + "@report": {}, + "reset": "إعادة ضبط", + "@reset": {}, + "select": "اختر", + "@select": {}, + "send": "أرسل", + "@send": {}, + "selectAll": "اختيار الكل", + "@selectAll": {}, + "unselectAll": "إلغاء اختيار الكل", + "@unselectAll": {}, + "server": "السيرفر", + "@server": {}, + "sessions": "الدورات", + "@sessions": {}, + "settings": "الإعدادات", + "@settings": {}, + "share": "شارك", + "@share": {}, + "skip": "تجاوز", + "@skip": {}, + "spaces": "الفضاءات", + "@spaces": {}, + "state": "الحالة", + "@state": {}, + "submit": "إرسال", + "@submit": {}, + "subspace": "فضاء فرعي", + "@subspace": {}, + "subspaces": "الفضاءات الفرعية", + "@subspaces": {}, + "tasks": "المهام", + "@tasks": {}, + "suggested": "مقترحة", + "@suggested": {}, + "joiningSuggested": "اقتراح الانضمام", + "@joiningSuggested": {}, + "removeSuggested": "حذف الاقتراح", + "@removeSuggested": {}, + "topic": "الموضوع", + "@topic": {}, + "unblock": "إلغاء الحظر", + "@unblock": {}, + "undefined": "غير محدد", + "@undefined": {}, + "unknown": "مجهول", + "@unknown": {}, + "unlink": "فك الربط", + "@unlink": {}, + "unmute": "إلغاء الكتم", + "@unmute": {}, + "unset": "إلغاء الضبط", + "@unset": {}, + "unverified": "لم يتم التحقق", + "@unverified": {}, + "upcoming": "القادم", + "@upcoming": {}, + "updates": "التحديثات", + "@updates": {}, + "verified": "تم التحقق", + "@verified": {}, + "version": "الإصدار", + "@version": {}, + "via": "عن طريق", + "@via": {}, + "video": "فيديو", + "@video": {}, + "yes": "نعم", + "@yes": {}, + "acter": "Acter", + "@acter": {}, + "selectChat": "اختر المحادثة", + "@selectChat": {}, + "recommendedSpaces": "الفضاءات الموصى بها", + "@recommendedSpaces": {}, + "mySpaces": "فضاءاتي", + "@mySpaces": {}, + "startDate": "تاريخ البدء", + "@startDate": {}, + "startTime": "موعد البدء", + "@startTime": {}, + "moreSubspaces": "المزيد من الفضاءات الفرعية", + "@moreSubspaces": {}, + "myTasks": "مهامي", + "@myTasks": {}, + "unlinkRoom": "فك ربط المحادثة", + "@unlinkRoom": {}, + "logOutConformationDescription1": "انتبه: ", + "@logOutConformationDescription1": {}, + "membersCount": "{count} أعضاء", + "@membersCount": {}, + "retrying": "تتم إعادة المحاولة …", + "@retrying": {}, + "includeScreenshot": "إدراج لقطة شاشة", + "@includeScreenshot": {}, + "jumpTo": "الانتقال إلى", + "@jumpTo": {}, + "taskList": "قائمة المهام", + "@taskList": {}, + "fatalError": "خطأ مهلك", + "@fatalError": {}, + "reportBug": "الإبلاغ عن خلل", + "@reportBug": {}, + "showStacktrace": "عرض تعقب التكديس (Stacktrace)", + "@showStacktrace": {}, + "hideStacktrace": "إخفاء تتبع المكدس (Stacktrace)", + "@hideStacktrace": {}, + "review": "مراجعة", + "@review": {}, + "activities": "النشاطات", + "@activities": {}, + "joining": "الانضمام", + "@joining": {}, + "rejecting": "رفض", + "@rejecting": {}, + "rejected": "رفض", + "@rejected": {}, + "update": "تحديث", + "@update": {}, + "event": "الفعالية", + "@event": {}, + "pin": "دبوس", + "@pin": {}, + "poll": "استبيان", + "@poll": {}, + "discussion": "النقاش", + "@discussion": {}, + "changingSettings": "تغيير الإعدادات…", + "@changingSettings": {}, + "assigningSelf": "تعيين ذاتي…", + "@assigningSelf": {}, + "unassigningSelf": "إلغاء التعيين الذاتي…", + "@unassigningSelf": {}, + "jumpToTabTutorialTitle": "الانتقال إلى", + "@jumpToTabTutorialTitle": {}, + "spaceOverviewTutorialTitle": "تفاصيل الفضاء", + "@spaceOverviewTutorialTitle": {}, + "homeTabTutorialTitle": "لوحة المتابعة", + "@homeTabTutorialTitle": {}, + "updatesTabTutorialTitle": "التحديثات", + "@updatesTabTutorialTitle": {}, + "chatsTabTutorialTitle": "المحادثات", + "@chatsTabTutorialTitle": {}, + "activityTabTutorialTitle": "النشاط", + "@activityTabTutorialTitle": {}, + "previous": "السابقة", + "@previous": {}, + "finish": "إنهاء", + "@finish": {}, + "wizzardContinue": "أكمل", + "@wizzardContinue": {}, + "emailOptional": "البريد الإلكتروني (اختياري)", + "@emailOptional": {}, + "sendEmail": "إرسال رسالة إلكترونية", + "@sendEmail": {}, + "pendingInvites": "الدعوات المعلقة", + "@pendingInvites": {}, + "appUnavailable": "التطبيق غير متاح", + "@appUnavailable": {}, + "editLink": "تعديل الرابط", + "@editLink": {}, + "done": "تم", + "@done": {}, + "otherSpaces": "فضاءات أخرى", + "@otherSpaces": {}, + "selectVisibility": "حدد قابلية الظهور", + "@selectVisibility": {}, + "confirmPassword": "تأكيد كلمة المرور", + "@confirmPassword": {}, + "taskName": "اسم المهمة", + "@taskName": {}, + "addingTask": "إضافة مهمة", + "@addingTask": {}, + "countTasksCompleted": "{count} أنجزت", + "@countTasksCompleted": {}, + "showCompleted": "عرض المنجزة", + "@showCompleted": {}, + "hideCompleted": "إخفاء المنجزة", + "@hideCompleted": {}, + "noAssignment": "لا يوجد تعيينات", + "@noAssignment": {}, + "assignMyself": "تكليف نفسي", + "@assignMyself": {}, + "removeMyself": "إزالة نفسي", + "@removeMyself": {}, + "updateTask": "تحديث المهمة", + "@updateTask": {}, + "updatingTask": "تحديث المهمة", + "@updatingTask": {}, + "editTitle": "تعديل العنوان", + "@editTitle": {}, + "updatingDescription": "تحديث الوصف", + "@updatingDescription": {}, + "visibilityTitle": "مستوى الرؤية", + "@visibilityTitle": {}, + "public": "عمومي", + "@public": {}, + "limited": "محدودة", + "@limited": {}, + "assignment": "التعيين", + "@assignment": {}, + "revoke": "إلغاء", + "@revoke": {}, + "updatingLinking": "تحديث الرابط", + "@updatingLinking": {}, + "seeAll": "عرض الكل", + "@seeAll": {}, + "addPin": "إضافة دبوس", + "@addPin": {}, + "addEvent": "إضافة حدث", + "@addEvent": {}, + "linkChat": "ربط المحادثة", + "@linkChat": {}, + "linkSpace": "ربط الفضاء", + "@linkSpace": {}, + "eventStarts": "البدء", + "@eventStarts": {}, + "eventEnded": "انتهت", + "@eventEnded": {}, + "live": "مباشر", + "@live": {}, + "ongoing": "مستمر", + "@ongoing": {}, + "myEvents": "الأحداث الخاصة بي", + "@myEvents": {}, + "eventStarted": "بدأت", + "@eventStarted": {}, + "happeningNow": "يحدث الآن", + "@happeningNow": {} } diff --git a/app/lib/l10n/app_da.arb b/app/lib/l10n/app_da.arb index bcc06e351450..3160ae61bd30 100644 --- a/app/lib/l10n/app_da.arb +++ b/app/lib/l10n/app_da.arb @@ -387,8 +387,6 @@ "@error": {}, "errorCreatingChat": "Fejl under oprettelse af chat: {error}", "@errorCreatingChat": {}, - "errorLoading": "Fejl med loading {error}", - "@errorLoading": {}, "errorSubmittingComment": "Fejl ved oprettelse af kommentar: {error}", "@errorSubmittingComment": {}, "eventDescriptionsData": "Beskrivelse af begivenhed data", @@ -401,8 +399,6 @@ "@eventTitleData": {}, "experimentalActerFeatures": "Eksperimentelle Acter funktioner", "@experimentalActerFeatures": {}, - "failed": "Fejl", - "@failed": {}, "file": "Fil", "@file": {}, "forgotPassword": "Glemt kodeord?", @@ -513,8 +509,6 @@ "@encryptionBackupEnabledExplainer": {}, "encryptionBackupRecoverExplainer": "Angiv din gendannelsesnøgle til at dekryptere krypteringssikkerhedskopien", "@encryptionBackupRecoverExplainer": {}, - "failedToLoad": "Kunne ikke indlæse: {error}", - "@failedToLoad": {}, "hintMessageUsername": "Unikt brugernavn til login og identifikation", "@hintMessageUsername": {}, "deletionFailed": "Sletning mislykkedes: {error}", diff --git a/app/lib/l10n/app_de.arb b/app/lib/l10n/app_de.arb index 817c3e3bc280..f362edbb3c39 100644 --- a/app/lib/l10n/app_de.arb +++ b/app/lib/l10n/app_de.arb @@ -219,8 +219,6 @@ "@failedToAdd": {}, "failedToConfirmToken": "Kode-Bestätigung fehlgeschlagen: {error}", "@failedToConfirmToken": {}, - "failedToLoad": "Laden fehlgeschlagen: {error}", - "@failedToLoad": {}, "failedToLoadInviteCodes": "Invite-Codes laden fehlgeschlagen: {error}", "@failedToLoadInviteCodes": {}, "failedToSubmitEmail": "Fehler beim Übermitteln der Email: {error}", @@ -811,8 +809,6 @@ "@someErrorOccurredLeavingRoom": {}, "errorCreatingChat": "Chat erstellen fehlgeschlagen: {error}", "@errorCreatingChat": {}, - "errorLoading": "Ladefehler: {error}", - "@errorLoading": {}, "eventDescriptionsData": "Eventbeschreibung", "@eventDescriptionsData": {}, "foundUsers": "User gefunden", @@ -849,8 +845,6 @@ "@noMembersFound": {}, "on": "auf", "@on": {}, - "failed": "fehlgeschlagen", - "@failed": {}, "parentSpace": "Elternspace", "@parentSpace": {}, "parentSpaceMustBeSelected": "Elternspace muss gewählt werden", diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index c89e52f797eb..dd4c3513b7fd 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -40,10 +40,15 @@ "alreadyConfirmed": "Already confirmed", "@alreadyConfirmed": {}, "analyticsTitle": "Help us help you", + "@analyticsTitle": {}, "analyticsDescription1": "By sharing crash analytics and error reports with us.", + "@analyticsDescription1": {}, "analyticsDescription2": "These are of course anonymized and do not contain any private information", + "@analyticsDescription2": {}, "sendCrashReportsTitle": "Send crash & error reports", + "@sendCrashReportsTitle": {}, "sendCrashReportsInfo": "Share crash tracebacks via sentry with the Acter team automatically", + "@sendCrashReportsInfo": {}, "and": "and", "@and": {}, "anInviteCodeYouWantToRedeem": "An invite code you want to redeem", @@ -179,16 +184,27 @@ "closeSessionAndDeleteData": "Close this session, deleting local data", "@closeSessionAndDeleteData": {}, "closeSpace": "Close Space", + "@closeSpace": {}, "closeChat": "Close Chat", + "@closeChat": {}, "closingRoomTitle": "Close this Room", + "@closingRoomTitle": {}, "closingRoomTitleDescription": "When closing this room, we will :\n\n - Kick everyone with a lower permission level then yours from it\n - Remove it as a child from the parent spaces (where you have the permissions to do so),\n - Set the invite rule to 'private'\n - You will leave the room.\n\nThis can not be undone. Are you sure you want to close this?", + "@closingRoomTitleDescription": {}, "closingRoom": "Closing ...", + "@closingRoom": {}, "closingRoomRemovingMembers": "Closing in process. Kicking member {kicked} / {total}", + "@closingRoomRemovingMembers": {}, "closingRoomMatrixMsg": "The room was closed", + "@closingRoomMatrixMsg": {}, "closingRoomRemovingFromParents": "Closing in process. Removing room from parent {currentParent} / {totalParents}", + "@closingRoomRemovingFromParents": {}, "closingRoomDoneBut": "Closed and you've left. But was unable to remove {skipped} other Users and remove it as child from {skippedParents} Spaces due to lack of permission. Others might still have access to it.", + "@closingRoomDoneBut": {}, "closingRoomDone": "Closed successfully.", + "@closingRoomDone": {}, "closingRoomFailed": "Closing failed: {error}", + "@closingRoomFailed": {}, "coBudget": "CoBudget", "@coBudget": {}, "code": "Code", @@ -244,6 +260,7 @@ "createDefaultChat": "Create default chat room, too", "@createDefaultChat": {}, "defaultChatName": "{name} chat", + "@defaultChatName": {}, "createDMWhenRedeeming": "Create DM when redeeming", "@createDMWhenRedeeming": {}, "createEventAndBringYourCommunity": "Create new event and bring your community together", @@ -450,8 +467,6 @@ "@errorCreatingCalendarEvent": {}, "errorCreatingChat": "Error creating chat: {error}", "@errorCreatingChat": {}, - "errorLoading": "Error loading {error}", - "@errorLoading": {}, "errorSubmittingComment": "Error submitting comment: {error}", "@errorSubmittingComment": {}, "errorUpdatingEvent": "Error updating event: {error}", @@ -466,12 +481,8 @@ "@eventTitleData": {}, "experimentalActerFeatures": "Experimental Acter features", "@experimentalActerFeatures": {}, - "failed": "failed", - "@failed": {}, "failedToAcceptInvite": "Failed to accept invite: {error}", "@failedToAcceptInvite": {}, - "failedToLoad": "Failed to load: {error}", - "@failedToLoad": {}, "failedToRejectInvite": "Failed to reject invite: {error}", "@failedToRejectInvite": {}, "file": "File", @@ -956,6 +967,12 @@ "@sasVerified": {}, "save": "Save", "@save": {}, + "saveFileAs": "Save file as", + "@saveFileAs" : {}, + "openFile": "Open", + "@openFile" : {}, + "shareFile": "Share", + "@shareFile" : {}, "saveChanges": "Save Changes", "@saveChanges": {}, "savingCode": "Saving code", @@ -1092,7 +1109,7 @@ "@addSuggested": {}, "removeSuggested": "Remove suggestion", "@removeSuggested": {}, - "superInvitations": "Super Invitations", + "superInvitations": "Invitation Codes", "@superInvitations": {}, "superInvites": "Invitation Codes", "@superInvites": {}, @@ -1866,6 +1883,7 @@ "avatarUploadFailed": "Failed to upload user avatar: {error}", "@avatarUploadFailed": {}, "sendEmail": "Send email", + "@sendEmail": {}, "inviteCopiedToClipboard": "Invite code copied to clipboard", "@inviteCopiedToClipboard": {}, "updateName": "Updating name", @@ -2043,23 +2061,41 @@ "addEvent": "Add Event", "@addEvent": {}, "linkChat": "Link Chat", + "@linkChat": {}, "linkSpace": "Link Space", + "@linkSpace": {}, "failedToUploadAvatar": "Failed to upload avatar: {error}", + "@failedToUploadAvatar": {}, "noMatchingTasksListFound": "No matching tasks list found", + "@noMatchingTasksListFound": {}, "noTasksListAvailableYet": "No tasks list available yet", + "@noTasksListAvailableYet": {}, "noTasksListAvailableDescription": "Share and manage important task with your community such as any TO-DO list so everyone is updated.", + "@noTasksListAvailableDescription": {}, "loadingMembersFailed": "Loading members failed: {error}", + "@loadingMembersFailed": {}, "ongoing": "Ongoing", + "@ongoing": {}, "noMatchingEventsFound": "No matching events found", + "@noMatchingEventsFound": {}, "noEventsFound": "No events found", + "@noEventsFound": {}, "noEventAvailableDescription": "Create new event and bring your community together.", + "@noEventAvailableDescription": {}, "myEvents": "My Events", + "@myEvents": {}, "eventStarted": "Started", + "@eventStarted": {}, "eventStarts": "Starts", + "@eventStarts": {}, "eventEnded": "Ended", + "@eventEnded": {}, "happeningNow": "Happening Now", + "@happeningNow": {}, "myUpcomingEvents": "My Upcoming Events", + "@myUpcomingEvents": {}, "live": "Live", + "@live": {}, "changeDate": "Change Date", "updatingDate": "Updating Date", "pleaseEnterALink": "Please enter a link", diff --git a/app/lib/l10n/app_es.arb b/app/lib/l10n/app_es.arb index 4ca8090d22d0..7a0dae888fac 100644 --- a/app/lib/l10n/app_es.arb +++ b/app/lib/l10n/app_es.arb @@ -455,8 +455,6 @@ "@errorCreatingCalendarEvent": {}, "errorCreatingChat": "Error al crear chat: {error}", "@errorCreatingChat": {}, - "errorLoading": "Error al cargar {error}", - "@errorLoading": {}, "errorUpdatingEvent": "Error al actualizar el evento: {error}", "@errorUpdatingEvent": {}, "eventDescriptionsData": "Datos de las descripciones de los eventos", @@ -467,12 +465,8 @@ "@events": {}, "experimentalActerFeatures": "Características de Acter Experimental", "@experimentalActerFeatures": {}, - "failed": "fallido", - "@failed": {}, "failedToAcceptInvite": "No se ha podido aceptar la invitación: {error}", "@failedToAcceptInvite": {}, - "failedToLoad": "No se ha podido cargar: {error}", - "@failedToLoad": {}, "failedToRejectInvite": "No se ha podido rechazar la invitación: {error}", "@failedToRejectInvite": {}, "file": "Archivo", diff --git a/app/lib/l10n/app_fr.arb b/app/lib/l10n/app_fr.arb index f5b072513706..f05504636f20 100644 --- a/app/lib/l10n/app_fr.arb +++ b/app/lib/l10n/app_fr.arb @@ -931,8 +931,6 @@ "@errorCreatingChat": {}, "errorSubmittingComment": "Erreur lors de l'envoi du commentaire : {error}", "@errorSubmittingComment": {}, - "failedToLoad": "Échec du chargement : {error}", - "@failedToLoad": {}, "getInTouchWithOtherChangeMakers": "Entrez en contact avec d'autres acteurs du changement, organisateurs ou activistes et discutez directement avec eux.", "@getInTouchWithOtherChangeMakers": {}, "formatMustBe": "Le format doit être @user:server.tld", @@ -997,8 +995,6 @@ "@encryptionBackupResetting": {}, "encryptionBackupRecoverInputHint": "Clé de récupération", "@encryptionBackupRecoverInputHint": {}, - "errorLoading": "Erreur de chargement {error}", - "@errorLoading": {}, "eventDescriptionsData": "Données relatives à la description des événements", "@eventDescriptionsData": {}, "eventTitleData": "Données sur le titre de l'événement", @@ -1489,8 +1485,6 @@ "@encryptionBackupRecoverRecovering": {}, "events": "Evénements", "@events": {}, - "failed": "échoué", - "@failed": {}, "file": "Fichier", "@file": {}, "from": "de", diff --git a/app/lib/l10n/app_pl.arb b/app/lib/l10n/app_pl.arb index 26b4a779d095..27c1f8e7a509 100644 --- a/app/lib/l10n/app_pl.arb +++ b/app/lib/l10n/app_pl.arb @@ -693,8 +693,6 @@ "@errorCreatingCalendarEvent": {}, "errorCreatingChat": "Błąd podczas tworzenia czatu: {error}", "@errorCreatingChat": {}, - "errorLoading": "Błąd ładowania {error}", - "@errorLoading": {}, "errorSubmittingComment": "Błąd przesyłania komentarza: {error}", "@errorSubmittingComment": {}, "eventDescriptionsData": "Dane opisów zdarzeń", @@ -707,10 +705,6 @@ "@eventTitleData": {}, "experimentalActerFeatures": "Funkcje Experimental Acter", "@experimentalActerFeatures": {}, - "failed": "nieudany", - "@failed": {}, - "failedToLoad": "Nie udało się załadować: {error}", - "@failedToLoad": {}, "file": "Plik", "@file": {}, "formatMustBe": "Format musi być następujący: @user:server.tld", diff --git a/app/pubspec.lock b/app/pubspec.lock index e5190791301c..b1635ba2287c 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1509,6 +1509,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + open_filex: + dependency: "direct main" + description: + name: open_filex + sha256: ba425ea49affd0a98a234aa9344b9ea5d4c4f7625a1377961eae9fe194c3d523 + url: "https://pub.dev" + source: hosted + version: "4.5.0" package_config: dependency: transitive description: @@ -1621,6 +1629,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + phosphor_flutter: + dependency: "direct main" + description: + name: phosphor_flutter + sha256: "8a14f238f28a0b54842c5a4dc20676598dd4811fcba284ed828bd5a262c11fde" + url: "https://pub.dev" + source: hosted + version: "2.1.0" photo_view: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index f37edac3c80a..8a8bc3ab1dd2 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -132,6 +132,8 @@ dependencies: shake_detector: path: ../packages/shake_detector + open_filex: ^4.5.0 + phosphor_flutter: ^2.1.0 dev_dependencies: flutter_test: diff --git a/native/acter/api.rsh b/native/acter/api.rsh index 2a636dfbf920..6be8747987c6 100644 --- a/native/acter/api.rsh +++ b/native/acter/api.rsh @@ -234,6 +234,11 @@ object OptionRsvpStatus { fn status_str() -> Option; } +object OptionComposeDraft { + /// get compose draft object + fn draft() -> Option; +} + object UserProfile { /// get user id fn user_id() -> UserId; @@ -330,6 +335,21 @@ object MxcUri { fn to_string() -> string; } +object ComposeDraft { + /// plain body text, always available + fn plain_text() -> string; + + /// formatted text + fn html_text() -> Option; + + /// event id, only valid for edit and reply states + fn event_id() -> Option; + + /// compose message state type. + /// One of `new`, `edit`, `reply`. + fn draft_type() -> string; +} + object RoomId { fn to_string() -> string; } @@ -1098,7 +1118,6 @@ object Room { /// leave this room fn leave() -> Future>; - } @@ -1356,6 +1375,15 @@ object Convo { fn redact_content(event_id: string, reason: Option) -> Future>; fn is_joined() -> bool; + + /// compose message state of the room + fn msg_draft() -> Future>; + + /// save composed message state of the room + fn save_msg_draft(text: string, html: Option, draft_type: string, event_id: Option) -> Future>; + + /// clear composed message state of the room + fn clear_msg_draft() -> Future>; } diff --git a/native/acter/src/api.rs b/native/acter/src/api.rs index 0ad7c4781f01..99beb7c5402b 100644 --- a/native/acter/src/api.rs +++ b/native/acter/src/api.rs @@ -84,8 +84,8 @@ pub use comments::{Comment, CommentDraft, CommentsManager}; pub use common::{ duration_from_secs, new_calendar_event_ref_builder, new_colorize_builder, new_link_ref_builder, new_obj_ref_builder, new_task_list_ref_builder, new_task_ref_builder, new_thumb_size, - DeviceRecord, MediaSource, MsgContent, OptionBuffer, OptionRsvpStatus, OptionString, - ReactionRecord, ThumbnailInfo, ThumbnailSize, + ComposeDraft, DeviceRecord, MediaSource, MsgContent, OptionBuffer, OptionComposeDraft, + OptionRsvpStatus, OptionString, ReactionRecord, ThumbnailInfo, ThumbnailSize, }; pub use convo::{ new_convo_settings_builder, Convo, ConvoDiff, CreateConvoSettings, CreateConvoSettingsBuilder, diff --git a/native/acter/src/api/common.rs b/native/acter/src/api/common.rs index fd57265e556b..6ca916476586 100644 --- a/native/acter/src/api/common.rs +++ b/native/acter/src/api/common.rs @@ -5,10 +5,15 @@ use acter_core::events::{ }; use anyhow::{Context, Result}; use core::time::Duration; -use matrix_sdk::media::{MediaFormat, MediaThumbnailSettings, MediaThumbnailSize}; +use matrix_sdk::{ + media::{MediaFormat, MediaThumbnailSettings, MediaThumbnailSize}, + ComposerDraft, ComposerDraftType, +}; use ruma::UInt; use ruma_client_api::media::get_content_thumbnail; -use ruma_common::{EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedMxcUri, OwnedUserId}; +use ruma_common::{ + EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, +}; use ruma_events::room::{ message::{ AudioInfo, AudioMessageEventContent, EmoteMessageEventContent, FileInfo, @@ -73,6 +78,20 @@ impl OptionRsvpStatus { self.status.as_ref().map(|x| x.to_string()) } } +#[derive(Clone)] +pub struct OptionComposeDraft { + draft: Option, +} + +impl OptionComposeDraft { + pub(crate) fn new(draft: Option) -> Self { + OptionComposeDraft { draft } + } + + pub fn draft(&self) -> Option { + self.draft.clone() + } +} pub struct MediaSource { inner: SdkMediaSource, @@ -468,6 +487,65 @@ impl MsgContent { } } +#[derive(Clone)] +pub struct ComposeDraft { + inner: ComposerDraft, +} + +impl ComposeDraft { + pub fn new( + plain_text: String, + html_text: Option, + msg_type: String, + event_id: Option, + ) -> Self { + let m_type = msg_type.clone(); + let draft_type = match (m_type.as_str(), event_id) { + ("new", None) => ComposerDraftType::NewMessage, + ("edit", Some(id)) => ComposerDraftType::Edit { event_id: id }, + ("reply", Some(id)) => ComposerDraftType::Reply { event_id: id }, + _ => ComposerDraftType::NewMessage, + }; + + ComposeDraft { + inner: ComposerDraft { + plain_text, + html_text, + draft_type, + }, + } + } + + pub fn inner(&self) -> ComposerDraft { + self.inner.clone() + } + + pub fn plain_text(&self) -> String { + self.inner.plain_text.clone() + } + + pub fn html_text(&self) -> Option { + self.inner.html_text.clone() + } + + // only valid for reply and edit drafts + pub fn event_id(&self) -> Option { + match &(self.inner.draft_type) { + ComposerDraftType::Edit { event_id } => Some(event_id.to_string()), + ComposerDraftType::Reply { event_id } => Some(event_id.to_string()), + ComposerDraftType::NewMessage => None, + } + } + + pub fn draft_type(&self) -> String { + match &(self.inner.draft_type) { + ComposerDraftType::NewMessage => "new".to_string(), + ComposerDraftType::Edit { event_id } => "edit".to_string(), + ComposerDraftType::Reply { event_id } => "reply".to_string(), + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ReactionRecord { sender_id: OwnedUserId, diff --git a/native/acter/src/api/convo.rs b/native/acter/src/api/convo.rs index eff3e2410163..62717da52098 100644 --- a/native/acter/src/api/convo.rs +++ b/native/acter/src/api/convo.rs @@ -2,13 +2,13 @@ use acter_core::{statics::default_acter_convo_states, Error}; use anyhow::{bail, Context, Result}; use derive_builder::Builder; use futures::stream::{Stream, StreamExt}; -use matrix_sdk::{executor::JoinHandle, RoomMemberships}; +use matrix_sdk::{executor::JoinHandle, ComposerDraft, ComposerDraftType, RoomMemberships}; use matrix_sdk_ui::{timeline::RoomExt, Timeline}; use ruma::assign; use ruma_client_api::room::{create_room, Visibility}; use ruma_common::{ - serde::Raw, MxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, - RoomOrAliasId, ServerName, UserId, + serde::Raw, MxcUri, OwnedEventId, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, + RoomId, RoomOrAliasId, ServerName, UserId, }; use ruma_events::{ receipt::{ReceiptThread, ReceiptType}, @@ -35,7 +35,7 @@ use super::{ receipt::ReceiptRecord, room::Room, utils::{remap_for_diff, ApiVectorDiff}, - RUNTIME, + ComposeDraft, OptionComposeDraft, RUNTIME, }; pub type ConvoDiff = ApiVectorDiff; @@ -313,6 +313,90 @@ impl Convo { }) .await? } + + pub async fn msg_draft(&self) -> Result { + if !self.is_joined() { + bail!("Unable to fetch composer draft of a room we are not in"); + } + let room = self.room.clone(); + RUNTIME + .spawn(async move { + let draft = room.load_composer_draft().await?; + + Ok(OptionComposeDraft::new(draft.map(|composer_draft| { + let (msg_type, event_id) = match composer_draft.draft_type { + ComposerDraftType::NewMessage => ("new".to_string(), None), + ComposerDraftType::Edit { event_id } => { + ("edit".to_string(), Some(event_id)) + } + ComposerDraftType::Reply { event_id } => { + ("reply".to_string(), Some(event_id)) + } + }; + ComposeDraft::new( + composer_draft.plain_text, + composer_draft.html_text, + msg_type, + event_id, + ) + }))) + }) + .await? + } + + pub async fn save_msg_draft( + &self, + text: String, + html: Option, + draft_type: String, + event_id: Option, + ) -> Result { + if !self.is_joined() { + bail!("Unable to save composer draft of a room we are not in"); + } + let room = self.room.clone(); + + let draft_type = match (draft_type.as_str(), event_id) { + ("new", None) => ComposerDraftType::NewMessage, + ("edit", Some(id)) => ComposerDraftType::Edit { + event_id: OwnedEventId::try_from(id)?, + }, + + ("reply", Some(id)) => ComposerDraftType::Reply { + event_id: OwnedEventId::try_from(id)?, + }, + + ("reply", _) | ("edit", _) => bail!("Invalid event id"), + + (draft_type, _) => bail!("Invalid draft type {draft_type}"), + }; + + let msg_draft = ComposerDraft { + plain_text: text, + html_text: html, + draft_type, + }; + + RUNTIME + .spawn(async move { + room.save_composer_draft(msg_draft).await?; + Ok(true) + }) + .await? + } + + pub async fn clear_msg_draft(&self) -> Result { + if !self.is_joined() { + bail!("Unable to remove composer draft of a room we are not in"); + } + let room = self.room.clone(); + RUNTIME + .spawn(async move { + let draft = room.clear_composer_draft(); + Ok(true) + }) + .await? + } } impl Deref for Convo { diff --git a/packages/acter_notifify/lib/platform/android.dart b/packages/acter_notifify/lib/platform/android.dart index 831978ee1dd1..708774ce57ec 100644 --- a/packages/acter_notifify/lib/platform/android.dart +++ b/packages/acter_notifify/lib/platform/android.dart @@ -1,10 +1,9 @@ import 'dart:async'; +import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; import 'package:acter_notifify/local.dart'; -import 'package:acter_notifify/util.dart'; - import 'package:acter_notifify/matrix.dart'; -import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'; +import 'package:acter_notifify/util.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:logging/logging.dart'; diff --git a/packages/rust_sdk/lib/acter_flutter_sdk.dart b/packages/rust_sdk/lib/acter_flutter_sdk.dart index a83c10d8f8f1..42aa40a0139e 100644 --- a/packages/rust_sdk/lib/acter_flutter_sdk.dart +++ b/packages/rust_sdk/lib/acter_flutter_sdk.dart @@ -116,8 +116,8 @@ Future?> remapToImage( return ResizeImage(image, width: cacheWidth, height: cacheHeight); } return image; - } catch (e) { - _log.severe('Error fetching avatar: $e'); + } catch (e, st) { + _log.severe('Error fetching avatar', e, st); return null; } } diff --git a/packages/rust_sdk/lib/acter_flutter_sdk_ffi.dart b/packages/rust_sdk/lib/acter_flutter_sdk_ffi.dart index 9cc4e580f17c..932184b0a930 100644 --- a/packages/rust_sdk/lib/acter_flutter_sdk_ffi.dart +++ b/packages/rust_sdk/lib/acter_flutter_sdk_ffi.dart @@ -7035,6 +7035,141 @@ class Api { return tmp7; } + OptionComposeDraft? __convoMsgDraftFuturePoll( + int boxed, + int postCobject, + int port, + ) { + final tmp0 = boxed; + final tmp2 = postCobject; + final tmp4 = port; + var tmp1 = 0; + var tmp3 = 0; + var tmp5 = 0; + tmp1 = tmp0; + tmp3 = tmp2; + tmp5 = tmp4; + final tmp6 = _convoMsgDraftFuturePoll( + tmp1, + tmp3, + tmp5, + ); + final tmp8 = tmp6.arg0; + final tmp9 = tmp6.arg1; + final tmp10 = tmp6.arg2; + final tmp11 = tmp6.arg3; + final tmp12 = tmp6.arg4; + final tmp13 = tmp6.arg5; + if (tmp8 == 0) { + return null; + } + if (tmp9 == 0) { + debugAllocation("handle error", tmp10, tmp11); + final ffi.Pointer tmp10_0 = ffi.Pointer.fromAddress(tmp10); + final tmp9_0 = + utf8.decode(tmp10_0.asTypedList(tmp11), allowMalformed: true); + if (tmp11 > 0) { + final ffi.Pointer tmp10_0; + tmp10_0 = ffi.Pointer.fromAddress(tmp10); + this.__deallocate(tmp10_0, tmp12, 1); + } + throw tmp9_0; + } + final ffi.Pointer tmp13_0 = ffi.Pointer.fromAddress(tmp13); + final tmp13_1 = _Box(this, tmp13_0, "drop_box_OptionComposeDraft"); + tmp13_1._finalizer = this._registerFinalizer(tmp13_1); + final tmp7 = OptionComposeDraft._(this, tmp13_1); + return tmp7; + } + + bool? __convoSaveMsgDraftFuturePoll( + int boxed, + int postCobject, + int port, + ) { + final tmp0 = boxed; + final tmp2 = postCobject; + final tmp4 = port; + var tmp1 = 0; + var tmp3 = 0; + var tmp5 = 0; + tmp1 = tmp0; + tmp3 = tmp2; + tmp5 = tmp4; + final tmp6 = _convoSaveMsgDraftFuturePoll( + tmp1, + tmp3, + tmp5, + ); + final tmp8 = tmp6.arg0; + final tmp9 = tmp6.arg1; + final tmp10 = tmp6.arg2; + final tmp11 = tmp6.arg3; + final tmp12 = tmp6.arg4; + final tmp13 = tmp6.arg5; + if (tmp8 == 0) { + return null; + } + if (tmp9 == 0) { + debugAllocation("handle error", tmp10, tmp11); + final ffi.Pointer tmp10_0 = ffi.Pointer.fromAddress(tmp10); + final tmp9_0 = + utf8.decode(tmp10_0.asTypedList(tmp11), allowMalformed: true); + if (tmp11 > 0) { + final ffi.Pointer tmp10_0; + tmp10_0 = ffi.Pointer.fromAddress(tmp10); + this.__deallocate(tmp10_0, tmp12, 1); + } + throw tmp9_0; + } + final tmp7 = tmp13 > 0; + return tmp7; + } + + bool? __convoClearMsgDraftFuturePoll( + int boxed, + int postCobject, + int port, + ) { + final tmp0 = boxed; + final tmp2 = postCobject; + final tmp4 = port; + var tmp1 = 0; + var tmp3 = 0; + var tmp5 = 0; + tmp1 = tmp0; + tmp3 = tmp2; + tmp5 = tmp4; + final tmp6 = _convoClearMsgDraftFuturePoll( + tmp1, + tmp3, + tmp5, + ); + final tmp8 = tmp6.arg0; + final tmp9 = tmp6.arg1; + final tmp10 = tmp6.arg2; + final tmp11 = tmp6.arg3; + final tmp12 = tmp6.arg4; + final tmp13 = tmp6.arg5; + if (tmp8 == 0) { + return null; + } + if (tmp9 == 0) { + debugAllocation("handle error", tmp10, tmp11); + final ffi.Pointer tmp10_0 = ffi.Pointer.fromAddress(tmp10); + final tmp9_0 = + utf8.decode(tmp10_0.asTypedList(tmp11), allowMalformed: true); + if (tmp11 > 0) { + final ffi.Pointer tmp10_0; + tmp10_0 = ffi.Pointer.fromAddress(tmp10); + this.__deallocate(tmp10_0, tmp12, 1); + } + throw tmp9_0; + } + final tmp7 = tmp13 > 0; + return tmp7; + } + EventId? __commentDraftSendFuturePoll( int boxed, int postCobject, @@ -16878,6 +17013,16 @@ class Api { _OptionRsvpStatusStatusStrReturn Function( int, )>(); + late final _optionComposeDraftDraftPtr = _lookup< + ffi.NativeFunction< + _OptionComposeDraftDraftReturn Function( + ffi.IntPtr, + )>>("__OptionComposeDraft_draft"); + + late final _optionComposeDraftDraft = _optionComposeDraftDraftPtr.asFunction< + _OptionComposeDraftDraftReturn Function( + int, + )>(); late final _userProfileUserIdPtr = _lookup< ffi.NativeFunction< ffi.IntPtr Function( @@ -17138,6 +17283,46 @@ class Api { _MxcUriToStringReturn Function( int, )>(); + late final _composeDraftPlainTextPtr = _lookup< + ffi.NativeFunction< + _ComposeDraftPlainTextReturn Function( + ffi.IntPtr, + )>>("__ComposeDraft_plain_text"); + + late final _composeDraftPlainText = _composeDraftPlainTextPtr.asFunction< + _ComposeDraftPlainTextReturn Function( + int, + )>(); + late final _composeDraftHtmlTextPtr = _lookup< + ffi.NativeFunction< + _ComposeDraftHtmlTextReturn Function( + ffi.IntPtr, + )>>("__ComposeDraft_html_text"); + + late final _composeDraftHtmlText = _composeDraftHtmlTextPtr.asFunction< + _ComposeDraftHtmlTextReturn Function( + int, + )>(); + late final _composeDraftEventIdPtr = _lookup< + ffi.NativeFunction< + _ComposeDraftEventIdReturn Function( + ffi.IntPtr, + )>>("__ComposeDraft_event_id"); + + late final _composeDraftEventId = _composeDraftEventIdPtr.asFunction< + _ComposeDraftEventIdReturn Function( + int, + )>(); + late final _composeDraftDraftTypePtr = _lookup< + ffi.NativeFunction< + _ComposeDraftDraftTypeReturn Function( + ffi.IntPtr, + )>>("__ComposeDraft_draft_type"); + + late final _composeDraftDraftType = _composeDraftDraftTypePtr.asFunction< + _ComposeDraftDraftTypeReturn Function( + int, + )>(); late final _roomIdToStringPtr = _lookup< ffi.NativeFunction< _RoomIdToStringReturn Function( @@ -20907,6 +21092,64 @@ class Api { int Function( int, )>(); + late final _convoMsgDraftPtr = _lookup< + ffi.NativeFunction< + ffi.IntPtr Function( + ffi.IntPtr, + )>>("__Convo_msg_draft"); + + late final _convoMsgDraft = _convoMsgDraftPtr.asFunction< + int Function( + int, + )>(); + late final _convoSaveMsgDraftPtr = _lookup< + ffi.NativeFunction< + ffi.IntPtr Function( + ffi.IntPtr, + ffi.IntPtr, + ffi.UintPtr, + ffi.UintPtr, + ffi.Uint8, + ffi.IntPtr, + ffi.UintPtr, + ffi.UintPtr, + ffi.IntPtr, + ffi.UintPtr, + ffi.UintPtr, + ffi.Uint8, + ffi.IntPtr, + ffi.UintPtr, + ffi.UintPtr, + )>>("__Convo_save_msg_draft"); + + late final _convoSaveMsgDraft = _convoSaveMsgDraftPtr.asFunction< + int Function( + int, + int, + int, + int, + int, + int, + int, + int, + int, + int, + int, + int, + int, + int, + int, + )>(); + late final _convoClearMsgDraftPtr = _lookup< + ffi.NativeFunction< + ffi.IntPtr Function( + ffi.IntPtr, + )>>("__Convo_clear_msg_draft"); + + late final _convoClearMsgDraft = _convoClearMsgDraftPtr.asFunction< + int Function( + int, + )>(); late final _commentDraftContentTextPtr = _lookup< ffi.NativeFunction< ffi.Void Function( @@ -29318,6 +29561,50 @@ class Api { int, int, )>(); + late final _convoMsgDraftFuturePollPtr = _lookup< + ffi.NativeFunction< + _ConvoMsgDraftFuturePollReturn Function( + ffi.IntPtr, + ffi.IntPtr, + ffi.Int64, + )>>("__Convo_msg_draft_future_poll"); + + late final _convoMsgDraftFuturePoll = _convoMsgDraftFuturePollPtr.asFunction< + _ConvoMsgDraftFuturePollReturn Function( + int, + int, + int, + )>(); + late final _convoSaveMsgDraftFuturePollPtr = _lookup< + ffi.NativeFunction< + _ConvoSaveMsgDraftFuturePollReturn Function( + ffi.IntPtr, + ffi.IntPtr, + ffi.Int64, + )>>("__Convo_save_msg_draft_future_poll"); + + late final _convoSaveMsgDraftFuturePoll = + _convoSaveMsgDraftFuturePollPtr.asFunction< + _ConvoSaveMsgDraftFuturePollReturn Function( + int, + int, + int, + )>(); + late final _convoClearMsgDraftFuturePollPtr = _lookup< + ffi.NativeFunction< + _ConvoClearMsgDraftFuturePollReturn Function( + ffi.IntPtr, + ffi.IntPtr, + ffi.Int64, + )>>("__Convo_clear_msg_draft_future_poll"); + + late final _convoClearMsgDraftFuturePoll = + _convoClearMsgDraftFuturePollPtr.asFunction< + _ConvoClearMsgDraftFuturePollReturn Function( + int, + int, + int, + )>(); late final _commentDraftSendFuturePollPtr = _lookup< ffi.NativeFunction< _CommentDraftSendFuturePollReturn Function( @@ -34823,6 +35110,37 @@ class OptionRsvpStatus { } } +class OptionComposeDraft { + final Api _api; + final _Box _box; + + OptionComposeDraft._(this._api, this._box); + + /// get compose draft object + ComposeDraft? draft() { + var tmp0 = 0; + tmp0 = _box.borrow(); + final tmp1 = _api._optionComposeDraftDraft( + tmp0, + ); + final tmp3 = tmp1.arg0; + final tmp4 = tmp1.arg1; + if (tmp3 == 0) { + return null; + } + final ffi.Pointer tmp4_0 = ffi.Pointer.fromAddress(tmp4); + final tmp4_1 = _Box(_api, tmp4_0, "drop_box_ComposeDraft"); + tmp4_1._finalizer = _api._registerFinalizer(tmp4_1); + final tmp2 = ComposeDraft._(_api, tmp4_1); + return tmp2; + } + + /// Manually drops the object and unregisters the FinalizableHandle. + void drop() { + _box.drop(); + } +} + class UserProfile { final Api _api; final _Box _box; @@ -35452,16 +35770,99 @@ class DeviceId { } } -class EventId { +class EventId { + final Api _api; + final _Box _box; + + EventId._(this._api, this._box); + + String toString() { + var tmp0 = 0; + tmp0 = _box.borrow(); + final tmp1 = _api._eventIdToString( + tmp0, + ); + final tmp3 = tmp1.arg0; + final tmp4 = tmp1.arg1; + final tmp5 = tmp1.arg2; + if (tmp4 == 0) { + print("returning empty string"); + return ""; + } + final ffi.Pointer tmp3_ptr = ffi.Pointer.fromAddress(tmp3); + List tmp3_buf = []; + final tmp3_precast = tmp3_ptr.cast(); + for (int i = 0; i < tmp4; i++) { + int char = tmp3_precast.elementAt(i).value; + tmp3_buf.add(char); + } + final tmp2 = utf8.decode(tmp3_buf, allowMalformed: true); + if (tmp5 > 0) { + final ffi.Pointer tmp3_0; + tmp3_0 = ffi.Pointer.fromAddress(tmp3); + _api.__deallocate(tmp3_0, tmp5 * 1, 1); + } + return tmp2; + } + + /// Manually drops the object and unregisters the FinalizableHandle. + void drop() { + _box.drop(); + } +} + +class MxcUri { + final Api _api; + final _Box _box; + + MxcUri._(this._api, this._box); + + String toString() { + var tmp0 = 0; + tmp0 = _box.borrow(); + final tmp1 = _api._mxcUriToString( + tmp0, + ); + final tmp3 = tmp1.arg0; + final tmp4 = tmp1.arg1; + final tmp5 = tmp1.arg2; + if (tmp4 == 0) { + print("returning empty string"); + return ""; + } + final ffi.Pointer tmp3_ptr = ffi.Pointer.fromAddress(tmp3); + List tmp3_buf = []; + final tmp3_precast = tmp3_ptr.cast(); + for (int i = 0; i < tmp4; i++) { + int char = tmp3_precast.elementAt(i).value; + tmp3_buf.add(char); + } + final tmp2 = utf8.decode(tmp3_buf, allowMalformed: true); + if (tmp5 > 0) { + final ffi.Pointer tmp3_0; + tmp3_0 = ffi.Pointer.fromAddress(tmp3); + _api.__deallocate(tmp3_0, tmp5 * 1, 1); + } + return tmp2; + } + + /// Manually drops the object and unregisters the FinalizableHandle. + void drop() { + _box.drop(); + } +} + +class ComposeDraft { final Api _api; final _Box _box; - EventId._(this._api, this._box); + ComposeDraft._(this._api, this._box); - String toString() { + /// plain body text, always available + String plainText() { var tmp0 = 0; tmp0 = _box.borrow(); - final tmp1 = _api._eventIdToString( + final tmp1 = _api._composeDraftPlainText( tmp0, ); final tmp3 = tmp1.arg0; @@ -35487,22 +35888,80 @@ class EventId { return tmp2; } - /// Manually drops the object and unregisters the FinalizableHandle. - void drop() { - _box.drop(); + /// formatted text + String? htmlText() { + var tmp0 = 0; + tmp0 = _box.borrow(); + final tmp1 = _api._composeDraftHtmlText( + tmp0, + ); + final tmp3 = tmp1.arg0; + final tmp4 = tmp1.arg1; + final tmp5 = tmp1.arg2; + final tmp6 = tmp1.arg3; + if (tmp3 == 0) { + return null; + } + if (tmp5 == 0) { + print("returning empty string"); + return ""; + } + final ffi.Pointer tmp4_ptr = ffi.Pointer.fromAddress(tmp4); + List tmp4_buf = []; + final tmp4_precast = tmp4_ptr.cast(); + for (int i = 0; i < tmp5; i++) { + int char = tmp4_precast.elementAt(i).value; + tmp4_buf.add(char); + } + final tmp2 = utf8.decode(tmp4_buf, allowMalformed: true); + if (tmp6 > 0) { + final ffi.Pointer tmp4_0; + tmp4_0 = ffi.Pointer.fromAddress(tmp4); + _api.__deallocate(tmp4_0, tmp6 * 1, 1); + } + return tmp2; } -} - -class MxcUri { - final Api _api; - final _Box _box; - MxcUri._(this._api, this._box); + /// event id, only valid for edit and reply states + String? eventId() { + var tmp0 = 0; + tmp0 = _box.borrow(); + final tmp1 = _api._composeDraftEventId( + tmp0, + ); + final tmp3 = tmp1.arg0; + final tmp4 = tmp1.arg1; + final tmp5 = tmp1.arg2; + final tmp6 = tmp1.arg3; + if (tmp3 == 0) { + return null; + } + if (tmp5 == 0) { + print("returning empty string"); + return ""; + } + final ffi.Pointer tmp4_ptr = ffi.Pointer.fromAddress(tmp4); + List tmp4_buf = []; + final tmp4_precast = tmp4_ptr.cast(); + for (int i = 0; i < tmp5; i++) { + int char = tmp4_precast.elementAt(i).value; + tmp4_buf.add(char); + } + final tmp2 = utf8.decode(tmp4_buf, allowMalformed: true); + if (tmp6 > 0) { + final ffi.Pointer tmp4_0; + tmp4_0 = ffi.Pointer.fromAddress(tmp4); + _api.__deallocate(tmp4_0, tmp6 * 1, 1); + } + return tmp2; + } - String toString() { + /// compose message state type. + /// One of `new`, `edit`, `reply`. + String draftType() { var tmp0 = 0; tmp0 = _box.borrow(); - final tmp1 = _api._mxcUriToString( + final tmp1 = _api._composeDraftDraftType( tmp0, ); final tmp3 = tmp1.arg0; @@ -43428,6 +43887,132 @@ class Convo { return tmp2; } + /// compose message state of the room + Future msgDraft() { + var tmp0 = 0; + tmp0 = _box.borrow(); + final tmp1 = _api._convoMsgDraft( + tmp0, + ); + final tmp3 = tmp1; + final ffi.Pointer tmp3_0 = ffi.Pointer.fromAddress(tmp3); + final tmp3_1 = _Box(_api, tmp3_0, "__Convo_msg_draft_future_drop"); + tmp3_1._finalizer = _api._registerFinalizer(tmp3_1); + final tmp2 = _nativeFuture(tmp3_1, _api.__convoMsgDraftFuturePoll); + return tmp2; + } + + /// save composed message state of the room + Future saveMsgDraft( + String text, + String? html, + String draftType, + String? eventId, + ) { + final tmp1 = text; + final tmp5 = html; + final tmp11 = draftType; + final tmp15 = eventId; + var tmp0 = 0; + var tmp2 = 0; + var tmp3 = 0; + var tmp4 = 0; + var tmp6 = 0; + var tmp8 = 0; + var tmp9 = 0; + var tmp10 = 0; + var tmp12 = 0; + var tmp13 = 0; + var tmp14 = 0; + var tmp16 = 0; + var tmp18 = 0; + var tmp19 = 0; + var tmp20 = 0; + tmp0 = _box.borrow(); + final tmp1_0 = utf8.encode(tmp1); + tmp3 = tmp1_0.length; + + final ffi.Pointer tmp2_0 = _api.__allocate(tmp3 * 1, 1); + final Uint8List tmp2_1 = tmp2_0.asTypedList(tmp3); + tmp2_1.setAll(0, tmp1_0); + tmp2 = tmp2_0.address; + tmp4 = tmp3; + if (tmp5 == null) { + tmp6 = 0; + } else { + tmp6 = 1; + final tmp7 = tmp5; + final tmp7_0 = utf8.encode(tmp7); + tmp9 = tmp7_0.length; + + final ffi.Pointer tmp8_0 = _api.__allocate(tmp9 * 1, 1); + final Uint8List tmp8_1 = tmp8_0.asTypedList(tmp9); + tmp8_1.setAll(0, tmp7_0); + tmp8 = tmp8_0.address; + tmp10 = tmp9; + } + final tmp11_0 = utf8.encode(tmp11); + tmp13 = tmp11_0.length; + + final ffi.Pointer tmp12_0 = _api.__allocate(tmp13 * 1, 1); + final Uint8List tmp12_1 = tmp12_0.asTypedList(tmp13); + tmp12_1.setAll(0, tmp11_0); + tmp12 = tmp12_0.address; + tmp14 = tmp13; + if (tmp15 == null) { + tmp16 = 0; + } else { + tmp16 = 1; + final tmp17 = tmp15; + final tmp17_0 = utf8.encode(tmp17); + tmp19 = tmp17_0.length; + + final ffi.Pointer tmp18_0 = _api.__allocate(tmp19 * 1, 1); + final Uint8List tmp18_1 = tmp18_0.asTypedList(tmp19); + tmp18_1.setAll(0, tmp17_0); + tmp18 = tmp18_0.address; + tmp20 = tmp19; + } + final tmp21 = _api._convoSaveMsgDraft( + tmp0, + tmp2, + tmp3, + tmp4, + tmp6, + tmp8, + tmp9, + tmp10, + tmp12, + tmp13, + tmp14, + tmp16, + tmp18, + tmp19, + tmp20, + ); + final tmp23 = tmp21; + final ffi.Pointer tmp23_0 = ffi.Pointer.fromAddress(tmp23); + final tmp23_1 = _Box(_api, tmp23_0, "__Convo_save_msg_draft_future_drop"); + tmp23_1._finalizer = _api._registerFinalizer(tmp23_1); + final tmp22 = _nativeFuture(tmp23_1, _api.__convoSaveMsgDraftFuturePoll); + return tmp22; + } + + /// clear composed message state of the room + Future clearMsgDraft() { + var tmp0 = 0; + tmp0 = _box.borrow(); + final tmp1 = _api._convoClearMsgDraft( + tmp0, + ); + final tmp3 = tmp1; + final ffi.Pointer tmp3_0 = ffi.Pointer.fromAddress(tmp3); + final tmp3_1 = _Box(_api, tmp3_0, "__Convo_clear_msg_draft_future_drop"); + tmp3_1._finalizer = _api._registerFinalizer(tmp3_1); + final tmp2 = _nativeFuture(tmp3_1, _api.__convoClearMsgDraftFuturePoll); + return tmp2; + } + /// Manually drops the object and unregisters the FinalizableHandle. void drop() { _box.drop(); @@ -57601,6 +58186,13 @@ class _OptionRsvpStatusStatusStrReturn extends ffi.Struct { external int arg3; } +class _OptionComposeDraftDraftReturn extends ffi.Struct { + @ffi.Uint8() + external int arg0; + @ffi.IntPtr() + external int arg1; +} + class _UserProfileGetDisplayNameReturn extends ffi.Struct { @ffi.Uint8() external int arg0; @@ -57741,6 +58333,46 @@ class _MxcUriToStringReturn extends ffi.Struct { external int arg2; } +class _ComposeDraftPlainTextReturn extends ffi.Struct { + @ffi.IntPtr() + external int arg0; + @ffi.UintPtr() + external int arg1; + @ffi.UintPtr() + external int arg2; +} + +class _ComposeDraftHtmlTextReturn extends ffi.Struct { + @ffi.Uint8() + external int arg0; + @ffi.IntPtr() + external int arg1; + @ffi.UintPtr() + external int arg2; + @ffi.UintPtr() + external int arg3; +} + +class _ComposeDraftEventIdReturn extends ffi.Struct { + @ffi.Uint8() + external int arg0; + @ffi.IntPtr() + external int arg1; + @ffi.UintPtr() + external int arg2; + @ffi.UintPtr() + external int arg3; +} + +class _ComposeDraftDraftTypeReturn extends ffi.Struct { + @ffi.IntPtr() + external int arg0; + @ffi.UintPtr() + external int arg1; + @ffi.UintPtr() + external int arg2; +} + class _RoomIdToStringReturn extends ffi.Struct { @ffi.IntPtr() external int arg0; @@ -61482,6 +62114,51 @@ class _ConvoRedactContentFuturePollReturn extends ffi.Struct { external int arg5; } +class _ConvoMsgDraftFuturePollReturn extends ffi.Struct { + @ffi.Uint8() + external int arg0; + @ffi.Uint8() + external int arg1; + @ffi.IntPtr() + external int arg2; + @ffi.UintPtr() + external int arg3; + @ffi.UintPtr() + external int arg4; + @ffi.IntPtr() + external int arg5; +} + +class _ConvoSaveMsgDraftFuturePollReturn extends ffi.Struct { + @ffi.Uint8() + external int arg0; + @ffi.Uint8() + external int arg1; + @ffi.IntPtr() + external int arg2; + @ffi.UintPtr() + external int arg3; + @ffi.UintPtr() + external int arg4; + @ffi.Uint8() + external int arg5; +} + +class _ConvoClearMsgDraftFuturePollReturn extends ffi.Struct { + @ffi.Uint8() + external int arg0; + @ffi.Uint8() + external int arg1; + @ffi.IntPtr() + external int arg2; + @ffi.UintPtr() + external int arg3; + @ffi.UintPtr() + external int arg4; + @ffi.Uint8() + external int arg5; +} + class _CommentDraftSendFuturePollReturn extends ffi.Struct { @ffi.Uint8() external int arg0;