Implement face search

This commit is contained in:
Vishnu Mohandas 2020-04-05 19:30:44 +05:30
parent 2089135720
commit 7f3ab5894b
9 changed files with 197 additions and 19 deletions

View file

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

View file

@ -34,18 +34,18 @@ class Photo {
var file = (await asset.originFile);
photo.localPath = file.path;
photo.hash = getHash(file);
await setThumbnail(photo);
return photo;
return setThumbnail(photo).then((value) => photo);
}
static Future<void> setThumbnail(Photo photo) async {
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>();
args["assetPath"] = photo.localPath;
args["thumbnailPath"] = thumbnailPath;
await compute(getThumbnailPath, args);
photo.thumbnailPath = thumbnailPath;
return compute(getThumbnailPath, args).then((value) => photo);
}
static String getHash(File file) {
@ -54,7 +54,8 @@ class Photo {
}
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) {

View file

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

View file

@ -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))));
}
}

View file

@ -7,8 +7,9 @@ import 'package:share_extend/share_extend.dart';
class DetailPage extends StatefulWidget {
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
_DetailPageState createState() => _DetailPageState();

View file

@ -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;
},
),
);
}
}

View file

@ -60,7 +60,7 @@ class _GalleryState extends State<Gallery> {
Widget _buildItem(BuildContext context, int index) {
var photo = photoLoader.getPhotos()[index];
return GestureDetector(
onTap: () async {
onTap: () {
routeToDetailPage(photo, context);
},
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(
photo: photo,
);

View file

@ -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),
);
}
}

View file

@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
import 'package:myapp/face_search_manager.dart';
import 'package:myapp/models/face.dart';
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 {
final FaceSearchManager _faceSearchManager = FaceSearchManager();
final FaceSearchManager _faceSearchManager = FaceSearchManager.instance;
@override
Widget build(BuildContext context) {
return Scaffold(
@ -63,15 +65,28 @@ class SearchPage extends StatelessWidget {
}
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;
},
),
);
}
}