diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fd5ab04..d8ca4ba 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -34,6 +34,8 @@ PODS: - DKImagePickerController/PhotoGallery - Flutter - Flutter (1.0.0) + - flutter_file_dialog (0.0.1): + - Flutter - flutter_secure_storage (6.0.0): - Flutter - FMDB (2.7.5): @@ -58,6 +60,7 @@ PODS: DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) + - flutter_file_dialog (from `.symlinks/plugins/flutter_file_dialog/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) @@ -78,6 +81,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter + flutter_file_dialog: + :path: ".symlinks/plugins/flutter_file_dialog/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" image_picker_ios: @@ -96,6 +101,7 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_file_dialog: 4c014a45b105709a27391e266c277d7e588e9299 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb diff --git a/lib/friendica_client.dart b/lib/friendica_client.dart index a3066d6..ab70093 100644 --- a/lib/friendica_client.dart +++ b/lib/friendica_client.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; @@ -258,6 +259,28 @@ class FriendicaClient { .mapError((error) => error as ExecError); } + FutureResult getFileBytes(Uri url) async { + _logger.finest('GET: $url'); + try { + final response = await http.get( + url, + headers: { + 'Authorization': _authHeader, + }, + ); + + if (response.statusCode != 200) { + return Result.error(ExecError( + type: ErrorType.authentication, + message: '${response.statusCode}: ${response.reasonPhrase}')); + } + return Result.ok(response.bodyBytes); + } catch (e) { + return Result.error( + ExecError(type: ErrorType.localError, message: e.toString())); + } + } + FutureResult, ExecError> getPostOrComment(String id, {bool fullContext = false}) async { return (await runCatchingAsync(() async { diff --git a/lib/models/media_attachment.dart b/lib/models/media_attachment.dart index 5fe73f5..9b49c6e 100644 --- a/lib/models/media_attachment.dart +++ b/lib/models/media_attachment.dart @@ -17,6 +17,8 @@ class MediaAttachment { final Uri thumbnailUri; + final Uri fullFileUri; + final String title; final String description; @@ -26,6 +28,7 @@ class MediaAttachment { required this.creationTimestamp, required this.metadata, required this.thumbnailUri, + required this.fullFileUri, required this.title, required this.explicitType, required this.description}); @@ -33,33 +36,20 @@ class MediaAttachment { MediaAttachment.randomBuilt() : uri = Uri.parse('http://localhost/${randomId()}'), creationTimestamp = DateTime.now().millisecondsSinceEpoch, + fullFileUri = Uri.parse(''), title = 'Random title ${randomId()}', thumbnailUri = Uri.parse('${randomId()}.jpg'), description = 'Random description ${randomId()}', explicitType = AttachmentMediaType.image, metadata = {'value1': randomId(), 'value2': randomId()}; - MediaAttachment.fromUriOnly(this.uri) - : creationTimestamp = 0, - thumbnailUri = Uri.file(''), - title = '', - explicitType = mediaTypeFromString(uri.path), - description = '', - metadata = {}; - - MediaAttachment.fromUriAndTime(this.uri, this.creationTimestamp) - : thumbnailUri = Uri.file(''), - title = '', - explicitType = mediaTypeFromString(uri.path), - description = '', - metadata = {}; - MediaAttachment.blank() : uri = Uri(), creationTimestamp = 0, - thumbnailUri = Uri.file(''), + thumbnailUri = Uri(), explicitType = AttachmentMediaType.unknown, title = '', + fullFileUri = Uri(), description = '', metadata = {}; @@ -68,8 +58,9 @@ class MediaAttachment { uri: Uri.parse(json['url'] ?? 'http://localhost'), creationTimestamp: 0, metadata: {}, - thumbnailUri: Uri.parse(json['preview_url'] ?? 'http://localhost'), + thumbnailUri: Uri.parse(json['url'] ?? 'http://localhost'), title: '', + fullFileUri: Uri.parse(json['remote_url'] ?? 'http://localhost'), explicitType: AttachmentMediaType.parse(json['type']), description: json['description'] ?? ''); diff --git a/lib/screens/image_viewer_screen.dart b/lib/screens/image_viewer_screen.dart index 154bb26..d90e6c5 100644 --- a/lib/screens/image_viewer_screen.dart +++ b/lib/screens/image_viewer_screen.dart @@ -1,17 +1,64 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; +import 'dart:io'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_file_dialog/flutter_file_dialog.dart'; +import 'package:flutter_portal/utils/snackbar_builder.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +import '../globals.dart'; import '../models/media_attachment.dart'; +import '../services/auth_service.dart'; class ImageViewerScreen extends StatelessWidget { final MediaAttachment attachment; const ImageViewerScreen({super.key, required this.attachment}); + Future saveImage(BuildContext context) async { + final appsDir = await getApplicationDocumentsDirectory(); + final filename = p.basename(attachment.fullFileUri.path); + print(filename); + final bytesResult = await getIt() + .currentClient + .value + .getFileBytes(attachment.uri); + if (bytesResult.isFailure) { + buildSnackbar(context, + 'Error getting full size version of file: ${bytesResult.error}'); + } + if (Platform.isAndroid || Platform.isIOS) { + final params = SaveFileDialogParams( + data: bytesResult.value, + fileName: filename, + ); + final result = await FlutterFileDialog.saveFile(params: params); + print(result); + } else { + final location = await FilePicker.platform.saveFile( + dialogTitle: 'Save Image', + fileName: filename, + initialDirectory: appsDir.path, + ); + print(location); + if (location != null) { + await File(location).writeAsBytes(bytesResult.value); + } + } + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(), + appBar: AppBar( + actions: [ + IconButton( + onPressed: () => saveImage(context), + icon: const Icon(Icons.download)) + ], + ), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/serializers/friendica/image_entry_friendica_extensions.dart b/lib/serializers/friendica/image_entry_friendica_extensions.dart index 5a95926..b981e07 100644 --- a/lib/serializers/friendica/image_entry_friendica_extensions.dart +++ b/lib/serializers/friendica/image_entry_friendica_extensions.dart @@ -28,11 +28,10 @@ extension ImageEntryFriendicaExtension on ImageEntry { MediaAttachment toMediaAttachment() { final thumbUri = Uri.parse(thumbnailUrl); - final extension = thumbUri.pathSegments.last.split('.').last; - final newFileName = '$id-0.$extension'; - final fullFileUri = Uri.https(thumbUri.authority, '/photo/$newFileName'); + final fullFileUri = scales.first.link; return MediaAttachment( uri: fullFileUri, + fullFileUri: fullFileUri, creationTimestamp: created.millisecondsSinceEpoch, metadata: {}, thumbnailUri: thumbUri, diff --git a/lib/serializers/friendica/media_attachment_friendica_extensions.dart b/lib/serializers/friendica/media_attachment_friendica_extensions.dart index e16aa47..fb5c34e 100644 --- a/lib/serializers/friendica/media_attachment_friendica_extensions.dart +++ b/lib/serializers/friendica/media_attachment_friendica_extensions.dart @@ -18,6 +18,7 @@ extension MediaAttachmentFriendicaExtensions on MediaAttachment { return MediaAttachment( uri: uri, + fullFileUri: uri, creationTimestamp: creationTimestamp, metadata: metadata, thumbnailUri: thumbnailUri, diff --git a/pubspec.lock b/pubspec.lock index 43e8fb4..6fc9085 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -174,6 +174,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.0.2" + flutter_file_dialog: + dependency: "direct main" + description: + name: flutter_file_dialog + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.2" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index a3ccc6e..ddd100a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: file_picker: ^5.2.4 path: ^1.8.2 image: ^3.2.2 + flutter_file_dialog: ^2.3.2 dev_dependencies: flutter_test: