mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor(mobile): optimize padding and metrics handling in thumbnail tile widget
This commit is contained in:
@@ -55,8 +55,7 @@ class ThumbnailTile extends ConsumerWidget {
|
|||||||
Container(color: lockSelection ? context.colorScheme.surfaceContainerHighest : assetContainerColor),
|
Container(color: lockSelection ? context.colorScheme.surfaceContainerHighest : assetContainerColor),
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final horizontalPadding = constraints.maxWidth * 0.02;
|
final metrics = _OverlayMetrics.fromConstraints(constraints);
|
||||||
final verticalPadding = constraints.maxHeight * 0.02;
|
|
||||||
|
|
||||||
return AnimatedContainer(
|
return AnimatedContainer(
|
||||||
duration: Durations.short4,
|
duration: Durations.short4,
|
||||||
@@ -81,11 +80,14 @@ class ThumbnailTile extends ConsumerWidget {
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: horizontalPadding, vertical: verticalPadding),
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: metrics.horizontalPadding,
|
||||||
|
vertical: metrics.verticalPadding,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [_AssetTypeIcons(asset: asset)],
|
children: [_AssetTypeIcons(asset: asset, metrics: metrics)],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -95,33 +97,36 @@ class ThumbnailTile extends ConsumerWidget {
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: horizontalPadding, vertical: verticalPadding),
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: metrics.horizontalPadding,
|
||||||
|
vertical: metrics.verticalPadding,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (asset != null && asset.isFavorite)
|
if (asset != null && asset.isFavorite)
|
||||||
const Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(right: 2.0),
|
padding: EdgeInsets.only(right: metrics.iconSpacing),
|
||||||
child: _TileOverlayIcon(Icons.favorite_rounded),
|
child: _TileOverlayIcon(Icons.favorite_rounded, metrics: metrics),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
const SizedBox.shrink(),
|
const SizedBox.shrink(),
|
||||||
if (shouldShowOwnerName)
|
if (shouldShowOwnerName)
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 1.0),
|
padding: EdgeInsets.symmetric(horizontal: metrics.iconSpacing),
|
||||||
child: _OwnerNameLabel(ownerName: ownerName!),
|
child: _OwnerNameLabel(ownerName: ownerName!, metrics: metrics),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (storageIndicator && asset != null)
|
if (storageIndicator && asset != null)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 2.0),
|
padding: EdgeInsets.only(right: metrics.iconSpacing),
|
||||||
child: _TileOverlayIcon(switch (asset.storage) {
|
child: _TileOverlayIcon(switch (asset.storage) {
|
||||||
AssetState.local => Icons.cloud_off_outlined,
|
AssetState.local => Icons.cloud_off_outlined,
|
||||||
AssetState.remote => Icons.cloud_outlined,
|
AssetState.remote => Icons.cloud_outlined,
|
||||||
AssetState.merged => Icons.cloud_done_outlined,
|
AssetState.merged => Icons.cloud_done_outlined,
|
||||||
}),
|
}, metrics: metrics),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -182,7 +187,8 @@ class _SelectionIndicator extends StatelessWidget {
|
|||||||
|
|
||||||
class _VideoIndicator extends StatelessWidget {
|
class _VideoIndicator extends StatelessWidget {
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
const _VideoIndicator(this.duration);
|
final _OverlayMetrics metrics;
|
||||||
|
const _VideoIndicator(this.duration, {required this.metrics});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -194,15 +200,15 @@ class _VideoIndicator extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
duration.format(),
|
duration.format(),
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 12,
|
fontSize: metrics.fontSize,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6))],
|
shadows: [Shadow(blurRadius: metrics.blurRadius, color: const Color.fromRGBO(0, 0, 0, 0.6))],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 2),
|
SizedBox(width: metrics.iconSpacing),
|
||||||
const _TileOverlayIcon(Icons.play_circle_outline_rounded),
|
_TileOverlayIcon(Icons.play_circle_outline_rounded, metrics: metrics),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -210,24 +216,32 @@ class _VideoIndicator extends StatelessWidget {
|
|||||||
|
|
||||||
class _TileOverlayIcon extends StatelessWidget {
|
class _TileOverlayIcon extends StatelessWidget {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
|
final _OverlayMetrics metrics;
|
||||||
|
|
||||||
const _TileOverlayIcon(this.icon);
|
const _TileOverlayIcon(this.icon, {required this.metrics});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Icon(
|
return Icon(
|
||||||
icon,
|
icon,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 16,
|
size: metrics.iconSize,
|
||||||
shadows: [const Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))],
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
blurRadius: metrics.blurRadius,
|
||||||
|
color: const Color.fromRGBO(0, 0, 0, 0.6),
|
||||||
|
offset: const Offset(0.0, 0.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AssetTypeIcons extends StatelessWidget {
|
class _AssetTypeIcons extends StatelessWidget {
|
||||||
final BaseAsset asset;
|
final BaseAsset asset;
|
||||||
|
final _OverlayMetrics metrics;
|
||||||
|
|
||||||
const _AssetTypeIcons({required this.asset});
|
const _AssetTypeIcons({required this.asset, required this.metrics});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -237,11 +251,21 @@ class _AssetTypeIcons extends StatelessWidget {
|
|||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (asset.isVideo) Padding(padding: const EdgeInsets.only(left: 2.0), child: _VideoIndicator(asset.duration)),
|
if (asset.isVideo)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: metrics.iconSpacing),
|
||||||
|
child: _VideoIndicator(asset.duration, metrics: metrics),
|
||||||
|
),
|
||||||
if (hasStack)
|
if (hasStack)
|
||||||
const Padding(padding: EdgeInsets.only(left: 2.0), child: _TileOverlayIcon(Icons.burst_mode_rounded)),
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: metrics.iconSpacing),
|
||||||
|
child: _TileOverlayIcon(Icons.burst_mode_rounded, metrics: metrics),
|
||||||
|
),
|
||||||
if (isLivePhoto)
|
if (isLivePhoto)
|
||||||
const Padding(padding: EdgeInsets.only(left: 2.0), child: _TileOverlayIcon(Icons.motion_photos_on_rounded)),
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: metrics.iconSpacing),
|
||||||
|
child: _TileOverlayIcon(Icons.motion_photos_on_rounded, metrics: metrics),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -249,18 +273,25 @@ class _AssetTypeIcons extends StatelessWidget {
|
|||||||
|
|
||||||
class _OwnerNameLabel extends StatelessWidget {
|
class _OwnerNameLabel extends StatelessWidget {
|
||||||
final String ownerName;
|
final String ownerName;
|
||||||
|
final _OverlayMetrics metrics;
|
||||||
|
|
||||||
const _OwnerNameLabel({required this.ownerName});
|
const _OwnerNameLabel({required this.ownerName, required this.metrics});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
ownerName,
|
ownerName,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 12,
|
fontSize: metrics.fontSize,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))],
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
blurRadius: metrics.blurRadius,
|
||||||
|
color: const Color.fromRGBO(0, 0, 0, 0.6),
|
||||||
|
offset: const Offset(0.0, 0.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
@@ -268,3 +299,64 @@ class _OwnerNameLabel extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _OverlayMetrics {
|
||||||
|
final double horizontalPadding;
|
||||||
|
final double verticalPadding;
|
||||||
|
final double iconSize;
|
||||||
|
final double fontSize;
|
||||||
|
final double iconSpacing;
|
||||||
|
final double blurRadius;
|
||||||
|
|
||||||
|
const _OverlayMetrics({
|
||||||
|
required this.horizontalPadding,
|
||||||
|
required this.verticalPadding,
|
||||||
|
required this.iconSize,
|
||||||
|
required this.fontSize,
|
||||||
|
required this.iconSpacing,
|
||||||
|
required this.blurRadius,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _OverlayMetrics.fromConstraints(BoxConstraints constraints) {
|
||||||
|
final tileSize = constraints.maxWidth;
|
||||||
|
|
||||||
|
if (tileSize < 80) {
|
||||||
|
return const _OverlayMetrics(
|
||||||
|
horizontalPadding: 1.0,
|
||||||
|
verticalPadding: 1.0,
|
||||||
|
iconSize: 14,
|
||||||
|
fontSize: 11,
|
||||||
|
iconSpacing: 1.0,
|
||||||
|
blurRadius: 3.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (tileSize < 120) {
|
||||||
|
return const _OverlayMetrics(
|
||||||
|
horizontalPadding: 2.0,
|
||||||
|
verticalPadding: 2.0,
|
||||||
|
iconSize: 16,
|
||||||
|
fontSize: 12,
|
||||||
|
iconSpacing: 2.0,
|
||||||
|
blurRadius: 5.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (tileSize < 180) {
|
||||||
|
return const _OverlayMetrics(
|
||||||
|
horizontalPadding: 3.0,
|
||||||
|
verticalPadding: 3.0,
|
||||||
|
iconSize: 18,
|
||||||
|
fontSize: 13,
|
||||||
|
iconSpacing: 3.0,
|
||||||
|
blurRadius: 6.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const _OverlayMetrics(
|
||||||
|
horizontalPadding: 4.0,
|
||||||
|
verticalPadding: 4.0,
|
||||||
|
iconSize: 20,
|
||||||
|
fontSize: 14,
|
||||||
|
iconSpacing: 4.0,
|
||||||
|
blurRadius: 7.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user