[mob] Move model & remove unused file (#5201)

## Description

## Tests
This commit is contained in:
Neeraj 2025-03-01 10:55:51 +05:30 committed by GitHub
commit c6ea0f1fd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 63 additions and 579 deletions

View File

@ -1,5 +1,5 @@
import "package:photos/models/api/metadata.dart";
import 'package:photos/models/collection/collection.dart';
import 'package:photos/services/file_magic_service.dart';
class CreateRequest {
String encryptedKey;

View File

@ -0,0 +1,54 @@
class UpdateMagicMetadataRequest {
final int id;
final MetadataRequest? magicMetadata;
UpdateMagicMetadataRequest({required this.id, required this.magicMetadata});
factory UpdateMagicMetadataRequest.fromJson(dynamic json) {
return UpdateMagicMetadataRequest(
id: json['id'],
magicMetadata: json['magicMetadata'] != null
? MetadataRequest.fromJson(json['magicMetadata'])
: null,
);
}
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
if (magicMetadata != null) {
map['magicMetadata'] = magicMetadata!.toJson();
}
return map;
}
}
class MetadataRequest {
int? version;
int? count;
String? data;
String? header;
MetadataRequest({
required this.version,
required this.count,
required this.data,
required this.header,
});
MetadataRequest.fromJson(dynamic json) {
version = json['version'];
count = json['count'];
data = json['data'];
header = json['header'];
}
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['version'] = version;
map['count'] = count;
map['data'] = data;
map['header'] = header;
return map;
}
}

View File

@ -29,6 +29,7 @@ import 'package:photos/models/api/collection/collection_file_item.dart';
import 'package:photos/models/api/collection/create_request.dart';
import "package:photos/models/api/collection/public_url.dart";
import "package:photos/models/api/collection/user.dart";
import "package:photos/models/api/metadata.dart";
import 'package:photos/models/collection/collection.dart';
import 'package:photos/models/collection/collection_items.dart';
import 'package:photos/models/file/file.dart';
@ -37,7 +38,6 @@ import "package:photos/models/metadata/collection_magic.dart";
import "package:photos/service_locator.dart";
import 'package:photos/services/app_lifecycle_service.dart';
import "package:photos/services/favorites_service.dart";
import 'package:photos/services/file_magic_service.dart';
import 'package:photos/services/local_sync_service.dart';
import 'package:photos/services/remote_sync_service.dart';
import "package:photos/utils/dialog_util.dart";
@ -666,6 +666,7 @@ class CollectionsService {
),
);
sync().ignore();
// not required once remote & local world are separate
LocalSyncService.instance.syncAll().ignore();
}

View File

@ -12,6 +12,7 @@ import 'package:photos/events/files_updated_event.dart';
import 'package:photos/events/force_reload_home_gallery_event.dart';
import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/extensions/list.dart';
import "package:photos/models/api/metadata.dart";
import 'package:photos/models/file/file.dart';
import "package:photos/models/metadata/common_keys.dart";
import "package:photos/models/metadata/file_magic.dart";
@ -195,58 +196,3 @@ class FileMagicService {
}
}
}
class UpdateMagicMetadataRequest {
final int id;
final MetadataRequest? magicMetadata;
UpdateMagicMetadataRequest({required this.id, required this.magicMetadata});
factory UpdateMagicMetadataRequest.fromJson(dynamic json) {
return UpdateMagicMetadataRequest(
id: json['id'],
magicMetadata: json['magicMetadata'] != null
? MetadataRequest.fromJson(json['magicMetadata'])
: null,
);
}
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
if (magicMetadata != null) {
map['magicMetadata'] = magicMetadata!.toJson();
}
return map;
}
}
class MetadataRequest {
int? version;
int? count;
String? data;
String? header;
MetadataRequest({
required this.version,
required this.count,
required this.data,
required this.header,
});
MetadataRequest.fromJson(dynamic json) {
version = json['version'];
count = json['count'];
data = json['data'];
header = json['header'];
}
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['version'] = version;
map['count'] = count;
map['data'] = data;
map['header'] = header;
return map;
}
}

View File

