diff --git a/lib/apis/record_api.dart b/lib/apis/record_api.dart index aa763d7..f8c07b3 100644 --- a/lib/apis/record_api.dart +++ b/lib/apis/record_api.dart @@ -141,6 +141,7 @@ class RecordApi { filename: filename, thumbnailUri: imageDigest.dbUri, digests: digests, + extraTags: ["image"], ); progressCallback?.call(.1); final status = await tryPreprocessRecord(client, record: record); @@ -151,6 +152,7 @@ class RecordApi { client, assets: digests.where((digest) => toUpload.any((diff) => digest.asset.hash == diff.hash)).toList(), progressCallback: (progress) => progressCallback?.call(.2 + progress * .6)); + await upsertRecord(client, record: record); progressCallback?.call(1); return record; } @@ -170,6 +172,7 @@ class RecordApi { filename: filename, thumbnailUri: "", digests: digests, + extraTags: ["voice", "message"], ); progressCallback?.call(.1); final status = await tryPreprocessRecord(client, record: record); @@ -180,6 +183,7 @@ class RecordApi { client, assets: digests.where((digest) => toUpload.any((diff) => digest.asset.hash == diff.hash)).toList(), progressCallback: (progress) => progressCallback?.call(.2 + progress * .6)); + await upsertRecord(client, record: record); progressCallback?.call(1); return record; } @@ -203,6 +207,7 @@ class RecordApi { filename: fileDigest.name, thumbnailUri: JsonTemplate.thumbUrl, digests: digests, + extraTags: ["document"], ); progressCallback?.call(.1); final status = await tryPreprocessRecord(client, record: record); @@ -213,6 +218,7 @@ class RecordApi { client, assets: digests.where((digest) => toUpload.any((diff) => digest.asset.hash == diff.hash)).toList(), progressCallback: (progress) => progressCallback?.call(.2 + progress * .6)); + await upsertRecord(client, record: record); progressCallback?.call(1); return record; } diff --git a/lib/models/records/record.dart b/lib/models/records/record.dart index 9d3a910..125a0c6 100644 --- a/lib/models/records/record.dart +++ b/lib/models/records/record.dart @@ -111,6 +111,7 @@ class Record { required String filename, required String thumbnailUri, required List digests, + List? extraTags, }) { final combinedRecordId = RecordId(id: Record.generateId(), ownerId: userId, isValid: true); return Record( @@ -118,11 +119,12 @@ class Record { combinedRecordId: combinedRecordId, assetUri: assetUri, name: filename, - tags: [ + tags: ([ filename, "message_item", - "message_id:${Message.generateId()}" - ], + "message_id:${Message.generateId()}", + "contacts-plus-plus" + ] + (extraTags ?? [])).unique(), recordType: recordType, thumbnailUri: thumbnailUri, isPublic: false, diff --git a/lib/widgets/messages/message_asset.dart b/lib/widgets/messages/message_asset.dart index a0117b4..e1f95ef 100644 --- a/lib/widgets/messages/message_asset.dart +++ b/lib/widgets/messages/message_asset.dart @@ -20,39 +20,46 @@ class MessageAsset extends StatelessWidget { @override Widget build(BuildContext context) { final content = jsonDecode(message.content); - PhotoAsset? photoAsset; - try { - photoAsset = PhotoAsset.fromTags((content["tags"] as List).map((e) => "$e").toList()); - } catch (_) {} final formattedName = FormatNode.fromText(content["name"]); return Container( constraints: const BoxConstraints(maxWidth: 300), child: Column( children: [ - CachedNetworkImage( - imageUrl: Aux.neosDbToHttp(content["thumbnailUri"]), - imageBuilder: (context, image) { - return InkWell( - onTap: () async { - await Navigator.push( - context, MaterialPageRoute(builder: (context) => - PhotoView( - minScale: PhotoViewComputedScale.contained, - imageProvider: photoAsset == null - ? image - : CachedNetworkImageProvider(Aux.neosDbToHttp(photoAsset.imageUri)), - heroAttributes: PhotoViewHeroAttributes(tag: message.id), - ), - ),); - }, - child: Hero( - tag: message.id, - child: ClipRRect(borderRadius: BorderRadius.circular(16), child: Image(image: image,)), - ), - ); - }, - errorWidget: (context, url, error) => const Icon(Icons.image_not_supported, size: 128,), - placeholder: (context, uri) => const CircularProgressIndicator(), + SizedBox( + height: 256, + width: double.infinity, + child: CachedNetworkImage( + imageUrl: Aux.neosDbToHttp(content["thumbnailUri"]), + imageBuilder: (context, image) { + return InkWell( + onTap: () async { + PhotoAsset? photoAsset; + try { + photoAsset = PhotoAsset.fromTags((content["tags"] as List).map((e) => "$e").toList()); + } catch (_) {} + await Navigator.push( + context, MaterialPageRoute(builder: (context) => + PhotoView( + minScale: PhotoViewComputedScale.contained, + imageProvider: photoAsset == null + ? image + : CachedNetworkImageProvider(Aux.neosDbToHttp(photoAsset.imageUri)), + heroAttributes: PhotoViewHeroAttributes(tag: message.id), + ), + ),); + }, + child: Hero( + tag: message.id, + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Image(image: image, fit: BoxFit.cover,), + ), + ), + ); + }, + errorWidget: (context, url, error) => const Icon(Icons.broken_image, size: 64,), + placeholder: (context, uri) => const Center(child: CircularProgressIndicator()), + ), ), const SizedBox(height: 8,), Row( diff --git a/lib/widgets/messages/message_audio_player.dart b/lib/widgets/messages/message_audio_player.dart index 1b98f44..67245aa 100644 --- a/lib/widgets/messages/message_audio_player.dart +++ b/lib/widgets/messages/message_audio_player.dart @@ -45,6 +45,14 @@ class _MessageAudioPlayerState extends State with WidgetsBin .then((value) => _audioPlayer.setFilePath(value.path)).whenComplete(() => _audioPlayer.setLoopMode(LoopMode.off)); } + @override + void didUpdateWidget(covariant MessageAudioPlayer oldWidget) { + super.didUpdateWidget(oldWidget); + final audioCache = Provider.of(context); + _audioFileFuture = audioCache.cachedNetworkAudioFile(AudioClipContent.fromMap(jsonDecode(widget.message.content))) + .then((value) => _audioPlayer.setFilePath(value.path)).whenComplete(() => _audioPlayer.setLoopMode(LoopMode.off)); + } + @override void dispose() { WidgetsBinding.instance.removeObserver(this); diff --git a/lib/widgets/messages/message_input_bar.dart b/lib/widgets/messages/message_input_bar.dart index 0e3cc35..f893ccc 100644 --- a/lib/widgets/messages/message_input_bar.dart +++ b/lib/widgets/messages/message_input_bar.dart @@ -47,6 +47,13 @@ class _MessageInputBarState extends State { set _isRecording(value) => _recordingStartTime = value ? DateTime.now() : null; bool _recordingCancelled = false; + @override + void dispose() { + _recorder.dispose(); + _messageTextController.dispose(); + super.dispose(); + } + Future sendTextMessage(ApiClient client, MessagingClient mClient, String content) async { if (content.isEmpty) return; final message = Message( @@ -160,7 +167,6 @@ class _MessageInputBarState extends State { if (_isRecording) { if (_recordingCancelled) { setState(() { - _recordingCancelled = false; _isRecording = false; }); final recording = await _recorder.stop(); @@ -319,7 +325,9 @@ class _MessageInputBarState extends State { ), child: switch((_attachmentPickerOpen, _isRecording)) { (_, true) => IconButton( - onPressed: () {}, + onPressed: () { + + }, icon: Icon(Icons.delete, color: _recordingCancelled ? Theme.of(context).colorScheme.error : null,), ), (false, _) => IconButton( @@ -513,13 +521,25 @@ class _MessageInputBarState extends State { }, icon: const Icon(Icons.send), ) : GestureDetector( + onTapUp: (_) { + _recordingCancelled = true; + }, onTapDown: widget.disabled ? null : (_) async { HapticFeedback.vibrate(); + if (!await _recorder.hasPermission()) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text("No permission to record audio."), + )); + } + return; + } + final dir = await getTemporaryDirectory(); await _recorder.start( - path: "${dir.path}/A-${const Uuid().v4()}.ogg", - encoder: AudioEncoder.opus, - samplingRate: 44100, + path: "${dir.path}/A-${const Uuid().v4()}.wav", + encoder: AudioEncoder.wav, + samplingRate: 44100 ); setState(() { _isRecording = true; @@ -527,7 +547,7 @@ class _MessageInputBarState extends State { }, child: IconButton( icon: const Icon(Icons.mic_outlined), - onPressed: () { + onPressed: _isSending ? null : () { // Empty onPressed for that sweet sweet ripple effect }, ),