From b2262137ac5b3c1d7e0a0ecdb75bd1036c397ee2 Mon Sep 17 00:00:00 2001 From: Roscoe Rubin-Rottenberg Date: Wed, 13 May 2026 21:15:29 -0400 Subject: [PATCH] feat: plyr sounds --- ios/Podfile.lock | 10 +- .../xcshareddata/swiftpm/Package.resolved | 34 +-- .../xcshareddata/swiftpm/Package.resolved | 34 +-- .../data/adapters/bsky/feed_adapter.dart | 28 +++ .../atproto/data/models/record_models.dart | 39 ++++ .../atproto/data/models/sound_models.dart | 23 +- .../models/sound_audio_track.dart | 118 +++++++++- .../services/audio_helper_service.dart | 20 +- .../services/audio_waveform_extractor.dart | 20 +- .../ui/video_editor_grounded_page.dart | 9 +- .../posting/ui/pages/recording_page.dart | 6 +- pubspec.lock | 216 ++++++++++-------- pubspec.yaml | 38 +-- .../data/models/sound_models_test.dart | 114 +++++++++ .../services/audio_helper_service_test.dart | 37 +++ .../Flutter/GeneratedPluginRegistrant.swift | 2 +- widgetbook/pubspec.yaml | 6 +- 17 files changed, 549 insertions(+), 205 deletions(-) create mode 100644 test/src/core/network/atproto/data/models/sound_models_test.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6328b6d8..9919f138 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -9,17 +9,17 @@ PODS: - PINCache - Cache (6.0.0) - Flutter (1.0.0) - - fvp (0.35.2): + - fvp (0.36.1): - Flutter - FlutterMacOS - - mdk (~> 0.35.1) + - mdk (~> 0.36.0) - GCDWebServer (3.5.4): - GCDWebServer/Core (= 3.5.4) - GCDWebServer/Core (3.5.4) - HLSCachingReverseProxyServer (0.1.0): - GCDWebServer (~> 3.5) - PINCache (>= 3.0.1-beta.3) - - mdk (0.35.1) + - mdk (0.36.0) - PINCache (3.0.4): - PINCache/Arc-exception-safe (= 3.0.4) - PINCache/Core (= 3.0.4) @@ -69,10 +69,10 @@ SPEC CHECKSUMS: better_player_plus: 3d40145c650bb83dde08f0d593b21a144196769f Cache: 4ca7e00363fca5455f26534e5607634c820ffc2d Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - fvp: 4fe77c2b30122233a57f425dafd219ed35565809 + fvp: f8b3b61cf8c696ecaa5608ae0d282ba710e65ae9 GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4 HLSCachingReverseProxyServer: 59935e1e0244ad7f3375d75b5ef46e8eb26ab181 - mdk: 59bbe9e2ac2a052455ab1b076c245680d66cf6c0 + mdk: 3bfb53e0bcc9643180eaa864360051f281e4c17b PINCache: d9a87a0ff397acffe9e2f0db972ac14680441158 PINOperation: fb563bcc9c32c26d6c78aaff967d405aa2ee74a7 pro_image_editor: 3dedac450f82a389877286fa9eb08852cefb04ea diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b18eecc5..400ecfce 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,17 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "85560b48b0ff099ad83fe53d67df3c67fbc2b7a6", - "version" : "12.10.0" - } - }, - { - "identity" : "flutterfire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/flutterfire", - "state" : { - "revision" : "a10a4148e769fadb01b1ff8d6bb76e9137f35b81", - "version" : "4.6.0-firebase-core-swift" + "revision" : "d10045cace0b4c335c4efa8f7df7e9a9fc5a7c60", + "version" : "12.13.0" } }, { @@ -41,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk", "state" : { - "revision" : "a5cd95c80e8efdd02155c6cea1cecf743bb683a5", - "version" : "3.3.0" + "revision" : "19dffda9a9caf8d86570ff846535902d8509d7bf", + "version" : "3.5.0" } }, { @@ -50,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "68ba955e540dcff5e0805970ef4b1fd0150be100", - "version" : "12.10.0" + "revision" : "c2c76bebcfbb90d90ea10599f934f9af160e1604", + "version" : "12.13.0" } }, { @@ -117,22 +108,13 @@ "version" : "2.30910.0" } }, - { - "identity" : "plcrashreporter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/microsoft/plcrashreporter.git", - "state" : { - "revision" : "0254f941c646b1ed17b243654723d0f071e990d0", - "version" : "1.12.2" - } - }, { "identity" : "posthog-ios", "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "19d0df316c68ff2e806eaa73563e7fb79788f978", - "version" : "3.50.0" + "revision" : "39e527bd41124ab907bfa31e4b3384b124c36e07", + "version" : "3.58.1" } }, { diff --git a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index b18eecc5..400ecfce 100644 --- a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,17 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "85560b48b0ff099ad83fe53d67df3c67fbc2b7a6", - "version" : "12.10.0" - } - }, - { - "identity" : "flutterfire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/flutterfire", - "state" : { - "revision" : "a10a4148e769fadb01b1ff8d6bb76e9137f35b81", - "version" : "4.6.0-firebase-core-swift" + "revision" : "d10045cace0b4c335c4efa8f7df7e9a9fc5a7c60", + "version" : "12.13.0" } }, { @@ -41,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk", "state" : { - "revision" : "a5cd95c80e8efdd02155c6cea1cecf743bb683a5", - "version" : "3.3.0" + "revision" : "19dffda9a9caf8d86570ff846535902d8509d7bf", + "version" : "3.5.0" } }, { @@ -50,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "68ba955e540dcff5e0805970ef4b1fd0150be100", - "version" : "12.10.0" + "revision" : "c2c76bebcfbb90d90ea10599f934f9af160e1604", + "version" : "12.13.0" } }, { @@ -117,22 +108,13 @@ "version" : "2.30910.0" } }, - { - "identity" : "plcrashreporter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/microsoft/plcrashreporter.git", - "state" : { - "revision" : "0254f941c646b1ed17b243654723d0f071e990d0", - "version" : "1.12.2" - } - }, { "identity" : "posthog-ios", "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "19d0df316c68ff2e806eaa73563e7fb79788f978", - "version" : "3.50.0" + "revision" : "39e527bd41124ab907bfa31e4b3384b124c36e07", + "version" : "3.58.1" } }, { diff --git a/lib/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart b/lib/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart index b4b06b7a..6eb3e3b7 100644 --- a/lib/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart +++ b/lib/src/core/network/atproto/data/adapters/bsky/feed_adapter.dart @@ -880,6 +880,34 @@ extension BskyRecordAdapter on Record { details: details, labels: labels, ), + plyrTrack: + ( + title, + artist, + fileType, + createdAt, + audioUrl, + album, + duration, + features, + imageUrl, + supportGate, + description, + audioBlob, + ) => PlyrTrackRecord( + title: title, + artist: artist, + fileType: fileType, + createdAt: createdAt, + audioUrl: audioUrl, + album: album, + duration: duration, + features: features, + imageUrl: imageUrl, + supportGate: supportGate, + description: description, + audioBlob: audioBlob, + ), bskyPost: (createdAt, text, facets, reply, langs, tags, selfLabels, embed) => BskyPostRecord( diff --git a/lib/src/core/network/atproto/data/models/record_models.dart b/lib/src/core/network/atproto/data/models/record_models.dart index 9d08d273..0ce429c7 100644 --- a/lib/src/core/network/atproto/data/models/record_models.dart +++ b/lib/src/core/network/atproto/data/models/record_models.dart @@ -72,6 +72,23 @@ abstract class Record with _$Record { List? labels, }) = AudioRecord; + @JsonSerializable(explicitToJson: true) + @FreezedUnionValue('fm.plyr.track') + const factory Record.plyrTrack({ + required String title, + required String artist, + required String fileType, + required DateTime createdAt, + String? audioUrl, + String? album, + int? duration, + List? features, + String? imageUrl, + PlyrSupportGate? supportGate, + String? description, + Blob? audioBlob, + }) = PlyrTrackRecord; + @JsonSerializable(explicitToJson: true) @FreezedUnionValue('app.bsky.feed.post') const factory Record.bskyPost({ @@ -105,6 +122,28 @@ abstract class Record with _$Record { } } +@freezed +abstract class PlyrSupportGate with _$PlyrSupportGate { + @JsonSerializable(explicitToJson: true) + const factory PlyrSupportGate({required String type}) = _PlyrSupportGate; + + factory PlyrSupportGate.fromJson(Map json) => + _$PlyrSupportGateFromJson(json); +} + +@freezed +abstract class PlyrFeaturedArtist with _$PlyrFeaturedArtist { + @JsonSerializable(explicitToJson: true) + const factory PlyrFeaturedArtist({ + required String did, + String? handle, + String? displayName, + }) = _PlyrFeaturedArtist; + + factory PlyrFeaturedArtist.fromJson(Map json) => + _$PlyrFeaturedArtistFromJson(json); +} + /// Skeleton of a ReplyRef. Needs to be hydrated. @freezed abstract class RecordReplyRef with _$RecordReplyRef { diff --git a/lib/src/core/network/atproto/data/models/sound_models.dart b/lib/src/core/network/atproto/data/models/sound_models.dart index d11b75a0..4f1f80fc 100644 --- a/lib/src/core/network/atproto/data/models/sound_models.dart +++ b/lib/src/core/network/atproto/data/models/sound_models.dart @@ -7,7 +7,7 @@ import 'package:spark/src/core/utils/uri_converter.dart'; part 'sound_models.freezed.dart'; part 'sound_models.g.dart'; -AudioRecord _audioRecordFromJson(dynamic json) { +Record _soundRecordFromJson(dynamic json) { if (json is! Map) { throw Exception( 'Expected Map but got ${json.runtimeType}', @@ -16,16 +16,17 @@ AudioRecord _audioRecordFromJson(dynamic json) { final jsonWithType = json.containsKey(r'$type') ? json - : {...json, r'$type': 'so.sprk.sound.audio'}; - - final record = Record.fromJson(jsonWithType); - if (record is AudioRecord) { - return record; - } - throw Exception('Expected AudioRecord but got ${record.runtimeType}'); + : { + ...json, + r'$type': json.containsKey('fileType') + ? 'fm.plyr.track' + : 'so.sprk.sound.audio', + }; + + return Record.fromJson(jsonWithType); } -Map _audioRecordToJson(AudioRecord record) => record.toJson(); +Map _soundRecordToJson(Record record) => record.toJson(); @freezed abstract class AudioDetails with _$AudioDetails { @@ -43,8 +44,8 @@ abstract class AudioView with _$AudioView { @AtUriConverter() required AtUri uri, required String cid, required ProfileViewBasic author, - @JsonKey(fromJson: _audioRecordFromJson, toJson: _audioRecordToJson) - required AudioRecord record, + @JsonKey(fromJson: _soundRecordFromJson, toJson: _soundRecordToJson) + required Record record, required String title, @UriConverter() required Uri coverArt, required DateTime indexedAt, diff --git a/lib/src/core/pro_video_editor/models/sound_audio_track.dart b/lib/src/core/pro_video_editor/models/sound_audio_track.dart index 88d9fb3d..1e02775b 100644 --- a/lib/src/core/pro_video_editor/models/sound_audio_track.dart +++ b/lib/src/core/pro_video_editor/models/sound_audio_track.dart @@ -6,6 +6,7 @@ import 'package:pro_image_editor/pro_image_editor.dart'; import 'package:spark/src/core/network/atproto/data/models/models.dart'; const _fallbackAudioDuration = Duration(seconds: 9); +const _fallbackAudioFileExtension = 'mp3'; AudioTrack? audioViewToAudioTrack(AudioView audio) { final audioUrl = audio.audio?.toString(); @@ -16,6 +17,8 @@ AudioTrack? audioViewToAudioTrack(AudioView audio) { audio.uri.toString(), audio.cid, authorAvatar: audio.author.avatar?.toString(), + audioFileExtension: audioFileExtension(audio), + audioMimeType: audioMimeType(audio), ), title: audio.title, subtitle: audio.author.handle, @@ -29,8 +32,20 @@ List audioViewsToAudioTracks(Iterable audios) { return audios.map(audioViewToAudioTrack).nonNulls.toList(); } -String encodeSoundTrackId(String uri, String cid, {String? authorAvatar}) { - return jsonEncode({'uri': uri, 'cid': cid, 'authorAvatar': authorAvatar}); +String encodeSoundTrackId( + String uri, + String cid, { + String? authorAvatar, + String? audioFileExtension, + String? audioMimeType, +}) { + return jsonEncode({ + 'uri': uri, + 'cid': cid, + 'authorAvatar': authorAvatar, + 'audioFileExtension': audioFileExtension, + 'audioMimeType': audioMimeType, + }); } RepoStrongRef? decodeSoundTrackStrongRef(String? encoded) { @@ -55,3 +70,102 @@ String? decodeSoundTrackAuthorAvatar(String? encoded) { return null; } } + +String decodeSoundTrackAudioFileExtension(String? encoded) { + if (encoded == null) return _fallbackAudioFileExtension; + try { + final map = jsonDecode(encoded) as Map; + return _normalizeAudioFileExtension(map['audioFileExtension'] as String?); + } catch (_) { + return _fallbackAudioFileExtension; + } +} + +String decodeSoundTrackAudioMimeType(String? encoded) { + if (encoded == null) return 'audio/mpeg'; + try { + final map = jsonDecode(encoded) as Map; + return _normalizeAudioMimeType( + map['audioMimeType'] as String?, + extension: map['audioFileExtension'] as String?, + ); + } catch (_) { + return 'audio/mpeg'; + } +} + +String audioFileExtension(AudioView audio) { + final record = audio.record; + if (record is PlyrTrackRecord) { + return _normalizeAudioFileExtension( + record.fileType, + mimeType: record.audioBlob?.mimeType, + ); + } + if (record is AudioRecord) { + return _normalizeAudioFileExtension(null, mimeType: record.sound.mimeType); + } + return _fallbackAudioFileExtension; +} + +String audioMimeType(AudioView audio) { + final record = audio.record; + if (record is PlyrTrackRecord) { + return _normalizeAudioMimeType( + record.audioBlob?.mimeType, + extension: record.fileType, + ); + } + if (record is AudioRecord) { + return _normalizeAudioMimeType(record.sound.mimeType); + } + return 'audio/mpeg'; +} + +String _normalizeAudioFileExtension(String? value, {String? mimeType}) { + final extension = value?.trim().toLowerCase().replaceFirst('.', ''); + if (extension != null && extension.isNotEmpty) { + final normalized = switch (extension) { + 'mpeg' => 'mp3', + 'mp4' => 'm4a', + 'x-m4a' => 'm4a', + 'x-wav' => 'wav', + _ => extension.replaceAll(RegExp(r'[^a-z0-9]'), ''), + }; + if (normalized.isNotEmpty) return normalized; + } + + final subtype = mimeType?.split('/').last.trim().toLowerCase(); + return switch (subtype) { + 'mpeg' => 'mp3', + 'mp3' => 'mp3', + 'mp4' => 'm4a', + 'm4a' => 'm4a', + 'x-m4a' => 'm4a', + 'aac' => 'aac', + 'wav' => 'wav', + 'x-wav' => 'wav', + 'flac' => 'flac', + 'ogg' => 'ogg', + _ => _fallbackAudioFileExtension, + }; +} + +String _normalizeAudioMimeType(String? mimeType, {String? extension}) { + final normalizedMime = mimeType?.trim().toLowerCase(); + if (normalizedMime != null && normalizedMime.startsWith('audio/')) { + return switch (normalizedMime) { + 'audio/x-m4a' => 'audio/mp4', + _ => normalizedMime, + }; + } + + return switch (_normalizeAudioFileExtension(extension)) { + 'm4a' => 'audio/mp4', + 'aac' => 'audio/aac', + 'wav' => 'audio/wav', + 'flac' => 'audio/flac', + 'ogg' => 'audio/ogg', + _ => 'audio/mpeg', + }; +} diff --git a/lib/src/core/pro_video_editor/services/audio_helper_service.dart b/lib/src/core/pro_video_editor/services/audio_helper_service.dart index ac252694..32591a9f 100644 --- a/lib/src/core/pro_video_editor/services/audio_helper_service.dart +++ b/lib/src/core/pro_video_editor/services/audio_helper_service.dart @@ -2,6 +2,7 @@ import 'package:audioplayers/audioplayers.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pro_image_editor/pro_image_editor.dart'; import 'package:pro_video_editor/pro_video_editor.dart'; +import 'package:spark/src/core/pro_video_editor/models/sound_audio_track.dart'; import 'package:video_player/video_player.dart'; const _resumeSeekTolerance = Duration(milliseconds: 250); @@ -42,6 +43,11 @@ bool shouldSeekCustomAudioOnResume({ return delta.abs() > _resumeSeekTolerance; } +String customAudioTempFilename(AudioTrack track) { + final extension = decodeSoundTrackAudioFileExtension(track.id); + return 'temp-audio.$extension'; +} + /// A helper service that manages audio playback alongside video playback. class AudioHelperService { /// Creates an instance of [AudioHelperService] for the @@ -95,7 +101,10 @@ class AudioHelperService { return DeviceFileSource(audio.file!.path); } if (audio.hasNetworkUrl) { - return UrlSource(audio.networkUrl!); + return UrlSource( + audio.networkUrl!, + mimeType: decodeSoundTrackAudioMimeType(track.id), + ); } return BytesSource(audio.bytes!); } @@ -235,13 +244,16 @@ class AudioHelperService { Future safeCustomAudioPath(AudioTrack? track) async { final directory = await getTemporaryDirectory(); - final audio = track?.audio; - if (audio == null) return null; + final effectiveTrack = track; + if (effectiveTrack == null) return null; + + final audio = effectiveTrack.audio; if (audio.hasFile) { return audio.file!.path; } else { - final filePath = '${directory.path}/temp-audio.mp3'; + final filePath = + '${directory.path}/${customAudioTempFilename(effectiveTrack)}'; if (audio.hasNetworkUrl) { return (await fetchVideoToFile(audio.networkUrl!, filePath)).path; diff --git a/lib/src/core/pro_video_editor/services/audio_waveform_extractor.dart b/lib/src/core/pro_video_editor/services/audio_waveform_extractor.dart index 21619d57..9048696f 100644 --- a/lib/src/core/pro_video_editor/services/audio_waveform_extractor.dart +++ b/lib/src/core/pro_video_editor/services/audio_waveform_extractor.dart @@ -24,8 +24,14 @@ class AudioWaveformExtractor { } /// Extracts waveform samples from an audio source. - Future> extractFromAudio(EditorAudio audio) async { - final path = await _getAudioPath(audio); + Future> extractFromAudio( + EditorAudio audio, { + String preferredExtension = 'mp3', + }) async { + final path = await _getAudioPath( + audio, + preferredExtension: preferredExtension, + ); if (path == null) return []; return extractFromPath(path); } @@ -67,15 +73,19 @@ class AudioWaveformExtractor { return null; } - Future _getAudioPath(EditorAudio audio) async { + Future _getAudioPath( + EditorAudio audio, { + required String preferredExtension, + }) async { if (audio.hasFile) return audio.file!.path; + final filename = 'temp_audio.$preferredExtension'; if (audio.hasNetworkUrl) { - return _downloadToTemp(audio.networkUrl!, 'temp_audio.mp3'); + return _downloadToTemp(audio.networkUrl!, filename); } if (audio.hasBytes) { - return _writeBytesToTemp(audio.bytes!, 'temp_audio.mp3'); + return _writeBytesToTemp(audio.bytes!, filename); } return null; diff --git a/lib/src/core/pro_video_editor/ui/video_editor_grounded_page.dart b/lib/src/core/pro_video_editor/ui/video_editor_grounded_page.dart index 3469b7dd..9a1df027 100644 --- a/lib/src/core/pro_video_editor/ui/video_editor_grounded_page.dart +++ b/lib/src/core/pro_video_editor/ui/video_editor_grounded_page.dart @@ -308,7 +308,10 @@ class _VideoEditorGroundedPageState extends State /// Extracts waveform data from a custom audio track. Future _extractCustomAudioWaveform(AudioTrack track) async { - final waveformData = await _waveformExtractor.extractFromAudio(track.audio); + final waveformData = await _waveformExtractor.extractFromAudio( + track.audio, + preferredExtension: decodeSoundTrackAudioFileExtension(track.id), + ); final authorAvatar = _decodeAuthorAvatar(track.id); if (mounted) { _videoTimelineState.setCustomAudio( @@ -649,7 +652,9 @@ class _VideoEditorGroundedPageState extends State unawaited(_videoController.pause()); unawaited(_audioService.pause()); - final customAudioTrack = parameters.customAudioTrack; + final customAudioTrack = parameters.audioTracks.isEmpty + ? null + : parameters.audioTracks.first; _selectedSoundRef = decodeSoundTrackStrongRef(customAudioTrack?.id); final sourceVideoPath = await _video.safeFilePath(); final shouldCompressForUpload = await _shouldCompressForUpload( diff --git a/lib/src/features/posting/ui/pages/recording_page.dart b/lib/src/features/posting/ui/pages/recording_page.dart index 3252b31c..77429525 100644 --- a/lib/src/features/posting/ui/pages/recording_page.dart +++ b/lib/src/features/posting/ui/pages/recording_page.dart @@ -744,7 +744,11 @@ class _RecordingPageState extends ConsumerState { await _guideAudioPlayer.setReleaseMode(ReleaseMode.stop); if (!mounted || requestId != _guideAudioPrepareRequestId) return; if (_preparedGuideAudioUrl != audioUrl) { - await _guideAudioPlayer.setSourceUrl(audioUrl); + final sound = ref.read(recordingProvider).selectedSound; + await _guideAudioPlayer.setSourceUrl( + audioUrl, + mimeType: decodeSoundTrackAudioMimeType(sound?.id), + ); if (!mounted || requestId != _guideAudioPrepareRequestId) return; _preparedGuideAudioUrl = audioUrl; } diff --git a/pubspec.lock b/pubspec.lock index 2434a171..746f05d0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: f698de6eb8a0dd7a9a931bbfe13568e8b77e702eb2deb13dd83480c5373e7746 + sha256: cc2ad182e5b3929a1a87a8eaf35413d8bb4baa4efec124bab3e538096cdbff0d url: "https://pub.dev" source: hosted - version: "1.3.68" + version: "1.3.70" accessibility_tools: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: app_badge_plus - sha256: "81130fe6150ce2a1f12af4bb7d6f646496d284d5f87f2c121af8258a453bbb3f" + sha256: c0d9e318d0c516764945d0d1997d4e99e9b7d26523e8404a35c70c9fe5ec249b url: "https://pub.dev" source: hosted - version: "1.2.8" + version: "1.2.10" archive: dependency: transitive description: @@ -181,10 +181,10 @@ packages: dependency: "direct main" description: name: better_player_plus - sha256: "8670ccf8fead5572bfcd1568c008f72f3202593ef07fc90921e2cbcaac77852b" + sha256: "133e212e68ce830d0fa72da02efe597571a9915b526927fa3b427c70dea64613" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.2.1" boolean_selector: dependency: transitive description: @@ -205,10 +205,10 @@ packages: dependency: transitive description: name: build - sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "4.0.5" + version: "4.0.6" build_config: dependency: transitive description: @@ -229,10 +229,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e" + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.15.0" built_collection: dependency: transitive description: @@ -245,10 +245,10 @@ packages: dependency: transitive description: name: built_value - sha256: "0730c18c770d05636a8f945c32a4d7d81cb6e0f0148c8db4ad12e7748f7e49af" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.12.5" + version: "8.12.6" cached_network_image: dependency: "direct main" description: @@ -285,10 +285,10 @@ packages: dependency: transitive description: name: camera_android_camerax - sha256: "2c178975759aac0f0ef7ce1ec698b6e2acd792127ea7f38fa79a424fbebeae7f" + sha256: b5064cf25a2787d122d0bf12e77c7b1033a2b983d0730e3091f770ee376efde5 url: "https://pub.dev" source: hosted - version: "0.7.1+2" + version: "0.7.2" camera_avfoundation: dependency: transitive description: @@ -301,10 +301,10 @@ packages: dependency: transitive description: name: camera_platform_interface - sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63" + sha256: "7ac852d77699acee79f0d438b793feee26721841e50973576419ff5c6d95e9b7" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" camera_web: dependency: transitive description: @@ -481,6 +481,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" file: dependency: transitive description: @@ -525,50 +533,50 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: "2f988dab915efde3b3105268dbd69efce0e8570d767a218ccd914afd0c10c8cc" + sha256: "15bf6f85a6c8cf786344b2f163350428fc4bce105bc5332d42fb951d90b7664e" url: "https://pub.dev" source: hosted - version: "4.6.0" + version: "4.8.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce" + sha256: "7aafd426ad1926cfb1a7e9e012df5eff7d9a55200b31b04bc65ef45329ea5dee" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "7.0.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "1399ab1f0ac3b503d8a9be64a4c997fc066bbf33f701f42866e5569f26205ebe" + sha256: "73b01cba93a4f673596a7664f8265eb538f007a84a8f5880359db8ff76c3471b" url: "https://pub.dev" source: hosted - version: "3.5.1" + version: "3.6.1" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "8dc372085b1647f05e3ec1b8bc1dada87c0062f93b2a6976f620eb85edc44f97" + sha256: "4ebb881070b40038d61799cff914b18a44649e2e47ab444fc32055c3d6bf2df0" url: "https://pub.dev" source: hosted - version: "16.1.3" + version: "16.2.1" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "6ea10f7df747542b17679d5939213c09163aab9c301b2f9b858cb55f38efdb54" + sha256: "50eb5e125ec38b4b0b900c9f096ec94f2eeb4243d5668604f1dea24340bdb428" url: "https://pub.dev" source: hosted - version: "4.7.8" + version: "4.7.10" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "1f9798c8021ccf22b7e43e7fba81becd42252cb168228379fcabb7c2ef7dd638" + sha256: "1dfd311e764ac42be99a0aa8d9d5f7602774085af4f873658769f1438f8af918" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "4.1.6" fixnum: dependency: transitive description: @@ -610,10 +618,10 @@ packages: dependency: "direct main" description: name: flutter_dotenv - sha256: d4130c4a43e0b13fefc593bc3961f2cb46e30cb79e253d4a526b1b5d24ae1ce4 + sha256: d41da11fb497314fbf89811ec30af02d1d898b47980a129f0a8c0a1720460ba2 url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.0.1" flutter_launcher_icons: dependency: "direct dev" description: @@ -655,18 +663,18 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 + sha256: "6848263f9744072d0977347c383fb8b57d9780319a6bf5238b5a2866a029de62" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.2.0" flutter_secure_storage_darwin: dependency: transitive description: name: flutter_secure_storage_darwin - sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" + sha256: "67cd1ff671add31dc13e45194398187a04bb63804b37fa47866afae296d73fcb" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.3.1" flutter_secure_storage_linux: dependency: transitive description: @@ -687,26 +695,26 @@ packages: dependency: transitive description: name: flutter_secure_storage_web - sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + sha256: "0191ba6ae4fca37144d7231a8df5cbdb450f2a858d90a0b60b8ded0af9f40ce7" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: "1ded017b39c8e15c8948ea855070a5ff8ff8b3d5e83f3446e02d6bb12add7ad9" + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -716,10 +724,10 @@ packages: dependency: "direct main" description: name: flutter_web_auth_2 - sha256: "432ff8c7b2834eaeec3378d99e24a0210b9ac2f453b3f7a7d739a5c09069fba3" + sha256: d354998934ddc338e69b999b2abaeb33c6fd09999d3a5f92ead1a6b49b49712e url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.0.2" flutter_web_auth_2_platform_interface: dependency: transitive description: @@ -769,10 +777,10 @@ packages: dependency: "direct main" description: name: fvp - sha256: e03c4ba02c367cde8610c09325d085c9b1efe4f7d98a563950993a1fee17a28b + sha256: "23274616c8ba8e18b9e07d95c5a52c026aa5d0bb240bd24a04b181dd9c2c8b66" url: "https://pub.dev" source: hosted - version: "0.35.2" + version: "0.36.2" get_it: dependency: "direct main" description: @@ -793,10 +801,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: db9df7a5898d894eeda4c78143f35c30a243558be439518972366880b80bf88e + sha256: "4e9391085e524954a51e3625b7c9c7e9851dc3f376603208bb45c24b9a66255d" url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.1.0" graphs: dependency: transitive description: @@ -817,10 +825,10 @@ packages: dependency: transitive description: name: hooks - sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.3" hotreloader: dependency: transitive description: @@ -873,18 +881,18 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "66810af8e99b2657ee98e5c6f02064f69bb63f7a70e343937f70946c5f8c6622" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.13+16" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: @@ -937,10 +945,10 @@ packages: dependency: transitive description: name: inspector - sha256: a01ea538b15947a9189835cc910041bab91aea3ad7c421684118aa1040189ded + sha256: "60782d94c8851b0eee3ffdfea9fd43e7c37f919b54cb587e4de9da0e09d491b5" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.0" intl: dependency: "direct main" description: @@ -1145,18 +1153,18 @@ packages: dependency: transitive description: name: package_info_plus - sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" + sha256: "4bf625947f6c7713ee242296a682e23e44823c09cf9d79e4f1238923c92db852" url: "https://pub.dev" source: hosted - version: "9.0.1" + version: "10.1.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "4.1.0" path: dependency: "direct main" description: @@ -1337,26 +1345,26 @@ packages: dependency: "direct main" description: name: posthog_flutter - sha256: "50a3a41b946635c4dcae289d58a27b76dc41dffeb7b8fa907020c05697c13bcb" + sha256: "55a46ecf63411a6ede4a85e9d31dd251223c375fa85c18b78a134e62e11dcd6b" url: "https://pub.dev" source: hosted - version: "5.23.1" + version: "5.24.2" pro_image_editor: dependency: "direct main" description: name: pro_image_editor - sha256: "0d26c103b9dc1a1c228af853ee85267917515f891d50e45cf3562b4e2d22d63d" + sha256: b9ef2e934b74c7e12e7803697433d00c12187fc83e210313ac20ec93370a61b4 url: "https://pub.dev" source: hosted - version: "12.2.0" + version: "12.4.4" pro_video_editor: dependency: "direct main" description: name: pro_video_editor - sha256: "7bf4148a9bd2c6383bea1a36a01ff5099087b964ebaeaa56278cc7a4decc0566" + sha256: "9d914ec472119bc1a71b750d52ed3ce79169a27211f41fd7d6397693ecd4c466" url: "https://pub.dev" source: hosted - version: "1.14.3" + version: "1.16.2" pub_semver: dependency: transitive description: @@ -1373,6 +1381,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" resizable_widget: dependency: transitive description: @@ -1425,18 +1441,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa" + sha256: a857d8b1479250aff6b57a51b2c02d31ca05848d441817c43f1640c885c286c0 url: "https://pub.dev" source: hosted - version: "12.0.2" + version: "13.1.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + sha256: "7f7ae28cf400d13f811e297ff37742dba83b79e0a6f5dce14eec0248274e6ce9" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.1.0" shared_preferences: dependency: "direct main" description: @@ -1542,18 +1558,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.3" source_helper: dependency: transitive description: name: source_helper - sha256: "1d3b229b2934034fb2e691fbb3d53e0f75a4af7b1407f88425ed8f209bcb1b8f" + sha256: "4227d54ceefd0bb8ca4c8fcb96e1719dc53f1ee1b6e2ca9d7a6069da160e4eae" url: "https://pub.dev" source: hosted - version: "1.3.11" + version: "1.3.12" source_map_stack_trace: dependency: transitive description: @@ -1582,10 +1598,10 @@ packages: dependency: "direct main" description: name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.2+1" sqflite_android: dependency: transitive description: @@ -1598,10 +1614,10 @@ packages: dependency: transitive description: name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + sha256: f8a08a13fb8f0f8c590df89d745000bed44a673ed94bac846739e1a016875c21 url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.5.7" sqflite_darwin: dependency: transitive description: @@ -1670,10 +1686,10 @@ packages: dependency: "direct main" description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.4.0+1" term_glyph: dependency: transitive description: @@ -1766,10 +1782,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" url_launcher_windows: dependency: transitive description: @@ -1790,10 +1806,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "81da85e9ca8885ade47f9685b953cb098970d11be4821ac765580a6607ea4373" + sha256: "4d35a36400983c3457c289d4d553b5308f506ea84f7e51c7a564651b5525209a" url: "https://pub.dev" source: hosted - version: "1.1.21" + version: "1.2.1" vector_graphics_codec: dependency: transitive description: @@ -1806,10 +1822,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" + sha256: "98e7e94de127b46a86ef46197fff84ff99f3d3b80a708390d717ad731efef598" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" vector_math: dependency: transitive description: @@ -1838,18 +1854,18 @@ packages: dependency: transitive description: name: video_player_avfoundation - sha256: af0e5b8a7a4876fb37e7cc8cb2a011e82bb3ecfa45844ef672e32cb14a1f259e + sha256: a39d6f28f8069564d8cc17396472f958dd9eaddf2d5c8e90aad4d793ac369bf3 url: "https://pub.dev" source: hosted - version: "2.9.4" + version: "2.9.6" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" + sha256: "16eaed5268c571c31840dc58ef8da5f0cd4db2a98490c3b8f1cf70122546c6e0" url: "https://pub.dev" source: hosted - version: "6.6.0" + version: "6.7.0" video_player_web: dependency: transitive description: @@ -1870,26 +1886,26 @@ packages: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "15.2.0" wakelock_plus: dependency: transitive description: name: wakelock_plus - sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39" + sha256: "2b09acadd7a2862d33c3577e77e7a2aabb684f47ccca1711f1413bd7307a6a72" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.6.0" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "24b84143787220a403491c2e5de0877fbbb87baf3f0b18a2a988973863db4b03" + sha256: "14b2e5b9e35c2631e656913c47adecdd71633ae92896a27a64c8f1fcfabc21cc" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" watcher: dependency: transitive description: @@ -1934,10 +1950,10 @@ packages: dependency: transitive description: name: widgetbook - sha256: d4723df880c59e5df00da7ee02c5e29faf230d860319547e8fea011821ae93a9 + sha256: "4ff857b441b7fc43432a69ec6fe56494184c895e36cec502e400648024710b9c" url: "https://pub.dev" source: hosted - version: "3.22.0" + version: "3.23.0" widgetbook_annotation: dependency: transitive description: @@ -1950,18 +1966,18 @@ packages: dependency: transitive description: name: widgetbook_generator - sha256: "61fbe531ee5d535346ada5ec327a07eb645ed4e2d2636ad633beb8f774f9c3e5" + sha256: "8f7d6157f95d4406876088152d374e64c6632df32abe9b7ac27f95202996ae13" url: "https://pub.dev" source: hosted - version: "3.22.0" + version: "3.23.0" win32: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: a1fc9eb9248baa05dfc12ed5b66e377b3e23f095eec078e0371622b9033810d9 url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "6.2.0" window_to_front: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 58bc53a4..371d26e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,65 +12,65 @@ workspace: dependencies: any_link_preview: ^3.0.3 - app_badge_plus: ^1.2.7 + app_badge_plus: ^1.2.10 assets: path: assets audio_waveforms: ^2.0.2 audioplayers: ^6.6.0 auto_route: ^11.1.0 - better_player_plus: ^1.1.5 + better_player_plus: ^1.2.1 poptart: ^0.1.0 poptart_lex: ^0.1.0 cached_network_image: ^3.4.1 camera: ^0.12.0+1 collection: ^1.19.1 crypto: ^3.0.7 - firebase_core: ^4.6.0 - firebase_messaging: ^16.1.3 + firebase_core: ^4.8.0 + firebase_messaging: ^16.2.1 fluentui_system_icons: ^1.1.273 flutter: sdk: flutter flutter_cache_manager: ^3.4.1 flutter_colorpicker: ^1.1.0 - flutter_dotenv: ^6.0.0 + flutter_dotenv: ^6.0.1 flutter_localizations: sdk: flutter flutter_riverpod: ^3.3.1 - flutter_secure_storage: ^10.0.0 - flutter_svg: ^2.2.4 - flutter_web_auth_2: ^5.0.1 + flutter_secure_storage: ^10.2.0 + flutter_svg: ^2.3.0 + flutter_web_auth_2: ^5.0.2 fonts: path: fonts freezed: ^3.2.5 freezed_annotation: ^3.1.0 - fvp: ^0.35.2 + fvp: ^0.36.2 get_it: ^9.2.1 - google_fonts: ^8.0.2 + google_fonts: ^8.1.0 http: ^1.6.0 image: ^4.8.0 - image_picker: ^1.2.1 + image_picker: ^1.2.2 intl: ^0.20.2 json_annotation: ^4.11.0 path: ^1.9.1 path_provider: ^2.1.5 photo_manager: ^3.9.0 - oauth2: ^2.0.3 + oauth2: ^2.0.5 pool: ^1.5.2 - posthog_flutter: ^5.23.0 - pro_image_editor: ^12.0.13 - pro_video_editor: 1.14.3 + posthog_flutter: ^5.24.2 + pro_image_editor: ^12.4.4 + pro_video_editor: ^1.16.2 riverpod_annotation: ^4.0.2 - share_plus: ^12.0.2 + share_plus: ^13.1.0 shared_preferences: ^2.5.5 skeletonizer: ^2.1.3 - sqflite: ^2.4.2 - synchronized: ^3.4.0 + sqflite: ^2.4.2+1 + synchronized: ^3.4.0+1 url_launcher: ^6.3.2 video_player: ^2.11.1 dev_dependencies: auto_route_generator: ^10.4.0 - build_runner: ^2.13.1 + build_runner: ^2.15.0 flutter_launcher_icons: ^0.14.4 flutter_lints: ^6.0.0 flutter_test: diff --git a/test/src/core/network/atproto/data/models/sound_models_test.dart b/test/src/core/network/atproto/data/models/sound_models_test.dart new file mode 100644 index 00000000..eed3b153 --- /dev/null +++ b/test/src/core/network/atproto/data/models/sound_models_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:spark/src/core/network/atproto/data/models/models.dart'; +import 'package:spark/src/core/pro_video_editor/models/sound_audio_track.dart'; + +void main() { + group('AudioView', () { + test('parses Spark audio records', () { + final audio = AudioView.fromJson({ + 'uri': 'at://did:plc:test123/so.sprk.sound.audio/spark', + 'cid': 'cid-spark', + 'author': {'did': 'did:plc:test123', 'handle': 'test.sprk.so'}, + 'record': { + r'$type': 'so.sprk.sound.audio', + 'sound': _blobJson(), + 'title': 'Original Sound', + 'createdAt': '2026-05-01T12:00:00.000Z', + }, + 'title': 'Original Sound', + 'coverArt': 'https://example.com/spark.jpg', + 'indexedAt': '2026-05-01T12:00:00.000Z', + 'audio': 'https://media.sprk.so/sound/did%3Aplc%3Atest123/cid', + }); + + expect(audio.record, isA()); + expect(audio.title, 'Original Sound'); + }); + + test('parses public Plyr tracks as sounds', () { + final audio = AudioView.fromJson({ + 'uri': 'at://did:plc:plyr123/fm.plyr.track/track', + 'cid': 'cid-plyr', + 'author': {'did': 'did:plc:plyr123', 'handle': 'artist.plyr.fm'}, + 'record': { + r'$type': 'fm.plyr.track', + 'title': "Governor's Ball Symphony", + 'artist': 'Dame', + 'fileType': 'mp3', + 'audioBlob': _blobJson(), + 'imageUrl': 'https://example.com/cover.jpg', + 'createdAt': '2026-05-01T12:00:00.000Z', + }, + 'title': "Governor's Ball Symphony", + 'coverArt': 'https://example.com/cover.jpg', + 'details': {'artist': 'Dame', 'title': "Governor's Ball Symphony"}, + 'indexedAt': '2026-05-01T12:00:00.000Z', + 'audio': 'https://media.sprk.so/sound/did%3Aplc%3Aplyr123/cid', + }); + + final record = audio.record; + expect(record, isA()); + expect((record as PlyrTrackRecord).artist, 'Dame'); + expect(audio.details?.artist, 'Dame'); + }); + + test('preserves m4a file type when converting Plyr sounds to tracks', () { + final audio = AudioView.fromJson({ + 'uri': 'at://did:plc:plyr123/fm.plyr.track/m4a-track', + 'cid': 'cid-m4a', + 'author': {'did': 'did:plc:plyr123', 'handle': 'artist.plyr.fm'}, + 'record': { + r'$type': 'fm.plyr.track', + 'title': 'M4A Track', + 'artist': 'Dame', + 'fileType': 'm4a', + 'audioBlob': _blobJson(mimeType: 'audio/mp4'), + 'createdAt': '2026-05-01T12:00:00.000Z', + }, + 'title': 'M4A Track', + 'coverArt': 'https://example.com/cover.jpg', + 'indexedAt': '2026-05-01T12:00:00.000Z', + 'audio': 'https://media.sprk.so/sound/did%3Aplc%3Aplyr123/cid', + }); + + final track = audioViewToAudioTrack(audio); + + expect(track, isNotNull); + expect(decodeSoundTrackAudioFileExtension(track!.id), 'm4a'); + expect(decodeSoundTrackAudioMimeType(track.id), 'audio/mp4'); + }); + + test('maps Spark audio/mp4 blobs to m4a tracks', () { + final audio = AudioView.fromJson({ + 'uri': 'at://did:plc:test123/so.sprk.sound.audio/m4a-spark', + 'cid': 'cid-spark-m4a', + 'author': {'did': 'did:plc:test123', 'handle': 'test.sprk.so'}, + 'record': { + r'$type': 'so.sprk.sound.audio', + 'sound': _blobJson(mimeType: 'audio/mp4'), + 'title': 'Spark M4A', + 'createdAt': '2026-05-01T12:00:00.000Z', + }, + 'title': 'Spark M4A', + 'coverArt': 'https://example.com/spark.jpg', + 'indexedAt': '2026-05-01T12:00:00.000Z', + 'audio': 'https://media.sprk.so/sound/did%3Aplc%3Atest123/cid', + }); + + final track = audioViewToAudioTrack(audio); + + expect(track, isNotNull); + expect(decodeSoundTrackAudioFileExtension(track!.id), 'm4a'); + expect(decodeSoundTrackAudioMimeType(track.id), 'audio/mp4'); + }); + }); +} + +Map _blobJson({String mimeType = 'audio/mpeg'}) { + return { + r'$type': 'blob', + 'mimeType': mimeType, + 'size': 42, + 'ref': {r'$link': 'bafkreigh2akiscaildc2'}, + }; +} diff --git a/test/src/core/pro_video_editor/services/audio_helper_service_test.dart b/test/src/core/pro_video_editor/services/audio_helper_service_test.dart index b9fdd660..54a9100e 100644 --- a/test/src/core/pro_video_editor/services/audio_helper_service_test.dart +++ b/test/src/core/pro_video_editor/services/audio_helper_service_test.dart @@ -1,5 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:pro_image_editor/pro_image_editor.dart'; import 'package:spark/src/core/pro_video_editor/services/audio_helper_service.dart'; +import 'package:spark/src/core/pro_video_editor/models/sound_audio_track.dart'; void main() { group('syncedCustomAudioPosition', () { @@ -116,4 +118,39 @@ void main() { ); }); }); + + group('customAudioTempFilename', () { + test('uses the encoded sound audio extension', () { + final track = AudioTrack( + id: encodeSoundTrackId( + 'at://did:plc:test/fm.plyr.track/track', + 'cid', + audioFileExtension: 'm4a', + audioMimeType: 'audio/mp4', + ), + title: 'M4A', + subtitle: 'artist', + duration: const Duration(seconds: 9), + audio: EditorAudio(networkUrl: 'https://example.com/audio'), + ); + + expect(customAudioTempFilename(track), 'temp-audio.m4a'); + expect(decodeSoundTrackAudioMimeType(track.id), 'audio/mp4'); + }); + + test('falls back to mp3 for legacy track ids', () { + final track = AudioTrack( + id: encodeSoundTrackId( + 'at://did:plc:test/so.sprk.sound.audio/track', + 'cid', + ), + title: 'Legacy', + subtitle: 'artist', + duration: const Duration(seconds: 9), + audio: EditorAudio(networkUrl: 'https://example.com/audio'), + ); + + expect(customAudioTempFilename(track), 'temp-audio.mp3'); + }); + }); } diff --git a/widgetbook/macos/Flutter/GeneratedPluginRegistrant.swift b/widgetbook/macos/Flutter/GeneratedPluginRegistrant.swift index 1bee2cf9..b2aaf360 100644 --- a/widgetbook/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/widgetbook/macos/Flutter/GeneratedPluginRegistrant.swift @@ -46,7 +46,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) } diff --git a/widgetbook/pubspec.yaml b/widgetbook/pubspec.yaml index 5020258c..535dd482 100644 --- a/widgetbook/pubspec.yaml +++ b/widgetbook/pubspec.yaml @@ -10,7 +10,7 @@ resolution: workspace dependencies: flutter: sdk: flutter - widgetbook: ^3.22.0 + widgetbook: ^3.23.0 widgetbook_annotation: ^3.11.0 spark: path: ../ @@ -25,8 +25,8 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 - widgetbook_generator: ^3.22.0 - build_runner: ^2.13.1 + widgetbook_generator: ^3.23.0 + build_runner: ^2.15.0 flutter: uses-material-design: true -- 2.51.2