[mob][photos] Remove moments (#6541)

## Description

- Removes the "Moments" section for all users
- Puts memories debugging section behind internal and local setting

## Tests

Tested in debug mode on my pixel phone.
This commit is contained in:
Laurens Priem
2025-07-15 14:00:16 +02:00
committed by GitHub
5 changed files with 45 additions and 448 deletions

View File

@@ -13,7 +13,6 @@ import "package:photos/models/collection/collection.dart";
import "package:photos/models/collection/collection_items.dart";
import "package:photos/models/search/search_result.dart";
import "package:photos/models/typedefs.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/collections_service.dart";
import "package:photos/services/search_service.dart";
import "package:photos/ui/viewer/gallery/collection_page.dart";
@@ -44,8 +43,6 @@ enum SectionType {
face,
magic,
location,
// includes year, month , day, event ResultType
moment,
album,
// People section shows the files shared by other persons
contacts,
@@ -60,8 +57,6 @@ extension SectionTypeExtensions on SectionType {
return S.of(context).people;
case SectionType.magic:
return S.of(context).discover;
case SectionType.moment:
return S.of(context).moments;
case SectionType.location:
return S.of(context).locations;
case SectionType.contacts:
@@ -79,8 +74,6 @@ extension SectionTypeExtensions on SectionType {
return S.of(context).searchPersonsEmptySection;
case SectionType.magic:
return S.of(context).searchDiscoverEmptySection;
case SectionType.moment:
return S.of(context).searchDatesEmptySection;
case SectionType.location:
return S.of(context).searchLocationEmptySection;
case SectionType.contacts:
@@ -98,7 +91,6 @@ extension SectionTypeExtensions on SectionType {
switch (this) {
case SectionType.face:
case SectionType.magic:
case SectionType.moment:
case SectionType.fileTypesAndExtension:
return false;
case SectionType.location:
@@ -108,18 +100,15 @@ extension SectionTypeExtensions on SectionType {
}
}
// TODO: lau: check if we should sort moment again
bool get sortByName =>
this != SectionType.face &&
this != SectionType.magic &&
this != SectionType.moment &&
this != SectionType.contacts;
bool get isEmptyCTAVisible {
switch (this) {
case SectionType.face:
case SectionType.magic:
case SectionType.moment:
case SectionType.fileTypesAndExtension:
return false;
case SectionType.location:
@@ -137,8 +126,6 @@ extension SectionTypeExtensions on SectionType {
case SectionType.magic:
// todo: later
return "temp";
case SectionType.moment:
return S.of(context).addNew;
case SectionType.location:
return S.of(context).addNew;
case SectionType.contacts:
@@ -156,8 +143,6 @@ extension SectionTypeExtensions on SectionType {
return Icons.adaptive.arrow_forward_outlined;
case SectionType.magic:
return null;
case SectionType.moment:
return null;
case SectionType.location:
return Icons.add_location_alt_outlined;
case SectionType.contacts:
@@ -235,14 +220,6 @@ extension SectionTypeExtensions on SectionType {
return SearchService.instance.getAllFace(limit);
case SectionType.magic:
return SearchService.instance.getMagicSectionResults(context!);
case SectionType.moment:
if (flagService.internalUser) {
// TODO: lau: remove this whole smart memories and moment altogether
return SearchService.instance.smartMemories(context!, limit);
}
return SearchService.instance.getRandomMomentsSearchResults(context!);
case SectionType.location:
return SearchService.instance.getAllLocationTags(limit);

View File

@@ -3,7 +3,6 @@ import "dart:math";
import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "package:intl/intl.dart";
import 'package:logging/logging.dart';
import "package:path_provider/path_provider.dart";
import "package:photos/core/configuration.dart";
@@ -261,56 +260,6 @@ class SearchService {
}
}
Future<List<GenericSearchResult>> getRandomMomentsSearchResults(
BuildContext context,
) async {
try {
final nonNullSearchResults = <GenericSearchResult>[];
final randomYear = getRadomYearSearchResult();
final randomMonth = getRandomMonthSearchResult(context);
final randomDate = getRandomDateResults(context);
final randomHoliday = getRandomHolidaySearchResult(context);
final searchResults = await Future.wait(
[randomYear, randomMonth, randomDate, randomHoliday],
);
for (GenericSearchResult? searchResult in searchResults) {
if (searchResult != null) {
nonNullSearchResults.add(searchResult);
}
}
return nonNullSearchResults;
} catch (e) {
_logger.severe("Error getting RandomMomentsSearchResult", e);
return [];
}
}
Future<GenericSearchResult?> getRadomYearSearchResult() async {
for (var yearData in YearsData.instance.yearsData..shuffle()) {
final List<EnteFile> filesInYear =
await _getFilesInYear(yearData.duration);
if (filesInYear.isNotEmpty) {
return GenericSearchResult(
ResultType.year,
yearData.year,
filesInYear,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: yearData.year,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.year,
matchedUploadedIDs: filesToUploadedFileIDs(filesInYear),
filterIcon: Icons.calendar_month_outlined,
),
);
}
}
//todo this throws error
return null;
}
Future<List<GenericSearchResult>> getMonthSearchResults(
BuildContext context,
String query,
@@ -343,35 +292,6 @@ class SearchService {
return searchResults;
}
Future<GenericSearchResult?> getRandomMonthSearchResult(
BuildContext context,
) async {
final months = getMonthData(context)..shuffle();
for (MonthData month in months) {
final matchedFiles =
await FilesDB.instance.getFilesCreatedWithinDurations(
_getDurationsOfMonthInEveryYear(month.monthNumber),
ignoreCollections(),
order: 'DESC',
);
if (matchedFiles.isNotEmpty) {
return GenericSearchResult(
ResultType.month,
month.name,
matchedFiles,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: month.name,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.month,
matchedUploadedIDs: filesToUploadedFileIDs(matchedFiles),
filterIcon: Icons.calendar_month_outlined,
),
);
}
}
return null;
}
Future<List<GenericSearchResult>> getHolidaySearchResults(
BuildContext context,
String query,
@@ -411,35 +331,6 @@ class SearchService {
return searchResults;
}
Future<GenericSearchResult?> getRandomHolidaySearchResult(
BuildContext context,
) async {
final holidays = getHolidays(context)..shuffle();
for (var holiday in holidays) {
final matchedFiles =
await FilesDB.instance.getFilesCreatedWithinDurations(
_getDurationsForCalendarDateInEveryYear(holiday.day, holiday.month),
ignoreCollections(),
order: 'DESC',
);
if (matchedFiles.isNotEmpty) {
return GenericSearchResult(
ResultType.event,
holiday.name,
matchedFiles,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: holiday.name,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.event,
matchedUploadedIDs: filesToUploadedFileIDs(matchedFiles),
filterIcon: Icons.event_outlined,
),
);
}
}
return null;
}
Future<List<GenericSearchResult>> getFileTypeResults(
BuildContext context,
String query,
@@ -1334,56 +1225,6 @@ class SearchService {
return searchResults;
}
Future<GenericSearchResult?> getRandomDateResults(
BuildContext context,
) async {
final allFiles = await getAllFilesForSearch();
if (allFiles.isEmpty) return null;
final length = allFiles.length;
final randomFile = allFiles[Random().nextInt(length)];
final creationTime = randomFile.creationTime!;
final originalDateTime = DateTime.fromMicrosecondsSinceEpoch(creationTime);
final startOfDay = DateTime(
originalDateTime.year,
originalDateTime.month,
originalDateTime.day,
);
final endOfDay = DateTime(
originalDateTime.year,
originalDateTime.month,
originalDateTime.day + 1,
);
final durationOfDay = [
startOfDay.microsecondsSinceEpoch,
endOfDay.microsecondsSinceEpoch,
];
final matchedFiles = await FilesDB.instance.getFilesCreatedWithinDurations(
[durationOfDay],
ignoreCollections(),
order: 'DESC',
);
final name = DateFormat.yMMMd(Localizations.localeOf(context).languageCode)
.format(originalDateTime.toLocal());
return GenericSearchResult(
ResultType.event,
name,
matchedFiles,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: name,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.event,
matchedUploadedIDs: filesToUploadedFileIDs(matchedFiles),
filterIcon: Icons.event_outlined,
),
);
}
Future<List<GenericSearchResult>> getContactSearchResults(
String query,
) async {

View File

@@ -2,16 +2,19 @@ import "dart:async";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
import "package:photos/core/constants.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/db/ml/db.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/models/ml/face/person.dart";
import "package:photos/models/search/generic_search_result.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
import 'package:photos/services/machine_learning/ml_service.dart';
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/services/notification_service.dart";
import "package:photos/services/search_service.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
@@ -19,6 +22,7 @@ import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
import "package:photos/ui/components/toggle_switch_widget.dart";
import 'package:photos/ui/notification/toast.dart';
import 'package:photos/ui/settings/common_settings.dart';
import "package:photos/ui/settings/debug/memories_debug_section.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/ml_util.dart";
@@ -32,6 +36,8 @@ class MLDebugSectionWidget extends StatefulWidget {
class _MLDebugSectionWidgetState extends State<MLDebugSectionWidget> {
Timer? _timer;
bool isExpanded = false;
bool _showMemoriesDebugSection = false;
bool _showAllMemories = false;
final Logger logger = Logger("MLDebugSectionWidget");
late final mlDataDB = MLDataDB.instance;
@override
@@ -578,6 +584,45 @@ class _MLDebugSectionWidgetState extends State<MLDebugSectionWidget> {
);
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Toggle memories section",
),
trailingWidget: ToggleSwitchWidget(
value: () => _showMemoriesDebugSection,
onChanged: () async {
_showMemoriesDebugSection = !_showMemoriesDebugSection;
if (mounted) {
setState(() {});
}
},
),
singleBorderRadius: 8,
isGestureDetectorDisabled: true,
),
if (_showMemoriesDebugSection) sectionOptionSpacing,
if (_showMemoriesDebugSection)
FutureBuilder<List<GenericSearchResult>>(
key: ValueKey(_showAllMemories),
future: SearchService.instance.smartMemories(
context,
_showAllMemories ? null : kSearchSectionLimit,
),
builder: (context, snapshot) {
if (snapshot.hasData) {
final memories = snapshot.data!;
return MemoriesDebugSection(memories, () async {
_showAllMemories = !_showAllMemories;
_timer?.cancel();
if (mounted) {
setState(() {});
}
});
}
return const SizedBox.shrink();
},
),
],
);
}

View File

@@ -1,259 +0,0 @@
import "dart:async";
import "dart:math";
import "package:figma_squircle/figma_squircle.dart";
import "package:flutter/material.dart";
import "package:photos/core/constants.dart";
import "package:photos/events/event.dart";
import "package:photos/models/search/generic_search_result.dart";
import "package:photos/models/search/recent_searches.dart";
import "package:photos/models/search/search_types.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/viewer/file/no_thumbnail_widget.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
import "package:photos/ui/viewer/search/result/search_result_page.dart";
import "package:photos/ui/viewer/search/search_section_cta.dart";
import "package:photos/ui/viewer/search_tab/section_header.dart";
import "package:photos/utils/navigation_util.dart";
class MomentsSection extends StatefulWidget {
final List<GenericSearchResult> momentsSearchResults;
const MomentsSection(this.momentsSearchResults, {super.key});
@override
State<MomentsSection> createState() => _MomentsSectionState();
}
class _MomentsSectionState extends State<MomentsSection> {
late List<GenericSearchResult> _momentsSearchResults;
final streamSubscriptions = <StreamSubscription>[];
@override
void initState() {
super.initState();
_momentsSearchResults = widget.momentsSearchResults;
final streamsToListenTo = SectionType.moment.sectionUpdateEvents();
for (Stream<Event> stream in streamsToListenTo) {
streamSubscriptions.add(
stream.listen((event) async {
_momentsSearchResults = (await SectionType.moment.getData(
context,
limit: kSearchSectionLimit,
)) as List<GenericSearchResult>;
setState(() {});
}),
);
}
}
@override
void dispose() {
for (var subscriptions in streamSubscriptions) {
subscriptions.cancel();
}
super.dispose();
}
@override
void didUpdateWidget(covariant MomentsSection oldWidget) {
super.didUpdateWidget(oldWidget);
_momentsSearchResults = widget.momentsSearchResults;
}
@override
Widget build(BuildContext context) {
if (_momentsSearchResults.isEmpty) {
final textTheme = getEnteTextTheme(context);
return Padding(
padding: const EdgeInsets.only(left: 12, right: 8),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
SectionType.moment.sectionTitle(context),
style: textTheme.largeBold,
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(
SectionType.moment.getEmptyStateText(context),
style: textTheme.smallMuted,
),
),
],
),
),
const SizedBox(width: 8),
const SearchSectionEmptyCTAIcon(SectionType.moment),
],
),
);
} else {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SectionHeader(
SectionType.moment,
hasMore:
(_momentsSearchResults.length >= kSearchSectionLimit - 1),
),
const SizedBox(height: 2),
SizedBox(
child: SingleChildScrollView(
clipBehavior: Clip.none,
padding: const EdgeInsets.symmetric(horizontal: 4.5),
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: _momentsSearchResults
.map(
(momentSearchResult) =>
MomentRecommendation(momentSearchResult),
)
.toList(),
),
),
),
],
),
);
}
}
}
class MomentRecommendation extends StatelessWidget {
static const _width = 100.0;
static const _height = 145.0;
static const _borderWidth = 1.0;
static const _cornerRadius = 5.0;
static const _cornerSmoothing = 1.0;
final GenericSearchResult momentSearchResult;
const MomentRecommendation(this.momentSearchResult, {super.key});
@override
Widget build(BuildContext context) {
final heroTag = momentSearchResult.heroTag() +
(momentSearchResult.previewThumbnail()?.tag ?? "");
final enteTextTheme = getEnteTextTheme(context);
return Padding(
padding: EdgeInsets.symmetric(horizontal: max(2.5 - _borderWidth, 0)),
child: GestureDetector(
onTap: () {
RecentSearches().add(momentSearchResult.name());
if (momentSearchResult.onResultTap != null) {
momentSearchResult.onResultTap!(context);
} else {
routeToPage(
context,
SearchResultPage(momentSearchResult),
);
}
},
child: SizedBox(
width: _width + _borderWidth * 2,
height: _height + _borderWidth * 2,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
ClipSmoothRect(
radius: SmoothBorderRadius(
cornerRadius: _cornerRadius + _borderWidth,
cornerSmoothing: _cornerSmoothing,
),
child: Container(
color: Colors.white.withOpacity(0.16),
width: _width + _borderWidth * 2,
height: _height + _borderWidth * 2,
),
),
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6.25,
offset: const Offset(-1.25, 2.5),
),
],
),
child: ClipSmoothRect(
radius: SmoothBorderRadius(
cornerRadius: _cornerRadius,
cornerSmoothing: _cornerSmoothing,
),
child: Stack(
alignment: Alignment.bottomCenter,
clipBehavior: Clip.none,
children: [
SizedBox(
width: _width,
height: _height,
child: momentSearchResult.previewThumbnail() != null
? Hero(
tag: heroTag,
child: ThumbnailWidget(
momentSearchResult.previewThumbnail()!,
shouldShowArchiveStatus: false,
shouldShowSyncStatus: false,
),
)
: const NoThumbnailWidget(),
),
Container(
height: 145,
width: 100,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0),
Colors.black.withOpacity(0),
Colors.black.withOpacity(0.5),
],
stops: const [
0,
0.1,
1,
],
),
),
),
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 76,
),
child: Padding(
padding: const EdgeInsets.only(
bottom: 8,
),
child: Text(
momentSearchResult.name(),
style: enteTextTheme.small.copyWith(
color: Colors.white,
),
maxLines: 3,
overflow: TextOverflow.fade,
),
),
),
],
),
),
),
],
),
),
),
);
}
}

View File

@@ -19,7 +19,6 @@ import 'package:photos/ui/viewer/search_tab/albums_section.dart';
import "package:photos/ui/viewer/search_tab/file_type_section.dart";
import "package:photos/ui/viewer/search_tab/locations_section.dart";
import "package:photos/ui/viewer/search_tab/magic_section.dart";
import "package:photos/ui/viewer/search_tab/moments_section.dart";
import "package:photos/ui/viewer/search_tab/people_section.dart";
class SearchTab extends StatefulWidget {
@@ -125,16 +124,10 @@ class _AllSearchSectionsState extends State<AllSearchSections> {
as List<GenericSearchResult>,
);
case SectionType.album:
// return const SizedBox.shrink();
return AlbumsSection(
snapshot.data!.elementAt(index)
as List<AlbumSearchResult>,
);
case SectionType.moment:
return MomentsSection(
snapshot.data!.elementAt(index)
as List<GenericSearchResult>,
);
case SectionType.location:
return LocationsSection(
snapshot.data!.elementAt(index)