Explorar el Código

Make image view swipable

Vishnu Mohandas hace 5 años
padre
commit
9fb790eefc

+ 9 - 8
lib/face_search_manager.dart

@@ -1,8 +1,10 @@
 import 'package:dio/dio.dart';
 import 'package:logger/logger.dart';
 import 'package:myapp/core/constants.dart' as Constants;
+import 'package:myapp/db/db_helper.dart';
 
 import 'models/face.dart';
+import 'models/photo.dart';
 import 'models/search_result.dart';
 
 class FaceSearchManager {
@@ -23,14 +25,13 @@ class FaceSearchManager {
         .catchError(_onError);
   }
 
-  Future<List<SearchResult>> getFaceSearchResults(Face face) {
-    return _dio
-        .get(Constants.ENDPOINT + "/search/face/" + face.faceID.toString(),
-            queryParameters: {"user": Constants.USER})
-        .then((response) => (response.data["results"] as List)
-            .map((result) => SearchResult(result))
-            .toList())
-        .catchError(_onError);
+  Future<List<Photo>> getFaceSearchResults(Face face) async {
+    var futures = _dio.get(
+        Constants.ENDPOINT + "/search/face/" + face.faceID.toString(),
+        queryParameters: {"user": Constants.USER}).then((response) => (response
+            .data["results"] as List)
+        .map((result) => (DatabaseHelper.instance.getPhotoByPath(result))));
+    return Future.wait(await futures);
   }
 
   void _onError(error) {

+ 43 - 15
lib/ui/detail_page.dart

@@ -1,20 +1,27 @@
 import 'dart:io';
 
 import 'package:flutter/material.dart';
-import 'package:logger/logger.dart';
 import 'package:myapp/core/lru_map.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:photo_view/photo_view.dart';
 import 'package:share_extend/share_extend.dart';
+import 'extents_page_view.dart';
 
-class DetailPage extends StatelessWidget {
-  final Photo photo;
+class DetailPage extends StatefulWidget {
+  final List<Photo> photos;
+  int selectedIndex;
 
-  const DetailPage(this.photo, {Key key}) : super(key: key);
+  DetailPage(this.photos, this.selectedIndex, {Key key}) : super(key: key);
+
+  @override
+  _DetailPageState createState() => _DetailPageState();
+}
+
+class _DetailPageState extends State<DetailPage> {
+  bool _shouldDisableScroll = false;
 
   @override
   Widget build(BuildContext context) {
-    Logger().i(photo.localPath);
     return Scaffold(
       appBar: AppBar(
         actions: <Widget>[
@@ -22,32 +29,53 @@ class DetailPage extends StatelessWidget {
           IconButton(
             icon: Icon(Icons.share),
             onPressed: () {
-              ShareExtend.share(photo.localPath, "image");
+              ShareExtend.share(
+                  widget.photos[widget.selectedIndex].localPath, "image");
             },
           )
         ],
       ),
       body: Center(
         child: Container(
-          child: _buildContent(context),
+          child: ExtentsPageView.extents(
+            itemBuilder: (context, index) {
+              return _buildItem(context, widget.photos[index]);
+            },
+            onPageChanged: (int index) {
+              widget.selectedIndex = index;
+            },
+            physics: _shouldDisableScroll
+                ? NeverScrollableScrollPhysics()
+                : PageScrollPhysics(),
+          ),
         ),
       ),
     );
   }
 
-  Widget _buildContent(BuildContext context) {
+  Widget _buildItem(BuildContext context, Photo photo) {
     var image = ImageLruCache.getData(photo.localPath) == null
         ? Image.file(
             File(photo.localPath),
             filterQuality: FilterQuality.low,
           )
         : ImageLruCache.getData(photo.localPath);
-    return GestureDetector(
-        onVerticalDragUpdate: (details) {
-          Navigator.pop(context);
-        },
-        child: PhotoView(
-          imageProvider: image.image,
-        ));
+    ValueChanged<PhotoViewScaleState> scaleStateChangedCallback = (value) {
+      var shouldDisableScroll;
+      if (value == PhotoViewScaleState.initial) {
+        shouldDisableScroll = false;
+      } else {
+        shouldDisableScroll = true;
+      }
+      if (shouldDisableScroll != _shouldDisableScroll) {
+        setState(() {
+          _shouldDisableScroll = shouldDisableScroll;
+        });
+      }
+    };
+    return PhotoView(
+      imageProvider: image.image,
+      scaleStateChangedCallback: scaleStateChangedCallback,
+    );
   }
 }

+ 381 - 0
lib/ui/extents_page_view.dart

@@ -0,0 +1,381 @@
+import 'package:flutter/gestures.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/widgets.dart' hide PageView;
+
+/// This is copy-pasted from the Flutter framework with a support added for building
+/// pages off screen using [Viewport.cacheExtents] and a [LayoutBuilder]
+///
+/// Based on commit 3932ffb1cd5dfa0c3891c60977ee4f9cd70ade66 on channel dev
+
+// Having this global (mutable) page controller is a bit of a hack. We need it
+// to plumb in the factory for _PagePosition, but it will end up accumulating
+// a large list of scroll positions. As long as you don't try to actually
+// control the scroll positions, everything should be fine.
+final PageController _defaultPageController = PageController();
+const PageScrollPhysics _kPagePhysics = PageScrollPhysics();
+
+/// A scrollable list that works page by page.
+///
+/// Each child of a page view is forced to be the same size as the viewport.
+///
+/// You can use a [PageController] to control which page is visible in the view.
+/// In addition to being able to control the pixel offset of the content inside
+/// the [PageView], a [PageController] also lets you control the offset in terms
+/// of pages, which are increments of the viewport size.
+///
+/// The [PageController] can also be used to control the
+/// [PageController.initialPage], which determines which page is shown when the
+/// [PageView] is first constructed, and the [PageController.viewportFraction],
+/// which determines the size of the pages as a fraction of the viewport size.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=J1gE9xvph-A}
+///
+/// See also:
+///
+///  * [PageController], which controls which page is visible in the view.
+///  * [SingleChildScrollView], when you need to make a single child scrollable.
+///  * [ListView], for a scrollable list of boxes.
+///  * [GridView], for a scrollable grid of boxes.
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+class ExtentsPageView extends StatefulWidget {
+  /// Creates a scrollable list that works page by page from an explicit [List]
+  /// of widgets.
+  ///
+  /// This constructor is appropriate for page views with a small number of
+  /// children because constructing the [List] requires doing work for every
+  /// child that could possibly be displayed in the page view, instead of just
+  /// those children that are actually visible.
+  ExtentsPageView({
+    Key key,
+    this.scrollDirection = Axis.horizontal,
+    this.reverse = false,
+    PageController controller,
+    this.physics,
+    this.pageSnapping = true,
+    this.onPageChanged,
+    List<Widget> children = const <Widget>[],
+    this.dragStartBehavior = DragStartBehavior.start,
+  })  : controller = controller ?? _defaultPageController,
+        childrenDelegate = SliverChildListDelegate(children),
+        extents = 0,
+        super(key: key);
+
+  /// Creates a scrollable list that works page by page using widgets that are
+  /// created on demand.
+  ///
+  /// This constructor is appropriate for page views with a large (or infinite)
+  /// number of children because the builder is called only for those children
+  /// that are actually visible.
+  ///
+  /// Providing a non-null [itemCount] lets the [PageView] compute the maximum
+  /// scroll extent.
+  ///
+  /// [itemBuilder] will be called only with indices greater than or equal to
+  /// zero and less than [itemCount].
+  ///
+  /// [PageView.builder] by default does not support child reordering. If
+  /// you are planning to change child order at a later time, consider using
+  /// [PageView] or [PageView.custom].
+  ExtentsPageView.builder({
+    Key key,
+    this.scrollDirection = Axis.horizontal,
+    this.reverse = false,
+    PageController controller,
+    this.physics,
+    this.pageSnapping = true,
+    this.onPageChanged,
+    @required IndexedWidgetBuilder itemBuilder,
+    int itemCount,
+    this.dragStartBehavior = DragStartBehavior.start,
+  })  : controller = controller ?? _defaultPageController,
+        childrenDelegate =
+            SliverChildBuilderDelegate(itemBuilder, childCount: itemCount),
+        extents = 0,
+        super(key: key);
+
+  ExtentsPageView.extents({
+    Key key,
+    this.extents = 1,
+    this.scrollDirection = Axis.horizontal,
+    this.reverse = false,
+    PageController controller,
+    this.physics,
+    this.pageSnapping = true,
+    this.onPageChanged,
+    @required IndexedWidgetBuilder itemBuilder,
+    int itemCount,
+    this.dragStartBehavior = DragStartBehavior.start,
+  })  : controller = controller ?? _defaultPageController,
+        childrenDelegate = SliverChildBuilderDelegate(
+          itemBuilder,
+          childCount: itemCount,
+          addAutomaticKeepAlives: false,
+          addRepaintBoundaries: false,
+        ),
+        super(key: key);
+
+  /// Creates a scrollable list that works page by page with a custom child
+  /// model.
+  ///
+  /// {@tool sample}
+  ///
+  /// This [PageView] uses a custom [SliverChildBuilderDelegate] to support child
+  /// reordering.
+  ///
+  /// ```dart
+  /// class MyPageView extends StatefulWidget {
+  ///   @override
+  ///   _MyPageViewState createState() => _MyPageViewState();
+  /// }
+  ///
+  /// class _MyPageViewState extends State<MyPageView> {
+  ///   List<String> items = <String>['1', '2', '3', '4', '5'];
+  ///
+  ///   void _reverse() {
+  ///     setState(() {
+  ///       items = items.reversed.toList();
+  ///     });
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Scaffold(
+  ///       body: SafeArea(
+  ///         child: PageView.custom(
+  ///           childrenDelegate: SliverChildBuilderDelegate(
+  ///             (BuildContext context, int index) {
+  ///               return KeepAlive(
+  ///                 data: items[index],
+  ///                 key: ValueKey<String>(items[index]),
+  ///               );
+  ///             },
+  ///             childCount: items.length,
+  ///             findChildIndexCallback: (Key key) {
+  ///               final ValueKey valueKey = key;
+  ///               final String data = valueKey.value;
+  ///               return items.indexOf(data);
+  ///             }
+  ///           ),
+  ///         ),
+  ///       ),
+  ///       bottomNavigationBar: BottomAppBar(
+  ///         child: Row(
+  ///           mainAxisAlignment: MainAxisAlignment.center,
+  ///           children: <Widget>[
+  ///             FlatButton(
+  ///               onPressed: () => _reverse(),
+  ///               child: Text('Reverse items'),
+  ///             ),
+  ///           ],
+  ///         ),
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  ///
+  /// class KeepAlive extends StatefulWidget {
+  ///   const KeepAlive({Key key, this.data}) : super(key: key);
+  ///
+  ///   final String data;
+  ///
+  ///   @override
+  ///   _KeepAliveState createState() => _KeepAliveState();
+  /// }
+  ///
+  /// class _KeepAliveState extends State<KeepAlive> with AutomaticKeepAliveClientMixin{
+  ///   @override
+  ///   bool get wantKeepAlive => true;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     super.build(context);
+  ///     return Text(widget.data);
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ExtentsPageView.custom({
+    Key key,
+    this.scrollDirection = Axis.horizontal,
+    this.reverse = false,
+    PageController controller,
+    this.physics,
+    this.pageSnapping = true,
+    this.onPageChanged,
+    @required this.childrenDelegate,
+    this.dragStartBehavior = DragStartBehavior.start,
+  })  : assert(childrenDelegate != null),
+        extents = 0,
+        controller = controller ?? _defaultPageController,
+        super(key: key);
+
+  /// The number of pages to build off screen.
+  ///
+  /// For example, a value of `1` builds one page ahead and one page behind,
+  /// for a total of three built pages.
+  ///
+  /// This is especially useful for making sure heavyweight widgets have a chance
+  /// to load off-screen before the user pulls it into the viewport.
+  final int extents;
+
+  /// The axis along which the page view scrolls.
+  ///
+  /// Defaults to [Axis.horizontal].
+  final Axis scrollDirection;
+
+  /// Whether the page view scrolls in the reading direction.
+  ///
+  /// For example, if the reading direction is left-to-right and
+  /// [scrollDirection] is [Axis.horizontal], then the page view scrolls from
+  /// left to right when [reverse] is false and from right to left when
+  /// [reverse] is true.
+  ///
+  /// Similarly, if [scrollDirection] is [Axis.vertical], then the page view
+  /// scrolls from top to bottom when [reverse] is false and from bottom to top
+  /// when [reverse] is true.
+  ///
+  /// Defaults to false.
+  final bool reverse;
+
+  /// An object that can be used to control the position to which this page
+  /// view is scrolled.
+  final PageController controller;
+
+  /// How the page view should respond to user input.
+  ///
+  /// For example, determines how the page view continues to animate after the
+  /// user stops dragging the page view.
+  ///
+  /// The physics are modified to snap to page boundaries using
+  /// [PageScrollPhysics] prior to being used.
+  ///
+  /// Defaults to matching platform conventions.
+  final ScrollPhysics physics;
+
+  /// Set to false to disable page snapping, useful for custom scroll behavior.
+  final bool pageSnapping;
+
+  /// Called whenever the page in the center of the viewport changes.
+  final ValueChanged<int> onPageChanged;
+
+  /// A delegate that provides the children for the [PageView].
+  ///
+  /// The [PageView.custom] constructor lets you specify this delegate
+  /// explicitly. The [PageView] and [PageView.builder] constructors create a
+  /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
+  /// respectively.
+  final SliverChildDelegate childrenDelegate;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  _PageViewState createState() => _PageViewState();
+}
+
+class _PageViewState extends State<ExtentsPageView> {
+  int _lastReportedPage = 0;
+
+  @override
+  void initState() {
+    super.initState();
+    _lastReportedPage = widget.controller.initialPage;
+  }
+
+  AxisDirection _getDirection(BuildContext context) {
+    switch (widget.scrollDirection) {
+      case Axis.horizontal:
+        assert(debugCheckHasDirectionality(context));
+        final TextDirection textDirection = Directionality.of(context);
+        final AxisDirection axisDirection =
+            textDirectionToAxisDirection(textDirection);
+        return widget.reverse
+            ? flipAxisDirection(axisDirection)
+            : axisDirection;
+      case Axis.vertical:
+        return widget.reverse ? AxisDirection.up : AxisDirection.down;
+    }
+    return null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final AxisDirection axisDirection = _getDirection(context);
+    final ScrollPhysics physics = widget.pageSnapping
+        ? _kPagePhysics.applyTo(widget.physics)
+        : widget.physics;
+
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification.depth == 0 &&
+            widget.onPageChanged != null &&
+            notification is ScrollUpdateNotification) {
+          final PageMetrics metrics = notification.metrics;
+          final int currentPage = metrics.page.round();
+          if (currentPage != _lastReportedPage) {
+            _lastReportedPage = currentPage;
+            widget.onPageChanged(currentPage);
+          }
+        }
+        return false;
+      },
+      child: Scrollable(
+        dragStartBehavior: widget.dragStartBehavior,
+        axisDirection: axisDirection,
+        controller: widget.controller,
+        physics: physics,
+        viewportBuilder: (BuildContext context, ViewportOffset position) {
+          return LayoutBuilder(
+            builder: (context, constraints) {
+              assert(constraints.hasBoundedHeight);
+              assert(constraints.hasBoundedWidth);
+
+              double cacheExtent;
+
+              switch (widget.scrollDirection) {
+                case Axis.vertical:
+                  cacheExtent = constraints.maxHeight * widget.extents;
+                  break;
+
+                case Axis.horizontal:
+                default:
+                  cacheExtent = constraints.maxWidth * widget.extents;
+                  break;
+              }
+
+              return Viewport(
+                cacheExtent: cacheExtent,
+                axisDirection: axisDirection,
+                offset: position,
+                slivers: <Widget>[
+                  SliverFillViewport(
+                    viewportFraction: widget.controller.viewportFraction,
+                    delegate: widget.childrenDelegate,
+                  ),
+                ],
+              );
+            },
+          );
+        },
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description
+        .add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection));
+    description.add(
+        FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'));
+    description.add(DiagnosticsProperty<PageController>(
+        'controller', widget.controller,
+        showName: false));
+    description.add(DiagnosticsProperty<ScrollPhysics>(
+        'physics', widget.physics,
+        showName: false));
+    description.add(FlagProperty('pageSnapping',
+        value: widget.pageSnapping, ifFalse: 'snapping disabled'));
+  }
+}

+ 9 - 34
lib/ui/face_search_results_page.dart

@@ -1,13 +1,10 @@
 import 'package:flutter/material.dart';
-import 'package:myapp/db/db_helper.dart';
 import 'package:myapp/face_search_manager.dart';
 import 'package:myapp/models/face.dart';
 import 'package:myapp/models/photo.dart';
-import 'package:myapp/models/search_result.dart';
 import 'package:myapp/ui/circular_network_image_widget.dart';
 import 'package:myapp/core/constants.dart' as Constants;
 import 'package:myapp/ui/image_widget.dart';
-import 'package:myapp/ui/network_image_detail_page.dart';
 
 import 'detail_page.dart';
 
@@ -38,14 +35,14 @@ class FaceSearchResultsPage extends StatelessWidget {
     );
   }
 
-  FutureBuilder<List<SearchResult>> _getBody() {
-    return FutureBuilder<List<SearchResult>>(
+  FutureBuilder<List<Photo>> _getBody() {
+    return FutureBuilder<List<Photo>>(
       future: _faceSearchManager.getFaceSearchResults(_face),
       builder: (context, snapshot) {
         if (snapshot.hasData) {
           return GridView.builder(
               itemBuilder: (_, index) =>
-                  _buildItem(context, snapshot.data[index].path),
+                  _buildItem(context, snapshot.data, index),
               itemCount: snapshot.data.length,
               gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                 crossAxisCount: 4,
@@ -57,40 +54,18 @@ class FaceSearchResultsPage extends StatelessWidget {
     );
   }
 
-  Widget _buildItem(BuildContext context, String path) {
+  Widget _buildItem(BuildContext context, List<Photo> photos, int index) {
     return GestureDetector(
       onTap: () async {
-        _routeToDetailPage(path, context);
+        _routeToDetailPage(photos, index, context);
       },
-      child: _getImage(path),
+      child: ImageWidget(photos[index]),
     );
   }
 
-  Widget _getImage(String path) {
-    return FutureBuilder<Photo>(
-      future: DatabaseHelper.instance.getPhotoByPath(path),
-      builder: (_, snapshot) {
-        if (snapshot.hasData) {
-          return ImageWidget(snapshot.data);
-        } else if (snapshot.hasError) {
-          return Container(
-            margin: EdgeInsets.all(2),
-            child: Image.network(Constants.ENDPOINT + "/" + path,
-                height: 124, width: 124, fit: BoxFit.cover),
-          );
-        } else {
-          return Text("Loading...");
-        }
-      },
-    );
-  }
-
-  void _routeToDetailPage(String path, BuildContext context) async {
-    Widget page = NetworkImageDetailPage(path);
-    var photo = await DatabaseHelper.instance.getPhotoByPath(path);
-    if (photo != null) {
-      page = DetailPage(photo);
-    }
+  void _routeToDetailPage(
+      List<Photo> photos, int index, BuildContext context) async {
+    var page = DetailPage(photos, index);
     Navigator.of(context).push(
       MaterialPageRoute(
         builder: (BuildContext context) {

+ 1 - 1
lib/ui/gallery.dart

@@ -160,7 +160,7 @@ class _GalleryState extends State<Gallery> {
   }
 
   void routeToDetailPage(Photo photo, BuildContext context) {
-    final page = DetailPage(photo);
+    final page = DetailPage(photoLoader.photos, photoLoader.photos.indexOf(photo));
     Navigator.of(context).push(
       MaterialPageRoute(
         builder: (BuildContext context) {