diff --git a/app/lib/widget/collection_list_item.dart b/app/lib/widget/collection_list_item.dart index d04c262b..1efc622e 100644 --- a/app/lib/widget/collection_list_item.dart +++ b/app/lib/widget/collection_list_item.dart @@ -1,25 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:nc_photos/account.dart'; import 'package:nc_photos/theme.dart'; -import 'package:nc_photos/widget/network_thumbnail.dart'; class CollectionListSmall extends StatelessWidget { const CollectionListSmall({ - Key? key, - required this.account, + super.key, required this.label, - required this.coverUrl, - required this.fallbackBuilder, + required this.child, this.onTap, - }) : super(key: key); + }); @override - build(BuildContext context) { + Widget build(BuildContext context) { Widget content = Stack( children: [ - SizedBox.expand( - child: _buildCoverImage(context), - ), + SizedBox.expand(child: child), Positioned( bottom: 0, left: 0, @@ -82,27 +76,7 @@ class CollectionListSmall extends StatelessWidget { ); } - Widget _buildCoverImage(BuildContext context) { - Widget buildPlaceholder() => Padding( - padding: const EdgeInsets.all(8), - child: fallbackBuilder(context), - ); - try { - return NetworkRectThumbnail( - account: account, - imageUrl: coverUrl!, - errorBuilder: (_) => buildPlaceholder(), - ); - } catch (_) { - return FittedBox( - child: buildPlaceholder(), - ); - } - } - - final Account account; final String label; - final String? coverUrl; - final Widget Function(BuildContext context) fallbackBuilder; + final Widget? child; final VoidCallback? onTap; } diff --git a/app/lib/widget/people_browser.dart b/app/lib/widget/people_browser.dart index 8a858d8f..3e6931d9 100644 --- a/app/lib/widget/people_browser.dart +++ b/app/lib/widget/people_browser.dart @@ -15,10 +15,10 @@ import 'package:nc_photos/exception_event.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; 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/collection_browser.dart'; import 'package:nc_photos/widget/collection_list_item.dart'; import 'package:nc_photos/widget/page_visibility_mixin.dart'; +import 'package:nc_photos/widget/person_thumbnail.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:to_string/to_string.dart'; @@ -186,14 +186,16 @@ class _ItemView extends StatelessWidget { @override Widget build(BuildContext context) { return CollectionListSmall( - account: account, label: item.name, - coverUrl: item.coverUrl, - fallbackBuilder: (context) => Icon( - Icons.person, - color: Theme.of(context).listPlaceholderForegroundColor, - ), onTap: onTap, + child: LayoutBuilder( + builder: (context, constraints) => PersonThumbnail( + account: account, + coverUrl: item.coverUrl, + person: item.person, + dimension: constraints.maxWidth, + ), + ), ); } diff --git a/app/lib/widget/people_browser/type.dart b/app/lib/widget/people_browser/type.dart index c44f32d5..9b4e521d 100644 --- a/app/lib/widget/people_browser/type.dart +++ b/app/lib/widget/people_browser/type.dart @@ -4,7 +4,11 @@ part of '../people_browser.dart'; class _Item { _Item(this.person) { try { - _coverUrl = person.getCoverUrl(k.coverSize, k.coverSize); + _coverUrl = person.getCoverUrl( + k.photoLargeSize, + k.photoLargeSize, + isKeepAspectRatio: true, + ); } catch (e, stackTrace) { _log.warning("[_Item] Failed while getCoverUrl", e, stackTrace); } diff --git a/app/lib/widget/person_thumbnail.dart b/app/lib/widget/person_thumbnail.dart new file mode 100644 index 00000000..2f86b2be --- /dev/null +++ b/app/lib/widget/person_thumbnail.dart @@ -0,0 +1,97 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:nc_photos/account.dart'; +import 'package:nc_photos/entity/person.dart'; +import 'package:nc_photos/theme.dart'; +import 'package:nc_photos/widget/network_thumbnail.dart'; + +class PersonThumbnail extends StatefulWidget { + const PersonThumbnail({ + super.key, + required this.dimension, + required this.account, + required this.coverUrl, + required this.person, + }); + + @override + State createState() => _PersonThumbnailState(); + + final double dimension; + final Account account; + final String? coverUrl; + final Person person; +} + +class _PersonThumbnailState extends State { + @override + Widget build(BuildContext context) { + Widget content; + try { + var m = Matrix4.identity(); + if (_layoutSize != null) { + final ratio = widget.dimension / + math.min(_layoutSize!.width, _layoutSize!.height); + final mm = widget.person.getCoverTransform( + widget.dimension.toInt(), + (_layoutSize!.width * ratio).toInt(), + (_layoutSize!.height * ratio).toInt(), + ); + if (mm != null) { + m = mm; + } + } + content = Transform( + transform: m, + child: NetworkRectThumbnail( + account: widget.account, + imageUrl: widget.coverUrl!, + errorBuilder: (_) => const _Placeholder(), + onSize: (size) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _layoutSize = size; + }); + }); + }, + ), + ); + if (_layoutSize == null) { + content = Opacity(opacity: 0, child: content); + } + } catch (_) { + content = const FittedBox( + child: _Placeholder(), + ); + } + + return ClipRRect( + child: SizedBox.square( + dimension: widget.dimension, + child: Container( + color: Theme.of(context).listPlaceholderBackgroundColor, + constraints: const BoxConstraints.expand(), + child: content, + ), + ), + ); + } + + Size? _layoutSize; +} + +class _Placeholder extends StatelessWidget { + const _Placeholder(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.person, + color: Theme.of(context).listPlaceholderForegroundColor, + ), + ); + } +} diff --git a/app/lib/widget/places_browser.dart b/app/lib/widget/places_browser.dart index d6647a63..1db901be 100644 --- a/app/lib/widget/places_browser.dart +++ b/app/lib/widget/places_browser.dart @@ -243,14 +243,12 @@ class _PlaceItem { }); Widget buildWidget(BuildContext context) => CollectionListSmall( - account: account, label: place, - coverUrl: thumbUrl, - fallbackBuilder: (context) => Icon( - Icons.location_on, - color: Theme.of(context).listPlaceholderForegroundColor, - ), onTap: onTap, + child: _PlaceThumbnail( + account: account, + coverUrl: thumbUrl, + ), ); final Account account; @@ -337,3 +335,43 @@ class _CountryItemView extends StatelessWidget { final String text; final VoidCallback? onTap; } + +class _PlaceThumbnail extends StatelessWidget { + const _PlaceThumbnail({ + required this.account, + required this.coverUrl, + }); + + @override + Widget build(BuildContext context) { + try { + return NetworkRectThumbnail( + account: account, + imageUrl: coverUrl!, + errorBuilder: (_) => const _Placeholder(), + ); + } catch (_) { + return const FittedBox( + child: _Placeholder(), + ); + } + } + + final Account account; + final String? coverUrl; +} + +class _Placeholder extends StatelessWidget { + const _Placeholder(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.location_on, + color: Theme.of(context).listPlaceholderForegroundColor, + ), + ); + } +} diff --git a/app/lib/widget/search_landing.dart b/app/lib/widget/search_landing.dart index 1fdf36aa..b3344cdc 100644 --- a/app/lib/widget/search_landing.dart +++ b/app/lib/widget/search_landing.dart @@ -1,5 +1,3 @@ -import 'dart:math' as math; - import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -23,6 +21,7 @@ import 'package:nc_photos/use_case/list_location_group.dart'; import 'package:nc_photos/widget/collection_browser.dart'; import 'package:nc_photos/widget/network_thumbnail.dart'; import 'package:nc_photos/widget/people_browser.dart'; +import 'package:nc_photos/widget/person_thumbnail.dart'; import 'package:nc_photos/widget/places_browser.dart'; import 'package:nc_photos/widget/settings/account_settings.dart'; import 'package:np_codegen/np_codegen.dart'; @@ -342,11 +341,14 @@ class _LandingPersonWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Center( - child: _PersonCoverImage( - dimension: 72, - account: account, - person: person, - coverUrl: coverUrl, + child: ClipRRect( + borderRadius: BorderRadius.circular(72 / 2), + child: PersonThumbnail( + dimension: 72, + account: account, + person: person, + coverUrl: coverUrl, + ), ), ), const SizedBox(height: 8), diff --git a/app/lib/widget/search_landing/view.dart b/app/lib/widget/search_landing/view.dart index 4391462b..ff2dfddf 100644 --- a/app/lib/widget/search_landing/view.dart +++ b/app/lib/widget/search_landing/view.dart @@ -1,92 +1,5 @@ part of '../search_landing.dart'; -class _PersonCoverPlaceholder extends StatelessWidget { - const _PersonCoverPlaceholder(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8), - child: Icon( - Icons.person, - color: Theme.of(context).listPlaceholderForegroundColor, - ), - ); - } -} - -class _PersonCoverImage extends StatefulWidget { - const _PersonCoverImage({ - required this.dimension, - required this.account, - required this.coverUrl, - required this.person, - }); - - @override - State createState() => _PersonCoverImageState(); - - final double dimension; - final Account account; - final String? coverUrl; - final Person person; -} - -class _PersonCoverImageState extends State<_PersonCoverImage> { - @override - Widget build(BuildContext context) { - Widget cover; - try { - var m = Matrix4.identity(); - if (_layoutSize != null) { - final ratio = widget.dimension / - math.min(_layoutSize!.width, _layoutSize!.height); - final mm = widget.person.getCoverTransform( - widget.dimension.toInt(), - (_layoutSize!.width * ratio).toInt(), - (_layoutSize!.height * ratio).toInt(), - ); - if (mm != null) { - m = mm; - } - } - cover = Transform( - transform: m, - child: NetworkRectThumbnail( - account: widget.account, - imageUrl: widget.coverUrl!, - errorBuilder: (_) => const _PersonCoverPlaceholder(), - onSize: (size) { - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - _layoutSize = size; - }); - }); - }, - ), - ); - } catch (_) { - cover = const FittedBox( - child: _PersonCoverPlaceholder(), - ); - } - - return SizedBox.square( - dimension: widget.dimension, - child: ClipRRect( - borderRadius: BorderRadius.circular(widget.dimension / 2), - child: Container( - color: Theme.of(context).listPlaceholderBackgroundColor, - constraints: const BoxConstraints.expand(), - child: cover, - ), - ), - ); - } - - Size? _layoutSize; -} - class _LocationCoverPlaceholder extends StatelessWidget { const _LocationCoverPlaceholder();