Parcourir la source

Implement face search

Vishnu Mohandas il y a 5 ans
Parent
commit
7f3ab5894b

+ 15 - 0
lib/face_search_manager.dart

@@ -3,11 +3,16 @@ import 'package:logger/logger.dart';
 import 'package:myapp/core/constants.dart' as Constants;
 import 'package:myapp/core/constants.dart' as Constants;
 
 
 import 'models/face.dart';
 import 'models/face.dart';
+import 'models/search_result.dart';
 
 
 class FaceSearchManager {
 class FaceSearchManager {
   final _logger = Logger();
   final _logger = Logger();
   final _dio = Dio();
   final _dio = Dio();
 
 
+  FaceSearchManager._privateConstructor();
+  static final FaceSearchManager instance =
+      FaceSearchManager._privateConstructor();
+
   Future<List<Face>> getFaces() {
   Future<List<Face>> getFaces() {
     return _dio
     return _dio
         .get(Constants.ENDPOINT + "/faces",
         .get(Constants.ENDPOINT + "/faces",
@@ -18,6 +23,16 @@ class FaceSearchManager {
         .catchError(_onError);
         .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);
+  }
+
   void _onError(error) {
   void _onError(error) {
     _logger.e(error);
     _logger.e(error);
   }
   }

+ 6 - 5
lib/models/photo.dart

@@ -34,18 +34,18 @@ class Photo {
     var file = (await asset.originFile);
     var file = (await asset.originFile);
     photo.localPath = file.path;
     photo.localPath = file.path;
     photo.hash = getHash(file);
     photo.hash = getHash(file);
-    await setThumbnail(photo);
-    return photo;
+    return setThumbnail(photo).then((value) => photo);
   }
   }
 
 
   static Future<void> setThumbnail(Photo photo) async {
   static Future<void> setThumbnail(Photo photo) async {
     var externalPath = (await getApplicationDocumentsDirectory()).path;
     var externalPath = (await getApplicationDocumentsDirectory()).path;
-    var thumbnailPath = externalPath + "/photos/thumbnails/" + photo.hash + ".thumbnail";
+    var thumbnailPath =
+        externalPath + "/photos/thumbnails/" + photo.hash + ".thumbnail";
     var args = Map<String, String>();
     var args = Map<String, String>();
     args["assetPath"] = photo.localPath;
     args["assetPath"] = photo.localPath;
     args["thumbnailPath"] = thumbnailPath;
     args["thumbnailPath"] = thumbnailPath;
-    await compute(getThumbnailPath, args);
     photo.thumbnailPath = thumbnailPath;
     photo.thumbnailPath = thumbnailPath;
+    return compute(getThumbnailPath, args).then((value) => photo);
   }
   }
 
 
   static String getHash(File file) {
   static String getHash(File file) {
@@ -54,7 +54,8 @@ class Photo {
 }
 }
 
 
 Future<void> getThumbnailPath(Map<String, String> args) async {
 Future<void> getThumbnailPath(Map<String, String> args) async {
-  return File(args["thumbnailPath"])..writeAsBytes(_getThumbnail(args["assetPath"]));
+  return File(args["thumbnailPath"])
+    ..writeAsBytes(_getThumbnail(args["assetPath"]));
 }
 }
 
 
 List<int> _getThumbnail(String path) {
 List<int> _getThumbnail(String path) {

+ 5 - 0
lib/models/search_result.dart

@@ -0,0 +1,5 @@
+class SearchResult {
+  final String url;
+
+  SearchResult(this.url);
+}

+ 23 - 0
lib/ui/circular_network_image_widget.dart

@@ -0,0 +1,23 @@
+import 'package:flutter/material.dart';
+
+class CircularNetworkImageWidget extends StatelessWidget {
+  final String _url;
+  final double _size;
+
+  const CircularNetworkImageWidget(String url, double size, {Key key})
+      : this._url = url,
+        this._size = size,
+        super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return new Container(
+        width: _size,
+        height: _size,
+        margin: const EdgeInsets.only(right: 8),
+        decoration: new BoxDecoration(
+            shape: BoxShape.circle,
+            image: new DecorationImage(
+                fit: BoxFit.contain, image: new NetworkImage(_url))));
+  }
+}

+ 2 - 1
lib/ui/detail_page.dart

@@ -7,8 +7,9 @@ import 'package:share_extend/share_extend.dart';
 
 
 class DetailPage extends StatefulWidget {
 class DetailPage extends StatefulWidget {
   final Photo photo;
   final Photo photo;
+  final String url;
 
 
-  const DetailPage({Key key, this.photo}) : super(key: key);
+  const DetailPage({Key key, this.photo, this.url}) : super(key: key);
 
 
   @override
   @override
   _DetailPageState createState() => _DetailPageState();
   _DetailPageState createState() => _DetailPageState();

+ 80 - 0
lib/ui/face_search_results_page.dart

@@ -0,0 +1,80 @@
+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/search_result.dart';
+import 'package:myapp/ui/circular_network_image_widget.dart';
+import 'package:myapp/core/constants.dart' as Constants;
+import 'package:myapp/ui/network_image_detail_page.dart';
+
+import 'detail_page.dart';
+
+class FaceSearchResultsPage extends StatelessWidget {
+  final FaceSearchManager _faceSearchManager = FaceSearchManager.instance;
+  final Face _face;
+
+  FaceSearchResultsPage({Key key, Face face})
+      : this._face = face,
+        super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text("Search results"),
+        actions: <Widget>[
+          Hero(
+            tag: "face_" + _face.faceID.toString(),
+            child: CircularNetworkImageWidget(
+                Constants.ENDPOINT + _face.thumbnailURL, 20),
+          )
+        ],
+      ),
+      body: Container(
+        child: _getBody(),
+      ),
+    );
+  }
+
+  FutureBuilder<List<SearchResult>> _getBody() {
+    return FutureBuilder<List<SearchResult>>(
+      future: _faceSearchManager.getFaceSearchResults(_face),
+      builder: (context, snapshot) {
+        if (snapshot.hasData) {
+          return GridView.builder(
+              itemBuilder: (_, index) => _buildItem(
+                  context, Constants.ENDPOINT + snapshot.data[index].url),
+              itemCount: snapshot.data.length,
+              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+                crossAxisCount: 4,
+              ));
+        } else {
+          return Text("Loading...");
+        }
+      },
+    );
+  }
+
+  Widget _buildItem(BuildContext context, String url) {
+    return GestureDetector(
+      onTap: () {
+        _routeToDetailPage(url, context);
+      },
+      child: Container(
+        margin: EdgeInsets.all(2),
+        child: Image.network(url, height: 124, width: 124, fit: BoxFit.cover),
+      ),
+    );
+  }
+
+  void _routeToDetailPage(String url, BuildContext context) {
+    final page = NetworkImageDetailPage(url);
+    Navigator.of(context).push(
+      MaterialPageRoute(
+        builder: (BuildContext context) {
+          return page;
+        },
+      ),
+    );
+  }
+}

+ 2 - 2
lib/ui/gallery.dart

@@ -60,7 +60,7 @@ class _GalleryState extends State<Gallery> {
   Widget _buildItem(BuildContext context, int index) {
   Widget _buildItem(BuildContext context, int index) {
     var photo = photoLoader.getPhotos()[index];
     var photo = photoLoader.getPhotos()[index];
     return GestureDetector(
     return GestureDetector(
-      onTap: () async {
+      onTap: () {
         routeToDetailPage(photo, context);
         routeToDetailPage(photo, context);
       },
       },
       onLongPress: () {
       onLongPress: () {
@@ -73,7 +73,7 @@ class _GalleryState extends State<Gallery> {
     );
     );
   }
   }
 
 
-  void routeToDetailPage(Photo photo, BuildContext context) async {
+  void routeToDetailPage(Photo photo, BuildContext context) {
     final page = DetailPage(
     final page = DetailPage(
       photo: photo,
       photo: photo,
     );
     );

+ 38 - 0
lib/ui/network_image_detail_page.dart

@@ -0,0 +1,38 @@
+import 'package:flutter/material.dart';
+
+class NetworkImageDetailPage extends StatelessWidget {
+  final String _url;
+
+  const NetworkImageDetailPage(this._url, {Key key}) : super(key: key);
+
+  @override
+  Widget build(Object context) {
+    return Scaffold(
+      appBar: AppBar(
+        actions: <Widget>[
+          // action button
+          IconButton(
+            icon: Icon(Icons.share),
+            onPressed: () {
+              // TODO
+            },
+          )
+        ],
+      ),
+      body: Center(
+        child: Container(
+          child: _buildContent(context),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildContent(BuildContext context) {
+    return GestureDetector(
+      onVerticalDragUpdate: (details) {
+        Navigator.pop(context);
+      },
+      child: Image.network(_url),
+    );
+  }
+}

+ 26 - 11
lib/ui/search_page.dart

@@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
 import 'package:myapp/face_search_manager.dart';
 import 'package:myapp/face_search_manager.dart';
 import 'package:myapp/models/face.dart';
 import 'package:myapp/models/face.dart';
 import 'package:myapp/core/constants.dart' as Constants;
 import 'package:myapp/core/constants.dart' as Constants;
+import 'package:myapp/ui/circular_network_image_widget.dart';
+import 'package:myapp/ui/face_search_results_page.dart';
 
 
 class SearchPage extends StatelessWidget {
 class SearchPage extends StatelessWidget {
-  final FaceSearchManager _faceSearchManager = FaceSearchManager();
+  final FaceSearchManager _faceSearchManager = FaceSearchManager.instance;
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return Scaffold(
     return Scaffold(
@@ -63,15 +65,28 @@ class SearchPage extends StatelessWidget {
   }
   }
 
 
   Widget _buildItem(BuildContext context, Face face) {
   Widget _buildItem(BuildContext context, Face face) {
-    return new Container(
-        width: 60.0,
-        height: 60.0,
-        margin: const EdgeInsets.only(right: 8),
-        decoration: new BoxDecoration(
-            shape: BoxShape.circle,
-            image: new DecorationImage(
-                fit: BoxFit.contain,
-                image:
-                    new NetworkImage(Constants.ENDPOINT + face.thumbnailURL))));
+    return GestureDetector(
+      onTap: () {
+        _routeToSearchResults(face, context);
+      },
+      child: Hero(
+        tag: "face_" + face.faceID.toString(),
+        child: CircularNetworkImageWidget(
+            Constants.ENDPOINT + face.thumbnailURL, 60),
+      ),
+    );
+  }
+
+  void _routeToSearchResults(Face face, BuildContext context) {
+    final page = FaceSearchResultsPage(
+      face: face,
+    );
+    Navigator.of(context).push(
+      MaterialPageRoute(
+        builder: (BuildContext context) {
+          return page;
+        },
+      ),
+    );
   }
   }
 }
 }