diff --git a/docs/docs/.vitepress/sidebar.ts b/docs/docs/.vitepress/sidebar.ts index 7e25c3c132..d7c0c20d5d 100644 --- a/docs/docs/.vitepress/sidebar.ts +++ b/docs/docs/.vitepress/sidebar.ts @@ -142,6 +142,7 @@ export const sidebar = [ text: "Video streaming", link: "/photos/faq/video-streaming", }, + { text: "Misc", link: "/photos/faq/misc" }, ], }, { diff --git a/docs/docs/photos/faq/misc.md b/docs/docs/photos/faq/misc.md new file mode 100644 index 0000000000..79549c9954 --- /dev/null +++ b/docs/docs/photos/faq/misc.md @@ -0,0 +1,20 @@ +--- +title: Miscellaneous general FAQ +description: Unsorted frequently asked questions about Ente Photos +--- + +# Miscellaneous FAQ + +## Exif Description + +Ente will try to read as much information from Exif when the image is uploaded, +but after that, only the fields which have been parsed into Ente can be +searched. + +The app still show all the fields in the raw Exif data in the file info panel +when someone taps on the "View all Exif" option, but otherwise the app is +unaware of these fields. + +In particular, for the description associated with a photo, the exact logic to +determine the description from the Exif when uploading the image can be seen +[in this part of the code](https://github.com/ente-io/ente/blob/0dcb185744da469848b41b668fe4b647226b6fe2/web/packages/gallery/services/exif.ts#L609-L620). diff --git a/mobile/lib/services/home_widget_service.dart b/mobile/lib/services/home_widget_service.dart index 0fc5d6d49c..8e11dfa574 100644 --- a/mobile/lib/services/home_widget_service.dart +++ b/mobile/lib/services/home_widget_service.dart @@ -101,11 +101,13 @@ class HomeWidgetService { await setData(key, path); + final subText = await SmartMemoriesService.getDateFormattedLocale( + creationTime: ogFile.creationTime!, + ); + final data = { "title": title, - "subText": SmartMemoriesService.getDateFormatted( - creationTime: ogFile.creationTime!, - ), + "subText": subText, "generatedId": ogFile.generatedID!, }; if (Platform.isIOS) { diff --git a/mobile/lib/services/language_service.dart b/mobile/lib/services/language_service.dart new file mode 100644 index 0000000000..a1a3a851a4 --- /dev/null +++ b/mobile/lib/services/language_service.dart @@ -0,0 +1,16 @@ +import "package:photos/generated/l10n.dart"; +import "package:photos/l10n/l10n.dart"; + +class LanguageService { + static Future get s async { + try { + return S.current; + } catch (_) {} + + final local = await getLocale(); + + final s = await S.load(local!); + + return s; + } +} diff --git a/mobile/lib/services/memory_home_widget_service.dart b/mobile/lib/services/memory_home_widget_service.dart index 7e24bed4a6..d40b8fb1a3 100644 --- a/mobile/lib/services/memory_home_widget_service.dart +++ b/mobile/lib/services/memory_home_widget_service.dart @@ -68,6 +68,10 @@ class MemoryHomeWidgetService { } await _memoryForceRefreshLock.synchronized(() async { + final result = await hasAnyBlockers(); + if (result) { + return; + } final isTotalEmpty = await _checkIfTotalEmpty(); forceFetchNewMemories ??= await getForceFetchCondition(isTotalEmpty); @@ -138,13 +142,11 @@ class MemoryHomeWidgetService { return {}; } - // flatten the memories to a list of files and take first 50 - final files = memories.take(50).toList().asMap().map( - (k, v) => MapEntry( - v.title, - v.memories.map((e) => e.file), - ), - ); + final files = Map.fromEntries( + memories.map((m) { + return MapEntry(m.title, m.memories.map((e) => e.file).toList()); + }).take(50), + ); return files; } diff --git a/mobile/lib/services/search_service.dart b/mobile/lib/services/search_service.dart index 1407e80958..b68a597916 100644 --- a/mobile/lib/services/search_service.dart +++ b/mobile/lib/services/search_service.dart @@ -1257,7 +1257,7 @@ class SearchService { searchResults.add( GenericSearchResult( ResultType.event, - memory.title, + memory.title + "(I)", files, hierarchicalSearchFilter: TopLevelGenericFilter( filterName: memory.title, diff --git a/mobile/lib/services/smart_memories_service.dart b/mobile/lib/services/smart_memories_service.dart index 5234f1afca..493fbd1a31 100644 --- a/mobile/lib/services/smart_memories_service.dart +++ b/mobile/lib/services/smart_memories_service.dart @@ -13,7 +13,6 @@ import "package:photos/core/constants.dart"; import "package:photos/db/memories_db.dart"; import "package:photos/db/ml/db.dart"; import "package:photos/extensions/stop_watch.dart"; -import "package:photos/generated/l10n.dart"; import "package:photos/l10n/l10n.dart"; import "package:photos/models/base_location.dart"; import "package:photos/models/file/file.dart"; @@ -31,6 +30,7 @@ import "package:photos/models/ml/face/face_with_embedding.dart"; import "package:photos/models/ml/face/person.dart"; import "package:photos/models/ml/vector.dart"; import "package:photos/service_locator.dart"; +import "package:photos/services/language_service.dart"; import "package:photos/services/location_service.dart"; import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; import "package:photos/services/machine_learning/ml_computer.dart"; @@ -119,7 +119,8 @@ class SmartMemoriesService { final local = await getLocale(); final languageCode = local?.languageCode ?? "en"; - final s = await S.load(local!); + final s = await LanguageService.s; + _logger.finest('get locale and S $t'); _logger.finest('all data fetched $t at ${DateTime.now()}, to computer'); @@ -315,6 +316,15 @@ class SmartMemoriesService { final seenTimes = await _memoriesDB.getSeenTimes(); final fillerMemories = await _getFillerResults(allFiles, now, seenTimes: seenTimes); + + final local = await getLocale(); + final languageCode = local?.languageCode ?? "en"; + final s = await LanguageService.s; + + _logger.finest('get locale and S'); + for (final memory in fillerMemories) { + memory.title = memory.createTitle(s, languageCode); + } return fillerMemories; } @@ -1534,12 +1544,26 @@ class SmartMemoriesService { return memoryResults; } + static Future getDateFormattedLocale({ + required int creationTime, + }) async { + final locale = await getLocale(); + + return getDateFormatted( + creationTime: creationTime, + languageCode: locale!.languageCode, + ); + } + static String getDateFormatted({ required int creationTime, BuildContext? context, + String? languageCode, }) { return DateFormat.yMMMd( - context != null ? Localizations.localeOf(context).languageCode : "en", + context != null + ? Localizations.localeOf(context).languageCode + : languageCode ?? "en", ).format( DateTime.fromMicrosecondsSinceEpoch(creationTime), ); diff --git a/mobile/lib/ui/home/memories/memory_cover_widget.dart b/mobile/lib/ui/home/memories/memory_cover_widget.dart index 5e5b9c96a2..55f8db652c 100644 --- a/mobile/lib/ui/home/memories/memory_cover_widget.dart +++ b/mobile/lib/ui/home/memories/memory_cover_widget.dart @@ -1,6 +1,5 @@ import "package:flutter/material.dart"; import "package:flutter/scheduler.dart"; -import "package:photos/generated/l10n.dart"; import "package:photos/models/memories/memory.dart"; import "package:photos/theme/colors.dart"; import "package:photos/theme/effects.dart"; @@ -19,7 +18,7 @@ class MemoryCoverWidget extends StatefulWidget { static const aspectRatio = 0.68; static const horizontalPadding = 2.5; final double maxScaleOffsetX; - final String? title; + final String title; const MemoryCoverWidget({ required this.memories, @@ -28,7 +27,7 @@ class MemoryCoverWidget extends StatefulWidget { required this.maxHeight, required this.maxWidth, required this.maxScaleOffsetX, - this.title, + required this.title, super.key, }); @@ -47,11 +46,8 @@ class _MemoryCoverWidgetState extends State { final widthOfScreen = MediaQuery.sizeOf(context).width; final index = _getNextMemoryIndex(); - final title = widget.title != null - ? widget.title! == "filler" - ? _getTitle(widget.memories[index]) - : widget.title! - : _getTitle(widget.memories[index]); + final title = widget.title; + final memory = widget.memories[index]; final isSeen = memory.isSeen(); final brightness = @@ -246,11 +242,4 @@ class _MemoryCoverWidgetState extends State { } return lastSeenIndex + 1; } - - String _getTitle(Memory memory) { - final present = DateTime.now(); - final then = DateTime.fromMicrosecondsSinceEpoch(memory.file.creationTime!); - final diffInYears = present.year - then.year; - return S.of(context).yearsAgo(diffInYears); - } } diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index d034c1cc2e..3ad966b2c0 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1030 +version: 1.0.0+1031 publish_to: none environment: diff --git a/server/compose.yaml b/server/compose.yaml index eb05b889a5..1def7f528e 100644 --- a/server/compose.yaml +++ b/server/compose.yaml @@ -73,6 +73,8 @@ services: configs: credentials_yaml: + # You'll need to recreate the containers (docker compose down && docker + # compose up) when changing this inline config for the changes to apply. content: | db: host: postgres diff --git a/server/docs/quickstart.md b/server/docs/quickstart.md index a24720b7c1..9e9760c8f3 100644 --- a/server/docs/quickstart.md +++ b/server/docs/quickstart.md @@ -91,7 +91,13 @@ docker compose down ``` Apart from this `my-ente` directory, the script does not install anything else -on your system. All persistent data is saved in volumes managed by Docker. +on your system. Settings and credentials are saved in `my-ente`, while other +persistent data is saved in volumes managed by Docker. + +> [!IMPORTANT] +> +> The `museum.yaml` contains your (unique) autogenerated museum, DB and S3 +> credentials, without which the data on your volumes will not be accessible. > [!CAUTION] > diff --git a/web/packages/gallery/components/viewer/FileViewer.tsx b/web/packages/gallery/components/viewer/FileViewer.tsx index 643eb620bf..2c16dd77be 100644 --- a/web/packages/gallery/components/viewer/FileViewer.tsx +++ b/web/packages/gallery/components/viewer/FileViewer.tsx @@ -827,8 +827,10 @@ export const FileViewer: React.FC = ({ }, [handleClose, files]); useEffect(() => { - psRef.current?.refreshCurrentSlideFavoriteButtonIfNeeded(); - }, [favoriteFileIDs, pendingFavoriteUpdates]); + if (open) { + psRef.current?.refreshCurrentSlideFavoriteButtonIfNeeded(); + } + }, [favoriteFileIDs, pendingFavoriteUpdates, open]); useEffect(() => { if (open) { @@ -840,7 +842,9 @@ export const FileViewer: React.FC = ({ disableDownload, haveUser, delegate: delegateRef.current!, - onClose: handleClose, + onClose: () => { + if (psRef.current) handleClose(); + }, onAnnotate: handleAnnotate, onViewInfo: handleViewInfo, onDownload: handleDownloadBarAction,