@ -12,12 +12,12 @@ import 'package:photos/events/files_updated_event.dart';
import 'package:photos/events/local_photos_updated_event.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/models/api/collection/create_request.dart';
import "package:photos/models/api/metadata.dart";
import 'package:photos/models/collection/collection.dart';
import 'package:photos/models/file/file.dart';
import "package:photos/models/metadata/collection_magic.dart";
import "package:photos/models/metadata/common_keys.dart";
import 'package:photos/services/collections_service.dart';
import 'package:photos/services/file_magic_service.dart';
import 'package:photos/utils/dialog_util.dart';
extension HiddenService on CollectionsService {

View File

@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/ente_theme.dart';
class StorageErrorWidget extends StatelessWidget {
const StorageErrorWidget({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(
Icons.error_outline_outlined,
color: strokeBaseDark,
),
const SizedBox(height: 8),
Text(
S.of(context).yourStorageDetailsCouldNotBeFetched,
style: getEnteTextTheme(context).small.copyWith(
color: textMutedDark,
),
),
],
),
);
}
}

View File

@ -1,66 +0,0 @@
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:logging/logging.dart";
import "package:photos/generated/l10n.dart";
import 'package:photos/models/file/file.dart';
import "package:photos/ui/components/buttons/chip_button_widget.dart";
import "package:photos/ui/components/info_item_widget.dart";
class ObjectsItemWidget extends StatelessWidget {
final EnteFile file;
const ObjectsItemWidget(this.file, {super.key});
@override
Widget build(BuildContext context) {
return InfoItemWidget(
key: const ValueKey("Objects"),
leadingIcon: Icons.image_search_outlined,
subtitleSection: _objectTags(context, file),
hasChipButtons: true,
);
}
Future<List<ChipButtonWidget>> _objectTags(
BuildContext context,
EnteFile file,
) async {
try {
final chipButtons = <ChipButtonWidget>[];
var objectTags = <String, double>{};
// final thumbnail = await getThumbnail(file);
// if (thumbnail != null) {
// objectTags = await ObjectDetectionService.instance.predict(thumbnail);
// }
if (objectTags.isEmpty) {
return [
ChipButtonWidget(
S.of(context).noResults,
noChips: true,
),
];
}
// sort by values
objectTags = Map.fromEntries(
objectTags.entries.toList()
..sort((e1, e2) => e2.value.compareTo(e1.value)),
);
for (MapEntry<String, double> entry in objectTags.entries) {
chipButtons.add(
ChipButtonWidget(
entry.key +
(kDebugMode
? "-" + (entry.value * 100).round().toString()
: ""),
),
);
}
return chipButtons;
} catch (e, s) {
Logger("ObjctsItemWidget").info(e, s);
return [];
}
}
}

View File

