diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c92536427..7f4e66ec9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,13 +1,6 @@ *Thank you so much for your contribution to FluffyChat ❤️❤️❤️* -Please make sure that your Pull Request meet the following **acceptance criteria**: - -- [ ] Code formatting and import sorting has been done with `dart format lib/ test/` and `dart run import_sorter:main --no-comments` -- [ ] The commit message uses the format of [Conventional Commits](https://www.conventionalcommits.org) -- [ ] The commit message describes what has been changed, why it has been changed and how it has been changed -- [ ] Every new feature or change of the design/GUI is linked to an approved design proposal in an issue -- [ ] Every new feature in the app or the build system has a strategy how this will be tested and maintained from now on for every release, e.g. a volunteer who takes over maintainership - +- [ ] I have read and understood the [contributing guidelines](https://github.com/krille-chan/fluffychat/blob/main/CONTRIBUTING.md). ### Pull Request has been tested on: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..f54a0fb85 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing to FluffyChat +Contributions are always welcome. Yet we might lack manpower to review all of them in time. + +To improve the process please make sure that you read the following guidelines carefully: + +## Contributing Guidelines + +1. Always create a Pull Request for any changes. +2. Whenever possible please make sure that your Pull Request only contains **one** commit. Cases where multiple commits make sense are very rare. +3. Do not add merge commits. Use rebases. +4. Every Pull Request should change only one thing. For bigger changes it is often better to split them up in multiple Pull Requests. +5. [Sign your commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). +6. Format the commit message as [Conventional Commits](https://www.conventionalcommits.org). +7. Format (`flutter format lib`) and sort impots (`dart run import_sorter:main --no-comments`) in all code files. +8. For bigger or complex changes (more than a couple of code lines) write an issue or refer to an existing issue and ask for approval from the maintainers (@krille-chan) **before** starting to implement it. This way you reduce the risk that your Pull Request get's declined. +9. Prefer simple and easy to maintain solutions over complexity and fancy ones. \ No newline at end of file diff --git a/lib/config/themes.dart b/lib/config/themes.dart index cd143d031..762f1f073 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -128,6 +128,11 @@ abstract class FluffyThemes { ), ), ), + progressIndicatorTheme: ProgressIndicatorThemeData( + strokeCap: StrokeCap.round, + color: colorScheme.primary, + refreshBackgroundColor: colorScheme.primaryContainer, + ), snackBarTheme: isColumnMode ? const SnackBarThemeData( showCloseIcon: true, diff --git a/lib/l10n/intl_nb.arb b/lib/l10n/intl_nb.arb index 78dcddb62..dae4831fb 100644 --- a/lib/l10n/intl_nb.arb +++ b/lib/l10n/intl_nb.arb @@ -2670,6 +2670,45 @@ }, "noMessagesYet": "Ingen meldinger enda", "@noMessagesYet": {}, + "notificationRuleMasterDescription": "Overstyrer alle andre regler og deaktiverer alle varsler.", + "@notificationRuleMasterDescription": {}, + "notificationRuleSuppressNotices": "Undertrykk automatiserte meldinger", + "@notificationRuleSuppressNotices": {}, + "startedKeyVerification": "{sender} startet nøkkelverifisering", + "@startedKeyVerification": { + "type": "String", + "placeholders": { + "sender": { + "type": "String" + } + } + }, + "transparent": "Gjennomsiktig", + "@transparent": {}, + "stickers": "Stickers", + "@stickers": {}, + "commandHint_ignore": "Ignorer den oppgitte matrix IDen", + "@commandHint_ignore": {}, + "commandHint_unignore": "Opphev ignorering av den gitte matrix IDen", + "@commandHint_unignore": {}, + "unreadChatsInApp": "{appname}: {unread} uleste chatter", + "@unreadChatsInApp": { + "type": "String", + "placeholders": { + "appname": { + "type": "String" + }, + "unread": { + "type": "String" + } + } + }, + "changeTheVisibilityOfChatHistory": "Endre synligheten til chatloggen", + "@changeTheVisibilityOfChatHistory": {}, + "changeTheCanonicalRoomAlias": "Endre hovedadressen til den offentlige chatten", + "@changeTheCanonicalRoomAlias": {}, + "sendRoomNotifications": "Send en @room varsling", + "@sendRoomNotifications": {}, "alwaysUse24HourFormat": "falsk", "commandHint_googly": "Send noen googly-øyne", "commandHint_cuddle": "Send en kose", @@ -2813,16 +2852,7 @@ "completedKeyVerification": "{sender} fullførte nøkkelverifisering", "isReadyForKeyVerification": "{sender} er klar for nøkkelverifisering", "requestedKeyVerification": "{sender} ba om nøkkelverifisering", - "startedKeyVerification": "{sender} startet nøkkelverifisering", - "transparent": "Gjennomsiktig", - "stickers": "Stickers", - "commandHint_ignore": "Ignorer den gitte matrix-ID-en", - "commandHint_unignore": "Fjern ignorering av den gitte matrix-ID-en", - "unreadChatsInApp": "{appname}: {unread} uleste chatter", "knockRestricted": "Knock-begrenset", - "changeTheVisibilityOfChatHistory": "Endre synligheten av chatthistorikken", - "changeTheCanonicalRoomAlias": "Endre den viktigste offentlige chat-adressen", - "sendRoomNotifications": "Send @rom varsler", "chatPermissionsDescription": "Definer hvilket maktnivå som er nødvendig for visse handlinger i denne chatten. Maktnivåene 0, 50 og 100 representerer vanligvis brukere, moderatorer og administratorer, men alle grader er mulige.", "whatIsAHomeserver": "Hva er en hjemserver?", "homeserverDescription": "Alle dataene dine lagres på hjemserveren, akkurat som en e-postleverandør. Du kan velge hvilken hjemserver du vil bruke, samtidig som du kan kommunisere med alle. Lær mer på https://matrix.org.", @@ -2843,8 +2873,6 @@ "userSpecificNotificationSettings": "Brukerspesifikke varslingsinnstillinger", "otherNotificationSettings": "Andre varslingsinnstillinger", "notificationRuleContainsUserNameDescription": "Varsler brukeren når en melding inneholder deres brukernavn.", - "notificationRuleMasterDescription": "Overstyrer alle andre regler og deaktiverer alle varsler.", - "notificationRuleSuppressNotices": "Undertrykk automatiserte meldinger", "notificationRuleMemberEvent": "Medlemsarrangement", "notificationRuleMemberEventDescription": "Undertrykker varsler for medlemsarrangementer.", "notificationRuleIsUserMention": "Brukerhenvisning", @@ -4683,57 +4711,10 @@ } } }, - "@startedKeyVerification": { - "type": "String", - "placeholders": { - "sender": { - "type": "String" - } - } - }, - "@transparent": { - "type": "String", - "placeholders": {} - }, - "@stickers": { - "type": "String", - "placeholders": {} - }, - "@commandHint_ignore": { - "type": "String", - "placeholders": {} - }, - "@commandHint_unignore": { - "type": "String", - "placeholders": {} - }, - "@unreadChatsInApp": { - "type": "String", - "placeholders": { - "appname": { - "type": "String" - }, - "unread": { - "type": "String" - } - } - }, "@knockRestricted": { "type": "String", "placeholders": {} }, - "@changeTheVisibilityOfChatHistory": { - "type": "String", - "placeholders": {} - }, - "@changeTheCanonicalRoomAlias": { - "type": "String", - "placeholders": {} - }, - "@sendRoomNotifications": { - "type": "String", - "placeholders": {} - }, "@chatPermissionsDescription": { "type": "String", "placeholders": {} @@ -4821,14 +4802,6 @@ "type": "String", "placeholders": {} }, - "@notificationRuleMasterDescription": { - "type": "String", - "placeholders": {} - }, - "@notificationRuleSuppressNotices": { - "type": "String", - "placeholders": {} - }, "@notificationRuleMemberEvent": { "type": "String", "placeholders": {} diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index b10397e23..1132d5725 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -65,6 +65,7 @@ class AudioPlayerState extends State { static const double buttonSize = 36; AudioPlayerStatus status = AudioPlayerStatus.notDownloaded; + double? _downloadProgress; late final MatrixState matrix; List? _waveform; @@ -79,8 +80,10 @@ class AudioPlayerState extends State { @override void dispose() { super.dispose(); + // #Pangea // final audioPlayer = matrix.voiceMessageEventId.value != widget.event.eventId final audioPlayer = matrix.voiceMessageEventId.value != widget.eventId + // Pangea# ? null : matrix.audioPlayer; if (audioPlayer != null) { @@ -111,16 +114,10 @@ class AudioPlayerState extends State { // stream: audioPlayer.positionStream.asBroadcastStream(), // builder: (context, _) => GestureDetector( // onTap: () => FluffyChatApp.router.go( - // // #Pangea - // // '/rooms/${widget.event.room.id}?event=${widget.event.eventId}', - // '/rooms/${widget.roomId}?event=${widget.eventId}', - // // Pangea# + // '/rooms/${widget.event.room.id}?event=${widget.event.eventId}', // ), // child: Text( - // // #Pangea - // // '🎙️ ${audioPlayer.position.minuteSecondString} / ${audioPlayer.duration?.minuteSecondString} - ${widget.event.senderFromMemoryOrFallback.calcDisplayname()}', - // '🎙️ ${audioPlayer.position.minuteSecondString} / ${audioPlayer.duration?.minuteSecondString} - ${widget.event?.senderFromMemoryOrFallback.calcDisplayname() ?? widget.senderId}', - // // Pangea# + // '🎙️ ${audioPlayer.position.minuteSecondString} / ${audioPlayer.duration?.minuteSecondString} - ${widget.event.senderFromMemoryOrFallback.calcDisplayname()}', // maxLines: 1, // overflow: TextOverflow.ellipsis, // ), @@ -151,8 +148,8 @@ class AudioPlayerState extends State { audioPlayer.pause(); audioPlayer.dispose(); matrix.voiceMessageEventId.value = matrix.audioPlayer = null; - matrix.voiceMessageEventId.removeListener(_onPlayerChange); // #Pangea + matrix.voiceMessageEventId.removeListener(_onPlayerChange); _onAudioStateChanged?.cancel(); // Pangea# } @@ -162,7 +159,6 @@ class AudioPlayerState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(matrix.context).clearMaterialBanners(); }); - final currentPlayer = // #Pangea // matrix.voiceMessageEventId.value != widget.event.eventId @@ -170,7 +166,6 @@ class AudioPlayerState extends State { // Pangea# ? null : matrix.audioPlayer; - if (currentPlayer != null) { // #Pangea currentPlayer.setSpeed(playbackSpeed); @@ -206,9 +201,25 @@ class AudioPlayerState extends State { setState(() => status = AudioPlayerStatus.downloading); try { // #Pangea - // matrixFile = await widget.event.downloadAndDecryptAttachment(); - matrixFile = await widget.event?.downloadAndDecryptAttachment(); - // Pangea# + // final fileSize = widget.event.content + final fileSize = widget.event?.content + // Pangea# + .tryGetMap('info') + ?.tryGet('size'); + // #Pangea + // matrixFile = await widget.event.downloadAndDecryptAttachment( + matrixFile = await widget.event?.downloadAndDecryptAttachment( + // Pangea# + onDownloadProgress: fileSize != null && fileSize > 0 + ? (progress) { + final progressPercentage = progress / fileSize; + setState(() { + _downloadProgress = + progressPercentage < 1 ? progressPercentage : null; + }); + } + : null, + ); // #Pangea // if (!kIsWeb) { @@ -266,7 +277,7 @@ class AudioPlayerState extends State { final audioPlayer = matrix.audioPlayer = AudioPlayer(); - // #Pangea +// #Pangea audioPlayer.setSpeed(playbackSpeed); _onAudioStateChanged?.cancel(); _onAudioStateChanged = @@ -326,8 +337,8 @@ class AudioPlayerState extends State { default: setState(() => playbackSpeed = 1.0); } - if (audioPlayer == null) return; // Pangea# + if (audioPlayer == null) return; switch (audioPlayer.speed) { // #Pangea // case 1.0: @@ -370,7 +381,7 @@ class AudioPlayerState extends State { widget.event?.content .tryGetMap('org.matrix.msc1767.audio') ?.tryGetList('waveform'); - // final eventWaveForm = widget.event?.content + // final eventWaveForm = widget.event.content // .tryGetMap('org.matrix.msc1767.audio') // ?.tryGetList('waveform'); // Pangea# @@ -409,8 +420,10 @@ class AudioPlayerState extends State { void initState() { super.initState(); matrix = Matrix.of(context); + // #Pangea WidgetsBinding.instance.addPostFrameCallback((_) => _onPlayerChange()); matrix.voiceMessageEventId.addListener(_onPlayerChange); + // Pangea# _waveform = _getWaveform(); // #Pangea @@ -507,6 +520,7 @@ class AudioPlayerState extends State { ? CircularProgressIndicator( strokeWidth: 2, color: widget.color, + value: _downloadProgress, ) : InkWell( borderRadius: BorderRadius.circular(64), @@ -576,8 +590,8 @@ class AudioPlayerState extends State { // #Pangea // thumbColor: widget.event.senderId == // widget.event.room.client.userID - // ? theme.colorScheme.onPrimary - // : theme.colorScheme.primary, + // ? theme.colorScheme.onPrimary + // : theme.colorScheme.primary, thumbColor: widget.senderId == Matrix.of(context).client.userID ? widget.color @@ -673,8 +687,9 @@ class AudioPlayerState extends State { // borderRadius: // BorderRadius.circular(AppConfig.borderRadius), // child: InkWell( - // borderRadius: - // BorderRadius.circular(AppConfig.borderRadius), + // borderRadius: BorderRadius.circular( + // AppConfig.borderRadius, + // ), // onTap: _toggleSpeed, // child: SizedBox( // width: 32, diff --git a/lib/pages/image_viewer/video_player.dart b/lib/pages/image_viewer/video_player.dart index 99e4c09f3..beb2e9c5b 100644 --- a/lib/pages/image_viewer/video_player.dart +++ b/lib/pages/image_viewer/video_player.dart @@ -41,7 +41,9 @@ class EventVideoPlayerState extends State { final mimetype = infoMap?.tryGet('mimetype'); return PlatformInfos.isAndroid ? mimetype != "video/quicktime" : true; } + // Pangea# + double? _downloadProgress; // The video_player package only doesn't support Windows and Linux. // #Pangea @@ -60,7 +62,20 @@ class EventVideoPlayerState extends State { // #Pangea setState(() => _error = null); // Pangea# - final videoFile = await widget.event.downloadAndDecryptAttachment(); + final fileSize = widget.event.content + .tryGetMap('info') + ?.tryGet('size'); + final videoFile = await widget.event.downloadAndDecryptAttachment( + onDownloadProgress: fileSize == null + ? null + : (progress) { + final progressPercentage = progress / fileSize; + setState(() { + _downloadProgress = + progressPercentage < 1 ? progressPercentage : null; + }); + }, + ); // Dispose the controllers if we already have them. _disposeControllers(); @@ -189,7 +204,11 @@ class EventVideoPlayerState extends State { ), ), // #Pangea - // const Center(child: CircularProgressIndicator.adaptive()), + // Center( + // child: CircularProgressIndicator.adaptive( + // value: _downloadProgress, + // ), + // ), _error != null ? Center( child: Column( @@ -220,7 +239,11 @@ class EventVideoPlayerState extends State { ], ), ) - : const Center(child: CircularProgressIndicator.adaptive()), + : Center( + child: CircularProgressIndicator.adaptive( + value: _downloadProgress, + ), + ), // Pangea# ], ); diff --git a/lib/utils/matrix_sdk_extensions/event_extension.dart b/lib/utils/matrix_sdk_extensions/event_extension.dart index b28ea258e..046df2598 100644 --- a/lib/utils/matrix_sdk_extensions/event_extension.dart +++ b/lib/utils/matrix_sdk_extensions/event_extension.dart @@ -14,7 +14,15 @@ extension LocalizedBody on Event { Future> _getFile(BuildContext context) => showFutureLoadingDialog( context: context, - future: downloadAndDecryptAttachment, + futureWithProgress: (onProgress) { + final fileSize = + infoMap['size'] is int ? infoMap['size'] as int : null; + return downloadAndDecryptAttachment( + onDownloadProgress: fileSize == null + ? null + : (bytes) => onProgress(bytes / fileSize), + ); + }, ); void saveFile(BuildContext context) async { diff --git a/pubspec.yaml b/pubspec.yaml index ff465e306..d189dbafa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -187,6 +187,10 @@ msix_config: sign_msix: false install_certificate: false +# Guidelines for adding a dependency override: +# 1. Don't do it if you can avoid it or fix it upstream in a manageable time +# 2. Always link an (upstream?) issue +# 3. Explain how and when this can be removed (overrides must be temporarily) dependency_overrides: fcm_shared_isolate: path: pangea_packages/fcm_shared_isolate