Browse Source

feat(mobile): Rework of the exif sheet (#1213)

* Draggable sheet in asset viewer page

* Minor improvements

* Fix display bug

* Fix some styling

Co-authored-by: Alex <alex.tran1502@gmail.com>
Matthias Rupp 2 years ago
parent
commit
89a6ed2a5b

BIN
mobile/flutter_01.png


+ 111 - 103
mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart

@@ -3,12 +3,13 @@ import 'package:flutter/material.dart';
 import 'package:flutter_map/flutter_map.dart';
 import 'package:flutter_map/flutter_map.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/shared/models/asset.dart';
 import 'package:immich_mobile/shared/models/asset.dart';
+import 'package:immich_mobile/shared/ui/drag_sheet.dart';
 import 'package:openapi/api.dart';
 import 'package:openapi/api.dart';
 import 'package:path/path.dart' as p;
 import 'package:path/path.dart' as p;
 import 'package:latlong2/latlong.dart';
 import 'package:latlong2/latlong.dart';
 import 'package:immich_mobile/utils/bytes_units.dart';
 import 'package:immich_mobile/utils/bytes_units.dart';
 
 
-class ExifBottomSheet extends ConsumerWidget {
+class ExifBottomSheet extends HookConsumerWidget {
   final Asset assetDetail;
   final Asset assetDetail;
 
 
   const ExifBottomSheet({Key? key, required this.assetDetail})
   const ExifBottomSheet({Key? key, required this.assetDetail})
@@ -65,6 +66,8 @@ class ExifBottomSheet extends ConsumerWidget {
       );
       );
     }
     }
 
 
+    final textColor = Theme.of(context).primaryColor;
+
     ExifResponseDto? exifInfo = assetDetail.remote?.exifInfo;
     ExifResponseDto? exifInfo = assetDetail.remote?.exifInfo;
 
 
     buildLocationText() {
     buildLocationText() {
@@ -72,120 +75,125 @@ class ExifBottomSheet extends ConsumerWidget {
         "${exifInfo?.city}, ${exifInfo?.state}",
         "${exifInfo?.city}, ${exifInfo?.state}",
         style: TextStyle(
         style: TextStyle(
           fontSize: 12,
           fontSize: 12,
-          color: Colors.grey[200],
           fontWeight: FontWeight.bold,
           fontWeight: FontWeight.bold,
+          color: textColor,
         ),
         ),
       );
       );
     }
     }
 
 
