Compare commits

..

22 commits

Author SHA1 Message Date
Neeraj Gupta ae61fc9c6f
Wrap add person name banner inside safeArea (#1887)
## Description

## Tests
2024-05-27 18:12:45 +05:30
Neeraj Gupta c291fa70d3 Wrap add person name banner inside safeArea 2024-05-27 18:12:21 +05:30
Laurens Priem 232acfa211
Face (#1885)
## Description

- Several fixes for Faces
2024-05-27 17:46:05 +05:30
laurenspriem f25f119ca1 [mob][photos] Copy 2024-05-27 17:26:14 +05:30
laurenspriem 89a61b3bf7 [mob][photos] Bump 2024-05-27 17:21:29 +05:30
laurenspriem 380d37267b [mob][photos] Don't pop too often 2024-05-27 17:19:06 +05:30
laurenspriem 9cf5691e42 [mob][photos] Delete instead of drop table 2024-05-27 17:09:33 +05:30
laurenspriem 8f474a4500 [mob][photos] Set MLController timer to 10 seconds 2024-05-27 15:54:10 +05:30
Manav Rathi c7be2270ff
[desktop] RC fixes (#1884) 2024-05-27 15:16:04 +05:30
laurenspriem ced1f0bd79 [mob][photos] Don't remove last cluster of person 2024-05-27 14:55:52 +05:30
Manav Rathi 9f58f1eeb3
Fix error on refresh while a folder watch is being set up
Notes:

From QA

> This error mostly happens if i add a watch folder and before watch folders
  start to upload and i refresh the app.

e is undefined in

    let {watches: e, removeWatch: n} = t;
    return 0 === e.length ? (0,...

Results in Next throwing

    Application error: a client-side exception has occurred (see the browser console for more information).
2024-05-27 14:42:56 +05:30
Manav Rathi 04be2b6a2c
Update electron updater
Trying to rule out https://github.com/electron-userland/electron-builder/issues/7127
2024-05-27 14:00:24 +05:30
laurenspriem 9f361237b1 [mob][photos] Fix cluster appbar not showing 2024-05-27 13:04:20 +05:30
ashilkn d413c4f4c1 [mob][photos] Add try catch + logs for debugging in FaceMLDataDB 2024-05-27 12:57:25 +05:30
ashilkn ee8976e92b [mob][photos] Add schema migration easier on FaceMLDataDB 2024-05-27 12:56:20 +05:30
laurenspriem baa90c42ad [mob][photos] Remove stale comments 2024-05-27 11:59:36 +05:30
laurenspriem 30ade541df [mob][photos] Logging 2024-05-27 11:57:46 +05:30
laurenspriem 86fb8ebfaf [mob][photos] Fix indexing issue on iOS 2024-05-27 11:57:40 +05:30
laurenspriem b2e8c3c0eb [mob][photos] Remove restriction for ML for F-Droid 2024-05-27 11:51:20 +05:30
laurenspriem b100f1d4bf [mob][photos] Catch and stopwatch on faces db creation 2024-05-27 11:28:05 +05:30
laurenspriem 7b4559f3ca [mob][photos] Reduce clustering frequency 2024-05-27 10:49:42 +05:30
laurenspriem 1ec7e02695 [mob][photos] Copy change 2024-05-25 12:03:34 +05:30
22 changed files with 178 additions and 1053 deletions

View file

@ -30,7 +30,7 @@
"compare-versions": "^6.1", "compare-versions": "^6.1",
"electron-log": "^5.1", "electron-log": "^5.1",
"electron-store": "^8.2", "electron-store": "^8.2",
"electron-updater": "^6.1", "electron-updater": "^6.2",
"ffmpeg-static": "^5.2", "ffmpeg-static": "^5.2",
"html-entities": "^2.5", "html-entities": "^2.5",
"jpeg-js": "^0.4", "jpeg-js": "^0.4",

View file

@ -743,10 +743,10 @@ buffer@^5.1.0, buffer@^5.5.0:
base64-js "^1.3.1" base64-js "^1.3.1"
ieee754 "^1.1.13" ieee754 "^1.1.13"
builder-util-runtime@9.2.3: builder-util-runtime@9.2.4:
version "9.2.3" version "9.2.4"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz#0a82c7aca8eadef46d67b353c638f052c206b83c" resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz#13cd1763da621e53458739a1e63f7fcba673c42a"
integrity sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw== integrity sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==
dependencies: dependencies:
debug "^4.3.4" debug "^4.3.4"
sax "^1.2.4" sax "^1.2.4"
@ -1251,12 +1251,12 @@ electron-store@^8.2:
conf "^10.2.0" conf "^10.2.0"
type-fest "^2.17.0" type-fest "^2.17.0"
electron-updater@^6.1: electron-updater@^6.2:
version "6.1.8" version "6.2.1"
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.1.8.tgz#17637bca165322f4e526b13c99165f43e6f697d8" resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.2.1.tgz#1c9adb9ba2a21a5dc50a8c434c45360d5e9fe6c9"
integrity sha512-hhOTfaFAd6wRHAfUaBhnAOYc+ymSGCWJLtFkw4xJqOvtpHmIdNHnXDV9m1MHC+A6q08Abx4Ykgyz/R5DGKNAMQ== integrity sha512-83eKIPW14qwZqUUM6wdsIRwVKZyjmHxQ4/8G+1C6iS5PdDt7b1umYQyj1/qPpH510GmHEQe4q0kCPe3qmb3a0Q==
dependencies: dependencies:
builder-util-runtime "9.2.3" builder-util-runtime "9.2.4"
fs-extra "^10.1.0" fs-extra "^10.1.0"
js-yaml "^4.1.0" js-yaml "^4.1.0"
lazy-val "^1.0.5" lazy-val "^1.0.5"

View file

@ -35,6 +35,15 @@ class FaceMLDataDB {
static final FaceMLDataDB instance = FaceMLDataDB._privateConstructor(); static final FaceMLDataDB instance = FaceMLDataDB._privateConstructor();
static final _migrationScripts = [
createFacesTable,
createFaceClustersTable,
createClusterPersonTable,
createClusterSummaryTable,
createNotPersonFeedbackTable,
fcClusterIDIndex,
];
// only have a single app-wide reference to the database // only have a single app-wide reference to the database
static Future<SqliteDatabase>? _sqliteAsyncDBFuture; static Future<SqliteDatabase>? _sqliteAsyncDBFuture;
@ -50,17 +59,42 @@ class FaceMLDataDB {
_logger.info("Opening sqlite_async access: DB path " + databaseDirectory); _logger.info("Opening sqlite_async access: DB path " + databaseDirectory);
final asyncDBConnection = final asyncDBConnection =
SqliteDatabase(path: databaseDirectory, maxReaders: 2); SqliteDatabase(path: databaseDirectory, maxReaders: 2);
await _onCreate(asyncDBConnection); final stopwatch = Stopwatch()..start();
_logger.info("FaceMLDataDB: Starting migration");
await _migrate(asyncDBConnection);
_logger.info(
"FaceMLDataDB Migration took ${stopwatch.elapsedMilliseconds} ms",
);
stopwatch.stop();
return asyncDBConnection; return asyncDBConnection;
} }
Future<void> _onCreate(SqliteDatabase asyncDBConnection) async { Future<void> _migrate(
await asyncDBConnection.execute(createFacesTable); SqliteDatabase database,
await asyncDBConnection.execute(createFaceClustersTable); ) async {
await asyncDBConnection.execute(createClusterPersonTable); final result = await database.execute('PRAGMA user_version');
await asyncDBConnection.execute(createClusterSummaryTable); final currentVersion = result[0]['user_version'] as int;
await asyncDBConnection.execute(createNotPersonFeedbackTable); final toVersion = _migrationScripts.length;
await asyncDBConnection.execute(fcClusterIDIndex);
if (currentVersion < toVersion) {
_logger.info("Migrating database from $currentVersion to $toVersion");
await database.writeTransaction((tx) async {
for (int i = currentVersion + 1; i <= toVersion; i++) {
try {
await tx.execute(_migrationScripts[i - 1]);
} catch (e) {
_logger.severe("Error running migration script index ${i - 1}", e);
rethrow;
}
}
await tx.execute('PRAGMA user_version = $toVersion');
});
} else if (currentVersion > toVersion) {
throw AssertionError(
"currentVersion($currentVersion) cannot be greater than toVersion($toVersion)",
);
}
} }
// bulkInsertFaces inserts the faces in the database in batches of 1000. // bulkInsertFaces inserts the faces in the database in batches of 1000.
@ -195,10 +229,10 @@ class FaceMLDataDB {
final db = await instance.asyncDB; final db = await instance.asyncDB;
await db.execute(deleteFacesTable); await db.execute(deleteFacesTable);
await db.execute(dropClusterPersonTable); await db.execute(deleteFaceClustersTable);
await db.execute(dropClusterSummaryTable); await db.execute(deleteClusterPersonTable);
await db.execute(deletePersonTable); await db.execute(deleteClusterSummaryTable);
await db.execute(dropNotPersonFeedbackTable); await db.execute(deleteNotPersonFeedbackTable);
} }
Future<Iterable<Uint8List>> getFaceEmbeddingsForCluster( Future<Iterable<Uint8List>> getFaceEmbeddingsForCluster(
@ -734,7 +768,7 @@ class FaceMLDataDB {
try { try {
final db = await instance.asyncDB; final db = await instance.asyncDB;
await db.execute(dropFaceClustersTable); await db.execute(deleteFaceClustersTable);
await db.execute(createFaceClustersTable); await db.execute(createFaceClustersTable);
await db.execute(fcClusterIDIndex); await db.execute(fcClusterIDIndex);
} catch (e, s) { } catch (e, s) {
@ -945,16 +979,15 @@ class FaceMLDataDB {
if (faces) { if (faces) {
await db.execute(deleteFacesTable); await db.execute(deleteFacesTable);
await db.execute(createFacesTable); await db.execute(createFacesTable);
await db.execute(dropFaceClustersTable); await db.execute(deleteFaceClustersTable);
await db.execute(createFaceClustersTable); await db.execute(createFaceClustersTable);
await db.execute(fcClusterIDIndex); await db.execute(fcClusterIDIndex);
} }
await db.execute(deletePersonTable); await db.execute(deleteClusterPersonTable);
await db.execute(dropClusterPersonTable); await db.execute(deleteNotPersonFeedbackTable);
await db.execute(dropNotPersonFeedbackTable); await db.execute(deleteClusterSummaryTable);
await db.execute(dropClusterSummaryTable); await db.execute(deleteFaceClustersTable);
await db.execute(dropFaceClustersTable);
await db.execute(createClusterPersonTable); await db.execute(createClusterPersonTable);
await db.execute(createNotPersonFeedbackTable); await db.execute(createNotPersonFeedbackTable);
@ -972,9 +1005,8 @@ class FaceMLDataDB {
final db = await instance.asyncDB; final db = await instance.asyncDB;
// Drop the tables // Drop the tables
await db.execute(deletePersonTable); await db.execute(deleteClusterPersonTable);
await db.execute(dropClusterPersonTable); await db.execute(deleteNotPersonFeedbackTable);
await db.execute(dropNotPersonFeedbackTable);
// Recreate the tables // Recreate the tables
await db.execute(createClusterPersonTable); await db.execute(createClusterPersonTable);

View file

@ -29,7 +29,7 @@ const createFacesTable = '''CREATE TABLE IF NOT EXISTS $facesTable (
); );
'''; ''';
const deleteFacesTable = 'DROP TABLE IF EXISTS $facesTable'; const deleteFacesTable = 'DELETE FROM $facesTable';
// End of Faces Table Fields & Schema Queries // End of Faces Table Fields & Schema Queries
//##region Face Clusters Table Fields & Schema Queries //##region Face Clusters Table Fields & Schema Queries
@ -48,15 +48,9 @@ CREATE TABLE IF NOT EXISTS $faceClustersTable (
// -- Creating a non-unique index on clusterID for query optimization // -- Creating a non-unique index on clusterID for query optimization
const fcClusterIDIndex = const fcClusterIDIndex =
'''CREATE INDEX IF NOT EXISTS idx_fcClusterID ON $faceClustersTable($fcClusterID);'''; '''CREATE INDEX IF NOT EXISTS idx_fcClusterID ON $faceClustersTable($fcClusterID);''';
const dropFaceClustersTable = 'DROP TABLE IF EXISTS $faceClustersTable'; const deleteFaceClustersTable = 'DELETE FROM $faceClustersTable';
//##endregion //##endregion
// People Table Fields & Schema Queries
const personTable = 'person';
const deletePersonTable = 'DROP TABLE IF EXISTS $personTable';
//End People Table Fields & Schema Queries
// Clusters Table Fields & Schema Queries // Clusters Table Fields & Schema Queries
const clusterPersonTable = 'cluster_person'; const clusterPersonTable = 'cluster_person';
const personIdColumn = 'person_id'; const personIdColumn = 'person_id';
@ -69,7 +63,7 @@ CREATE TABLE IF NOT EXISTS $clusterPersonTable (
PRIMARY KEY($personIdColumn, $clusterIDColumn) PRIMARY KEY($personIdColumn, $clusterIDColumn)
); );
'''; ''';
const dropClusterPersonTable = 'DROP TABLE IF EXISTS $clusterPersonTable'; const deleteClusterPersonTable = 'DELETE FROM $clusterPersonTable';
// End Clusters Table Fields & Schema Queries // End Clusters Table Fields & Schema Queries
/// Cluster Summary Table Fields & Schema Queries /// Cluster Summary Table Fields & Schema Queries
@ -85,7 +79,7 @@ CREATE TABLE IF NOT EXISTS $clusterSummaryTable (
); );
'''; ''';
const dropClusterSummaryTable = 'DROP TABLE IF EXISTS $clusterSummaryTable'; const deleteClusterSummaryTable = 'DELETE FROM $clusterSummaryTable';
/// End Cluster Summary Table Fields & Schema Queries /// End Cluster Summary Table Fields & Schema Queries
@ -99,5 +93,5 @@ CREATE TABLE IF NOT EXISTS $notPersonFeedback (
PRIMARY KEY($personIdColumn, $clusterIDColumn) PRIMARY KEY($personIdColumn, $clusterIDColumn)
); );
'''; ''';
const dropNotPersonFeedbackTable = 'DROP TABLE IF EXISTS $notPersonFeedback'; const deleteNotPersonFeedbackTable = 'DELETE FROM $notPersonFeedback';
// End Clusters Table Fields & Schema Queries // End Clusters Table Fields & Schema Queries

View file

@ -814,7 +814,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Incorrect recovery key"), MessageLookupByLibrary.simpleMessage("Incorrect recovery key"),
"indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"), "indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage( "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"), "Indexing is paused. It will automatically resume when device is ready."),
"insecureDevice": "insecureDevice":
MessageLookupByLibrary.simpleMessage("Insecure device"), MessageLookupByLibrary.simpleMessage("Insecure device"),
"installManually": "installManually":

View file

@ -8794,10 +8794,10 @@ class S {
); );
} }
/// `Indexing is paused, will automatically resume when device is ready` /// `Indexing is paused. It will automatically resume when device is ready.`
String get indexingIsPaused { String get indexingIsPaused {
return Intl.message( return Intl.message(
'Indexing is paused, will automatically resume when device is ready', 'Indexing is paused. It will automatically resume when device is ready.',
name: 'indexingIsPaused', name: 'indexingIsPaused',
desc: '', desc: '',
args: [], args: [],

View file

@ -1236,5 +1236,5 @@
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.", "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
"foundFaces": "Found faces", "foundFaces": "Found faces",
"clusteringProgress": "Clustering progress", "clusteringProgress": "Clustering progress",
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready" "indexingIsPaused": "Indexing is paused. It will automatically resume when device is ready."
} }

View file

@ -246,17 +246,11 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
unawaited(SemanticSearchService.instance.init()); unawaited(SemanticSearchService.instance.init());
MachineLearningController.instance.init(); MachineLearningController.instance.init();
// Can not including existing tf/ml binaries as they are not being built if (flagService.faceSearchEnabled) {
// from source. unawaited(FaceMlService.instance.init());
// See https://gitlab.com/fdroid/fdroiddata/-/merge_requests/12671#note_1294346819 } else {
if (!UpdateService.instance.isFdroidFlavor()) { if (LocalSettings.instance.isFaceIndexingEnabled) {
// unawaited(ObjectDetectionService.instance.init()); unawaited(LocalSettings.instance.toggleFaceIndexing());
if (flagService.faceSearchEnabled) {
unawaited(FaceMlService.instance.init());
} else {
if (LocalSettings.instance.isFaceIndexingEnabled) {
unawaited(LocalSettings.instance.toggleFaceIndexing());
}
} }
} }
PersonService.init( PersonService.init(

View file

@ -43,6 +43,7 @@ import 'package:photos/services/machine_learning/face_ml/face_ml_result.dart';
import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import 'package:photos/services/machine_learning/file_ml/file_ml.dart'; import 'package:photos/services/machine_learning/file_ml/file_ml.dart';
import 'package:photos/services/machine_learning/file_ml/remote_fileml_service.dart'; import 'package:photos/services/machine_learning/file_ml/remote_fileml_service.dart';
import "package:photos/services/machine_learning/machine_learning_controller.dart";
import "package:photos/services/search_service.dart"; import "package:photos/services/search_service.dart";
import "package:photos/utils/file_util.dart"; import "package:photos/utils/file_util.dart";
import 'package:photos/utils/image_ml_isolate.dart'; import 'package:photos/utils/image_ml_isolate.dart';
@ -99,7 +100,7 @@ class FaceMlService {
final int _fileDownloadLimit = 5; final int _fileDownloadLimit = 5;
final int _embeddingFetchLimit = 200; final int _embeddingFetchLimit = 200;
final int _kForceClusteringFaceCount = 4000; final int _kForceClusteringFaceCount = 8000;
Future<void> init({bool initializeImageMlIsolate = false}) async { Future<void> init({bool initializeImageMlIsolate = false}) async {
if (LocalSettings.instance.isFaceIndexingEnabled == false) { if (LocalSettings.instance.isFaceIndexingEnabled == false) {
@ -163,9 +164,16 @@ class FaceMlService {
pauseIndexingAndClustering(); pauseIndexingAndClustering();
} }
}); });
if (Platform.isIOS &&
MachineLearningController.instance.isDeviceHealthy) {
_logger.info("Starting face indexing and clustering on iOS from init");
unawaited(indexAndClusterAll());
}
_listenIndexOnDiffSync(); _listenIndexOnDiffSync();
_listenOnPeopleChangedSync(); _listenOnPeopleChangedSync();
_logger.info('init done');
}); });
} }
@ -1016,9 +1024,13 @@ class FaceMlService {
File? file; File? file;
if (enteFile.fileType == FileType.video) { if (enteFile.fileType == FileType.video) {
try { try {
file = await getThumbnailForUploadedFile(enteFile); file = await getThumbnailForUploadedFile(enteFile);
} on PlatformException catch (e, s) { } on PlatformException catch (e, s) {
_logger.severe("Could not get thumbnail for $enteFile due to PlatformException", e, s); _logger.severe(
"Could not get thumbnail for $enteFile due to PlatformException",
e,
s,
);
throw ThumbnailRetrievalException(e.toString(), s); throw ThumbnailRetrievalException(e.toString(), s);
} }
} else { } else {

View file

@ -4,7 +4,6 @@ import "dart:io";
import "package:battery_info/battery_info_plugin.dart"; import "package:battery_info/battery_info_plugin.dart";
import "package:battery_info/model/android_battery_info.dart"; import "package:battery_info/model/android_battery_info.dart";
import "package:battery_info/model/iso_battery_info.dart"; import "package:battery_info/model/iso_battery_info.dart";
import "package:flutter/foundation.dart" show kDebugMode;
import "package:logging/logging.dart"; import "package:logging/logging.dart";
import "package:photos/core/event_bus.dart"; import "package:photos/core/event_bus.dart";
import "package:photos/events/machine_learning_control_event.dart"; import "package:photos/events/machine_learning_control_event.dart";
@ -19,8 +18,7 @@ class MachineLearningController {
static const kMaximumTemperature = 42; // 42 degree celsius static const kMaximumTemperature = 42; // 42 degree celsius
static const kMinimumBatteryLevel = 20; // 20% static const kMinimumBatteryLevel = 20; // 20%
static const kDefaultInteractionTimeout = static const kDefaultInteractionTimeout = Duration(seconds: 10);
kDebugMode ? Duration(seconds: 3) : Duration(seconds: 5);
static const kUnhealthyStates = ["over_heat", "over_voltage", "dead"]; static const kUnhealthyStates = ["over_heat", "over_voltage", "dead"];
bool _isDeviceHealthy = true; bool _isDeviceHealthy = true;
@ -31,6 +29,7 @@ class MachineLearningController {
bool get isDeviceHealthy => _isDeviceHealthy; bool get isDeviceHealthy => _isDeviceHealthy;
void init() { void init() {
_logger.info('init called');
if (Platform.isAndroid) { if (Platform.isAndroid) {
_startInteractionTimer(); _startInteractionTimer();
BatteryInfoPlugin() BatteryInfoPlugin()
@ -47,6 +46,7 @@ class MachineLearningController {
}); });
} }
_fireControlEvent(); _fireControlEvent();
_logger.info('init done');
} }
void onUserInteraction() { void onUserInteraction() {

View file

@ -1,65 +0,0 @@
// import "dart:typed_data";
// import 'dart:ui' as ui;
// import 'package:flutter/material.dart';
// import 'package:flutter/rendering.dart';
// class Captures {
// static Future<Uint8List> capture(GlobalKey key) async {
// final double pixelRatio =
// MediaQuery.of(key.currentContext!).devicePixelRatio;
// final RenderRepaintBoundary boundary =
// key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
// final ui.Image image = await boundary.toImage(pixelRatio: pixelRatio);
// final ByteData? byteData =
// await image.toByteData(format: ui.ImageByteFormat.png);
// final Uint8List pngBytes = byteData!.buffer.asUint8List();
// print("PNG BYTES ====== ${pngBytes}");
// return pngBytes;
// }
// }
import "dart:io";
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import "package:path_provider/path_provider.dart";
class Captures {
Future<Uint8List?> capture(GlobalKey key) async {
try {
final double pixelRatio =
MediaQuery.of(key.currentContext!).devicePixelRatio;
final RenderRepaintBoundary boundary =
key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final ui.Image image = await boundary.toImage(pixelRatio: pixelRatio);
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
final Uint8List pngBytes = byteData!.buffer.asUint8List();
print("PNG BYTES ====== ${pngBytes}");
return pngBytes;
} catch (e) {
print(e);
}
return null;
}
Future<String> saveImage(GlobalKey key) async {
String path = "";
try {
final Uint8List? bytes = await capture(key);
final Directory root = await getTemporaryDirectory();
final String directoryPath = '${root.path}/enteTempFiles';
// Create the directory if it doesn't exist
final DateTime timeStamp = DateTime.now();
await Directory(directoryPath).create(recursive: true);
final String filePath = '$directoryPath/$timeStamp.jpg';
final file = await File(filePath).writeAsBytes(bytes!);
path = file.path;
} catch (e) {
debugPrint(e.toString());
}
return path;
}
}

View file

@ -1,790 +0,0 @@
import "dart:ui";
import "package:figma_squircle/figma_squircle.dart";
import "package:flutter/material.dart";
import "package:photos/models/file/file.dart";
import "package:photos/ui/TEMP/captureImage.dart";
import "package:photos/ui/TEMP/widget_to_image.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
class ShowImagePreviewFromTap extends StatefulWidget {
const ShowImagePreviewFromTap({
required this.tempEnteFile,
super.key,
});
final List<EnteFile> tempEnteFile;
@override
State<ShowImagePreviewFromTap> createState() =>
_ShowImagePreviewFromTapState();
}
class _ShowImagePreviewFromTapState extends State<ShowImagePreviewFromTap> {
// late String tempImagePath;
// final ValueNotifier<Uint8List?> bytesNotifier =
// ValueNotifier<Uint8List?>(null);
late GlobalKey _widgetImageKey;
final ValueNotifier<String?> tempImagePath = ValueNotifier<String?>(null);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
//delay of 1 second before capturing the image
await Future.delayed(const Duration(milliseconds: 100));
tempImagePath.value = await Captures().saveImage(_widgetImageKey);
Navigator.of(context).pop(tempImagePath.value);
});
}
@override
Widget build(BuildContext context) {
final int length = widget.tempEnteFile.length;
Widget placeholderWidget = const SizedBox(
height: 250,
width: 250,
);
if (length == 1) {
placeholderWidget = BackDrop(
backDropImage: widget.tempEnteFile[0],
children: [
Padding(
padding: const EdgeInsets.all(18.0),
child: ClipSmoothRect(
radius: SmoothBorderRadius(
cornerRadius: 7.5,
cornerSmoothing: 1,
),
child: ThumbnailWidget(
widget.tempEnteFile[0],
shouldShowArchiveStatus: false,
shouldShowSyncStatus: false,
),
),
),
],
);
} else if (length == 2) {
placeholderWidget = BackDrop(
backDropImage: widget.tempEnteFile[0],
children: [
Positioned(
top: 65,
left: 90,
child: CustomImage(
height: 100,
width: 100,
collages: widget.tempEnteFile[0],
zIndex: 0.2,
),
),
Positioned(
top: 20,
left: 0,
child: CustomImage(
height: 100,
width: 100,
collages: widget.tempEnteFile[1],
zIndex: -0.2,
),
),
],
);
} else if (length == 3) {
placeholderWidget = BackDrop(
backDropImage: widget.tempEnteFile[0],
children: [
Positioned(
top: 30,
left: 0,
child: CustomImage(
height: 80,
width: 80,
collages: widget.tempEnteFile[1],
zIndex: -0.4,
),
),
Positioned(
top: 80,
left: 110,
child: CustomImage(
height: 80,
width: 80,
collages: widget.tempEnteFile[2],
zIndex: 0.4,
),
),
Positioned(
top: 40,
left: 40,
child: CustomImage(
height: 100,
width: 100,
collages: widget.tempEnteFile[0],
zIndex: 0.0,
),
),
],
);
} else if (length > 3) {
placeholderWidget = BackDrop(
backDropImage: widget.tempEnteFile[0],
children: [
Positioned(
top: 10,
left: 10,
child: CustomImage(
height: 80,
width: 80,
collages: widget.tempEnteFile[1],
zIndex: 0,
),
),
Positioned(
top: 95,
left: 30,
child: CustomImage(
height: 80,
width: 80,
collages: widget.tempEnteFile[2],
zIndex: 0,
),
),
Positioned(
top: 35,
left: 60,
child: CustomImage(
height: 100,
width: 100,
collages: widget.tempEnteFile[0],
zIndex: 0.0,
),
),
Positioned(
top: 15,
left: 140,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"+" "$length",
style: const TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
),
),
],
);
}
return Offstage(
offstage: false,
child: Center(
child: Column(
children: [
WidgetToImage(
builder: (key) {
_widgetImageKey = key;
return placeholderWidget;
},
),
],
),
),
);
}
}
class BackDrop extends StatelessWidget {
const BackDrop({
super.key,
required this.backDropImage,
required this.children,
});
final List<Widget> children;
final EnteFile backDropImage;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(4.0),
height: 200,
width: 200,
child: Stack(
children: [
ClipSmoothRect(
radius: SmoothBorderRadius(
cornerRadius: 7.5,
cornerSmoothing: 1,
),
child: ThumbnailWidget(
backDropImage,
shouldShowArchiveStatus: false,
shouldShowSyncStatus: false,
),
),
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
child: Container(
color: Colors.transparent,
),
),
...children,
],
),
);
}
}
class CustomImage extends StatelessWidget {
const CustomImage({
required this.width,
required this.height,
super.key,
required this.collages,
required this.zIndex,
});
final EnteFile collages;
final double zIndex;
final double height;
final double width;
@override
Widget build(BuildContext context) {
return Container(
transform: Matrix4.rotationZ(zIndex),
height: height,
width: width,
child: ClipSmoothRect(
radius: SmoothBorderRadius(
cornerRadius: 7.5,
cornerSmoothing: 1,
),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: ThumbnailWidget(
collages,
shouldShowArchiveStatus: false,
shouldShowSyncStatus: false,
),
),
);
}
}
// import "dart:ui";
// import "package:figma_squircle/figma_squircle.dart";
// import "package:flutter/material.dart";
// import "package:photos/models/file/file.dart";
// import "package:photos/ui/TEMP/captureImage.dart";
// import "package:photos/ui/TEMP/widget_to_image.dart";
// import "package:photos/ui/viewer/file/thumbnail_widget.dart";
// class ShowImagePrev {
// late GlobalKey _widgetImageKey;
// final ValueNotifier<String?> tempImagePath = ValueNotifier<String?>(null);
// Future<String?> imageToWidgetFunction(List<EnteFile> tempEnteFile) async {
// showImagePreviewFromTap(tempEnteFile);
// await Future.delayed(const Duration(milliseconds: 100));
// tempImagePath.value = await Captures().saveImage(_widgetImageKey);
// print("VALUE IS ==================${tempImagePath.value}");
// if (tempImagePath.value != null) {
// return tempImagePath.value;
// }
// return null;
// }
// Widget showImagePreviewFromTap(List<EnteFile> tempEnteFile) {
// final int length = tempEnteFile.length;
// Widget placeholderWidget = const SizedBox(
// height: 250,
// width: 250,
// );
// if (length == 1) {
// placeholderWidget = BackDrop(
// backDropImage: tempEnteFile[0],
// children: [
// Padding(
// padding: const EdgeInsets.all(18.0),
// child: ClipSmoothRect(
// radius: SmoothBorderRadius(
// cornerRadius: 7.5,
// cornerSmoothing: 1,
// ),
// child: ThumbnailWidget(
// tempEnteFile[0],
// shouldShowArchiveStatus: false,
// shouldShowSyncStatus: false,
// ),
// ),
// ),
// ],
// );
// } else if (length == 2) {
// placeholderWidget = BackDrop(
// backDropImage: tempEnteFile[0],
// children: [
// Positioned(
// top: 65,
// left: 90,
// child: CustomImage(
// height: 100,
// width: 100,
// collages: tempEnteFile[0],
// zIndex: 0.2,
// ),
// ),
// Positioned(
// top: 20,
// left: 0,
// child: CustomImage(
// height: 100,
// width: 100,
// collages: tempEnteFile[1],
// zIndex: -0.2,
// ),
// ),
// ],
// );
// } else if (length == 3) {
// placeholderWidget = BackDrop(
// backDropImage: tempEnteFile[0],
// children: [
// Positioned(
// top: 30,
// left: 0,
// child: CustomImage(
// height: 80,
// width: 80,
// collages: tempEnteFile[1],
// zIndex: -0.4,
// ),
// ),
// Positioned(
// top: 80,
// left: 110,
// child: CustomImage(
// height: 80,
// width: 80,
// collages: tempEnteFile[2],
// zIndex: 0.4,
// ),
// ),
// Positioned(
// top: 40,
// left: 40,
// child: CustomImage(
// height: 100,
// width: 100,
// collages: tempEnteFile[0],
// zIndex: 0.0,
// ),
// ),
// ],
// );
// } else if (length > 3) {
// placeholderWidget = BackDrop(
// backDropImage: tempEnteFile[0],
// children: [
// Positioned(
// top: 10,
// left: 10,
// child: CustomImage(
// height: 80,
// width: 80,
// collages: tempEnteFile[1],
// zIndex: 0,
// ),
// ),
// Positioned(
// top: 95,
// left: 30,
// child: CustomImage(
// height: 80,
// width: 80,
// collages: tempEnteFile[2],
// zIndex: 0,
// ),
// ),
// Positioned(
// top: 35,
// left: 60,
// child: CustomImage(
// height: 100,
// width: 100,
// collages: tempEnteFile[0],
// zIndex: 0.0,
// ),
// ),
// Positioned(
// top: 15,
// left: 140,
// child: Container(
// padding: const EdgeInsets.all(8),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(12),
// ),
// child: Text(
// "+ $length",
// style: const TextStyle(
// fontWeight: FontWeight.w600,
// color: Colors.black,
// ),
// ),
// ),
// ),
// ],
// );
// }
// return Center(
// child: WidgetToImage(
// builder: (key) {
// _widgetImageKey = key;
// return placeholderWidget;
// },
// ),
// );
// }
// }
// class BackDrop extends StatelessWidget {
// const BackDrop({
// super.key,
// required this.backDropImage,
// required this.children,
// });
// final List<Widget> children;
// final EnteFile backDropImage;
// @override
// Widget build(BuildContext context) {
// return Container(
// padding: const EdgeInsets.all(4.0),
// height: 200,
// width: 200,
// child: Stack(
// children: [
// ClipSmoothRect(
// radius: SmoothBorderRadius(
// cornerRadius: 7.5,
// cornerSmoothing: 1,
// ),
// child: ThumbnailWidget(
// backDropImage,
// shouldShowArchiveStatus: false,
// shouldShowSyncStatus: false,
// ),
// ),
// BackdropFilter(
// filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
// child: Container(
// color: Colors.transparent,
// ),
// ),
// ...children,
// ],
// ),
// );
// }
// }
// class CustomImage extends StatelessWidget {
// const CustomImage({
// required this.width,
// required this.height,
// super.key,
// required this.collages,
// required this.zIndex,
// });
// final EnteFile collages;
// final double zIndex;
// final double height;
// final double width;
// @override
// Widget build(BuildContext context) {
// return Container(
// transform: Matrix4.rotationZ(zIndex),
// height: height,
// width: width,
// child: ClipSmoothRect(
// radius: SmoothBorderRadius(
// cornerRadius: 7.5,
// cornerSmoothing: 1,
// ),
// clipBehavior: Clip.antiAliasWithSaveLayer,
// child: ThumbnailWidget(
// collages,
// shouldShowArchiveStatus: false,
// shouldShowSyncStatus: false,
// ),
// ),
// );
// }
// }
// import "dart:ui";
// import "package:figma_squircle/figma_squircle.dart";
// import "package:flutter/material.dart";
// import "package:photos/models/file/file.dart";
// import "package:photos/ui/TEMP/captureImage.dart";
// import "package:photos/ui/TEMP/widget_to_image.dart";
// import "package:photos/ui/viewer/file/thumbnail_widget.dart";
// class ShowImagePrev {
// late GlobalKey _widgetImageKey;
// final ValueNotifier<String?> tempImagePath = ValueNotifier<String?>(null);
// ShowImagePrev() {
// _widgetImageKey = GlobalKey();
// }
// Future<String?> imageToWidgetFunction(List<EnteFile> tempEnteFile) async {
// showImagePreviewFromTap(tempEnteFile);
// // Build the widget to ensure the GlobalKey is assigned correctly
// WidgetsBinding.instance.addPostFrameCallback((_) async {
// await Future.delayed(const Duration(milliseconds: 100));
// tempImagePath.value = await Captures().saveImage(_widgetImageKey);
// print("VALUE IS ==================${tempImagePath.value}");
// });
// return tempImagePath.value;
// }
// Widget showImagePreviewFromTap(List<EnteFile> tempEnteFile) {
// final int length = tempEnteFile.length;
// Widget placeholderWidget = const SizedBox(
// height: 250,
// width: 250,
// );
// if (length == 1) {
// placeholderWidget = BackDrop(
// backDropImage: tempEnteFile[0],
// children: [
// Padding(
// padding: const EdgeInsets.all(18.0),
// child: ClipSmoothRect(
// radius: SmoothBorderRadius(
// cornerRadius: 7.5,
// cornerSmoothing: 1,
// ),
// child: ThumbnailWidget(
// tempEnteFile[0],
// shouldShowArchiveStatus: false,
// shouldShowSyncStatus: false,
// ),
// ),
// ),
// ],
// );
// } else if (length == 2) {
// placeholderWidget = BackDrop(
// backDropImage: tempEnteFile[0],
// children: [
// Positioned(
// top: 65,
// left: 90,
// child: CustomImage(
// height: 100,
// width: 100,
// collages: tempEnteFile[0],
// zIndex: 0.2,
// ),
// ),
// Positioned(
// top: 20,
// left: 0,
// child: CustomImage(
// height: 100,
// width: 100,
// collages: tempEnteFile[1],
// zIndex: -0.2,
// ),
// ),
// ],
// );
// } else if (length == 3) {
// placeholderWidget = BackDrop(
// backDropImage: tempEnteFile[0],
// children: [
// Positioned(
// top: 30,
// left: 0,
// child: CustomImage(
// height: 80,
// width: 80,
// collages: tempEnteFile[1],
// zIndex: -0.4,
// ),
// ),
// Positioned(
// top: 80,
// left: 110,
// child: CustomImage(
// height: 80,
// width: 80,
// collages: tempEnteFile[2],
// zIndex: 0.4,
// ),
// ),
// Positioned(
// top: 40,
// left: 40,
// child: CustomImage(
// height: 100,
// width: 100,
// collages: tempEnteFile[0],
// zIndex: 0.0,
// ),
// ),
// ],
// );
// } else if (length > 3) {
// placeholderWidget = BackDrop(
// backDropImage: tempEnteFile[0],
// children: [
// Positioned(
// top: 10,
// left: 10,
// child: CustomImage(
// height: 80,
// width: 80,
// collages: tempEnteFile[1],
// zIndex: 0,
// ),
// ),
// Positioned(
// top: 95,
// left: 30,
// child: CustomImage(
// height: 80,
// width: 80,
// collages: tempEnteFile[2],
// zIndex: 0,
// ),
// ),
// Positioned(
// top: 35,
// left: 60,
// child: CustomImage(
// height: 100,
// width: 100,
// collages: tempEnteFile[0],
// zIndex: 0.0,
// ),
// ),
// Positioned(
// top: 15,
// left: 140,
// child: Container(
// padding: const EdgeInsets.all(8),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(12),
// ),
// child: Text(
// "+ $length",
// style: const TextStyle(
// fontWeight: FontWeight.w600,
// color: Colors.black,
// ),
// ),
// ),
// ),
// ],
// );
// }
// return Center(
// child: WidgetToImage(
// builder: (key) {
// _widgetImageKey = key;
// return placeholderWidget;
// },
// ),
// );
// }
// }
// class BackDrop extends StatelessWidget {
// const BackDrop({
// super.key,
// required this.backDropImage,
// required this.children,
// });
// final List<Widget> children;
// final EnteFile backDropImage;
// @override
// Widget build(BuildContext context) {
// return Container(
// padding: const EdgeInsets.all(4.0),
// height: 200,
// width: 200,
// child: Stack(
// children: [
// ClipSmoothRect(
// radius: SmoothBorderRadius(
// cornerRadius: 7.5,
// cornerSmoothing: 1,
// ),
// child: ThumbnailWidget(
// backDropImage,
// shouldShowArchiveStatus: false,
// shouldShowSyncStatus: false,
// ),
// ),
// BackdropFilter(
// filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
// child: Container(
// color: Colors.transparent,
// ),
// ),
// ...children,
// ],
// ),
// );
// }
// }
// class CustomImage extends StatelessWidget {
// const CustomImage({
// required this.width,
// required this.height,
// super.key,
// required this.collages,
// required this.zIndex,
// });
// final EnteFile collages;
// final double zIndex;
// final double height;
// final double width;
// @override
// Widget build(BuildContext context) {
// return Container(
// transform: Matrix4.rotationZ(zIndex),
// height: height,
// width: width,
// child: ClipSmoothRect(
// radius: SmoothBorderRadius(
// cornerRadius: 7.5,
// cornerSmoothing: 1,
// ),
// clipBehavior: Clip.antiAliasWithSaveLayer,
// child: ThumbnailWidget(
// collages,
// shouldShowArchiveStatus: false,
// shouldShowSyncStatus: false,
// ),
// ),
// );
// }
// }

View file

@ -1,19 +0,0 @@
import "package:flutter/material.dart";
class WidgetToImage extends StatefulWidget {
const WidgetToImage({super.key, required this.builder});
final Function(GlobalKey key) builder;
@override
State<WidgetToImage> createState() => _WidgetToImageState();
}
class _WidgetToImageState extends State<WidgetToImage> {
final globalKey = GlobalKey();
@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: globalKey,
child: widget.builder(globalKey),
);
}
}

View file

@ -110,7 +110,6 @@ class CollectionActions {
BuildContext context, BuildContext context,
List<EnteFile> files, List<EnteFile> files,
) async { ) async {
print("CREATED LINK");
final dialog = createProgressDialog( final dialog = createProgressDialog(
context, context,
S.of(context).creatingLink, S.of(context).creatingLink,

View file

@ -89,8 +89,8 @@ class _MachineLearningSettingsPageState
iconButtonType: IconButtonType.secondary, iconButtonType: IconButtonType.secondary,
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
Navigator.pop(context); if (Navigator.canPop(context)) Navigator.pop(context);
Navigator.pop(context); if (Navigator.canPop(context)) Navigator.pop(context);
}, },
), ),
], ],

View file

@ -25,7 +25,6 @@ import 'package:photos/services/machine_learning/face_ml/feedback/cluster_feedba
import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import "package:photos/theme/colors.dart"; import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart"; import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/TEMP/show_images_prevew.dart";
import 'package:photos/ui/actions/collection/collection_file_actions.dart'; import 'package:photos/ui/actions/collection/collection_file_actions.dart';
import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
import 'package:photos/ui/collections/collection_action_sheet.dart'; import 'package:photos/ui/collections/collection_action_sheet.dart';
@ -603,25 +602,6 @@ class _FileSelectionActionsWidgetState
} }
} }
ValueNotifier<String?> generatedPathNotifier = ValueNotifier<String?>(null);
//Future function to go to next page
Future<String?> _nextPageForTesting(List<EnteFile> tempfile) async {
final String? tempImagePath = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ShowImagePreviewFromTap(
tempEnteFile: tempfile,
),
),
);
if (tempImagePath != null) {
print("Temp image path: $tempImagePath");
return tempImagePath;
}
return tempImagePath;
}
Future<void> _onCreatedSharedLinkClicked() async { Future<void> _onCreatedSharedLinkClicked() async {
if (split.ownedByCurrentUser.isEmpty) { if (split.ownedByCurrentUser.isEmpty) {
showShortToast( showShortToast(
@ -632,26 +612,16 @@ class _FileSelectionActionsWidgetState
} }
_cachedCollectionForSharedLink ??= await collectionActions _cachedCollectionForSharedLink ??= await collectionActions
.createSharedCollectionLink(context, split.ownedByCurrentUser); .createSharedCollectionLink(context, split.ownedByCurrentUser);
final List<EnteFile> tempEnteFile = split.ownedByCurrentUser;
final actionResult = await showActionSheet( final actionResult = await showActionSheet(
context: context, context: context,
buttons: [ buttons: [
ButtonWidget( ButtonWidget(
labelText: S.of(context).shareLink, labelText: S.of(context).copyLink,
buttonType: ButtonType.neutral, buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large, buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true, shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first, buttonAction: ButtonAction.first,
isInAlert: true, isInAlert: true,
// onTap: () async {
// // Add a return statement at the end of the function
// generatedPathNotifier.value =
// await _nextPageForTesting(tempEnteFile);
// await shareText(generatedPathNotifier.value!);
// return Future<void>.value();
// },
), ),
ButtonWidget( ButtonWidget(
labelText: S.of(context).manageLink, labelText: S.of(context).manageLink,
@ -676,7 +646,6 @@ class _FileSelectionActionsWidgetState
); );
if (actionResult?.action != null) { if (actionResult?.action != null) {
if (actionResult!.action == ButtonAction.first) { if (actionResult!.action == ButtonAction.first) {
//generatedPathNotifier.value = await _nextPageForTesting(tempEnteFile);
await _copyLink(); await _copyLink();
} }
if (actionResult.action == ButtonAction.second) { if (actionResult.action == ButtonAction.second) {
@ -788,7 +757,6 @@ class _FileSelectionActionsWidgetState
} }
Future<void> _copyLink() async { Future<void> _copyLink() async {
print("INSIDE COPY LINK");
if (_cachedCollectionForSharedLink != null) { if (_cachedCollectionForSharedLink != null) {
final String collectionKey = Base58Encode( final String collectionKey = Base58Encode(
CollectionsService.instance CollectionsService.instance
@ -796,8 +764,6 @@ class _FileSelectionActionsWidgetState
); );
final String url = final String url =
"${_cachedCollectionForSharedLink!.publicURLs?.first?.url}#$collectionKey"; "${_cachedCollectionForSharedLink!.publicURLs?.first?.url}#$collectionKey";
await shareText(url);
//await shareImageAndUrl(context, generatedPathNotifier.value!, url);
await Clipboard.setData(ClipboardData(text: url)); await Clipboard.setData(ClipboardData(text: url));
showShortToast(context, S.of(context).linkCopiedToClipboard); showShortToast(context, S.of(context).linkCopiedToClipboard);
} }

View file

@ -97,7 +97,7 @@ class _AppBarWidgetState extends State<ClusterAppBar> {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
actions: kDebugMode ? _getDefaultActions(context) : null, actions: _getDefaultActions(context),
); );
} }

View file

@ -161,43 +161,45 @@ class _ClusterPageState extends State<ClusterPage> {
), ),
), ),
showNamingBanner showNamingBanner
? Dismissible( ? SafeArea(
key: const Key("namingBanner"), child: Dismissible(
direction: DismissDirection.horizontal, key: const Key("namingBanner"),
onDismissed: (direction) { direction: DismissDirection.horizontal,
setState(() { onDismissed: (direction) {
userDismissedNamingBanner = true; setState(() {
}); userDismissedNamingBanner = true;
}, });
child: PeopleBanner(
type: PeopleBannerType.addName,
faceWidget: PersonFaceWidget(
files.first,
clusterID: widget.clusterID,
),
actionIcon: Icons.add_outlined,
text: S.of(context).addAName,
subText: S.of(context).findPeopleByName,
onTap: () async {
if (widget.personID == null) {
final result = await showAssignPersonAction(
context,
clusterID: widget.clusterID,
);
if (result != null &&
result is (PersonEntity, EnteFile)) {
Navigator.pop(context);
// ignore: unawaited_futures
routeToPage(context, PeoplePage(person: result.$1));
} else if (result != null && result is PersonEntity) {
Navigator.pop(context);
// ignore: unawaited_futures
routeToPage(context, PeoplePage(person: result));
}
} else {
showShortToast(context, "No personID or clusterID");
}
}, },
child: PeopleBanner(
type: PeopleBannerType.addName,
faceWidget: PersonFaceWidget(
files.first,
clusterID: widget.clusterID,
),
actionIcon: Icons.add_outlined,
text: S.of(context).addAName,
subText: S.of(context).findPeopleByName,
onTap: () async {
if (widget.personID == null) {
final result = await showAssignPersonAction(
context,
clusterID: widget.clusterID,
);
if (result != null &&
result is (PersonEntity, EnteFile)) {
Navigator.pop(context);
// ignore: unawaited_futures
routeToPage(context, PeoplePage(person: result.$1));
} else if (result != null && result is PersonEntity) {
Navigator.pop(context);
// ignore: unawaited_futures
routeToPage(context, PeoplePage(person: result));
}
} else {
showShortToast(context, "No personID or clusterID");
}
},
),
), ),
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),

View file

@ -38,12 +38,17 @@ class _PersonClustersPageState extends State<PersonClustersPage> {
.getClusterFilesForPersonID(widget.person.remoteID), .getClusterFilesForPersonID(widget.person.remoteID),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
final List<int> keys = snapshot.data!.keys.toList(); final clusters = snapshot.data!;
final List<int> keys = clusters.keys.toList();
// Sort the clusters by the number of files in each cluster, largest first
keys.sort(
(b, a) => clusters[a]!.length.compareTo(clusters[b]!.length),
);
return ListView.builder( return ListView.builder(
itemCount: keys.length, itemCount: keys.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final int clusterID = keys[index]; final int clusterID = keys[index];
final List<EnteFile> files = snapshot.data![keys[index]]!; final List<EnteFile> files = clusters[clusterID]!;
return InkWell( return InkWell(
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
@ -93,34 +98,37 @@ class _PersonClustersPageState extends State<PersonClustersPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Text( Text(
"${snapshot.data![keys[index]]!.length} photos", "${files.length} photos",
style: getEnteTextTheme(context).body, style: getEnteTextTheme(context).body,
), ),
GestureDetector( (index != 0)
onTap: () async { ? GestureDetector(
try { onTap: () async {
await PersonService.instance try {
.removeClusterToPerson( await PersonService.instance
personID: widget.person.remoteID, .removeClusterToPerson(
clusterID: clusterID, personID: widget.person.remoteID,
); clusterID: clusterID,
_logger.info( );
"Removed cluster $clusterID from person ${widget.person.remoteID}", _logger.info(
); "Removed cluster $clusterID from person ${widget.person.remoteID}",
Bus.instance.fire(PeopleChangedEvent()); );
setState(() {}); Bus.instance
} catch (e) { .fire(PeopleChangedEvent());
_logger.severe( setState(() {});
"removing cluster from person,", } catch (e) {
e, _logger.severe(
); "removing cluster from person,",
} e,
}, );
child: const Icon( }
CupertinoIcons.minus_circled, },
color: Colors.red, child: const Icon(
), CupertinoIcons.minus_circled,
), color: Colors.red,
),
)
: const SizedBox.shrink(),
], ],
), ),
), ),

View file

@ -218,11 +218,3 @@ void shareSelected(
shareButtonKey: shareButtonKey, shareButtonKey: shareButtonKey,
); );
} }
Future<void> shareImageAndUrl(
BuildContext context,
String imagePath,
String url,
) async {
await Share.shareFiles([imagePath], text: url);
}

View file

@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.8.110+634 version: 0.8.112+636
publish_to: none publish_to: none
environment: environment:

View file

@ -153,12 +153,12 @@ const Title_ = styled("div")`
`; `;
interface WatchList { interface WatchList {
watches: FolderWatch[]; watches: FolderWatch[] | undefined;
removeWatch: (watch: FolderWatch) => void; removeWatch: (watch: FolderWatch) => void;
} }
const WatchList: React.FC<WatchList> = ({ watches, removeWatch }) => { const WatchList: React.FC<WatchList> = ({ watches, removeWatch }) => {
return watches.length === 0 ? ( return (watches ?? []).length === 0 ? (
<NoWatches /> <NoWatches />
) : ( ) : (
<WatchesContainer> <WatchesContainer>