use media id as filename
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run

This commit is contained in:
otsmr 2026-06-20 01:08:39 +02:00
parent f14a94d639
commit 4c42d55afd
7 changed files with 111 additions and 21 deletions

View file

@ -1,5 +1,10 @@
# Changelog # Changelog
## 0.3.3
- Fix: Multiple UI issues
- Fix: Camera initialization issue
## 0.3.2 ## 0.3.2
- Fix: Multiple smaller performance and UI issues - Fix: Multiple smaller performance and UI issues

View file

@ -33,7 +33,7 @@ import 'package:twonly/src/utils/startup_guard.dart';
final _initMutex = Mutex(); final _initMutex = Mutex();
/// This function is used to initialized the absolute minimum so it /// This function is used to initialize the absolute minimum so it
/// can also be used by the backend without the UI was loaded. /// can also be used by the backend without the UI was loaded.
Future<bool> twonlyMinimumInitialization() async { Future<bool> twonlyMinimumInitialization() async {
Log.info('twonlyMinimumInitialization: called'); Log.info('twonlyMinimumInitialization: called');

View file

@ -314,11 +314,15 @@ class MediaFileService {
await tempPath.copy(storedPath.path); await tempPath.copy(storedPath.path);
if (userService.currentUser.storeMediaFilesInGallery) { if (userService.currentUser.storeMediaFilesInGallery) {
if (mediaFile.type == MediaType.video) { if (mediaFile.type == MediaType.video) {
await saveVideoToGallery(storedPath.path); await saveVideoToGallery(
storedPath.path,
name: mediaFile.mediaId,
);
} else { } else {
await saveImageToGallery( await saveImageToGallery(
storedPath.readAsBytesSync(), storedPath.readAsBytesSync(),
createdAt: mediaFile.createdAt, createdAt: mediaFile.createdAt,
name: mediaFile.mediaId,
); );
} }
} }

View file

@ -11,6 +11,7 @@ import 'package:gal/gal.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
import 'package:path/path.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart';
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
@ -36,6 +37,7 @@ extension ShortCutsExtension on BuildContext {
Future<String?> saveImageToGallery( Future<String?> saveImageToGallery(
Uint8List imageBytes, { Uint8List imageBytes, {
DateTime? createdAt, DateTime? createdAt,
String? name,
}) async { }) async {
var bytesToProcess = imageBytes; var bytesToProcess = imageBytes;
@ -75,7 +77,7 @@ Future<String?> saveImageToGallery(
await Gal.requestAccess(toAlbum: true); await Gal.requestAccess(toAlbum: true);
} }
try { try {
await Gal.putImageBytes(jpgImages, album: 'twonly'); await Gal.putImageBytes(jpgImages, album: 'twonly', name: name ?? 'image');
return null; return null;
} on GalException catch (e) { } on GalException catch (e) {
Log.error(e); Log.error(e);
@ -83,17 +85,45 @@ Future<String?> saveImageToGallery(
} }
} }
Future<String?> saveVideoToGallery(String videoPath) async { Future<String?> saveVideoToGallery(
String videoPath, {
String? name,
}) async {
final hasAccess = await Gal.hasAccess(toAlbum: true); final hasAccess = await Gal.hasAccess(toAlbum: true);
if (!hasAccess) { if (!hasAccess) {
await Gal.requestAccess(toAlbum: true); await Gal.requestAccess(toAlbum: true);
} }
var pathToSave = videoPath;
File? tempFile;
try { try {
await Gal.putVideo(videoPath, album: 'twonly'); if (name != null) {
final file = File(videoPath);
final extension = file.path.split('.').last;
final tempDir = Directory.systemTemp;
tempFile = File(join(tempDir.path, '$name.$extension'));
if (tempFile.existsSync()) {
try {
tempFile.deleteSync();
} catch (_) {}
}
file.copySync(tempFile.path);
pathToSave = tempFile.path;
}
await Gal.putVideo(pathToSave, album: 'twonly');
return null; return null;
} on GalException catch (e) { } on GalException catch (e) {
Log.error(e); Log.error(e);
return e.type.message; return e.type.message;
} finally {
if (tempFile != null && tempFile.existsSync()) {
try {
tempFile.deleteSync();
} catch (e) {
Log.error('Failed to delete temp video file: $e');
}
}
} }
} }

View file

@ -310,11 +310,18 @@ class MemoriesViewState extends State<MemoriesView> with AutomaticKeepAliveClien
if (item != null) { if (item != null) {
final media = item.mediaService; final media = item.mediaService;
if (media.mediaFile.type == MediaType.video) { if (media.mediaFile.type == MediaType.video) {
await saveVideoToGallery(media.storedPath.path); await saveVideoToGallery(
media.storedPath.path,
name: media.mediaFile.mediaId,
);
} else if (media.mediaFile.type == MediaType.image || } else if (media.mediaFile.type == MediaType.image ||
media.mediaFile.type == MediaType.gif) { media.mediaFile.type == MediaType.gif) {
final imageBytes = await media.storedPath.readAsBytes(); final imageBytes = await media.storedPath.readAsBytes();
await saveImageToGallery(imageBytes, createdAt: media.mediaFile.createdAt); await saveImageToGallery(
imageBytes,
createdAt: media.mediaFile.createdAt,
name: media.mediaFile.mediaId,
);
} }
} }
setProgress((i + 1) / selectedList.length); setProgress((i + 1) / selectedList.length);

View file

@ -193,11 +193,18 @@ class _SynchronizedImageViewerScreenState
try { try {
if (item.mediaFile.type == MediaType.video) { if (item.mediaFile.type == MediaType.video) {
await saveVideoToGallery(item.storedPath.path); await saveVideoToGallery(
item.storedPath.path,
name: item.mediaFile.mediaId,
);
} else if (item.mediaFile.type == MediaType.image || } else if (item.mediaFile.type == MediaType.image ||
item.mediaFile.type == MediaType.gif) { item.mediaFile.type == MediaType.gif) {
final imageBytes = await item.storedPath.readAsBytes(); final imageBytes = await item.storedPath.readAsBytes();
await saveImageToGallery(imageBytes, createdAt: item.mediaFile.createdAt); await saveImageToGallery(
imageBytes,
createdAt: item.mediaFile.createdAt,
name: item.mediaFile.mediaId,
);
} }
if (!mounted) return; if (!mounted) return;
showSnackbar( showSnackbar(

View file

@ -13,6 +13,7 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/android_photo_picker.service.dart'; import 'package:twonly/src/services/android_photo_picker.service.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart' show ShortCutsExtension, sha256File; import 'package:twonly/src/utils/misc.dart' show ShortCutsExtension, sha256File;
import 'package:twonly/src/visual/components/selectable_thumbnail.comp.dart'; import 'package:twonly/src/visual/components/selectable_thumbnail.comp.dart';
import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/components/snackbar.dart';
@ -84,8 +85,8 @@ class _ImportFromGalleryViewState extends State<ImportFromGalleryView> {
final hash = Uint8List.fromList(sha256.convert(bytes).bytes); final hash = Uint8List.fromList(sha256.convert(bytes).bytes);
final exsits = await twonlyDB.mediaFilesDao.getMediaByHash(hash); final exists = await twonlyDB.mediaFilesDao.getMediaByHash(hash);
if (exsits.isNotEmpty) { if (exists.isNotEmpty) {
duplicated += 1; duplicated += 1;
continue; continue;
} }
@ -345,8 +346,8 @@ class _ImportFromGalleryViewState extends State<ImportFromGalleryView> {
final hash = Uint8List.fromList(await sha256File(file)); final hash = Uint8List.fromList(await sha256File(file));
final exsits = await twonlyDB.mediaFilesDao.getMediaByHash(hash); final exists = await twonlyDB.mediaFilesDao.getMediaByHash(hash);
if (exsits.isNotEmpty) { if (exists.isNotEmpty) {
duplicated += 1; duplicated += 1;
continue; continue;
} }
@ -363,14 +364,50 @@ class _ImportFromGalleryViewState extends State<ImportFromGalleryView> {
type = MediaType.image; type = MediaType.image;
} }
final mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia( MediaFile? mediaFile;
MediaFilesCompanion( var isRestored = false;
type: Value(type),
createdAt: Value(createdAt), if (Platform.isIOS) {
storedFileHash: Value(hash), try {
stored: const Value(true), final assetName = await asset.titleAsync;
), final dotIndex = assetName.lastIndexOf('.');
); final baseName = dotIndex != -1 ? assetName.substring(0, dotIndex) : assetName;
final uuidRegex = RegExp(
r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$',
);
if (uuidRegex.hasMatch(baseName)) {
final existing = await twonlyDB.mediaFilesDao.getMediaFileById(baseName);
if (existing != null) {
final mediaService = MediaFileService(existing);
if (!mediaService.storedPath.existsSync()) {
await twonlyDB.mediaFilesDao.updateMedia(
baseName,
MediaFilesCompanion(
storedFileHash: Value(hash),
stored: const Value(true),
),
);
mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(baseName);
isRestored = true;
}
}
}
} catch (e) {
Log.error('Error checking iOS asset UUID name: $e');
}
}
if (!isRestored) {
mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia(
MediaFilesCompanion(
type: Value(type),
createdAt: Value(createdAt),
storedFileHash: Value(hash),
stored: const Value(true),
),
);
}
if (mediaFile != null) { if (mediaFile != null) {
final mediaService = MediaFileService(mediaFile); final mediaService = MediaFileService(mediaFile);