mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-23 01:06:21 +01:00
328 lines
9.2 KiB
Dart
328 lines
9.2 KiB
Dart
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.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:logging/logging.dart';
|
|
import 'package:nc_photos/account.dart';
|
|
import 'package:nc_photos/api/api.dart';
|
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
|
import 'package:nc_photos/app_localizations.dart';
|
|
import 'package:nc_photos/bloc/list_person.dart';
|
|
import 'package:nc_photos/entity/person.dart';
|
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
|
import 'package:nc_photos/help_utils.dart' as help_utils;
|
|
import 'package:nc_photos/iterable_extension.dart';
|
|
import 'package:nc_photos/k.dart' as k;
|
|
import 'package:nc_photos/snack_bar_manager.dart';
|
|
import 'package:nc_photos/theme.dart';
|
|
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
|
import 'package:nc_photos/widget/person_browser.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
class PeopleBrowserArguments {
|
|
PeopleBrowserArguments(this.account);
|
|
|
|
final Account account;
|
|
}
|
|
|
|
/// Show a list of all people associated with this account
|
|
class PeopleBrowser extends StatefulWidget {
|
|
static const routeName = "/people-browser";
|
|
|
|
static Route buildRoute(PeopleBrowserArguments args) => MaterialPageRoute(
|
|
builder: (context) => PeopleBrowser.fromArgs(args),
|
|
);
|
|
|
|
PeopleBrowser({
|
|
Key? key,
|
|
required this.account,
|
|
}) : super(key: key);
|
|
|
|
PeopleBrowser.fromArgs(PeopleBrowserArguments args, {Key? key})
|
|
: this(
|
|
key: key,
|
|
account: args.account,
|
|
);
|
|
|
|
@override
|
|
createState() => _PeopleBrowserState();
|
|
|
|
final Account account;
|
|
}
|
|
|
|
class _PeopleBrowserState extends State<PeopleBrowser> {
|
|
@override
|
|
initState() {
|
|
super.initState();
|
|
_initBloc();
|
|
}
|
|
|
|
@override
|
|
build(BuildContext context) {
|
|
return AppTheme(
|
|
child: Scaffold(
|
|
body: BlocListener<ListPersonBloc, ListPersonBlocState>(
|
|
bloc: _bloc,
|
|
listener: (context, state) => _onStateChange(context, state),
|
|
child: BlocBuilder<ListPersonBloc, ListPersonBlocState>(
|
|
bloc: _bloc,
|
|
builder: (context, state) => _buildContent(context, state),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _initBloc() {
|
|
_bloc = ListPersonBloc.of(widget.account);
|
|
if (_bloc.state is ListPersonBlocInit) {
|
|
_log.info("[_initBloc] Initialize bloc");
|
|
_reqQuery();
|
|
} else {
|
|
// process the current state
|
|
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
|
setState(() {
|
|
_onStateChange(context, _bloc.state);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
Widget _buildContent(BuildContext context, ListPersonBlocState state) {
|
|
if (state is ListPersonBlocSuccess && _items.isEmpty) {
|
|
return Column(
|
|
children: [
|
|
AppBar(
|
|
title: Text("People"),
|
|
elevation: 0,
|
|
actions: [
|
|
Stack(
|
|
fit: StackFit.passthrough,
|
|
children: [
|
|
IconButton(
|
|
onPressed: () {
|
|
launch(help_utils.peopleUrl);
|
|
},
|
|
icon: const Icon(Icons.help_outline),
|
|
tooltip: L10n.global().helpTooltip,
|
|
),
|
|
Positioned.directional(
|
|
textDirection: Directionality.of(context),
|
|
end: 0,
|
|
top: 0,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8, vertical: 10),
|
|
child: Icon(
|
|
Icons.circle,
|
|
color: Colors.red,
|
|
size: 8,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
Expanded(
|
|
child: EmptyListIndicator(
|
|
icon: Icons.person_outlined,
|
|
text: L10n.global().listEmptyText,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
} else {
|
|
return Stack(
|
|
children: [
|
|
Theme(
|
|
data: Theme.of(context).copyWith(
|
|
accentColor: AppTheme.getOverscrollIndicatorColor(context),
|
|
),
|
|
child: CustomScrollView(
|
|
slivers: [
|
|
_buildAppBar(context),
|
|
SliverPadding(
|
|
padding: const EdgeInsets.only(top: 8),
|
|
sliver: SliverStaggeredGrid.extentBuilder(
|
|
maxCrossAxisExtent: 192,
|
|
itemCount: _items.length,
|
|
itemBuilder: _buildItem,
|
|
staggeredTileBuilder: (index) =>
|
|
const StaggeredTile.count(1, 1),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (state is ListPersonBlocLoading)
|
|
Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: const LinearProgressIndicator(),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget _buildAppBar(BuildContext context) {
|
|
return SliverAppBar(
|
|
title: Text("People"),
|
|
floating: true,
|
|
);
|
|
}
|
|
|
|
Widget _buildItem(BuildContext context, int index) {
|
|
final item = _items[index];
|
|
return item.buildWidget(context);
|
|
}
|
|
|
|
void _onStateChange(BuildContext context, ListPersonBlocState state) {
|
|
if (state is ListPersonBlocInit) {
|
|
_items = [];
|
|
} else if (state is ListPersonBlocSuccess ||
|
|
state is ListPersonBlocLoading) {
|
|
_transformItems(state.items);
|
|
} else if (state is ListPersonBlocFailure) {
|
|
_transformItems(state.items);
|
|
SnackBarManager().showSnackBar(SnackBar(
|
|
content: Text(exception_util.toUserString(state.exception)),
|
|
duration: k.snackBarDurationNormal,
|
|
));
|
|
}
|
|
}
|
|
|
|
void _onItemTap(Person person) {
|
|
Navigator.pushNamed(context, PersonBrowser.routeName,
|
|
arguments: PersonBrowserArguments(widget.account, person));
|
|
}
|
|
|
|
void _transformItems(List<Person> items) {
|
|
_items = items
|
|
.where((element) => element.name != null)
|
|
.sorted((a, b) => a.name!.compareTo(b.name!))
|
|
.map((e) => _PersonListItem(
|
|
account: widget.account,
|
|
name: e.name!,
|
|
faceUrl: api_util.getFacePreviewUrl(widget.account, e.faces.first,
|
|
size: 256),
|
|
onTap: () => _onItemTap(e),
|
|
))
|
|
.toList();
|
|
// _items = [];
|
|
}
|
|
|
|
void _reqQuery() {
|
|
_bloc.add(ListPersonBlocQuery(widget.account));
|
|
}
|
|
|
|
late ListPersonBloc _bloc;
|
|
|
|
var _items = <_ListItem>[];
|
|
|
|
static final _log = Logger("widget.people_browser._PeopleBrowserState");
|
|
}
|
|
|
|
abstract class _ListItem {
|
|
_ListItem({
|
|
this.onTap,
|
|
});
|
|
|
|
Widget buildWidget(BuildContext context);
|
|
|
|
final VoidCallback? onTap;
|
|
}
|
|
|
|
class _PersonListItem extends _ListItem {
|
|
_PersonListItem({
|
|
required this.account,
|
|
required this.name,
|
|
required this.faceUrl,
|
|
VoidCallback? onTap,
|
|
}) : super(onTap: onTap);
|
|
|
|
@override
|
|
buildWidget(BuildContext context) {
|
|
final content = Padding(
|
|
padding: const EdgeInsets.all(8),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: Center(
|
|
child: AspectRatio(
|
|
aspectRatio: 1,
|
|
child: _buildFaceImage(context),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Align(
|
|
alignment: Alignment.center,
|
|
child: Text(
|
|
name + "\n",
|
|
style: Theme.of(context).textTheme.bodyText1!.copyWith(
|
|
color: AppTheme.getPrimaryTextColor(context),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
if (onTap != null) {
|
|
return InkWell(
|
|
onTap: onTap,
|
|
child: content,
|
|
);
|
|
} else {
|
|
return content;
|
|
}
|
|
}
|
|
|
|
Widget _buildFaceImage(BuildContext context) {
|
|
Widget cover;
|
|
try {
|
|
cover = FittedBox(
|
|
clipBehavior: Clip.hardEdge,
|
|
fit: BoxFit.cover,
|
|
child: CachedNetworkImage(
|
|
imageUrl: faceUrl!,
|
|
httpHeaders: {
|
|
"Authorization": Api.getAuthorizationHeaderValue(account),
|
|
},
|
|
fadeInDuration: const Duration(),
|
|
filterQuality: FilterQuality.high,
|
|
errorWidget: (context, url, error) {
|
|
// just leave it empty
|
|
return Container();
|
|
},
|
|
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
|
),
|
|
);
|
|
} catch (_) {
|
|
cover = Icon(
|
|
Icons.person,
|
|
color: Colors.white.withOpacity(.8),
|
|
size: 64,
|
|
);
|
|
}
|
|
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(128),
|
|
child: Container(
|
|
color: AppTheme.getListItemBackgroundColor(context),
|
|
constraints: const BoxConstraints.expand(),
|
|
child: cover,
|
|
),
|
|
);
|
|
}
|
|
|
|
final Account account;
|
|
final String name;
|
|
final String? faceUrl;
|
|
}
|