Support for adding descriptions

File description
This commit is contained in:
Neeraj Gupta 2022-11-06 11:16:35 +05:30 committed by GitHub
commit 65bf985933
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 417 additions and 208 deletions

View file

@ -211,6 +211,10 @@ class File extends EnteFile {
}
}
String? get caption {
return pubMagicMetadata?.caption;
}
String get thumbnailUrl {
final endpoint = Configuration.instance.getHttpEndpoint();
if (endpoint != kDefaultProductionEndpoint ||

View file

@ -14,6 +14,7 @@ const subTypeKey = 'subType';
const pubMagicKeyEditedTime = 'editedTime';
const pubMagicKeyEditedName = 'editedName';
const pubMagicKeyCaption = "caption";
class MagicMetadata {
// 0 -> visible
@ -39,8 +40,9 @@ class MagicMetadata {
class PubMagicMetadata {
int? editedTime;
String? editedName;
String? caption;
PubMagicMetadata({this.editedTime, this.editedName});
PubMagicMetadata({this.editedTime, this.editedName, this.caption});
factory PubMagicMetadata.fromEncodedJson(String encodedJson) =>
PubMagicMetadata.fromJson(jsonDecode(encodedJson));
@ -53,6 +55,7 @@ class PubMagicMetadata {
return PubMagicMetadata(
editedTime: map[pubMagicKeyEditedTime],
editedName: map[pubMagicKeyEditedName],
caption: map[pubMagicKeyCaption],
);
}
}

View file

@ -22,5 +22,6 @@ enum ResultType {
year,
fileType,
fileExtension,
fileCaption,
event
}

View file

@ -209,6 +209,30 @@ class SearchService {
return searchResults;
}
Future<List<GenericSearchResult>> getCaptionResults(
String query,
) async {
final List<GenericSearchResult> searchResults = [];
if (query.isEmpty) {
return searchResults;
}
final RegExp pattern = RegExp(query, caseSensitive: false);
final List<File> allFiles = await _getAllFiles();
final matchedFiles = allFiles
.where((e) => e.caption != null && pattern.hasMatch(e.caption))
.toList();
if (matchedFiles.isNotEmpty) {
searchResults.add(
GenericSearchResult(
ResultType.fileCaption,
query,
matchedFiles,
),
);
}
return searchResults;
}
Future<List<GenericSearchResult>> getFileExtensionResults(
String query,
) async {

View file

@ -11,6 +11,7 @@ class EnteColorScheme {
// Backdrop Colors
final Color backdropBase;
final Color backdropBaseMute;
final Color backdropFaint;
// Text Colors
final Color textBase;
@ -53,6 +54,7 @@ class EnteColorScheme {
this.backgroundElevated2,
this.backdropBase,
this.backdropBaseMute,
this.backdropFaint,
this.textBase,
this.textMuted,
this.textFaint,
@ -84,7 +86,8 @@ const EnteColorScheme lightScheme = EnteColorScheme(
backgroundElevatedLight,
backgroundElevated2Light,
backdropBaseLight,
backdropBaseMuteLight,
backdropMutedLight,
backdropFaintLight,
textBaseLight,
textMutedLight,
textFaintLight,
@ -107,7 +110,8 @@ const EnteColorScheme darkScheme = EnteColorScheme(
backgroundElevatedDark,
backgroundElevated2Dark,
backdropBaseDark,
backdropBaseMuteDark,
backdropMutedDark,
backdropFaintDark,
textBaseDark,
textMutedDark,
textFaintDark,
@ -136,10 +140,12 @@ const Color backgroundElevated2Dark = Color.fromRGBO(37, 37, 37, 1);
// Backdrop Colors
const Color backdropBaseLight = Color.fromRGBO(255, 255, 255, 0.75);
const Color backdropBaseMuteLight = Color.fromRGBO(255, 255, 255, 0.30);
const Color backdropMutedLight = Color.fromRGBO(255, 255, 255, 0.30);
const Color backdropFaintLight = Color.fromRGBO(255, 255, 255, 0.15);
const Color backdropBaseDark = Color.fromRGBO(0, 0, 0, 0.65);
const Color backdropBaseMuteDark = Color.fromRGBO(0, 0, 0, 0.20);
const Color backdropMutedDark = Color.fromRGBO(0, 0, 0, 0.20);
const Color backdropFaintDark = Color.fromRGBO(0, 0, 0, 0.08);
// Text Colors
const Color textBaseLight = Color.fromRGBO(0, 0, 0, 1);

View file

@ -29,7 +29,7 @@ class BackupSettingsScreen extends StatelessWidget {
actionIcons: [
IconButtonWidget(
icon: Icons.close_outlined,
isSecondary: true,
iconButtonType: IconButtonType.secondary,
onTap: () {
Navigator.pop(context);
Navigator.pop(context);

View file

@ -20,7 +20,7 @@ class _HomeHeaderWidgetState extends State<HomeHeaderWidget> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButtonWidget(
isPrimary: true,
iconButtonType: IconButtonType.primary,
icon: Icons.menu_outlined,
onTap: () {
Scaffold.of(context).openDrawer();

View file

@ -2,10 +2,14 @@ import 'package:flutter/material.dart';
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/ente_theme.dart';
enum IconButtonType {
primary,
secondary,
rounded,
}
class IconButtonWidget extends StatefulWidget {
final bool isPrimary;
final bool isSecondary;
final bool isRounded;
final IconButtonType iconButtonType;
final IconData icon;
final bool disableGestureDetector;
final VoidCallback? onTap;
@ -14,9 +18,7 @@ class IconButtonWidget extends StatefulWidget {
final Color? iconColor;
const IconButtonWidget({
required this.icon,
this.isPrimary = false,
this.isSecondary = false,
this.isRounded = false,
required this.iconButtonType,
this.disableGestureDetector = false,
this.onTap,
this.defaultColor,
@ -41,13 +43,12 @@ class _IconButtonWidgetState extends State<IconButtonWidget> {
@override
Widget build(BuildContext context) {
if (!widget.isPrimary && !widget.isRounded && !widget.isSecondary) {
return const SizedBox.shrink();
}
final colorTheme = getEnteColorScheme(context);
iconStateColor ??
(iconStateColor = widget.defaultColor ??
(widget.isRounded ? colorTheme.fillFaint : null));
(widget.iconButtonType == IconButtonType.rounded
? colorTheme.fillFaint
: null));
return widget.disableGestureDetector
? _iconButton(colorTheme)
: GestureDetector(
@ -72,7 +73,7 @@ class _IconButtonWidgetState extends State<IconButtonWidget> {
child: Icon(
widget.icon,
color: widget.iconColor ??
(widget.isSecondary
(widget.iconButtonType == IconButtonType.secondary
? colorTheme.strokeMuted
: colorTheme.strokeBase),
size: 24,
@ -85,7 +86,9 @@ class _IconButtonWidgetState extends State<IconButtonWidget> {
final colorTheme = getEnteColorScheme(context);
setState(() {
iconStateColor = widget.pressedColor ??
(widget.isRounded ? colorTheme.fillMuted : colorTheme.fillFaint);
(widget.iconButtonType == IconButtonType.rounded
? colorTheme.fillMuted
: colorTheme.fillFaint);
});
}

View file

@ -54,7 +54,7 @@ class NotificationWarningWidget extends StatelessWidget {
const SizedBox(width: 12),
IconButtonWidget(
icon: actionIcon,
isRounded: true,
iconButtonType: IconButtonType.rounded,
iconColor: strokeBaseDark,
defaultColor: fillFaintDark,
pressedColor: fillMutedDark,

View file

@ -3,6 +3,7 @@ import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/icon_button_widget.dart';
class TitleBarWidget extends StatelessWidget {
final IconButtonWidget? leading;
final String? title;
final String? caption;
final Widget? flexibleSpaceTitle;
@ -10,7 +11,9 @@ class TitleBarWidget extends StatelessWidget {
final List<Widget>? actionIcons;
final bool isTitleH2WithoutLeading;
final bool isFlexibleSpaceDisabled;
final bool isOnTopOfScreen;
const TitleBarWidget({
this.leading,
this.title,
this.caption,
this.flexibleSpaceTitle,
@ -18,6 +21,7 @@ class TitleBarWidget extends StatelessWidget {
this.actionIcons,
this.isTitleH2WithoutLeading = false,
this.isFlexibleSpaceDisabled = false,
this.isOnTopOfScreen = true,
super.key,
});
@ -27,13 +31,14 @@ class TitleBarWidget extends StatelessWidget {
final textTheme = getEnteTextTheme(context);
final colorTheme = getEnteColorScheme(context);
return SliverAppBar(
primary: isOnTopOfScreen ? true : false,
toolbarHeight: toolbarHeight,
leadingWidth: 48,
automaticallyImplyLeading: false,
pinned: true,
expandedHeight: 102,
expandedHeight: isFlexibleSpaceDisabled ? toolbarHeight : 102,
centerTitle: false,
titleSpacing: 0,
titleSpacing: 4,
title: Padding(
padding: EdgeInsets.only(left: isTitleH2WithoutLeading ? 16 : 0),
child: Column(
@ -67,9 +72,10 @@ class TitleBarWidget extends StatelessWidget {
],
leading: isTitleH2WithoutLeading
? null
: IconButtonWidget(
: leading ??
IconButtonWidget(
icon: Icons.arrow_back_outlined,
isPrimary: true,
iconButtonType: IconButtonType.primary,
onTap: () {
Navigator.pop(context);
},

View file

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:page_transition/page_transition.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/models/file.dart';
@ -12,6 +13,8 @@ import 'package:photos/models/magic_metadata.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/models/trash_file.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/create_collection_page.dart';
import 'package:photos/ui/viewer/file/file_info_widget.dart';
import 'package:photos/utils/delete_file_util.dart';
@ -73,8 +76,13 @@ class FadingBottomBarState extends State<FadingBottomBar> {
Platform.isAndroid ? Icons.info_outline : CupertinoIcons.info,
color: Colors.white,
),
onPressed: () {
_displayInfo(widget.file);
onPressed: () async {
await _displayInfo(widget.file);
safeRefresh(); //to instantly show the new caption if keypad is closed after pressing 'done' - here the caption will be updated before the bottom sheet is closed
await Future.delayed(
const Duration(milliseconds: 500),
); //Waiting for some time till the caption gets updated in db if the user closes the bottom sheet without pressing 'done'
safeRefresh();
},
),
),
@ -183,10 +191,32 @@ class FadingBottomBarState extends State<FadingBottomBar> {
),
child: Padding(
padding: EdgeInsets.only(bottom: safeAreaBottomPadding),
child: Row(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
widget.file.caption?.isNotEmpty ?? false
? Padding(
padding: const EdgeInsets.fromLTRB(
16,
28,
16,
12,
),
child: Text(
widget.file.caption,
style: getEnteTextTheme(context)
.small
.copyWith(color: textBaseDark),
textAlign: TextAlign.center,
),
)
: const SizedBox.shrink(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: children,
),
],
),
),
),
),
@ -249,11 +279,19 @@ class FadingBottomBarState extends State<FadingBottomBar> {
}
Future<void> _displayInfo(File file) async {
return showModalBottomSheet<void>(
final colorScheme = getEnteColorScheme(context);
return showBarModalBottomSheet(
topControl: const SizedBox.shrink(),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
backgroundColor: colorScheme.backgroundBase,
barrierColor: backdropFaintDark,
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return FileInfoWidget(file);
return Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: FileInfoWidget(file),
);
},
);
}

View file

@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:photos/models/file.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/utils/magic_util.dart';
class FileCaptionWidget extends StatefulWidget {
final File file;
const FileCaptionWidget({required this.file, super.key});
@override
State<FileCaptionWidget> createState() => _FileCaptionWidgetState();
}
class _FileCaptionWidgetState extends State<FileCaptionWidget> {
int maxLength = 280;
int currentLength = 0;
final _textController = TextEditingController();
final _focusNode = FocusNode();
String? editedCaption;
String? hintText = "Add a description...";
@override
void initState() {
_focusNode.addListener(() {
final caption = widget.file.caption;
if (_focusNode.hasFocus && caption != null) {
_textController.text = caption;
editedCaption = caption;
}
});
editedCaption = widget.file.caption;
if (editedCaption != null && editedCaption!.isNotEmpty) {
hintText = editedCaption;
}
super.initState();
}
@override
void dispose() {
if (editedCaption != null) {
editFileCaption(null, widget.file, editedCaption);
}
_textController.dispose();
_focusNode.removeListener(() {});
super.dispose();
}
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textTheme = getEnteTextTheme(context);
return TextField(
onEditingComplete: () async {
if (editedCaption != null) {
await editFileCaption(context, widget.file, editedCaption);
if (mounted) {
setState(() {});
}
}
_focusNode.unfocus();
},
controller: _textController,
focusNode: _focusNode,
decoration: InputDecoration(
counterStyle: textTheme.mini.copyWith(color: colorScheme.textMuted),
counterText: currentLength > 99
? currentLength.toString() + " / " + maxLength.toString()
: "",
contentPadding: const EdgeInsets.all(16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(2),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(2),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
filled: true,
fillColor: colorScheme.fillFaint,
hintText: hintText,
hintStyle: getEnteTextTheme(context)
.small
.copyWith(color: colorScheme.textMuted),
),
style: getEnteTextTheme(context).small,
cursorWidth: 1.5,
maxLength: maxLength,
minLines: 1,
maxLines: 6,
textCapitalization: TextCapitalization.sentences,
keyboardType: TextInputType.text,
onChanged: (value) {
setState(() {
hintText = "Add a description...";
currentLength = value.length;
editedCaption = value;
});
},
);
}
}

View file

@ -9,10 +9,13 @@ import 'package:photos/db/files_db.dart';
import "package:photos/ente_theme_data.dart";
import "package:photos/models/file.dart";
import "package:photos/models/file_type.dart";
import 'package:photos/ui/common/DividerWithPadding.dart';
import 'package:photos/ui/components/divider_widget.dart';
import 'package:photos/ui/components/icon_button_widget.dart';
import 'package:photos/ui/components/title_bar_widget.dart';
import 'package:photos/ui/viewer/file/collections_list_of_file_widget.dart';
import 'package:photos/ui/viewer/file/device_folders_list_of_file_widget.dart';
import 'package:photos/ui/viewer/file/raw_exif_button.dart';
import 'package:photos/ui/viewer/file/file_caption_widget.dart';
import 'package:photos/ui/viewer/file/raw_exif_list_tile_widget.dart';
import "package:photos/utils/date_time_util.dart";
import "package:photos/utils/exif_util.dart";
import "package:photos/utils/file_util.dart";
@ -90,9 +93,17 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
final bool showDimension =
_exifData["resolution"] != null && _exifData["megaPixels"] != null;
final listTiles = <Widget>[
widget.file.uploadedFileID == null ||
Configuration.instance.getUserID() != file.ownerID
? const SizedBox.shrink()
: Padding(
padding: const EdgeInsets.only(top: 8, bottom: 4),
child: FileCaptionWidget(file: widget.file),
),
ListTile(
horizontalTitleGap: 2,
leading: const Padding(
padding: EdgeInsets.only(top: 8, left: 6),
padding: EdgeInsets.only(top: 8),
child: Icon(Icons.calendar_today_rounded),
),
title: Text(
@ -121,17 +132,17 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
)
: const SizedBox.shrink(),
),
const DividerWithPadding(left: 70, right: 20),
ListTile(
horizontalTitleGap: 2,
leading: _isImage
? const Padding(
padding: EdgeInsets.only(top: 8, left: 6),
padding: EdgeInsets.only(top: 8),
child: Icon(
Icons.image,
),
)
: const Padding(
padding: EdgeInsets.only(top: 8, left: 6),
padding: EdgeInsets.only(top: 8),
child: Icon(
Icons.video_camera_back,
size: 27,
@ -169,13 +180,10 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
icon: const Icon(Icons.edit),
),
),
const DividerWithPadding(left: 70, right: 20),
showExifListTile
? ListTile(
leading: const Padding(
padding: EdgeInsets.only(left: 6),
child: Icon(Icons.camera_rounded),
),
horizontalTitleGap: 2,
leading: const Icon(Icons.camera_rounded),
title: Text(_exifData["takenOnDevice"] ?? "--"),
subtitle: Row(
children: [
@ -207,27 +215,22 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
],
),
)
: const SizedBox.shrink(),
showExifListTile
? const DividerWithPadding(left: 70, right: 20)
: const SizedBox.shrink(),
: null,
SizedBox(
height: 62,
child: ListTile(
leading: const Padding(
padding: EdgeInsets.only(left: 6),
child: Icon(Icons.folder_outlined),
),
horizontalTitleGap: 0,
leading: const Icon(Icons.folder_outlined),
title: fileIsBackedup
? CollectionsListOfFileWidget(allCollectionIDsOfFile)
: DeviceFoldersListOfFileWidget(allDeviceFoldersOfFile),
),
),
const DividerWithPadding(left: 70, right: 20),
(file.uploadedFileID != null && file.updationTime != null)
? ListTile(
horizontalTitleGap: 2,
leading: const Padding(
padding: EdgeInsets.only(top: 8, left: 6),
padding: EdgeInsets.only(top: 8),
child: Icon(Icons.cloud_upload_outlined),
),
title: Text(
@ -247,48 +250,53 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
),
),
)
: const SizedBox.shrink(),
_isImage
? Padding(
padding: const EdgeInsets.fromLTRB(0, 24, 0, 16),
child: SafeArea(
child: RawExifButton(_exif, widget.file),
),
)
: const SizedBox(
height: 12,
)
: null,
_isImage ? RawExifListTileWidget(_exif, widget.file) : null,
];
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
onPressed: () {
Navigator.pop(context);
listTiles.removeWhere(
(element) => element == null,
);
return SafeArea(
top: false,
child: Scrollbar(
thickness: 4,
radius: const Radius.circular(2),
thumbVisibility: true,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: CustomScrollView(
shrinkWrap: true,
slivers: <Widget>[
TitleBarWidget(
isFlexibleSpaceDisabled: true,
title: "Details",
isOnTopOfScreen: false,
leading: IconButtonWidget(
icon: Icons.close_outlined,
iconButtonType: IconButtonType.primary,
onTap: () => Navigator.pop(context),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index.isOdd) {
return index == 1
? const SizedBox.shrink()
: const DividerWidget(dividerType: DividerType.menu);
} else {
return listTiles[index ~/ 2];
}
},
icon: const Icon(
Icons.close,
),
),
const SizedBox(width: 6),
Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Text(
"Details",
style: Theme.of(context).textTheme.bodyText1,
),
childCount: (listTiles.length * 2) - 1,
),
)
],
),
),
...listTiles
],
),
);
}

View file

@ -1,100 +0,0 @@
// @dart=2.9
import 'package:exif/exif.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:photos/ente_theme_data.dart';
import "package:photos/models/file.dart";
import 'package:photos/ui/viewer/file/exif_info_dialog.dart';
import 'package:photos/utils/toast_util.dart';
enum Status {
loading,
exifIsAvailable,
noExif,
}
class RawExifButton extends StatelessWidget {
final File file;
final Map<String, IfdTag> exif;
const RawExifButton(this.exif, this.file, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
Status exifStatus = Status.loading;
if (exif == null) {
exifStatus = Status.loading;
} else if (exif.isNotEmpty) {
exifStatus = Status.exifIsAvailable;
} else {
exifStatus = Status.noExif;
}
return GestureDetector(
onTap:
exifStatus == Status.loading || exifStatus == Status.exifIsAvailable
? () {
showDialog(
context: context,
builder: (BuildContext context) {
return ExifInfoDialog(file);
},
barrierColor: Colors.black87,
);
}
: exifStatus == Status.noExif
? () {
showShortToast(context, "This image has no exif data");
}
: null,
child: Container(
height: 40,
width: 140,
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.inverseBackgroundColor
.withOpacity(0.12),
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
),
child: Center(
child: exifStatus == Status.loading
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CupertinoActivityIndicator(
radius: 8,
),
SizedBox(
width: 8,
),
Text('EXIF')
],
)
: exifStatus == Status.exifIsAvailable
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.feed_outlined),
SizedBox(
width: 8,
),
Text('Raw EXIF'),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.feed_outlined),
SizedBox(
width: 8,
),
Text('No EXIF'),
],
),
),
),
);
}
}

View file

@ -0,0 +1,71 @@
// @dart=2.9
import 'package:exif/exif.dart';
import 'package:flutter/material.dart';
import 'package:photos/ente_theme_data.dart';
import "package:photos/models/file.dart";
import 'package:photos/ui/viewer/file/exif_info_dialog.dart';
import 'package:photos/utils/toast_util.dart';
enum Status {
loading,
exifIsAvailable,
noExif,
}
class RawExifListTileWidget extends StatelessWidget {
final File file;
final Map<String, IfdTag> exif;
const RawExifListTileWidget(this.exif, this.file, {Key key})
: super(key: key);
@override
Widget build(BuildContext context) {
Status exifStatus = Status.loading;
if (exif == null) {
exifStatus = Status.loading;
} else if (exif.isNotEmpty) {
exifStatus = Status.exifIsAvailable;
} else {
exifStatus = Status.noExif;
}
return GestureDetector(
onTap: exifStatus == Status.exifIsAvailable
? () {
showDialog(
context: context,
builder: (BuildContext context) {
return ExifInfoDialog(file);
},
barrierColor: Colors.black87,
);
}
: exifStatus == Status.noExif
? () {
showShortToast(context, "This image has no exif data");
}
: null,
child: ListTile(
horizontalTitleGap: 2,
leading: const Padding(
padding: EdgeInsets.only(top: 8),
child: Icon(Icons.feed_outlined),
),
title: const Text("EXIF"),
subtitle: Text(
exifStatus == Status.loading
? "Loading EXIF data.."
: exifStatus == Status.exifIsAvailable
? "View all EXIF data"
: "No EXIF data",
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Theme.of(context)
.colorScheme
.defaultTextColor
.withOpacity(0.5),
),
),
),
);
}
}

View file

@ -29,17 +29,16 @@ class NoResultWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
margin: const EdgeInsets.all(8),
Container(
margin: const EdgeInsets.only(top: 8),
child: const Text(
"No results found",
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 16,
),
),
),
),
Container(
margin: const EdgeInsets.only(top: 16),
child: Text(
@ -61,6 +60,7 @@ class NoResultWidget extends StatelessWidget {
\u2022 Types of files (e.g. "Videos", ".gif")
\u2022 Years and months (e.g. "2022", "January")
\u2022 Holidays (e.g. "Christmas")
\u2022 Photo descriptions (e.g. #fun)
''',
style: TextStyle(
fontSize: 14,

View file

@ -125,6 +125,8 @@ class SearchResultWidget extends StatelessWidget {
return "Type";
case ResultType.fileExtension:
return "File Extension";
case ResultType.fileCaption:
return "Description";
default:
return type.name.toUpperCase();
}

View file

@ -34,7 +34,7 @@ class _SearchIconWidgetState extends State<SearchIconWidget> {
return Hero(
tag: "search_icon",
child: IconButtonWidget(
isPrimary: true,
iconButtonType: IconButtonType.primary,
icon: Icons.search,
onTap: () {
Navigator.push(
@ -196,6 +196,9 @@ class _SearchWidgetState extends State<SearchWidget> {
await _searchService.getFileTypeResults(query);
allResults.addAll(fileTypeSearchResults);
final fileCaptionResults = await _searchService.getCaptionResults(query);
allResults.addAll(fileCaptionResults);
final fileExtnResult =
await _searchService.getFileExtensionResults(query);
allResults.addAll(fileExtnResult);

View file

@ -10,6 +10,7 @@ import 'package:photos/models/file.dart';
import 'package:photos/models/magic_metadata.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/services/file_magic_service.dart';
import 'package:photos/ui/common/progress_dialog.dart';
import 'package:photos/ui/common/rename_dialog.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
@ -123,7 +124,23 @@ Future<bool> editFilename(
);
return true;
} catch (e) {
showToast(context, 'something went wrong');
showToast(context, 'Something went wrong');
return false;
}
}
Future<bool> editFileCaption(
BuildContext context,
File file,
String caption,
) async {
try {
await _updatePublicMetadata(context, [file], pubMagicKeyCaption, caption);
return true;
} catch (e) {
if (context != null) {
showToast(context, "Something went wrong");
}
return false;
}
}
@ -137,19 +154,27 @@ Future<void> _updatePublicMetadata(
if (files.isEmpty) {
return;
}
final dialog = createProgressDialog(context, 'please wait...');
ProgressDialog dialog;
if (context != null) {
dialog = createProgressDialog(context, 'Please wait...');
await dialog.show();
}
try {
final Map<String, dynamic> update = {key: value};
await FileMagicService.instance.updatePublicMagicMetadata(files, update);
showShortToast(context, 'done');
if (context != null) {
showShortToast(context, 'Done');
await dialog.hide();
}
if (_shouldReloadGallery(key)) {
Bus.instance.fire(ForceReloadHomeGalleryEvent());
}
} catch (e, s) {
_logger.severe("failed to update $key = $value", e, s);
if (context != null) {
await dialog.hide();
}
rethrow;
}
}

View file

@ -744,6 +744,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
modal_bottom_sheet:
dependency: "direct main"
description:
name: modal_bottom_sheet
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
motionphoto:
dependency: "direct main"
description:

View file

@ -79,6 +79,7 @@ dependencies:
lottie: ^1.2.2
media_extension:
git: "https://github.com/ente-io/media_extension.git"
modal_bottom_sheet: ^2.1.2
motionphoto:
git: "https://github.com/ente-io/motionphoto.git"
move_to_background: ^1.0.2
@ -91,7 +92,7 @@ dependencies:
path: #dart
path_provider: ^2.0.1
pedantic: ^1.9.2
photo_manager: ^2.4.1
photo_manager: ^2.5.0
photo_view: ^0.14.0
pinput: ^1.2.2
provider: ^6.0.0