-    return Padding(
-      padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8),
-      child: ListView(
-        children: [
-          if (exifInfo?.dateTimeOriginal != null)
-            Text(
-              DateFormat('date_format'.tr()).format(
-                exifInfo!.dateTimeOriginal!.toLocal(),
-              ),
-              style: TextStyle(
-                color: Colors.grey[400],
-                fontWeight: FontWeight.bold,
-                fontSize: 14,
-              ),
-            ),
-          Padding(
-            padding: const EdgeInsets.only(top: 16.0),
-            child: Text(
-              "exif_bottom_sheet_description",
-              style: TextStyle(
-                color: Colors.grey[500],
-                fontSize: 11,
-              ),
-            ).tr(),
-          ),
-
-          // Location
-          if (assetDetail.latitude != null)
-            Padding(
-              padding: const EdgeInsets.only(top: 32.0),
-              child: Column(
-                crossAxisAlignment: CrossAxisAlignment.start,
-                children: [
-                  Divider(
-                    thickness: 1,
-                    color: Colors.grey[600],
-                  ),
-                  Text(
-                    "exif_bottom_sheet_location",
-                    style: TextStyle(fontSize: 11, color: Colors.grey[400]),
-                  ).tr(),
-                  if (assetDetail.latitude != null &&
-                      assetDetail.longitude != null)
-                    buildMap(),
-                  if (exifInfo != null &&
-                      exifInfo.city != null &&
-                      exifInfo.state != null)
-                    buildLocationText(),
-                  Text(
-                    "${assetDetail.latitude?.toStringAsFixed(4)}, ${assetDetail.longitude?.toStringAsFixed(4)}",
-                    style: TextStyle(fontSize: 12, color: Colors.grey[400]),
-                  )
-                ],
+    return SingleChildScrollView(
+      child: Card(
+        margin: const EdgeInsets.all(0),
+        child: Container(
+          margin: const EdgeInsets.symmetric(horizontal: 8.0),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              const SizedBox(height: 12),
+              const Align(
+                alignment: Alignment.center,
+                child: CustomDraggingHandle(),
               ),
               ),
-            ),
-          // Detail
-          if (exifInfo != null)
-            Padding(
-              padding: const EdgeInsets.only(top: 32.0),
-              child: Column(
-                crossAxisAlignment: CrossAxisAlignment.start,
-                children: [
-                  Divider(
-                    thickness: 1,
-                    color: Colors.grey[600],
+              const SizedBox(height: 12),
+              if (exifInfo?.dateTimeOriginal != null)
+                Text(
+                  DateFormat('date_format'.tr()).format(
+                    exifInfo!.dateTimeOriginal!.toLocal(),
                   ),
                   ),
-                  Padding(
-                    padding: const EdgeInsets.only(bottom: 8.0),
-                    child: Text(
-                      "exif_bottom_sheet_details",
-                      style: TextStyle(fontSize: 11, color: Colors.grey[400]),
-                    ).tr(),
+                  style: const TextStyle(
+                    fontWeight: FontWeight.bold,
+                    fontSize: 14,
                   ),
                   ),
-                  ListTile(
-                    contentPadding: const EdgeInsets.all(0),
-                    dense: true,
-                    textColor: Colors.grey[300],
-                    iconColor: Colors.grey[300],
-                    leading: const Icon(Icons.image),
-                    title: Text(
-                      "${exifInfo.imageName!}${p.extension(assetDetail.remote!.originalPath)}",
-                      style: const TextStyle(fontWeight: FontWeight.bold),
-                    ),
-                    subtitle: exifInfo.exifImageHeight != null
-                        ? Text(
-                            "${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth}  ${formatBytes(exifInfo.fileSizeInByte!)} ",
-                          )
-                        : null,
+                ),
+
+              // Location
+              if (assetDetail.latitude != null)
+                Padding(
+                  padding: const EdgeInsets.only(top: 32.0),
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: [
+                      const Divider(
+                        thickness: 1,
+                      ),
+                      Text(
+                        "exif_bottom_sheet_location",
+                        style: TextStyle(fontSize: 11, color: textColor),
+                      ).tr(),
+                      if (assetDetail.latitude != null &&
+                          assetDetail.longitude != null)
+                        buildMap(),
+                      if (exifInfo != null &&
+                          exifInfo.city != null &&
+                          exifInfo.state != null)
+                        buildLocationText(),
+                      Text(
+                        "${assetDetail.latitude?.toStringAsFixed(4)}, ${assetDetail.longitude?.toStringAsFixed(4)}",
+                        style: const TextStyle(fontSize: 12),
+                      )
+                    ],
                   ),
                   ),
-                  if (exifInfo.make != null)
-                    ListTile(
-                      contentPadding: const EdgeInsets.all(0),
-                      dense: true,
-                      textColor: Colors.grey[300],
-                      iconColor: Colors.grey[300],
-                      leading: const Icon(Icons.camera),
-                      title: Text(
-                        "${exifInfo.make} ${exifInfo.model}",
-                        style: const TextStyle(fontWeight: FontWeight.bold),
+                ),
+              // Detail
+              if (exifInfo != null)
+                Padding(
+                  padding: const EdgeInsets.only(top: 32.0),
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: [
+                      Divider(
+                        thickness: 1,
+                        color: Colors.grey[600],
                       ),
                       ),
-                      subtitle: Text(
-                        "ƒ/${exifInfo.fNumber}   1/${(1 / (exifInfo.exposureTime ?? 1)).toStringAsFixed(0)}   ${exifInfo.focalLength} mm   ISO${exifInfo.iso} ",
+                      Padding(
+                        padding: const EdgeInsets.only(bottom: 8.0),
+                        child: Text(
+                          "exif_bottom_sheet_details",
+                          style: TextStyle(fontSize: 11, color: textColor),
+                        ).tr(),
                       ),
                       ),
-                    ),
-                ],
+                      ListTile(
+                        contentPadding: const EdgeInsets.all(0),
+                        dense: true,
+                        leading: const Icon(Icons.image),
+                        title: Text(
+                          "${exifInfo.imageName!}${p.extension(assetDetail.remote!.originalPath)}",
+                          style: TextStyle(
+                            fontWeight: FontWeight.bold,
+                            color: textColor,
+                          ),
+                        ),
+                        subtitle: exifInfo.exifImageHeight != null
+                            ? Text(
+                                "${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth}  ${formatBytes(exifInfo.fileSizeInByte ?? 0)} ",
+                              )
+                            : null,
+                      ),
+                      if (exifInfo.make != null)
+                        ListTile(
+                          contentPadding: const EdgeInsets.all(0),
+                          dense: true,
+                          leading: const Icon(Icons.camera),
+                          title: Text(
+                            "${exifInfo.make} ${exifInfo.model}",
+                            style: TextStyle(
+                              color: textColor,
+                              fontWeight: FontWeight.bold,
+                            ),
+                          ),
+                          subtitle: Text(
+                            "ƒ/${exifInfo.fNumber}   1/${(1 / (exifInfo.exposureTime ?? 1)).toStringAsFixed(0)}   ${exifInfo.focalLength} mm   ISO${exifInfo.iso} ",
+                          ),
+                        ),
+                    ],
+                  ),
+                ),
+              const SizedBox(
+                height: 50,
               ),
               ),
-            ),
-        ],
+            ],
+          ),
+        ),
       ),
       ),
     );
     );
   }
   }

