Browse Source

Implement a lazy loading gallery

Vishnu Mohandas 4 years ago
parent
commit
8b73805af4

+ 146 - 0
lib/ui/huge_listview/lazy_loading_gallery.dart

@@ -0,0 +1,146 @@
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:logging/logging.dart';
+import 'package:photos/models/file.dart';
+import 'package:photos/ui/huge_listview/place_holder_widget.dart';
+import 'package:photos/ui/thumbnail_widget.dart';
+import 'package:photos/utils/date_time_util.dart';
+import 'package:visibility_detector/visibility_detector.dart';
+
+class LazyLoadingGallery extends StatelessWidget {
+  static const kSubGalleryItemLimit = 80;
+  static final _logger = Logger("LazyLoadingDayGallery");
+  final files;
+  final selectedFiles;
+  final tag;
+  LazyLoadingGallery(this.files, this.selectedFiles, this.tag, {Key key})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      children: <Widget>[
+        getDayWidget(files[0].creationTime),
+        _getGallery(files),
+      ],
+    );
+  }
+
+  Widget _getGallery(List<File> files) {
+    _logger.info("Building sub gallery of length: " + files.length.toString());
+    List<Widget> childGalleries = [];
+    for (int index = 0; index < files.length; index += kSubGalleryItemLimit) {
+      childGalleries.add(LazyLoadingGridView(
+        tag,
+        files.sublist(index, min(index + kSubGalleryItemLimit, files.length)),
+        selectedFiles,
+      ));
+    }
+
+    return Padding(
+      padding: const EdgeInsets.only(bottom: 12),
+      child: Column(
+        children: childGalleries,
+      ),
+    );
+  }
+}
+
+class LazyLoadingGridView extends StatefulWidget {
+  final tag;
+  final files;
+  final selectedFiles;
+
+  LazyLoadingGridView(this.tag, this.files, this.selectedFiles, {Key key})
+      : super(key: key);
+
+  @override
+  _LazyLoadingGridViewState createState() => _LazyLoadingGridViewState();
+}
+
+class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
+  bool _isVisible = false;
+
+  @override
+  Widget build(BuildContext context) {
+    if (!_isVisible) {
+      return VisibilityDetector(
+        key: Key(widget.tag + widget.files[0].creationTime.toString()),
+        onVisibilityChanged: (visibility) {
+          if (visibility.visibleFraction > 0 && !_isVisible) {
+            setState(() {
+              _isVisible = true;
+            });
+          }
+        },
+        child: PlaceHolderWidget(widget.files.length),
+      );
+    } else {
+      return GridView.builder(
+        shrinkWrap: true,
+        physics:
+            NeverScrollableScrollPhysics(), // to disable GridView's scrolling
+        itemBuilder: (context, index) {
+          return _buildFile(context, widget.files[index]);
+        },
+        itemCount: widget.files.length,
+        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+          crossAxisCount: 4,
+        ),
+      );
+    }
+  }
+
+  Widget _buildFile(BuildContext context, File file) {
+    return GestureDetector(
+      onTap: () {
+        if (widget.selectedFiles.files.isNotEmpty) {
+          _selectFile(file);
+        } else {
+          _routeToDetailPage(file, context);
+        }
+      },
+      onLongPress: () {
+        HapticFeedback.lightImpact();
+        _selectFile(file);
+      },
+      child: Container(
+        margin: const EdgeInsets.all(2.0),
+        decoration: BoxDecoration(
+          border: widget.selectedFiles.files.contains(file)
+              ? Border.all(
+                  width: 4.0,
+                  color: Theme.of(context).accentColor,
+                )
+              : null,
+        ),
+        child: Hero(
+          tag: widget.tag + file.tag(),
+          child: ThumbnailWidget(file),
+        ),
+      ),
+    );
+  }
+
+  void _selectFile(File file) {
+    widget.selectedFiles.toggleSelection(file);
+  }
+
+  void _routeToDetailPage(File file, BuildContext context) {
+    // TODO
+    // final page = DetailPage(
+    //   _files,
+    //   _files.indexOf(file),
+    //   widget.tagPrefix,
+    // );
+    // Navigator.of(context).push(
+    //   MaterialPageRoute(
+    //     builder: (BuildContext context) {
+    //       return page;
+    //     },
+    //   ),
+    // );
+  }
+}

+ 3 - 12
lib/ui/huge_listview/place_holder_widget.dart

@@ -1,13 +1,10 @@
 import 'package:flutter/material.dart';
 
 class PlaceHolderWidget extends StatelessWidget {
-  const PlaceHolderWidget({
+  const PlaceHolderWidget(this.count,{
     Key key,
-    @required this.day,
-    @required this.count,
   }) : super(key: key);
 
-  final Widget day;
   final int count;
 
   static final _gridViewCache = Map<int, GridView>();
@@ -17,7 +14,6 @@ class PlaceHolderWidget extends StatelessWidget {
     if (!_gridViewCache.containsKey(count)) {
       _gridViewCache[count] = GridView.builder(
         shrinkWrap: true,
-        padding: EdgeInsets.only(bottom: 12),
         physics: NeverScrollableScrollPhysics(),
         itemBuilder: (context, index) {
           return Container(
@@ -31,11 +27,6 @@ class PlaceHolderWidget extends StatelessWidget {
         ),
       );
     }
-    return Column(
-      children: <Widget>[
-        day,
-        _gridViewCache[count],
-      ],
-    );
+    return _gridViewCache[count];
   }
-}
+}

+ 33 - 0
lib/utils/date_time_util.dart

@@ -1,3 +1,5 @@
+import 'package:flutter/material.dart';
+
 Map<int, String> _months = {
   1: "Jan",
   2: "Feb",
@@ -151,3 +153,34 @@ bool isLeapYear(DateTime dateTime) {
     return false;
   }
 }
+
+Widget getDayWidget(int timestamp) {
+  return Container(
+    padding: const EdgeInsets.fromLTRB(10, 8, 0, 10),
+    alignment: Alignment.centerLeft,
+    child: Text(
+      _getDayTitle(timestamp),
+      style: TextStyle(
+        fontSize: 14,
+        color: Colors.white.withOpacity(0.85),
+      ),
+    ),
+  );
+}
+
+String _getDayTitle(int timestamp) {
+  final date = DateTime.fromMicrosecondsSinceEpoch(timestamp);
+  final now = DateTime.now();
+  var title = getDayAndMonth(date);
+  if (date.year == now.year && date.month == now.month) {
+    if (date.day == now.day) {
+      title = "Today";
+    } else if (date.day == now.day - 1) {
+      title = "Yesterday";
+    }
+  }
+  if (date.year != DateTime.now().year) {
+    title += " " + date.year.toString();
+  }
+  return title;
+}