This commit is contained in:
otsmr 2025-05-29 00:33:08 +02:00
parent e7b48ecf63
commit 25d2edf89e
4 changed files with 75 additions and 23 deletions

View file

@ -91,11 +91,6 @@ Future retryMediaUpload({int maxRetries = 3}) async {
} }
} }
}); });
if (maxRetries == 0) return;
// retry upload
// Future.delayed(const Duration(milliseconds: 1000), () {
// retryMediaUpload(maxRetries: maxRetries - 1);
// });
} }
Future<int?> initMediaUpload() async { Future<int?> initMediaUpload() async {
@ -576,7 +571,7 @@ Future<void> deleteMediaFile(int mediaUploadId, String type) async {
} }
} }
Future<String> getMediaFilePath(int mediaId, String type) async { Future<String> getMediaFilePath(dynamic mediaId, String type) async {
final basedir = await getApplicationSupportDirectory(); final basedir = await getApplicationSupportDirectory();
final mediaSendDir = Directory(join(basedir.path, 'media', type)); final mediaSendDir = Directory(join(basedir.path, 'media', type));
if (!await mediaSendDir.exists()) { if (!await mediaSendDir.exists()) {
@ -585,6 +580,15 @@ Future<String> getMediaFilePath(int mediaId, String type) async {
return join(mediaSendDir.path, '$mediaId'); return join(mediaSendDir.path, '$mediaId');
} }
Future<String> getMediaBaseFilePath(String type) async {
final basedir = await getApplicationSupportDirectory();
final mediaSendDir = Directory(join(basedir.path, 'media', type));
if (!await mediaSendDir.exists()) {
await mediaSendDir.create(recursive: true);
}
return mediaSendDir.path;
}
/// combines two utf8 list /// combines two utf8 list
Uint8List combineUint8Lists(Uint8List list1, Uint8List list2) { Uint8List combineUint8Lists(Uint8List list1, Uint8List list2) {
final combinedLength = 4 + list1.length + list2.length; final combinedLength = 4 + list1.length + list2.length;

View file

@ -1,6 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:path/path.dart';
import 'package:twonly/src/providers/api/media_send.dart';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -9,11 +12,13 @@ class SaveToGalleryButton extends StatefulWidget {
final Future<Uint8List?> Function() getMergedImage; final Future<Uint8List?> Function() getMergedImage;
final String? sendNextMediaToUserName; final String? sendNextMediaToUserName;
final File? videoFilePath; final File? videoFilePath;
final int? mediaUploadId;
const SaveToGalleryButton( const SaveToGalleryButton(
{super.key, {super.key,
required this.getMergedImage, required this.getMergedImage,
this.sendNextMediaToUserName, this.sendNextMediaToUserName,
this.mediaUploadId,
this.videoFilePath}); this.videoFilePath});
@override @override
@ -41,12 +46,26 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
}); });
String? res; String? res;
String memoryPath = await getMediaBaseFilePath("memories");
if (widget.mediaUploadId != null) {
memoryPath = join(memoryPath, "${widget.mediaUploadId!}");
} else {
final Random random = Random();
String token = uint8ListToHex(
List<int>.generate(32, (i) => random.nextInt(256)));
memoryPath = join(memoryPath, token);
}
if (widget.videoFilePath != null) { if (widget.videoFilePath != null) {
memoryPath += ".mp4";
await File(widget.videoFilePath!.path).copy(memoryPath);
res = await saveVideoToGallery(widget.videoFilePath!.path); res = await saveVideoToGallery(widget.videoFilePath!.path);
} else { } else {
memoryPath += ".png";
Uint8List? imageBytes = await widget.getMergedImage(); Uint8List? imageBytes = await widget.getMergedImage();
if (imageBytes == null || !context.mounted) return; if (imageBytes == null || !context.mounted) return;
await File(memoryPath).writeAsBytes(imageBytes);
res = await saveImageToGallery(imageBytes); res = await saveImageToGallery(imageBytes);
} }
if (res == null) { if (res == null) {

View file

@ -535,6 +535,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
children: [ children: [
SaveToGalleryButton( SaveToGalleryButton(
getMergedImage: getMergedImage, getMergedImage: getMergedImage,
mediaUploadId: mediaUploadId,
videoFilePath: widget.videoFilePath, videoFilePath: widget.videoFilePath,
sendNextMediaToUserName: sendNextMediaToUserName, sendNextMediaToUserName: sendNextMediaToUserName,
), ),

View file

@ -21,7 +21,7 @@ class GalleryItem {
this.imagePath, this.imagePath,
this.videoPath, this.videoPath,
}); });
final int id; final String id;
final List<Message> messages; final List<Message> messages;
final DateTime date; final DateTime date;
final File? imagePath; final File? imagePath;
@ -135,6 +135,40 @@ class GalleryMainViewState extends State<GalleryMainView> {
initAsync(); initAsync();
} }
Future<List<GalleryItem>> loadMemoriesDirectory() async {
final directoryPath = await send.getMediaBaseFilePath("memories");
final directory = Directory(directoryPath);
List<GalleryItem> items = [];
if (await directory.exists()) {
final files = directory.listSync();
for (var file in files) {
if (file is File) {
final fileName = file.uri.pathSegments.last;
File? imagePath;
File? videoPath;
if (fileName.contains(".png")) {
imagePath = file;
} else if (fileName.contains(".mp4")) {
videoPath = file;
} else {
break;
}
final creationDate = await file.lastModified();
items.add(GalleryItem(
id: fileName,
messages: [],
date: creationDate,
imagePath: imagePath,
videoPath: videoPath,
));
}
}
}
return items;
}
Future initAsync() async { Future initAsync() async {
List<Message> storedMediaFiles = List<Message> storedMediaFiles =
await twonlyDatabase.messagesDao.getAllStoredMediaFiles(); await twonlyDatabase.messagesDao.getAllStoredMediaFiles();
@ -160,7 +194,7 @@ class GalleryMainViewState extends State<GalleryMainView> {
.putIfAbsent( .putIfAbsent(
id, id,
() => GalleryItem( () => GalleryItem(
id: id, id: id.toString(),
messages: [], messages: [],
date: message.sendAt, date: message.sendAt,
imagePath: imagePath, imagePath: imagePath,
@ -174,7 +208,9 @@ class GalleryMainViewState extends State<GalleryMainView> {
months = []; months = [];
String lastMonth = ""; String lastMonth = "";
galleryItems = items.values.toList(); galleryItems = await loadMemoriesDirectory();
galleryItems += items.values.toList();
galleryItems.sort((a, b) => b.date.compareTo(a.date));
for (var i = 0; i < galleryItems.length; i++) { for (var i = 0; i < galleryItems.length; i++) {
String month = DateFormat('MMMM yyyy').format(galleryItems[i].date); String month = DateFormat('MMMM yyyy').format(galleryItems[i].date);
if (lastMonth != month) { if (lastMonth != month) {
@ -232,9 +268,9 @@ class GalleryMainViewState extends State<GalleryMainView> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => GalleryPhotoViewWrapper( builder: (context) => GalleryPhotoViewWrapper(
galleryItems: galleryItems, galleryItems: galleryItems,
backgroundDecoration: const BoxDecoration( // backgroundDecoration: const BoxDecoration(
color: Colors.black, // color: Colors.black,
), // ),
initialIndex: index, initialIndex: index,
scrollDirection: verticalGallery ? Axis.vertical : Axis.horizontal, scrollDirection: verticalGallery ? Axis.vertical : Axis.horizontal,
), ),
@ -298,12 +334,10 @@ class _GalleryPhotoViewWrapperState extends State<GalleryPhotoViewWrapper> {
FilledButton.icon( FilledButton.icon(
icon: FaIcon(FontAwesomeIcons.solidPaperPlane), icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
onPressed: () async { onPressed: () async {
await Navigator.push( Navigator.push(
context, context,
PageRouteBuilder( MaterialPageRoute(
opaque: false, builder: (context) => ShareImageEditorView(
pageBuilder: (context, a1, a2) =>
ShareImageEditorView(
videoFilePath: videoFilePath:
widget.galleryItems[currentIndex].videoPath, widget.galleryItems[currentIndex].videoPath,
imageBytes: widget imageBytes: widget
@ -312,12 +346,6 @@ class _GalleryPhotoViewWrapperState extends State<GalleryPhotoViewWrapper> {
mirrorVideo: false, mirrorVideo: false,
useHighQuality: true, useHighQuality: true,
), ),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return child;
},
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
), ),
); );
}, },