+ 6 - 2
mobile/lib/modules/asset_viewer/views/gallery_viewer.dart

@@ -69,9 +69,12 @@ class GalleryViewerPage extends HookConsumerWidget {
 
 
     void showInfo() {
     void showInfo() {
       showModalBottomSheet(
       showModalBottomSheet(
-        backgroundColor: Colors.black,
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(15.0),
+        ),
         barrierColor: Colors.transparent,
         barrierColor: Colors.transparent,
-        isScrollControlled: false,
+        backgroundColor: Colors.transparent,
+        isScrollControlled: true,
         context: context,
         context: context,
         builder: (context) {
         builder: (context) {
           return ExifBottomSheet(assetDetail: assetDetail!);
           return ExifBottomSheet(assetDetail: assetDetail!);
@@ -162,6 +165,7 @@ class GalleryViewerPage extends HookConsumerWidget {
                   heroTag: assetList[index].id,
                   heroTag: assetList[index].id,
                   loadPreview: isLoadPreview.value,
                   loadPreview: isLoadPreview.value,
                   loadOriginal: isLoadOriginal.value,
                   loadOriginal: isLoadOriginal.value,
+                  showExifSheet: showInfo,
                 );
                 );
               }
               }
             } else {
             } else {

+ 3 - 14
mobile/lib/modules/asset_viewer/views/image_viewer_page.dart

@@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
 import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
 import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
 import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
-import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
 import 'package:immich_mobile/modules/asset_viewer/ui/remote_photo_view.dart';
 import 'package:immich_mobile/modules/asset_viewer/ui/remote_photo_view.dart';
 import 'package:immich_mobile/modules/home/services/asset.service.dart';
 import 'package:immich_mobile/modules/home/services/asset.service.dart';
 import 'package:immich_mobile/shared/models/asset.dart';
 import 'package:immich_mobile/shared/models/asset.dart';
@@ -17,6 +16,7 @@ class ImageViewerPage extends HookConsumerWidget {
   final String authToken;
   final String authToken;
   final ValueNotifier<bool> isZoomedListener;
   final ValueNotifier<bool> isZoomedListener;
   final void Function() isZoomedFunction;
   final void Function() isZoomedFunction;
+  final void Function()? showExifSheet;
   final bool loadPreview;
   final bool loadPreview;
   final bool loadOriginal;
   final bool loadOriginal;
 
 
@@ -29,6 +29,7 @@ class ImageViewerPage extends HookConsumerWidget {
     required this.isZoomedListener,
     required this.isZoomedListener,
     required this.loadPreview,
     required this.loadPreview,
     required this.loadOriginal,
     required this.loadOriginal,
+    this.showExifSheet,
   }) : super(key: key);
   }) : super(key: key);
 
 
   Asset? assetDetail;
   Asset? assetDetail;
@@ -56,18 +57,6 @@ class ImageViewerPage extends HookConsumerWidget {
       [],
       [],
     );
     );
 
 
-    showInfo() {
-      showModalBottomSheet(
-        backgroundColor: Colors.black,
-        barrierColor: Colors.transparent,
-        isScrollControlled: false,
-        context: context,
-        builder: (context) {
-          return ExifBottomSheet(assetDetail: assetDetail ?? asset);
-        },
-      );
-    }
-
     return Stack(
     return Stack(
       children: [
       children: [
         Center(
         Center(
@@ -81,7 +70,7 @@ class ImageViewerPage extends HookConsumerWidget {
               isZoomedFunction: isZoomedFunction,
               isZoomedFunction: isZoomedFunction,
               isZoomedListener: isZoomedListener,
               isZoomedListener: isZoomedListener,
               onSwipeDown: () => AutoRouter.of(context).pop(),
               onSwipeDown: () => AutoRouter.of(context).pop(),
-              onSwipeUp: asset.isRemote ? showInfo : () {},
+              onSwipeUp: (asset.isRemote && showExifSheet  != null) ? showExifSheet! : () {},
             ),
             ),
           ),
           ),
         ),
         ),

+ 1 - 50
mobile/lib/modules/home/ui/control_bottom_app_bar.dart

@@ -5,6 +5,7 @@ import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
 import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
+import 'package:immich_mobile/shared/ui/drag_sheet.dart';
 import 'package:immich_mobile/utils/image_url_builder.dart';
 import 'package:immich_mobile/utils/image_url_builder.dart';
 import 'package:openapi/api.dart';
 import 'package:openapi/api.dart';
 
 
@@ -200,53 +201,3 @@ class AddToAlbumTitleRow extends StatelessWidget {
     );
     );
   }
   }
 }
 }
-
-class CustomDraggingHandle extends StatelessWidget {
-  const CustomDraggingHandle({super.key});
-
-  @override
-  Widget build(BuildContext context) {
-    return Container(
-      height: 5,
-      width: 30,
-      decoration: BoxDecoration(
-        color: Colors.grey[500],
-        borderRadius: BorderRadius.circular(16),
-      ),
-    );
-  }
-}
-
-class ControlBoxButton extends StatelessWidget {
-  const ControlBoxButton({
-    Key? key,
-    required this.label,
-    required this.iconData,
-    required this.onPressed,
-  }) : super(key: key);
-
-  final String label;
-  final IconData iconData;
-  final Function onPressed;
-
-  @override
-  Widget build(BuildContext context) {
-    return MaterialButton(
-      padding: const EdgeInsets.all(10),
-      shape: const CircleBorder(),
-      onPressed: () => onPressed(),
-      child: Column(
-        mainAxisAlignment: MainAxisAlignment.start,
-        crossAxisAlignment: CrossAxisAlignment.center,
-        children: [
-          Icon(iconData, size: 24),
-          const SizedBox(height: 6),
-          Text(
-            label,
-            style: const TextStyle(fontSize: 12.0),
-          ),
-        ],
-      ),
-    );
-  }
-}

+ 51 - 0
mobile/lib/shared/ui/drag_sheet.dart

@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+
+class CustomDraggingHandle extends StatelessWidget {
+  const CustomDraggingHandle({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      height: 5,
+      width: 30,
+      decoration: BoxDecoration(
+        color: Colors.grey[500],
+        borderRadius: BorderRadius.circular(16),
+      ),
+    );
+  }
+}
+
+class ControlBoxButton extends StatelessWidget {
+  const ControlBoxButton({
+    Key? key,
+    required this.label,
+    required this.iconData,
+    required this.onPressed,
+  }) : super(key: key);
+
+  final String label;
+  final IconData iconData;
+  final Function onPressed;
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialButton(
+      padding: const EdgeInsets.all(10),
+      shape: const CircleBorder(),
+      onPressed: () => onPressed(),
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.start,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: [
+          Icon(iconData, size: 24),
+          const SizedBox(height: 6),
+          Text(
+            label,
+            style: const TextStyle(fontSize: 12.0),
+          ),
+        ],
+      ),
+    );
+  }
+}