Implement face search
This commit is contained in:
parent
2089135720
commit
7f3ab5894b
9 changed files with 197 additions and 19 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
5
lib/models/search_result.dart
Normal file
5
lib/models/search_result.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
class SearchResult {
|
||||
final String url;
|
||||
|
||||
SearchResult(this.url);
|
||||
}
|
23
lib/ui/circular_network_image_widget.dart
Normal file
23
lib/ui/circular_network_image_widget.dart
Normal 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))));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
80
lib/ui/face_search_results_page.dart
Normal file
80
lib/ui/face_search_results_page.dart
Normal 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;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
|
|
38
lib/ui/network_image_detail_page.dart
Normal file
38
lib/ui/network_image_detail_page.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue