mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
refactor(mobile): Use widgets in Immich asset grid (#7140)
* Refactors to use widgets in immich asset grid Adds comments * Fixes asset thumbnail absoluteOffset * feat(mobile): Uses gradient image placeholders in memory lane and in the asset grid (#7142) * Uses gradient image placeholders in memory lane and in the asset grid * Changes to create placeholders in immmich image.thumbnail * removed unused import * uses context.isDarkTheme and fixed function signature for allAssetsSelected
This commit is contained in:
parent
0730b54ca9
commit
70e5febb72
@ -10,6 +10,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
@ -127,184 +128,6 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
||||
assets.firstWhereOrNull((e) => !_selectedAssets.contains(e)) == null;
|
||||
}
|
||||
|
||||
Widget _buildThumbnailOrPlaceholder(Asset asset, int index) {
|
||||
return ThumbnailImage(
|
||||
asset: asset,
|
||||
index: index,
|
||||
loadAsset: widget.renderList.loadAsset,
|
||||
totalAssets: widget.renderList.totalAssets,
|
||||
multiselectEnabled: widget.selectionActive,
|
||||
isSelected: widget.selectionActive && _selectedAssets.contains(asset),
|
||||
onSelect: () => _selectAssets([asset]),
|
||||
onDeselect: widget.canDeselect ||
|
||||
widget.preselectedAssets == null ||
|
||||
!widget.preselectedAssets!.contains(asset)
|
||||
? () => _deselectAssets([asset])
|
||||
: null,
|
||||
useGrayBoxPlaceholder: true,
|
||||
showStorageIndicator: widget.showStorageIndicator,
|
||||
heroOffset: widget.heroOffset,
|
||||
showStack: widget.showStack,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAssetRow(
|
||||
Key key,
|
||||
BuildContext context,
|
||||
List<Asset> assets,
|
||||
int absoluteOffset,
|
||||
double width,
|
||||
) {
|
||||
// Default: All assets have the same width
|
||||
final widthDistribution = List.filled(assets.length, 1.0);
|
||||
|
||||
if (widget.dynamicLayout) {
|
||||
final aspectRatios =
|
||||
assets.map((e) => (e.width ?? 1) / (e.height ?? 1)).toList();
|
||||
final meanAspectRatio = aspectRatios.sum / assets.length;
|
||||
|
||||
// 1: mean width
|
||||
// 0.5: width < mean - threshold
|
||||
// 1.5: width > mean + threshold
|
||||
final arConfiguration = aspectRatios.map((e) {
|
||||
if (e - meanAspectRatio > 0.3) return 1.5;
|
||||
if (e - meanAspectRatio < -0.3) return 0.5;
|
||||
return 1.0;
|
||||
});
|
||||
|
||||
// Normalize:
|
||||
final sum = arConfiguration.sum;
|
||||
widthDistribution.setRange(
|
||||
0,
|
||||
widthDistribution.length,
|
||||
arConfiguration.map((e) => (e * assets.length) / sum),
|
||||
);
|
||||
}
|
||||
return Row(
|
||||
key: key,
|
||||
children: assets.mapIndexed((int index, Asset asset) {
|
||||
final bool last = index + 1 == widget.assetsPerRow;
|
||||
return Container(
|
||||
key: ValueKey(index),
|
||||
width: width * widthDistribution[index],
|
||||
height: width,
|
||||
margin: EdgeInsets.only(
|
||||
bottom: widget.margin,
|
||||
right: last ? 0.0 : widget.margin,
|
||||
),
|
||||
child: _buildThumbnailOrPlaceholder(asset, absoluteOffset + index),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTitle(
|
||||
BuildContext context,
|
||||
String title,
|
||||
List<Asset> assets,
|
||||
) {
|
||||
return GroupDividerTitle(
|
||||
text: title,
|
||||
multiselectEnabled: widget.selectionActive,
|
||||
onSelect: () => _selectAssets(assets),
|
||||
onDeselect: () => _deselectAssets(assets),
|
||||
selected: _allAssetsSelected(assets),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMonthTitle(BuildContext context, DateTime date) {
|
||||
final monthFormat = DateTime.now().year == date.year
|
||||
? DateFormat.MMMM()
|
||||
: DateFormat.yMMMM();
|
||||
final String title = monthFormat.format(date);
|
||||
return Padding(
|
||||
key: Key("month-$title"),
|
||||
padding: const EdgeInsets.only(left: 12.0, top: 24.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlaceHolderRow(Key key, int num, double width, double height) {
|
||||
return Row(
|
||||
key: key,
|
||||
children: [
|
||||
for (int i = 0; i < num; i++)
|
||||
Container(
|
||||
key: ValueKey(i),
|
||||
width: width,
|
||||
height: height,
|
||||
margin: EdgeInsets.only(
|
||||
bottom: widget.margin,
|
||||
right: i + 1 == num ? 0.0 : widget.margin,
|
||||
),
|
||||
color: Colors.grey,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(
|
||||
BuildContext context,
|
||||
RenderAssetGridElement section,
|
||||
bool scrolling,
|
||||
) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth / widget.assetsPerRow -
|
||||
widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow;
|
||||
final rows =
|
||||
(section.count + widget.assetsPerRow - 1) ~/ widget.assetsPerRow;
|
||||
final List<Asset> assetsToRender = scrolling
|
||||
? []
|
||||
: widget.renderList.loadAssets(section.offset, section.count);
|
||||
return Column(
|
||||
key: ValueKey(section.offset),
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (section.type == RenderAssetGridElementType.monthTitle)
|
||||
_buildMonthTitle(context, section.date),
|
||||
if (section.type == RenderAssetGridElementType.groupDividerTitle ||
|
||||
section.type == RenderAssetGridElementType.monthTitle)
|
||||
_buildTitle(
|
||||
context,
|
||||
section.title!,
|
||||
scrolling
|
||||
? []
|
||||
: widget.renderList
|
||||
.loadAssets(section.offset, section.totalCount),
|
||||
),
|
||||
for (int i = 0; i < rows; i++)
|
||||
scrolling
|
||||
? _buildPlaceHolderRow(
|
||||
ValueKey(i),
|
||||
i + 1 == rows
|
||||
? section.count - i * widget.assetsPerRow
|
||||
: widget.assetsPerRow,
|
||||
width,
|
||||
width,
|
||||
)
|
||||
: _buildAssetRow(
|
||||
ValueKey(i),
|
||||
context,
|
||||
assetsToRender.nestedSlice(
|
||||
i * widget.assetsPerRow,
|
||||
min((i + 1) * widget.assetsPerRow, section.count),
|
||||
),
|
||||
section.offset + i * widget.assetsPerRow,
|
||||
width,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _itemBuilder(BuildContext c, int position) {
|
||||
int index = position;
|
||||
if (widget.topWidget != null) {
|
||||
@ -314,8 +137,23 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
||||
index--;
|
||||
}
|
||||
|
||||
final item = widget.renderList.elements[index];
|
||||
return _buildSection(c, item, _scrolling);
|
||||
final section = widget.renderList.elements[index];
|
||||
return _Section(
|
||||
showStorageIndicator: widget.showStorageIndicator,
|
||||
selectedAssets: _selectedAssets,
|
||||
selectionActive: widget.selectionActive,
|
||||
section: section,
|
||||
margin: widget.margin,
|
||||
renderList: widget.renderList,
|
||||
assetsPerRow: widget.assetsPerRow,
|
||||
scrolling: _scrolling,
|
||||
dynamicLayout: widget.dynamicLayout,
|
||||
selectAssets: _selectAssets,
|
||||
deselectAssets: _deselectAssets,
|
||||
allAssetsSelected: _allAssetsSelected,
|
||||
showStack: widget.showStack,
|
||||
heroOffset: widget.heroOffset,
|
||||
);
|
||||
}
|
||||
|
||||
Text _labelBuilder(int pos) {
|
||||
@ -485,3 +323,292 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A single row of all placeholder widgets
|
||||
class _PlaceholderRow extends StatelessWidget {
|
||||
final int number;
|
||||
final double width;
|
||||
final double height;
|
||||
final double margin;
|
||||
|
||||
const _PlaceholderRow({
|
||||
super.key,
|
||||
required this.number,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.margin,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
for (int i = 0; i < number; i++)
|
||||
ThumbnailPlaceholder(
|
||||
key: ValueKey(i),
|
||||
width: width,
|
||||
height: height,
|
||||
margin: EdgeInsets.only(
|
||||
bottom: margin,
|
||||
right: i + 1 == number ? 0.0 : margin,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A section for the render grid
|
||||
class _Section extends StatelessWidget {
|
||||
final RenderAssetGridElement section;
|
||||
final Set<Asset> selectedAssets;
|
||||
final bool scrolling;
|
||||
final double margin;
|
||||
final int assetsPerRow;
|
||||
final RenderList renderList;
|
||||
final bool selectionActive;
|
||||
final bool dynamicLayout;
|
||||
final Function(List<Asset>) selectAssets;
|
||||
final Function(List<Asset>) deselectAssets;
|
||||
final bool Function(List<Asset>) allAssetsSelected;
|
||||
final bool showStack;
|
||||
final int heroOffset;
|
||||
final bool showStorageIndicator;
|
||||
|
||||
const _Section({
|
||||
required this.section,
|
||||
required this.scrolling,
|
||||
required this.margin,
|
||||
required this.assetsPerRow,
|
||||
required this.renderList,
|
||||
required this.selectionActive,
|
||||
required this.dynamicLayout,
|
||||
required this.selectAssets,
|
||||
required this.deselectAssets,
|
||||
required this.allAssetsSelected,
|
||||
required this.selectedAssets,
|
||||
required this.showStack,
|
||||
required this.heroOffset,
|
||||
required this.showStorageIndicator,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth / assetsPerRow -
|
||||
margin * (assetsPerRow - 1) / assetsPerRow;
|
||||
final rows = (section.count + assetsPerRow - 1) ~/ assetsPerRow;
|
||||
final List<Asset> assetsToRender = scrolling
|
||||
? []
|
||||
: renderList.loadAssets(section.offset, section.count);
|
||||
return Column(
|
||||
key: ValueKey(section.offset),
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (section.type == RenderAssetGridElementType.monthTitle)
|
||||
_MonthTitle(date: section.date),
|
||||
if (section.type == RenderAssetGridElementType.groupDividerTitle ||
|
||||
section.type == RenderAssetGridElementType.monthTitle)
|
||||
_Title(
|
||||
selectionActive: selectionActive,
|
||||
title: section.title!,
|
||||
assets: scrolling
|
||||
? []
|
||||
: renderList.loadAssets(section.offset, section.totalCount),
|
||||
allAssetsSelected: allAssetsSelected,
|
||||
selectAssets: selectAssets,
|
||||
deselectAssets: deselectAssets,
|
||||
),
|
||||
for (int i = 0; i < rows; i++)
|
||||
scrolling
|
||||
? _PlaceholderRow(
|
||||
key: ValueKey(i),
|
||||
number: i + 1 == rows
|
||||
? section.count - i * assetsPerRow
|
||||
: assetsPerRow,
|
||||
width: width,
|
||||
height: width,
|
||||
margin: margin,
|
||||
)
|
||||
: _AssetRow(
|
||||
key: ValueKey(i),
|
||||
assets: assetsToRender.nestedSlice(
|
||||
i * assetsPerRow,
|
||||
min((i + 1) * assetsPerRow, section.count),
|
||||
),
|
||||
absoluteOffset: section.offset + i * assetsPerRow,
|
||||
width: width,
|
||||
assetsPerRow: assetsPerRow,
|
||||
margin: margin,
|
||||
dynamicLayout: dynamicLayout,
|
||||
renderList: renderList,
|
||||
selectedAssets: selectedAssets,
|
||||
isSelectionActive: selectionActive,
|
||||
showStack: showStack,
|
||||
heroOffset: heroOffset,
|
||||
showStorageIndicator: showStorageIndicator,
|
||||
selectionActive: selectionActive,
|
||||
onSelect: (asset) => selectAssets([asset]),
|
||||
onDeselect: (asset) => deselectAssets([asset]),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The month title row for a section
|
||||
class _MonthTitle extends StatelessWidget {
|
||||
final DateTime date;
|
||||
|
||||
const _MonthTitle({
|
||||
required this.date,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final monthFormat = DateTime.now().year == date.year
|
||||
? DateFormat.MMMM()
|
||||
: DateFormat.yMMMM();
|
||||
final String title = monthFormat.format(date);
|
||||
return Padding(
|
||||
key: Key("month-$title"),
|
||||
padding: const EdgeInsets.only(left: 12.0, top: 24.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A title row
|
||||
class _Title extends StatelessWidget {
|
||||
final String title;
|
||||
final List<Asset> assets;
|
||||
final bool selectionActive;
|
||||
final Function(List<Asset>) selectAssets;
|
||||
final Function(List<Asset>) deselectAssets;
|
||||
final Function(List<Asset>) allAssetsSelected;
|
||||
|
||||
const _Title({
|
||||
required this.title,
|
||||
required this.assets,
|
||||
required this.selectionActive,
|
||||
required this.selectAssets,
|
||||
required this.deselectAssets,
|
||||
required this.allAssetsSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GroupDividerTitle(
|
||||
text: title,
|
||||
multiselectEnabled: selectionActive,
|
||||
onSelect: () => selectAssets(assets),
|
||||
onDeselect: () => deselectAssets(assets),
|
||||
selected: allAssetsSelected(assets),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The row of assets
|
||||
class _AssetRow extends StatelessWidget {
|
||||
final List<Asset> assets;
|
||||
final Set<Asset> selectedAssets;
|
||||
final int absoluteOffset;
|
||||
final double width;
|
||||
final bool dynamicLayout;
|
||||
final double margin;
|
||||
final int assetsPerRow;
|
||||
final RenderList renderList;
|
||||
final bool selectionActive;
|
||||
final bool showStorageIndicator;
|
||||
final int heroOffset;
|
||||
final bool showStack;
|
||||
final Function(Asset)? onSelect;
|
||||
final Function(Asset)? onDeselect;
|
||||
final bool isSelectionActive;
|
||||
|
||||
const _AssetRow({
|
||||
super.key,
|
||||
required this.assets,
|
||||
required this.absoluteOffset,
|
||||
required this.width,
|
||||
required this.dynamicLayout,
|
||||
required this.margin,
|
||||
required this.assetsPerRow,
|
||||
required this.renderList,
|
||||
required this.selectionActive,
|
||||
required this.showStorageIndicator,
|
||||
required this.heroOffset,
|
||||
required this.showStack,
|
||||
required this.isSelectionActive,
|
||||
required this.selectedAssets,
|
||||
this.onSelect,
|
||||
this.onDeselect,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Default: All assets have the same width
|
||||
final widthDistribution = List.filled(assets.length, 1.0);
|
||||
|
||||
if (dynamicLayout) {
|
||||
final aspectRatios =
|
||||
assets.map((e) => (e.width ?? 1) / (e.height ?? 1)).toList();
|
||||
final meanAspectRatio = aspectRatios.sum / assets.length;
|
||||
|
||||
// 1: mean width
|
||||
// 0.5: width < mean - threshold
|
||||
// 1.5: width > mean + threshold
|
||||
final arConfiguration = aspectRatios.map((e) {
|
||||
if (e - meanAspectRatio > 0.3) return 1.5;
|
||||
if (e - meanAspectRatio < -0.3) return 0.5;
|
||||
return 1.0;
|
||||
});
|
||||
|
||||
// Normalize:
|
||||
final sum = arConfiguration.sum;
|
||||
widthDistribution.setRange(
|
||||
0,
|
||||
widthDistribution.length,
|
||||
arConfiguration.map((e) => (e * assets.length) / sum),
|
||||
);
|
||||
}
|
||||
return Row(
|
||||
key: key,
|
||||
children: assets.mapIndexed((int index, Asset asset) {
|
||||
final bool last = index + 1 == assetsPerRow;
|
||||
return Container(
|
||||
width: width * widthDistribution[index],
|
||||
height: width,
|
||||
margin: EdgeInsets.only(
|
||||
bottom: margin,
|
||||
right: last ? 0.0 : margin,
|
||||
),
|
||||
child: ThumbnailImage(
|
||||
asset: asset,
|
||||
index: absoluteOffset + index,
|
||||
loadAsset: renderList.loadAsset,
|
||||
totalAssets: renderList.totalAssets,
|
||||
multiselectEnabled: selectionActive,
|
||||
isSelected: isSelectionActive && selectedAssets.contains(asset),
|
||||
onSelect: () => onSelect?.call(asset),
|
||||
onDeselect: () => onDeselect?.call(asset),
|
||||
showStorageIndicator: showStorageIndicator,
|
||||
heroOffset: heroOffset,
|
||||
showStack: showStack,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ class ThumbnailImage extends StatelessWidget {
|
||||
final int totalAssets;
|
||||
final bool showStorageIndicator;
|
||||
final bool showStack;
|
||||
final bool useGrayBoxPlaceholder;
|
||||
final bool isSelected;
|
||||
final bool multiselectEnabled;
|
||||
final Function? onSelect;
|
||||
@ -30,7 +29,6 @@ class ThumbnailImage extends StatelessWidget {
|
||||
required this.totalAssets,
|
||||
this.showStorageIndicator = true,
|
||||
this.showStack = false,
|
||||
this.useGrayBoxPlaceholder = false,
|
||||
this.isSelected = false,
|
||||
this.multiselectEnabled = false,
|
||||
this.onDeselect,
|
||||
@ -138,6 +136,8 @@ class ThumbnailImage extends StatelessWidget {
|
||||
: asset.id + heroOffset,
|
||||
child: ImmichImage.thumbnail(
|
||||
asset,
|
||||
height: 300,
|
||||
width: 300,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
|
||||
class ThumbnailPlaceholder extends StatelessWidget {
|
||||
final EdgeInsets margin;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const ThumbnailPlaceholder({
|
||||
super.key,
|
||||
this.margin = EdgeInsets.zero,
|
||||
this.width = 250,
|
||||
this.height = 250,
|
||||
});
|
||||
|
||||
static const _brightColors = [
|
||||
Color(0xFFF1F3F4),
|
||||
Color(0xFFB4B6B8),
|
||||
];
|
||||
|
||||
static const _darkColors = [
|
||||
Color(0xFF3B3F42),
|
||||
Color(0xFF2B2F32),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
margin: margin,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: context.isDarkTheme ? _darkColors : _brightColors,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
|
||||
import 'package:immich_mobile/modules/memories/providers/memory.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||
@ -60,7 +61,10 @@ class MemoryLane extends HookConsumerWidget {
|
||||
fit: BoxFit.cover,
|
||||
width: 130,
|
||||
height: 200,
|
||||
useGrayBoxPlaceholder: true,
|
||||
placeholder: const ThumbnailPlaceholder(
|
||||
width: 130,
|
||||
height: 200,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_local_image_provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
@ -17,14 +18,14 @@ class ImmichImage extends StatelessWidget {
|
||||
this.width,
|
||||
this.height,
|
||||
this.fit = BoxFit.cover,
|
||||
this.useGrayBoxPlaceholder = false,
|
||||
this.placeholder = const ThumbnailPlaceholder(),
|
||||
this.isThumbnail = false,
|
||||
this.thumbnailSize = 250,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Asset? asset;
|
||||
final bool useGrayBoxPlaceholder;
|
||||
final Widget? placeholder;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final BoxFit fit;
|
||||
@ -47,7 +48,10 @@ class ImmichImage extends StatelessWidget {
|
||||
fit: fit,
|
||||
width: width,
|
||||
height: height,
|
||||
useGrayBoxPlaceholder: true,
|
||||
placeholder: ThumbnailPlaceholder(
|
||||
height: thumbnailSize.toDouble(),
|
||||
width: thumbnailSize.toDouble(),
|
||||
),
|
||||
thumbnailSize: thumbnailSize,
|
||||
);
|
||||
}
|
||||
@ -99,7 +103,6 @@ class ImmichImage extends StatelessWidget {
|
||||
asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
if (asset == null) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
@ -119,13 +122,9 @@ class ImmichImage extends StatelessWidget {
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
fadeOutDuration: const Duration(milliseconds: 400),
|
||||
placeholderBuilder: (context) {
|
||||
if (useGrayBoxPlaceholder) {
|
||||
if (placeholder != null) {
|
||||
// Use the gray box placeholder
|
||||
return const SizedBox.expand(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(color: Colors.grey),
|
||||
),
|
||||
);
|
||||
return placeholder!;
|
||||
}
|
||||
// No placeholder
|
||||
return const SizedBox();
|
||||
|
Loading…
Reference in New Issue
Block a user