@ -1,74 +0,0 @@
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/theme/ente_theme.dart';
///https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=10854%3A57947&t=H5AvR79OYDnB9ekw-4
class NewPersonItemWidget extends StatelessWidget {
const NewPersonItemWidget({
super.key,
});
@override
Widget build(BuildContext context) {
final textTheme = getEnteTextTheme(context);
final colorScheme = getEnteColorScheme(context);
const sideOfThumbnail = 60.0;
return LayoutBuilder(
builder: (context, constraints) {
return Stack(
alignment: Alignment.center,
children: [
Row(
children: [
ClipRRect(
borderRadius: const BorderRadius.horizontal(
left: Radius.circular(4),
),
child: SizedBox(
height: sideOfThumbnail,
width: sideOfThumbnail,
child: Icon(
Icons.add_outlined,
color: colorScheme.strokeMuted,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 12),
child: Text(
S.of(context).addNewPerson,
style:
textTheme.body.copyWith(color: colorScheme.textMuted),
),
),
],
),
IgnorePointer(
child: DottedBorder(
dashPattern: const [4],
color: colorScheme.strokeFainter,
strokeWidth: 1,
padding: const EdgeInsets.all(0),
borderType: BorderType.RRect,
radius: const Radius.circular(4),
child: SizedBox(
//Have to decrease the height and width by 1 pt as the stroke
//dotted border gives is of strokeAlign.center, so 0.5 inside and
// outside. Here for the row, stroke should be inside so we
//decrease the size of this sizedBox by 1 (so it shrinks 0.5 from
//every side) so that the strokeAlign.center of this sizedBox
//looks like a strokeAlign.inside in the row.
height: sideOfThumbnail - 1,
//This width will work for this only if the row widget takes up the
//full size it's parent (stack).
width: constraints.maxWidth - 1,
),
),
),
],
);
},
);
}
}

View File

@ -1,175 +0,0 @@
import "dart:math";
import "dart:typed_data";
import "package:computer/computer.dart";
import "package:flutter_image_compress/flutter_image_compress.dart";
import "package:image/image.dart" as img;
import "package:logging/logging.dart";
import "package:photos/models/ml/face/box.dart";
/// Bounding box of a face.
///
/// [xMin] and [yMin] are the coordinates of the top left corner of the box, and
/// [width] and [height] are the width and height of the box.
///
/// One unit is equal to one pixel in the original image.
class FaceBoxImage {
final int xMin;
final int yMin;
final int width;
final int height;
FaceBoxImage({
required this.xMin,
required this.yMin,
required this.width,
required this.height,
});
}
final _logger = Logger("FaceUtil");
final _computer = Computer.shared();
const _faceImageBufferFactor = 0.2;
///Convert img.Image to ui.Image and use RawImage to display.
Future<List<img.Image>> generateImgFaceThumbnails(
String imagePath,
List<FaceBox> faceBoxes,
) async {
final faceThumbnails = <img.Image>[];
final image = await decodeToImgImage(imagePath);
for (FaceBox faceBox in faceBoxes) {
final croppedImage = cropFaceBoxFromImage(image, faceBox);
faceThumbnails.add(croppedImage);
}
return faceThumbnails;
}
Future<List<Uint8List>> generateJpgFaceThumbnails(
String imagePath,
List<FaceBox> faceBoxes,
) async {
final image = await decodeToImgImage(imagePath);
final croppedImages = <img.Image>[];
for (FaceBox faceBox in faceBoxes) {
final croppedImage = cropFaceBoxFromImage(image, faceBox);
croppedImages.add(croppedImage);
}
return await _computer
.compute(_encodeImagesToJpg, param: {"images": croppedImages});
}
Future<img.Image> decodeToImgImage(String imagePath) async {
img.Image? image =
await _computer.compute(_decodeImageFile, param: {"filePath": imagePath});
if (image == null) {
_logger.info(
"Failed to decode image. Compressing to jpg and decoding",
);
final compressedJPGImage =
await FlutterImageCompress.compressWithFile(imagePath);
image = await _computer.compute(
_decodeJpg,
param: {"image": compressedJPGImage},
);
if (image == null) {
throw Exception("Failed to decode image");
} else {
return image;
}
} else {
return image;
}
}
/// Returns an Image from 'package:image/image.dart'
img.Image cropFaceBoxFromImage(img.Image image, FaceBox faceBox) {
final squareFaceBox = _getSquareFaceBoxImage(image, faceBox);
final squareFaceBoxWithBuffer =
_addBufferAroundFaceBox(squareFaceBox, _faceImageBufferFactor);
return img.copyCrop(
image,
x: squareFaceBoxWithBuffer.xMin,
y: squareFaceBoxWithBuffer.yMin,
width: squareFaceBoxWithBuffer.width,
height: squareFaceBoxWithBuffer.height,
antialias: false,
);
}
/// Returns a square face box image from the original image with
/// side length equal to the maximum of the width and height of the face box in
/// the OG image.
FaceBoxImage _getSquareFaceBoxImage(img.Image image, FaceBox faceBox) {
final width = (image.width * faceBox.width).round();
final height = (image.height * faceBox.height).round();
final side = max(width, height);
final xImage = (image.width * faceBox.x).round();
final yImage = (image.height * faceBox.y).round();
if (height >= width) {
final xImageAdj = (xImage - (height - width) / 2).round();
return FaceBoxImage(
xMin: xImageAdj,
yMin: yImage,
width: side,
height: side,
);
} else {
final yImageAdj = (yImage - (width - height) / 2).round();
return FaceBoxImage(
xMin: xImage,
yMin: yImageAdj,
width: side,
height: side,
);
}
}
///To add some buffer around the face box so that the face isn't cropped
///too close to the face.
FaceBoxImage _addBufferAroundFaceBox(
FaceBoxImage faceBoxImage,
double bufferFactor,
) {
final heightBuffer = faceBoxImage.height * bufferFactor;
final widthBuffer = faceBoxImage.width * bufferFactor;
final xMinWithBuffer = faceBoxImage.xMin - widthBuffer;
final yMinWithBuffer = faceBoxImage.yMin - heightBuffer;
final widthWithBuffer = faceBoxImage.width + 2 * widthBuffer;
final heightWithBuffer = faceBoxImage.height + 2 * heightBuffer;
//Do not add buffer if the top left edge of the image is out of bounds
//after adding the buffer.
if (xMinWithBuffer < 0 || yMinWithBuffer < 0) {
return faceBoxImage;
}
//Another similar case that can be handled is when the bottom right edge
//of the image is out of bounds after adding the buffer. But the
//the visual difference is not as significant as when the top left edge
//is out of bounds, so we are not handling that case.
return FaceBoxImage(
xMin: xMinWithBuffer.round(),
yMin: yMinWithBuffer.round(),
width: widthWithBuffer.round(),
height: heightWithBuffer.round(),
);
}
List<Uint8List> _encodeImagesToJpg(Map args) {
final images = args["images"] as List<img.Image>;
return images.map((img.Image image) => img.encodeJpg(image)).toList();
}
Future<img.Image?> _decodeImageFile(Map args) async {
return await img.decodeImageFile(args["filePath"]);
}
img.Image? _decodeJpg(Map args) {
return img.decodeJpg(args["image"])!;
}

View File

@ -24,6 +24,7 @@ import 'package:photos/events/files_updated_event.dart';
import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/events/subscription_purchased_event.dart';
import 'package:photos/main.dart';
import "package:photos/models/api/metadata.dart";
import "package:photos/models/backup/backup_item.dart";
import "package:photos/models/backup/backup_item_status.dart";
import 'package:photos/models/file/file.dart';
@ -34,7 +35,6 @@ import 'package:photos/module/upload/model/upload_url.dart';
import "package:photos/module/upload/service/multipart.dart";
import "package:photos/service_locator.dart";
import 'package:photos/services/collections_service.dart';
import "package:photos/services/file_magic_service.dart";
import 'package:photos/services/local_sync_service.dart';
import "package:photos/services/preview_video_store.dart";
import 'package:photos/services/sync_service.dart';
@ -751,7 +751,8 @@ class FileUploader {
if (SyncService.instance.shouldStopSync()) {
throw SyncStopRequestedError();
}
final stillLocked = await _uploadLocks.isLocked(lockKey, _processType.toString());
final stillLocked =
await _uploadLocks.isLocked(lockKey, _processType.toString());
if (!stillLocked) {
_logger.warning('file ${file.tag} report paused is missing');
throw LockFreedError();

View File

@ -17,13 +17,13 @@ import 'package:photo_manager/photo_manager.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/errors.dart';
import "package:photos/models/api/metadata.dart";
import "package:photos/models/ffmpeg/ffprobe_props.dart";
import "package:photos/models/file/extensions/file_props.dart";
import 'package:photos/models/file/file.dart';
import 'package:photos/models/file/file_type.dart';
import "package:photos/models/location/location.dart";
import "package:photos/models/metadata/file_magic.dart";
import "package:photos/services/file_magic_service.dart";
import "package:photos/utils/exif_util.dart";
import 'package:photos/utils/file_util.dart';
import "package:uuid/uuid.dart";

View File

@ -1,14 +0,0 @@
String convertLatLng(double decimal, bool isLat) {
final degree = "${decimal.toString().split(".")[0]}°";
final minutesBeforeConversion =
double.parse("0.${decimal.toString().split(".")[1]}");
final minutes = "${(minutesBeforeConversion * 60).toString().split('.')[0]}'";
final secondsBeforeConversion = double.parse(
"0.${(minutesBeforeConversion * 60).toString().split('.')[1]}",
);
final seconds =
'${double.parse((secondsBeforeConversion * 60).toString()).toStringAsFixed(0)}" ';
final dmsOutput =
"$degree$minutes$seconds${isLat ? decimal > 0 ? 'N' : 'S' : decimal > 0 ? 'E' : 'W'}";
return dmsOutput;
}

View File

@ -1,157 +0,0 @@
// ignore_for_file: implementation_imports
import "dart:io";
import "package:dio/dio.dart";
import "package:logging/logging.dart";
import "package:photos/core/constants.dart";
import "package:photos/core/network/network.dart";
import 'package:photos/module/upload/model/xml.dart';
import "package:photos/service_locator.dart";
final _enteDio = NetworkClient.instance.enteDio;
final _dio = NetworkClient.instance.getDio();
class PartETag extends XmlParsableObject {
final int partNumber;
final String eTag;
PartETag(this.partNumber, this.eTag);
@override
String get elementName => "Part";
@override
Map<String, dynamic> toMap() {
return {
"PartNumber": partNumber,
"ETag": eTag,
};
}
}
class MultipartUploadURLs {
final String objectKey;
final List<String> partsURLs;
final String completeURL;
MultipartUploadURLs({
required this.objectKey,
required this.partsURLs,
required this.completeURL,
});
factory MultipartUploadURLs.fromMap(Map<String, dynamic> map) {
return MultipartUploadURLs(
objectKey: map["urls"]["objectKey"],
partsURLs: (map["urls"]["partURLs"] as List).cast<String>(),
completeURL: map["urls"]["completeURL"],
);
}
}
Future<int> calculatePartCount(int fileSize) async {
final partCount = (fileSize / multipartPartSize).ceil();
return partCount;
}
Future<MultipartUploadURLs> getMultipartUploadURLs(int count) async {
try {
assert(
flagService.internalUser,
"Multipart upload should not be enabled for external users.",
);
final response = await _enteDio.get(
"/files/multipart-upload-urls",
queryParameters: {
"count": count,
},
);
return MultipartUploadURLs.fromMap(response.data);
} on Exception catch (e) {
Logger("MultipartUploadURL").severe(e);
rethrow;
}
}
Future<String> putMultipartFile(
MultipartUploadURLs urls,
File encryptedFile,
) async {
// upload individual parts and get their etags
final etags = await uploadParts(urls.partsURLs, encryptedFile);
// complete the multipart upload
await completeMultipartUpload(etags, urls.completeURL);
return urls.objectKey;
}
Future<Map<int, String>> uploadParts(
List<String> partsURLs,
File encryptedFile,
) async {
final partsLength = partsURLs.length;
final etags = <int, String>{};
for (int i = 0; i < partsLength; i++) {
final partURL = partsURLs[i];
final isLastPart = i == partsLength - 1;
final fileSize = isLastPart
? encryptedFile.lengthSync() % multipartPartSize
: multipartPartSize;
final response = await _dio.put(
partURL,
data: encryptedFile.openRead(
i * multipartPartSize,
isLastPart ? null : multipartPartSize,
),
options: Options(
headers: {
Headers.contentLengthHeader: fileSize,
},
),
);
final eTag = response.headers.value("etag");
if (eTag?.isEmpty ?? true) {
throw Exception('ETAG_MISSING');
}
etags[i] = eTag!;
}
return etags;
}
Future<void> completeMultipartUpload(
Map<int, String> partEtags,
String completeURL,
) async {
final body = convertJs2Xml({
'CompleteMultipartUpload': partEtags.entries
.map(
(e) => PartETag(
e.key + 1,
e.value,
),
)
.toList(),
}).replaceAll('"', '').replaceAll('&quot;', '');
try {
await _dio.post(
completeURL,
data: body,
options: Options(
contentType: "text/xml",
),
);
} catch (e) {
Logger("MultipartUpload").severe(e);
rethrow;
}
}