mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-23 01:06:21 +01:00
Migrate to new person API
This commit is contained in:
parent
620c840ccc
commit
a0b62cac35
10 changed files with 329 additions and 76 deletions
|
@ -453,6 +453,8 @@ class _OcsFacerecognition {
|
||||||
_OcsFacerecognition(this._ocs);
|
_OcsFacerecognition(this._ocs);
|
||||||
|
|
||||||
_OcsFacerecognitionPersons persons() => _OcsFacerecognitionPersons(this);
|
_OcsFacerecognitionPersons persons() => _OcsFacerecognitionPersons(this);
|
||||||
|
_OcsFacerecognitionPerson person(String name) =>
|
||||||
|
_OcsFacerecognitionPerson(this, name);
|
||||||
|
|
||||||
final _Ocs _ocs;
|
final _Ocs _ocs;
|
||||||
}
|
}
|
||||||
|
@ -483,6 +485,42 @@ class _OcsFacerecognitionPersons {
|
||||||
static final _log = Logger("api.api._OcsFacerecognitionPersons");
|
static final _log = Logger("api.api._OcsFacerecognitionPersons");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _OcsFacerecognitionPerson {
|
||||||
|
_OcsFacerecognitionPerson(this._facerecognition, this._name);
|
||||||
|
|
||||||
|
_OcsFacerecognitionPersonFaces faces() =>
|
||||||
|
_OcsFacerecognitionPersonFaces(this);
|
||||||
|
|
||||||
|
final _OcsFacerecognition _facerecognition;
|
||||||
|
final String _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OcsFacerecognitionPersonFaces {
|
||||||
|
_OcsFacerecognitionPersonFaces(this._person);
|
||||||
|
|
||||||
|
Future<Response> get() async {
|
||||||
|
try {
|
||||||
|
return await _person._facerecognition._ocs._api.request(
|
||||||
|
"GET",
|
||||||
|
"ocs/v2.php/apps/facerecognition/api/v1/person/${_person._name}/faces",
|
||||||
|
header: {
|
||||||
|
"OCS-APIRequest": "true",
|
||||||
|
},
|
||||||
|
queryParameters: {
|
||||||
|
"format": "json",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe("[get] Failed while get", e);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final _OcsFacerecognitionPerson _person;
|
||||||
|
|
||||||
|
static final _log = Logger("api.api._OcsFacerecognitionPersonFaces");
|
||||||
|
}
|
||||||
|
|
||||||
class _OcsFilesSharing {
|
class _OcsFilesSharing {
|
||||||
_OcsFilesSharing(this._ocs);
|
_OcsFilesSharing(this._ocs);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/person.dart';
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
|
|
||||||
/// Return the preview image URL for [file]. See [getFilePreviewUrlRelative]
|
/// Return the preview image URL for [file]. See [getFilePreviewUrlRelative]
|
||||||
|
@ -71,20 +70,20 @@ String getTrashbinPath(Account account) =>
|
||||||
/// Return the face image URL. See [getFacePreviewUrlRelative]
|
/// Return the face image URL. See [getFacePreviewUrlRelative]
|
||||||
String getFacePreviewUrl(
|
String getFacePreviewUrl(
|
||||||
Account account,
|
Account account,
|
||||||
Face face, {
|
int faceId, {
|
||||||
required int size,
|
required int size,
|
||||||
}) {
|
}) {
|
||||||
return "${account.url}/"
|
return "${account.url}/"
|
||||||
"${getFacePreviewUrlRelative(account, face, size: size)}";
|
"${getFacePreviewUrlRelative(account, faceId, size: size)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the relative URL of the face image
|
/// Return the relative URL of the face image
|
||||||
String getFacePreviewUrlRelative(
|
String getFacePreviewUrlRelative(
|
||||||
Account account,
|
Account account,
|
||||||
Face face, {
|
int faceId, {
|
||||||
required int size,
|
required int size,
|
||||||
}) {
|
}) {
|
||||||
return "index.php/apps/facerecognition/face/${face.id}/thumb/$size";
|
return "index.php/apps/facerecognition/face/$faceId/thumb/$size";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query the app password for [account]
|
/// Query the app password for [account]
|
||||||
|
|
99
lib/bloc/list_face.dart
Normal file
99
lib/bloc/list_face.dart
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/entity/face.dart';
|
||||||
|
import 'package:nc_photos/entity/face/data_source.dart';
|
||||||
|
import 'package:nc_photos/entity/person.dart';
|
||||||
|
|
||||||
|
abstract class ListFaceBlocEvent {
|
||||||
|
const ListFaceBlocEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListFaceBlocQuery extends ListFaceBlocEvent {
|
||||||
|
const ListFaceBlocQuery(this.account, this.person);
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() {
|
||||||
|
return "$runtimeType {"
|
||||||
|
"account: $account, "
|
||||||
|
"person: $person, "
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
final Person person;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ListFaceBlocState {
|
||||||
|
const ListFaceBlocState(this.account, this.items);
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() {
|
||||||
|
return "$runtimeType {"
|
||||||
|
"account: $account, "
|
||||||
|
"items: List {length: ${items.length}}, "
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account? account;
|
||||||
|
final List<Face> items;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListFaceBlocInit extends ListFaceBlocState {
|
||||||
|
ListFaceBlocInit() : super(null, const []);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListFaceBlocLoading extends ListFaceBlocState {
|
||||||
|
const ListFaceBlocLoading(Account? account, List<Face> items)
|
||||||
|
: super(account, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListFaceBlocSuccess extends ListFaceBlocState {
|
||||||
|
const ListFaceBlocSuccess(Account? account, List<Face> items)
|
||||||
|
: super(account, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ListFaceBlocFailure extends ListFaceBlocState {
|
||||||
|
const ListFaceBlocFailure(Account? account, List<Face> items, this.exception)
|
||||||
|
: super(account, items);
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() {
|
||||||
|
return "$runtimeType {"
|
||||||
|
"super: ${super.toString()}, "
|
||||||
|
"exception: $exception, "
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
final dynamic exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all people recognized in an account
|
||||||
|
class ListFaceBloc extends Bloc<ListFaceBlocEvent, ListFaceBlocState> {
|
||||||
|
ListFaceBloc() : super(ListFaceBlocInit());
|
||||||
|
|
||||||
|
@override
|
||||||
|
mapEventToState(ListFaceBlocEvent event) async* {
|
||||||
|
_log.info("[mapEventToState] $event");
|
||||||
|
if (event is ListFaceBlocQuery) {
|
||||||
|
yield* _onEventQuery(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<ListFaceBlocState> _onEventQuery(ListFaceBlocQuery ev) async* {
|
||||||
|
try {
|
||||||
|
yield ListFaceBlocLoading(ev.account, state.items);
|
||||||
|
yield ListFaceBlocSuccess(ev.account, await _query(ev));
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe("[_onEventQuery] Exception while request", e, stackTrace);
|
||||||
|
yield ListFaceBlocFailure(ev.account, state.items, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Face>> _query(ListFaceBlocQuery ev) {
|
||||||
|
final personRepo = FaceRepo(FaceRemoteDataSource());
|
||||||
|
return personRepo.list(ev.account, ev.person);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final _log = Logger("bloc.list_personListFaceBloc");
|
||||||
|
}
|
42
lib/entity/face.dart
Normal file
42
lib/entity/face.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/entity/person.dart';
|
||||||
|
|
||||||
|
class Face with EquatableMixin {
|
||||||
|
Face({
|
||||||
|
required this.id,
|
||||||
|
required this.fileId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() {
|
||||||
|
return "$runtimeType {"
|
||||||
|
"id: '$id', "
|
||||||
|
"fileId: '$fileId', "
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
get props => [
|
||||||
|
id,
|
||||||
|
fileId,
|
||||||
|
];
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final int fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FaceRepo {
|
||||||
|
const FaceRepo(this.dataSrc);
|
||||||
|
|
||||||
|
/// See [FaceDataSource.list]
|
||||||
|
Future<List<Face>> list(Account account, Person person) =>
|
||||||
|
this.dataSrc.list(account, person);
|
||||||
|
|
||||||
|
final FaceDataSource dataSrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class FaceDataSource {
|
||||||
|
/// List all faces associated to [person]
|
||||||
|
Future<List<Face>> list(Account account, Person person);
|
||||||
|
}
|
59
lib/entity/face/data_source.dart
Normal file
59
lib/entity/face/data_source.dart
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/api/api.dart';
|
||||||
|
import 'package:nc_photos/entity/face.dart';
|
||||||
|
import 'package:nc_photos/entity/person.dart';
|
||||||
|
import 'package:nc_photos/exception.dart';
|
||||||
|
import 'package:nc_photos/type.dart';
|
||||||
|
|
||||||
|
class FaceRemoteDataSource implements FaceDataSource {
|
||||||
|
const FaceRemoteDataSource();
|
||||||
|
|
||||||
|
@override
|
||||||
|
list(Account account, Person person) async {
|
||||||
|
_log.info("[list] $person");
|
||||||
|
final response = await Api(account)
|
||||||
|
.ocs()
|
||||||
|
.facerecognition()
|
||||||
|
.person(person.name)
|
||||||
|
.faces()
|
||||||
|
.get();
|
||||||
|
if (!response.isGood) {
|
||||||
|
_log.severe("[list] Failed requesting server: $response");
|
||||||
|
throw ApiException(
|
||||||
|
response: response,
|
||||||
|
message: "Failed communicating with server: ${response.statusCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
final json = jsonDecode(response.body);
|
||||||
|
final List<JsonObj> dataJson = json["ocs"]["data"].cast<JsonObj>();
|
||||||
|
return _FaceParser().parseList(dataJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final _log = Logger("entity.face.data_source.FaceRemoteDataSource");
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FaceParser {
|
||||||
|
List<Face> parseList(List<JsonObj> jsons) {
|
||||||
|
final product = <Face>[];
|
||||||
|
for (final j in jsons) {
|
||||||
|
try {
|
||||||
|
product.add(parseSingle(j));
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe("[parseList] Failed parsing json: ${jsonEncode(j)}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return product;
|
||||||
|
}
|
||||||
|
|
||||||
|
Face parseSingle(JsonObj json) {
|
||||||
|
return Face(
|
||||||
|
id: json["id"],
|
||||||
|
fileId: json["fileId"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final _log = Logger("entity.face.data_source._FaceParser");
|
||||||
|
}
|
|
@ -1,39 +1,32 @@
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
|
||||||
class Face with EquatableMixin {
|
class Person with EquatableMixin {
|
||||||
Face({
|
Person({
|
||||||
required this.id,
|
required this.name,
|
||||||
required this.fileId,
|
required this.thumbFaceId,
|
||||||
|
required this.count,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get props => [
|
toString() {
|
||||||
id,
|
return "$runtimeType {"
|
||||||
fileId,
|
"name: '$name', "
|
||||||
];
|
"thumbFaceId: '$thumbFaceId', "
|
||||||
|
"count: '$count', "
|
||||||
final int id;
|
"}";
|
||||||
final int fileId;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class Person with EquatableMixin {
|
|
||||||
Person({
|
|
||||||
this.name,
|
|
||||||
required this.id,
|
|
||||||
required this.faces,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get props => [
|
get props => [
|
||||||
name,
|
name,
|
||||||
id,
|
thumbFaceId,
|
||||||
faces,
|
count,
|
||||||
];
|
];
|
||||||
|
|
||||||
final String? name;
|
final String name;
|
||||||
final int id;
|
final int thumbFaceId;
|
||||||
final List<Face> faces;
|
final int count;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PersonRepo {
|
class PersonRepo {
|
||||||
|
|
|
@ -44,17 +44,10 @@ class _PersonParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
Person parseSingle(JsonObj json) {
|
Person parseSingle(JsonObj json) {
|
||||||
final faces = (json["faces"] as List)
|
|
||||||
.cast<JsonObj>()
|
|
||||||
.map((e) => Face(
|
|
||||||
id: e["id"],
|
|
||||||
fileId: e["file-id"],
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
return Person(
|
return Person(
|
||||||
name: json["name"],
|
name: json["name"],
|
||||||
id: json["id"],
|
thumbFaceId: json["thumbFaceId"],
|
||||||
faces: faces,
|
count: json["count"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,21 +2,21 @@ import 'package:idb_shim/idb_client.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_db.dart';
|
import 'package:nc_photos/app_db.dart';
|
||||||
|
import 'package:nc_photos/entity/face.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/person.dart';
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
|
|
||||||
class PopulatePerson {
|
class PopulatePerson {
|
||||||
/// Return a list of files belonging to this person
|
/// Return a list of files of the faces
|
||||||
Future<List<File>> call(Account account, Person person) async {
|
Future<List<File>> call(Account account, List<Face> faces) async {
|
||||||
return await AppDb.use((db) async {
|
return await AppDb.use((db) async {
|
||||||
final transaction =
|
final transaction =
|
||||||
db.transaction(AppDb.fileDbStoreName, idbModeReadOnly);
|
db.transaction(AppDb.fileDbStoreName, idbModeReadOnly);
|
||||||
final store = transaction.objectStore(AppDb.fileDbStoreName);
|
final store = transaction.objectStore(AppDb.fileDbStoreName);
|
||||||
final index = store.index(AppDbFileDbEntry.indexName);
|
final index = store.index(AppDbFileDbEntry.indexName);
|
||||||
final products = <File>[];
|
final products = <File>[];
|
||||||
for (final f in person.faces) {
|
for (final f in faces) {
|
||||||
try {
|
try {
|
||||||
products.add(await _populateOne(account, f, store, index));
|
products.add(await _populateOne(account, f, store, index));
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
|
|
@ -191,12 +191,11 @@ class _PeopleBrowserState extends State<PeopleBrowser> {
|
||||||
|
|
||||||
void _transformItems(List<Person> items) {
|
void _transformItems(List<Person> items) {
|
||||||
_items = items
|
_items = items
|
||||||
.where((element) => element.name != null)
|
.sorted((a, b) => a.name.compareTo(b.name))
|
||||||
.sorted((a, b) => a.name!.compareTo(b.name!))
|
|
||||||
.map((e) => _PersonListItem(
|
.map((e) => _PersonListItem(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
name: e.name!,
|
name: e.name,
|
||||||
faceUrl: api_util.getFacePreviewUrl(widget.account, e.faces.first,
|
faceUrl: api_util.getFacePreviewUrl(widget.account, e.thumbFaceId,
|
||||||
size: 256),
|
size: 256),
|
||||||
onTap: () => _onItemTap(e),
|
onTap: () => _onItemTap(e),
|
||||||
))
|
))
|
||||||
|
|
|
@ -1,26 +1,32 @@
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/bloc/list_face.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
|
import 'package:nc_photos/entity/face.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/person.dart';
|
import 'package:nc_photos/entity/person.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/notified_action.dart';
|
import 'package:nc_photos/notified_action.dart';
|
||||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
import 'package:nc_photos/share_handler.dart';
|
import 'package:nc_photos/share_handler.dart';
|
||||||
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/throttler.dart';
|
import 'package:nc_photos/throttler.dart';
|
||||||
import 'package:nc_photos/use_case/add_to_album.dart';
|
import 'package:nc_photos/use_case/add_to_album.dart';
|
||||||
|
@ -74,7 +80,7 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initPerson();
|
_initBloc();
|
||||||
_thumbZoomLevel = Pref.inst().getAlbumBrowserZoomLevelOr(0);
|
_thumbZoomLevel = Pref.inst().getAlbumBrowserZoomLevelOr(0);
|
||||||
|
|
||||||
_filePropertyUpdatedListener.begin();
|
_filePropertyUpdatedListener.begin();
|
||||||
|
@ -90,20 +96,21 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
build(BuildContext context) {
|
build(BuildContext context) {
|
||||||
return AppTheme(
|
return AppTheme(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: Builder(
|
body: BlocListener<ListFaceBloc, ListFaceBlocState>(
|
||||||
builder: (context) => _buildContent(context),
|
bloc: _bloc,
|
||||||
|
listener: (context, state) => _onStateChange(context, state),
|
||||||
|
child: BlocBuilder<ListFaceBloc, ListFaceBlocState>(
|
||||||
|
bloc: _bloc,
|
||||||
|
builder: (context, state) => _buildContent(context),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initPerson() async {
|
void _initBloc() {
|
||||||
final items = await PopulatePerson()(widget.account, widget.person);
|
_log.info("[_initBloc] Initialize bloc");
|
||||||
if (mounted) {
|
_reqQuery();
|
||||||
setState(() {
|
|
||||||
_transformItems(items);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context) {
|
Widget _buildContent(BuildContext context) {
|
||||||
|
@ -162,7 +169,7 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.person.name!,
|
widget.person.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppTheme.getPrimaryTextColor(context),
|
color: AppTheme.getPrimaryTextColor(context),
|
||||||
),
|
),
|
||||||
|
@ -172,7 +179,7 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
),
|
),
|
||||||
if (_backingFiles != null)
|
if (_backingFiles != null)
|
||||||
Text(
|
Text(
|
||||||
"${_backingFiles!.length} photos",
|
"${itemStreamListItems.length} photos",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
@ -208,7 +215,7 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: api_util.getFacePreviewUrl(
|
imageUrl: api_util.getFacePreviewUrl(
|
||||||
widget.account, widget.person.faces.first,
|
widget.account, widget.person.thumbFaceId,
|
||||||
size: 64),
|
size: 64),
|
||||||
httpHeaders: {
|
httpHeaders: {
|
||||||
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
|
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
|
||||||
|
@ -284,6 +291,20 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onStateChange(BuildContext context, ListFaceBlocState state) {
|
||||||
|
if (state is ListFaceBlocInit) {
|
||||||
|
_backingFiles = null;
|
||||||
|
} else if (state is ListFaceBlocSuccess || state is ListFaceBlocLoading) {
|
||||||
|
_transformItems(state.items);
|
||||||
|
} else if (state is ListFaceBlocFailure) {
|
||||||
|
_transformItems(state.items);
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(exception_util.toUserString(state.exception)),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onItemTap(int index) {
|
void _onItemTap(int index) {
|
||||||
Navigator.pushNamed(context, Viewer.routeName,
|
Navigator.pushNamed(context, Viewer.routeName,
|
||||||
arguments: ViewerArguments(widget.account, _backingFiles!, index));
|
arguments: ViewerArguments(widget.account, _backingFiles!, index));
|
||||||
|
@ -420,26 +441,33 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _transformItems(List<File> items) {
|
void _transformItems(List<Face> items) async {
|
||||||
_backingFiles = items
|
final files = await PopulatePerson()(widget.account, items);
|
||||||
|
_backingFiles = files
|
||||||
.sorted(compareFileDateTimeDescending)
|
.sorted(compareFileDateTimeDescending)
|
||||||
.where((element) =>
|
.where((element) =>
|
||||||
file_util.isSupportedFormat(element) && element.isArchived != true)
|
file_util.isSupportedFormat(element) && element.isArchived != true)
|
||||||
.toList();
|
.toList();
|
||||||
itemStreamListItems = _backingFiles!
|
setState(() {
|
||||||
.mapWithIndex((i, f) => _ListItem(
|
itemStreamListItems = _backingFiles!
|
||||||
index: i,
|
.mapWithIndex((i, f) => _ListItem(
|
||||||
file: f,
|
index: i,
|
||||||
account: widget.account,
|
file: f,
|
||||||
previewUrl: api_util.getFilePreviewUrl(
|
account: widget.account,
|
||||||
widget.account,
|
previewUrl: api_util.getFilePreviewUrl(
|
||||||
f,
|
widget.account,
|
||||||
width: _thumbSize,
|
f,
|
||||||
height: _thumbSize,
|
width: _thumbSize,
|
||||||
),
|
height: _thumbSize,
|
||||||
onTap: () => _onItemTap(i),
|
),
|
||||||
))
|
onTap: () => _onItemTap(i),
|
||||||
.toList();
|
))
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reqQuery() {
|
||||||
|
_bloc.add(ListFaceBlocQuery(widget.account, widget.person));
|
||||||
}
|
}
|
||||||
|
|
||||||
int get _thumbSize {
|
int get _thumbSize {
|
||||||
|
@ -456,13 +484,16 @@ class _PersonBrowserState extends State<PersonBrowser>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ListFaceBloc _bloc = ListFaceBloc();
|
||||||
List<File>? _backingFiles;
|
List<File>? _backingFiles;
|
||||||
|
|
||||||
var _thumbZoomLevel = 0;
|
var _thumbZoomLevel = 0;
|
||||||
|
|
||||||
late final Throttler _refreshThrottler = Throttler(
|
late final Throttler _refreshThrottler = Throttler(
|
||||||
onTriggered: (_) {
|
onTriggered: (_) {
|
||||||
_initPerson();
|
if (mounted) {
|
||||||
|
_transformItems(_bloc.state.items);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
logTag: "_PersonBrowserState.refresh",
|
logTag: "_PersonBrowserState.refresh",
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue