From ad9373312b2260d45638feaa2f05ffbd92766d00 Mon Sep 17 00:00:00 2001
From: martyfuhry <martyfuhry@gmail.com>
Date: Fri, 10 Feb 2023 21:31:15 -0500
Subject: [PATCH] feat(mobile): Responsive display of exif data in bottom sheet
 (#1725)

* two column view of exif

* fixes padding

* fixed divider when no map

* fixed map visibility in two column
---
 .../asset_viewer/ui/exif_bottom_sheet.dart    | 327 +++++++++++-------
 1 file changed, 200 insertions(+), 127 deletions(-)

diff --git a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart
index 7977742bb1..e528bb1b78 100644
--- a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart
+++ b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart
@@ -14,53 +14,60 @@ class ExifBottomSheet extends HookConsumerWidget {
   const ExifBottomSheet({Key? key, required this.assetDetail})
       : super(key: key);
 
+  bool get showMap => assetDetail.latitude != null && assetDetail.longitude != null;
+
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     buildMap() {
       return Padding(
         padding: const EdgeInsets.symmetric(vertical: 16.0),
-        child: Container(
-          height: 150,
-          width: MediaQuery.of(context).size.width,
-          decoration: const BoxDecoration(
-            borderRadius: BorderRadius.all(Radius.circular(15)),
-          ),
-          child: FlutterMap(
-            options: MapOptions(
-              center: LatLng(
-                assetDetail.latitude ?? 0,
-                assetDetail.longitude ?? 0,
+        child: LayoutBuilder(
+          builder: (context, constraints) {
+            return Container(
+              height: 150,
+              width: constraints.maxWidth,
+              decoration: const BoxDecoration(
+                borderRadius: BorderRadius.all(Radius.circular(15)),
               ),
-              zoom: 16.0,
-            ),
-            layers: [
-              TileLayerOptions(
-                urlTemplate:
-                    "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
-                subdomains: ['a', 'b', 'c'],
-                attributionBuilder: (_) {
-                  return const Text(
-                    "© OpenStreetMap",
-                    style: TextStyle(fontSize: 10),
-                  );
-                },
-              ),
-              MarkerLayerOptions(
-                markers: [
-                  Marker(
-                    anchorPos: AnchorPos.align(AnchorAlign.top),
-                    point: LatLng(
-                      assetDetail.latitude ?? 0,
-                      assetDetail.longitude ?? 0,
-                    ),
-                    builder: (ctx) => const Image(
-                      image: AssetImage('assets/location-pin.png'),
-                    ),
+              child: FlutterMap(
+                options: MapOptions(
+                  interactiveFlags: InteractiveFlag.none,
+                  center: LatLng(
+                    assetDetail.latitude ?? 0,
+                    assetDetail.longitude ?? 0,
+                  ),
+                  zoom: 16.0,
+                ),
+                layers: [
+                  TileLayerOptions(
+                    urlTemplate:
+                        "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
+                    subdomains: ['a', 'b', 'c'],
+                    attributionBuilder: (_) {
+                      return const Text(
+                        "© OpenStreetMap",
+                        style: TextStyle(fontSize: 10),
+                      );
+                    },
+                  ),
+                  MarkerLayerOptions(
+                    markers: [
+                      Marker(
+                        anchorPos: AnchorPos.align(AnchorAlign.top),
+                        point: LatLng(
+                          assetDetail.latitude ?? 0,
+                          assetDetail.longitude ?? 0,
+                        ),
+                        builder: (ctx) => const Image(
+                          image: AssetImage('assets/location-pin.png'),
+                        ),
+                      ),
+                    ],
                   ),
                 ],
               ),
-            ],
-          ),
+            );
+          },
         ),
       );
     }
@@ -91,6 +98,107 @@ class ExifBottomSheet extends HookConsumerWidget {
       return text.isEmpty ? null : Text(text);
     }
 
+    buildDragHeader() {
+      return Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: const [
+          SizedBox(height: 12),
+          Align(
+            alignment: Alignment.center,
+            child: CustomDraggingHandle(),
+          ),
+          SizedBox(height: 12),
+        ],
+      );
+    }
+
+    buildLocation() {
+      // Guard no lat/lng
+      if (!showMap) {
+        return Container();
+      }
+
+      return Column(
+        children: [
+          // Location
+          Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Text(
+                "exif_bottom_sheet_location",
+                style: TextStyle(fontSize: 11, color: textColor),
+              ).tr(),
+              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),
+              )
+            ],
+          ),
+        ],
+      );
+    }
+
+    buildDate() {
+      return Text(
+        DateFormat('date_format'.tr()).format(
+          assetDetail.createdAt.toLocal(),
+        ),
+        style: const TextStyle(
+          fontWeight: FontWeight.bold,
+          fontSize: 14,
+        ),
+      );
+    }
+
+    buildDetail() {
+      return Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          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(
+              assetDetail.fileName,
+              style: TextStyle(
+                fontWeight: FontWeight.bold,
+                color: textColor,
+              ),
+            ),
+            subtitle: buildSizeText(assetDetail),
+          ),
+          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}   ${exifInfo.exposureTime}   ${exifInfo.focalLength} mm   ISO${exifInfo.iso} ",
+              ),
+            ),
+        ],
+      );
+    }
+
     return SingleChildScrollView(
       child: Card(
         shape: const RoundedRectangleBorder(
@@ -102,104 +210,69 @@ class ExifBottomSheet extends HookConsumerWidget {
         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(),
-              ),
-              const SizedBox(height: 12),
-              Text(
-                DateFormat('date_format'.tr()).format(
-                  assetDetail.createdAt.toLocal(),
-                ),
-                style: const TextStyle(
-                  fontWeight: FontWeight.bold,
-                  fontSize: 14,
-                ),
-              ),
-
-              // Location
-              if (assetDetail.latitude != null && assetDetail.longitude != null)
-                Padding(
-                  padding: const EdgeInsets.only(top: 32.0),
+          child: LayoutBuilder(
+            builder: (context, constraints) {
+              if (constraints.maxWidth > 600) {
+                // Two column
+                return Padding(
+                  padding: const EdgeInsets.symmetric(horizontal: 12.0),
                   child: Column(
-                    crossAxisAlignment: CrossAxisAlignment.start,
+                    crossAxisAlignment: CrossAxisAlignment.stretch,
                     children: [
-                      const Divider(
-                        thickness: 1,
+                      buildDragHeader(),
+                      buildDate(),
+                      const SizedBox(height: 32.0),
+                      Row(
+                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: [
+                          Flexible(
+                            flex: showMap ? 5 : 0,
+                            child: Padding(
+                              padding: const EdgeInsets.only(right: 8.0),
+                              child: buildLocation(),
+                            ),
+                          ),
+                          Flexible(
+                            flex: 5,
+                            child: Padding(
+                              padding: const EdgeInsets.only(left: 8.0),
+                              child: buildDetail(),
+                            ),
+                          ),
+                        ],
                       ),
-                      Text(
-                        "exif_bottom_sheet_location",
-                        style: TextStyle(fontSize: 11, color: textColor),
-                      ).tr(),
-                      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),
-                      )
+                      const SizedBox(height: 50),
                     ],
                   ),
-                ),
-              // Detail
-              Padding(
-                padding: const EdgeInsets.only(top: 32.0),
-                child: Column(
-                  crossAxisAlignment: CrossAxisAlignment.start,
-                  children: [
+                );
+              }
+
+              // One column
+              return Column(
+                crossAxisAlignment: CrossAxisAlignment.stretch,
+                children: [
+                  buildDragHeader(),
+                  buildDate(),
+                  const SizedBox(height: 16.0),
+                  if (showMap)
                     Divider(
                       thickness: 1,
                       color: Colors.grey[600],
                     ),
-                    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(
-                        assetDetail.fileName,
-                        style: TextStyle(
-                          fontWeight: FontWeight.bold,
-                          color: textColor,
-                        ),
-                      ),
-                      subtitle: buildSizeText(assetDetail),
-                    ),
-                    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}   ${exifInfo.exposureTime}   ${exifInfo.focalLength} mm   ISO${exifInfo.iso} ",
-                        ),
-                      ),
-                  ],
-                ),
-              ),
-              const SizedBox(
-                height: 50,
-              ),
-            ],
+                  const SizedBox(height: 16.0),
+                  buildLocation(),
+                  const SizedBox(height: 16.0),
+                  Divider(
+                    thickness: 1,
+                    color: Colors.grey[600],
+                  ),
+                  const SizedBox(height: 16.0),
+                  buildDetail(),
+                  const SizedBox(height: 50),
+                ],
+              );
+            },
           ),
         ),
       ),