mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02:00
Move selection logic to asset grid class
This commit is contained in:
parent
347ac70063
commit
a117e897ca
@ -8,11 +8,17 @@ class DailyTitleText extends ConsumerWidget {
|
|||||||
const DailyTitleText({
|
const DailyTitleText({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.isoDate,
|
required this.isoDate,
|
||||||
required this.assetGroup,
|
required this.multiselectEnabled,
|
||||||
|
required this.onSelect,
|
||||||
|
required this.onDeselect,
|
||||||
|
required this.selected,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String isoDate;
|
final String isoDate;
|
||||||
final List<AssetResponseDto> assetGroup;
|
final bool multiselectEnabled;
|
||||||
|
final Function onSelect;
|
||||||
|
final Function onDeselect;
|
||||||
|
final bool selected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -23,51 +29,12 @@ class DailyTitleText extends ConsumerWidget {
|
|||||||
: "daily_title_text_date_year".tr();
|
: "daily_title_text_date_year".tr();
|
||||||
var dateText =
|
var dateText =
|
||||||
DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
|
DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
|
||||||
var isMultiSelectEnable =
|
|
||||||
ref.watch(homePageStateProvider).isMultiSelectEnable;
|
|
||||||
var selectedDateGroup = ref.watch(homePageStateProvider).selectedDateGroup;
|
|
||||||
var selectedItems = ref.watch(homePageStateProvider).selectedItems;
|
|
||||||
|
|
||||||
void _handleTitleIconClick() {
|
void handleTitleIconClick() {
|
||||||
if (isMultiSelectEnable &&
|
if (selected) {
|
||||||
selectedDateGroup.contains(dateText) &&
|
onDeselect();
|
||||||
selectedDateGroup.length == 1 &&
|
|
||||||
selectedItems.length <= assetGroup.length) {
|
|
||||||
// Multi select is active - click again on the icon while it is the only active group -> disable multi select
|
|
||||||
ref.watch(homePageStateProvider.notifier).disableMultiSelect();
|
|
||||||
} else if (isMultiSelectEnable &&
|
|
||||||
selectedDateGroup.contains(dateText) &&
|
|
||||||
selectedItems.length != assetGroup.length) {
|
|
||||||
// Multi select is active - click again on the icon while it is not the only active group -> remove that group from selected group/items
|
|
||||||
ref
|
|
||||||
.watch(homePageStateProvider.notifier)
|
|
||||||
.removeSelectedDateGroup(dateText);
|
|
||||||
ref
|
|
||||||
.watch(homePageStateProvider.notifier)
|
|
||||||
.removeMultipleSelectedItem(assetGroup);
|
|
||||||
} else if (isMultiSelectEnable &&
|
|
||||||
selectedDateGroup.contains(dateText) &&
|
|
||||||
selectedDateGroup.length > 1) {
|
|
||||||
ref
|
|
||||||
.watch(homePageStateProvider.notifier)
|
|
||||||
.removeSelectedDateGroup(dateText);
|
|
||||||
ref
|
|
||||||
.watch(homePageStateProvider.notifier)
|
|
||||||
.removeMultipleSelectedItem(assetGroup);
|
|
||||||
} else if (isMultiSelectEnable && !selectedDateGroup.contains(dateText)) {
|
|
||||||
ref
|
|
||||||
.watch(homePageStateProvider.notifier)
|
|
||||||
.addSelectedDateGroup(dateText);
|
|
||||||
ref
|
|
||||||
.watch(homePageStateProvider.notifier)
|
|
||||||
.addMultipleSelectedItems(assetGroup);
|
|
||||||
} else {
|
} else {
|
||||||
ref
|
onSelect();
|
||||||
.watch(homePageStateProvider.notifier)
|
|
||||||
.enableMultiSelect(assetGroup.toSet());
|
|
||||||
ref
|
|
||||||
.watch(homePageStateProvider.notifier)
|
|
||||||
.addSelectedDateGroup(dateText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,8 +56,8 @@ class DailyTitleText extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: _handleTitleIconClick,
|
onTap: handleTitleIconClick,
|
||||||
child: isMultiSelectEnable && selectedDateGroup.contains(dateText)
|
child: multiselectEnabled && selected
|
||||||
? Icon(
|
? Icon(
|
||||||
Icons.check_circle_rounded,
|
Icons.check_circle_rounded,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
|
@ -1,40 +1,36 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
class DisableMultiSelectButton extends ConsumerWidget {
|
class DisableMultiSelectButton extends ConsumerWidget {
|
||||||
const DisableMultiSelectButton({
|
const DisableMultiSelectButton({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
required this.selectedItemCount,
|
required this.selectedItemCount,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Function onPressed;
|
final Function onPressed;
|
||||||
final int selectedItemCount;
|
final int selectedItemCount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Positioned(
|
return Padding(
|
||||||
top: 10,
|
padding: const EdgeInsets.only(left: 16.0, top: 15),
|
||||||
left: 0,
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
padding: const EdgeInsets.only(left: 16.0, top: 46),
|
child: ElevatedButton.icon(
|
||||||
child: Padding(
|
onPressed: () {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
onPressed();
|
||||||
child: ElevatedButton.icon(
|
},
|
||||||
onPressed: () {
|
icon: const Icon(Icons.close_rounded),
|
||||||
onPressed();
|
label: Text(
|
||||||
},
|
'$selectedItemCount',
|
||||||
icon: const Icon(Icons.close_rounded),
|
style: const TextStyle(
|
||||||
label: Text(
|
fontWeight: FontWeight.w600,
|
||||||
'$selectedItemCount',
|
fontSize: 18,
|
||||||
style: const TextStyle(
|
),
|
||||||
fontWeight: FontWeight.w600,
|
),
|
||||||
fontSize: 18,
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
),
|
}
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:collection';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@ -5,35 +6,27 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/src/widgets/framework.dart';
|
import 'package:flutter/src/widgets/framework.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart';
|
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
import 'asset_grid_data_structure.dart';
|
import 'asset_grid_data_structure.dart';
|
||||||
import 'daily_title_text.dart';
|
import 'daily_title_text.dart';
|
||||||
|
import 'disable_multi_select_button.dart';
|
||||||
import 'draggable_scrollbar_custom.dart';
|
import 'draggable_scrollbar_custom.dart';
|
||||||
|
|
||||||
class ImmichAssetGrid extends HookConsumerWidget {
|
typedef ImmichAssetGridSelectionListener = void Function(bool);
|
||||||
|
|
||||||
|
class ImmichAssetGridState extends State<ImmichAssetGrid> {
|
||||||
final ItemScrollController _itemScrollController = ItemScrollController();
|
final ItemScrollController _itemScrollController = ItemScrollController();
|
||||||
final ItemPositionsListener _itemPositionsListener =
|
final ItemPositionsListener _itemPositionsListener =
|
||||||
ItemPositionsListener.create();
|
ItemPositionsListener.create();
|
||||||
|
|
||||||
final List<RenderAssetGridElement> renderList;
|
bool _scrolling = false;
|
||||||
final int assetsPerRow;
|
bool _multiselect = false;
|
||||||
final double margin;
|
Set<String> _selectedAssets = HashSet();
|
||||||
final bool showStorageIndicator;
|
|
||||||
|
|
||||||
ImmichAssetGrid({
|
|
||||||
super.key,
|
|
||||||
required this.renderList,
|
|
||||||
required this.assetsPerRow,
|
|
||||||
required this.showStorageIndicator,
|
|
||||||
this.margin = 5.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
List<AssetResponseDto> get _assets {
|
List<AssetResponseDto> get _assets {
|
||||||
return renderList
|
return widget.renderList
|
||||||
.map((e) {
|
.map((e) {
|
||||||
if (e.type == RenderAssetGridElementType.assetRow) {
|
if (e.type == RenderAssetGridElementType.assetRow) {
|
||||||
return e.assetRow!.assets;
|
return e.assetRow!.assets;
|
||||||
@ -44,10 +37,49 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||||||
.flattened
|
.flattened
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _selectAssets(List<AssetResponseDto> assets) {
|
||||||
|
setState(() {
|
||||||
|
|
||||||
|
if (!_multiselect) {
|
||||||
|
_multiselect = true;
|
||||||
|
widget.listener?.call(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var e in assets) {
|
||||||
|
_selectedAssets.add(e.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _deselectAssets(List<AssetResponseDto> assets) {
|
||||||
|
setState(() {
|
||||||
|
for (var e in assets) {
|
||||||
|
_selectedAssets.remove(e.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedAssets.isEmpty) {
|
||||||
|
_multiselect = false;
|
||||||
|
widget.listener?.call(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _deselectAll() {
|
||||||
|
setState(() {
|
||||||
|
_multiselect = false;
|
||||||
|
_selectedAssets.clear();
|
||||||
|
});
|
||||||
|
widget.listener?.call(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _allAssetsSelected(List<AssetResponseDto> assets) {
|
||||||
|
return _multiselect && assets.firstWhereOrNull((e) => !_selectedAssets.contains(e.id)) == null;
|
||||||
|
}
|
||||||
|
|
||||||
double _getItemSize(BuildContext context) {
|
double _getItemSize(BuildContext context) {
|
||||||
return MediaQuery.of(context).size.width / assetsPerRow -
|
return MediaQuery.of(context).size.width / widget.assetsPerRow -
|
||||||
margin * (assetsPerRow - 1) / assetsPerRow;
|
widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildThumbnailOrPlaceholder(
|
Widget _buildThumbnailOrPlaceholder(
|
||||||
@ -60,7 +92,10 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||||||
return ThumbnailImage(
|
return ThumbnailImage(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
assetList: _assets,
|
assetList: _assets,
|
||||||
showStorageIndicator: showStorageIndicator,
|
multiselectEnabled: _multiselect,
|
||||||
|
isSelected: _selectedAssets.contains(asset.id),
|
||||||
|
onSelect: () => _selectAssets([asset]),
|
||||||
|
onDeselect: () => _deselectAssets([asset]),
|
||||||
useGrayBoxPlaceholder: true,
|
useGrayBoxPlaceholder: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -78,7 +113,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||||||
key: Key("asset-${asset.id}"),
|
key: Key("asset-${asset.id}"),
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
margin: EdgeInsets.only(top: margin, right: last ? 0.0 : margin),
|
margin: EdgeInsets.only(top: widget.margin, right: last ? 0.0 : widget.margin),
|
||||||
child: _buildThumbnailOrPlaceholder(asset, scrolling),
|
child: _buildThumbnailOrPlaceholder(asset, scrolling),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
@ -89,7 +124,10 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||||||
BuildContext context, String title, List<AssetResponseDto> assets) {
|
BuildContext context, String title, List<AssetResponseDto> assets) {
|
||||||
return DailyTitleText(
|
return DailyTitleText(
|
||||||
isoDate: title,
|
isoDate: title,
|
||||||
assetGroup: assets,
|
multiselectEnabled: _multiselect,
|
||||||
|
onSelect: () => _selectAssets(assets),
|
||||||
|
onDeselect: () => _deselectAssets(assets),
|
||||||
|
selected: _allAssetsSelected(assets),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,22 +149,22 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _itemBuilder(BuildContext c, int position, bool scrolling) {
|
Widget _itemBuilder(BuildContext c, int position) {
|
||||||
final item = renderList[position];
|
final item = widget.renderList[position];
|
||||||
|
|
||||||
if (item.type == RenderAssetGridElementType.dayTitle) {
|
if (item.type == RenderAssetGridElementType.dayTitle) {
|
||||||
return _buildTitle(c, item.title!, item.relatedAssetList!);
|
return _buildTitle(c, item.title!, item.relatedAssetList!);
|
||||||
} else if (item.type == RenderAssetGridElementType.monthTitle) {
|
} else if (item.type == RenderAssetGridElementType.monthTitle) {
|
||||||
return _buildMonthTitle(c, item.title!);
|
return _buildMonthTitle(c, item.title!);
|
||||||
} else if (item.type == RenderAssetGridElementType.assetRow) {
|
} else if (item.type == RenderAssetGridElementType.assetRow) {
|
||||||
return _buildAssetRow(c, item.assetRow!, scrolling);
|
return _buildAssetRow(c, item.assetRow!, _scrolling);
|
||||||
}
|
}
|
||||||
|
|
||||||
return const Text("Invalid widget type!");
|
return const Text("Invalid widget type!");
|
||||||
}
|
}
|
||||||
|
|
||||||
Text _labelBuilder(int pos) {
|
Text _labelBuilder(int pos) {
|
||||||
final date = renderList[pos].date;
|
final date = widget.renderList[pos].date;
|
||||||
return Text(DateFormat.yMMMd().format(date),
|
return Text(DateFormat.yMMMd().format(date),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@ -135,26 +173,27 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildMultiSelectIndicator() {
|
||||||
|
return DisableMultiSelectButton(
|
||||||
|
onPressed: () => _deselectAll(),
|
||||||
|
selectedItemCount: _selectedAssets.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
Widget _buildAssetGrid() {
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final scrolling = useState(false);
|
|
||||||
|
|
||||||
final useDragScrolling = _assets.length > 100;
|
final useDragScrolling = _assets.length > 100;
|
||||||
|
|
||||||
void dragScrolling(bool active) {
|
void dragScrolling(bool active) {
|
||||||
scrolling.value = active;
|
setState(() {
|
||||||
}
|
_scrolling = active;
|
||||||
|
});
|
||||||
Widget itemBuilder(BuildContext c, int position) {
|
|
||||||
return _itemBuilder(c, position, scrolling.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final listWidget = ScrollablePositionedList.builder(
|
final listWidget = ScrollablePositionedList.builder(
|
||||||
itemBuilder: itemBuilder,
|
itemBuilder: _itemBuilder,
|
||||||
itemPositionsListener: _itemPositionsListener,
|
itemPositionsListener: _itemPositionsListener,
|
||||||
itemScrollController: _itemScrollController,
|
itemScrollController: _itemScrollController,
|
||||||
itemCount: renderList.length,
|
itemCount: widget.renderList.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!useDragScrolling) {
|
if (!useDragScrolling) {
|
||||||
@ -162,15 +201,48 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return DraggableScrollbar.semicircle(
|
return DraggableScrollbar.semicircle(
|
||||||
scrollStateListener: dragScrolling,
|
scrollStateListener: dragScrolling,
|
||||||
itemPositionsListener: _itemPositionsListener,
|
itemPositionsListener: _itemPositionsListener,
|
||||||
controller: _itemScrollController,
|
controller: _itemScrollController,
|
||||||
backgroundColor: Theme.of(context).hintColor,
|
backgroundColor: Theme.of(context).hintColor,
|
||||||
labelTextBuilder: _labelBuilder,
|
labelTextBuilder: _labelBuilder,
|
||||||
labelConstraints: const BoxConstraints(maxHeight: 28),
|
labelConstraints: const BoxConstraints(maxHeight: 28),
|
||||||
scrollbarAnimationDuration: const Duration(seconds: 1),
|
scrollbarAnimationDuration: const Duration(seconds: 1),
|
||||||
scrollbarTimeToFade: const Duration(seconds: 4),
|
scrollbarTimeToFade: const Duration(seconds: 4),
|
||||||
child: listWidget,
|
child: listWidget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
_buildAssetGrid(),
|
||||||
|
if (_multiselect) _buildMultiSelectIndicator(),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ImmichAssetGrid extends StatefulWidget {
|
||||||
|
final List<RenderAssetGridElement> renderList;
|
||||||
|
final int assetsPerRow;
|
||||||
|
final double margin;
|
||||||
|
final bool showStorageIndicator;
|
||||||
|
final ImmichAssetGridSelectionListener? listener;
|
||||||
|
|
||||||
|
|
||||||
|
ImmichAssetGrid({
|
||||||
|
super.key,
|
||||||
|
required this.renderList,
|
||||||
|
required this.assetsPerRow,
|
||||||
|
required this.showStorageIndicator,
|
||||||
|
this.listener,
|
||||||
|
this.margin = 5.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return ImmichAssetGridState();
|
||||||
|
}
|
||||||
|
}
|
@ -1,176 +1,174 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
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:collection/collection.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
class ThumbnailImage extends HookConsumerWidget {
|
|
||||||
final AssetResponseDto asset;
|
class ThumbnailImage extends HookConsumerWidget {
|
||||||
final List<AssetResponseDto> assetList;
|
final AssetResponseDto asset;
|
||||||
final bool showStorageIndicator;
|
final List<AssetResponseDto> assetList;
|
||||||
final bool useGrayBoxPlaceholder;
|
final bool showStorageIndicator;
|
||||||
|
final bool useGrayBoxPlaceholder;
|
||||||
const ThumbnailImage({
|
final bool isSelected;
|
||||||
Key? key,
|
final bool multiselectEnabled;
|
||||||
required this.asset,
|
final Function? onSelect;
|
||||||
required this.assetList,
|
final Function? onDeselect;
|
||||||
this.showStorageIndicator = true,
|
|
||||||
this.useGrayBoxPlaceholder = false,
|
const ThumbnailImage({
|
||||||
}) : super(key: key);
|
Key? key,
|
||||||
|
required this.asset,
|
||||||
@override
|
required this.assetList,
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
this.showStorageIndicator = true,
|
||||||
var box = Hive.box(userInfoBox);
|
this.useGrayBoxPlaceholder = false,
|
||||||
var thumbnailRequestUrl = getThumbnailUrl(asset);
|
this.isSelected = false,
|
||||||
var selectedAsset = ref.watch(homePageStateProvider).selectedItems;
|
this.multiselectEnabled = false,
|
||||||
var isMultiSelectEnable =
|
this.onDeselect,
|
||||||
ref.watch(homePageStateProvider).isMultiSelectEnable;
|
this.onSelect,
|
||||||
var deviceId = ref.watch(authenticationProvider).deviceId;
|
}) : super(key: key);
|
||||||
|
|
||||||
Widget buildSelectionIcon(AssetResponseDto asset) {
|
@override
|
||||||
if (selectedAsset.contains(asset)) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Icon(
|
var box = Hive.box(userInfoBox);
|
||||||
Icons.check_circle,
|
var thumbnailRequestUrl = getThumbnailUrl(asset);
|
||||||
color: Theme.of(context).primaryColor,
|
var deviceId = ref.watch(authenticationProvider).deviceId;
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const Icon(
|
Widget buildSelectionIcon(AssetResponseDto asset) {
|
||||||
Icons.circle_outlined,
|
if (isSelected) {
|
||||||
color: Colors.white,
|
return Icon(
|
||||||
);
|
Icons.check_circle,
|
||||||
}
|
color: Theme.of(context).primaryColor,
|
||||||
}
|
);
|
||||||
|
} else {
|
||||||
return GestureDetector(
|
return const Icon(
|
||||||
onTap: () {
|
Icons.circle_outlined,
|
||||||
if (isMultiSelectEnable &&
|
color: Colors.white,
|
||||||
selectedAsset.contains(asset) &&
|
);
|
||||||
selectedAsset.length == 1) {
|
}
|
||||||
ref.watch(homePageStateProvider.notifier).disableMultiSelect();
|
}
|
||||||
} else if (isMultiSelectEnable &&
|
|
||||||
selectedAsset.contains(asset) &&
|
return GestureDetector(
|
||||||
selectedAsset.length > 1) {
|
onTap: () {
|
||||||
ref
|
if (multiselectEnabled) {
|
||||||
.watch(homePageStateProvider.notifier)
|
if (isSelected) {
|
||||||
.removeSingleSelectedItem(asset);
|
onDeselect?.call();
|
||||||
} else if (isMultiSelectEnable && !selectedAsset.contains(asset)) {
|
} else {
|
||||||
ref
|
onSelect?.call();
|
||||||
.watch(homePageStateProvider.notifier)
|
}
|
||||||
.addSingleSelectedItem(asset);
|
} else {
|
||||||
} else {
|
AutoRouter.of(context).push(
|
||||||
AutoRouter.of(context).push(
|
GalleryViewerRoute(
|
||||||
GalleryViewerRoute(
|
assetList: assetList,
|
||||||
assetList: assetList,
|
asset: asset,
|
||||||
asset: asset,
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
},
|
||||||
},
|
onLongPress: () {
|
||||||
onLongPress: () {
|
onSelect?.call();
|
||||||
// Enable multi select function
|
HapticFeedback.heavyImpact();
|
||||||
ref.watch(homePageStateProvider.notifier).enableMultiSelect({asset});
|
},
|
||||||
HapticFeedback.heavyImpact();
|
child: Hero(
|
||||||
},
|
tag: asset.id,
|
||||||
child: Hero(
|
child: Stack(
|
||||||
tag: asset.id,
|
children: [
|
||||||
child: Stack(
|
Container(
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
Container(
|
border: multiselectEnabled && isSelected
|
||||||
decoration: BoxDecoration(
|
? Border.all(
|
||||||
border: isMultiSelectEnable && selectedAsset.contains(asset)
|
color: Theme.of(context).primaryColorLight,
|
||||||
? Border.all(
|
width: 10,
|
||||||
color: Theme.of(context).primaryColorLight,
|
)
|
||||||
width: 10,
|
: const Border(),
|
||||||
)
|
),
|
||||||
: const Border(),
|
child: CachedNetworkImage(
|
||||||
),
|
cacheKey: 'thumbnail-image-${asset.id}',
|
||||||
child: CachedNetworkImage(
|
width: 300,
|
||||||
cacheKey: 'thumbnail-image-${asset.id}',
|
height: 300,
|
||||||
width: 300,
|
memCacheHeight: 200,
|
||||||
height: 300,
|
maxWidthDiskCache: 200,
|
||||||
memCacheHeight: 200,
|
maxHeightDiskCache: 200,
|
||||||
maxWidthDiskCache: 200,
|
fit: BoxFit.cover,
|
||||||
maxHeightDiskCache: 200,
|
imageUrl: thumbnailRequestUrl,
|
||||||
fit: BoxFit.cover,
|
httpHeaders: {
|
||||||
imageUrl: thumbnailRequestUrl,
|
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||||
httpHeaders: {
|
},
|
||||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
fadeInDuration: const Duration(milliseconds: 250),
|
||||||
},
|
progressIndicatorBuilder: (context, url, downloadProgress) {
|
||||||
fadeInDuration: const Duration(milliseconds: 250),
|
if (useGrayBoxPlaceholder) {
|
||||||
progressIndicatorBuilder: (context, url, downloadProgress) {
|
return const DecoratedBox(
|
||||||
if (useGrayBoxPlaceholder) {
|
decoration: BoxDecoration(color: Colors.grey),
|
||||||
return const DecoratedBox(
|
);
|
||||||
decoration: BoxDecoration(color: Colors.grey),
|
}
|
||||||
);
|
return Transform.scale(
|
||||||
}
|
scale: 0.2,
|
||||||
return Transform.scale(
|
child: CircularProgressIndicator(
|
||||||
scale: 0.2,
|
value: downloadProgress.progress,
|
||||||
child: CircularProgressIndicator(
|
),
|
||||||
value: downloadProgress.progress,
|
);
|
||||||
),
|
},
|
||||||
);
|
errorWidget: (context, url, error) {
|
||||||
},
|
debugPrint("Error getting thumbnail $url = $error");
|
||||||
errorWidget: (context, url, error) {
|
CachedNetworkImage.evictFromCache(thumbnailRequestUrl);
|
||||||
debugPrint("Error getting thumbnail $url = $error");
|
|
||||||
CachedNetworkImage.evictFromCache(thumbnailRequestUrl);
|
return Icon(
|
||||||
|
Icons.image_not_supported_outlined,
|
||||||
return Icon(
|
color: Theme.of(context).primaryColor,
|
||||||
Icons.image_not_supported_outlined,
|
);
|
||||||
color: Theme.of(context).primaryColor,
|
},
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
if (multiselectEnabled)
|
||||||
),
|
Padding(
|
||||||
if (isMultiSelectEnable)
|
padding: const EdgeInsets.all(3.0),
|
||||||
Padding(
|
child: Align(
|
||||||
padding: const EdgeInsets.all(3.0),
|
alignment: Alignment.topLeft,
|
||||||
child: Align(
|
child: buildSelectionIcon(asset),
|
||||||
alignment: Alignment.topLeft,
|
),
|
||||||
child: buildSelectionIcon(asset),
|
),
|
||||||
),
|
if (showStorageIndicator)
|
||||||
),
|
Positioned(
|
||||||
if (showStorageIndicator)
|
right: 10,
|
||||||
Positioned(
|
bottom: 5,
|
||||||
right: 10,
|
child: Icon(
|
||||||
bottom: 5,
|
(deviceId != asset.deviceId)
|
||||||
child: Icon(
|
? Icons.cloud_done_outlined
|
||||||
(deviceId != asset.deviceId)
|
: Icons.photo_library_rounded,
|
||||||
? Icons.cloud_done_outlined
|
color: Colors.white,
|
||||||
: Icons.photo_library_rounded,
|
size: 18,
|
||||||
color: Colors.white,
|
),
|
||||||
size: 18,
|
),
|
||||||
),
|
if (asset.type != AssetTypeEnum.IMAGE)
|
||||||
),
|
Positioned(
|
||||||
if (asset.type != AssetTypeEnum.IMAGE)
|
top: 5,
|
||||||
Positioned(
|
right: 5,
|
||||||
top: 5,
|
child: Row(
|
||||||
right: 5,
|
children: [
|
||||||
child: Row(
|
Text(
|
||||||
children: [
|
asset.duration.toString().substring(0, 7),
|
||||||
Text(
|
style: const TextStyle(
|
||||||
asset.duration.toString().substring(0, 7),
|
color: Colors.white,
|
||||||
style: const TextStyle(
|
fontSize: 10,
|
||||||
color: Colors.white,
|
),
|
||||||
fontSize: 10,
|
),
|
||||||
),
|
const Icon(
|
||||||
),
|
Icons.play_circle_outline_rounded,
|
||||||
const Icon(
|
color: Colors.white,
|
||||||
Icons.play_circle_outline_rounded,
|
),
|
||||||
color: Colors.white,
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
);
|
}
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
|
@ -5,7 +5,6 @@ import 'package:immich_mobile/modules/home/providers/home_page_render_list_provi
|
|||||||
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
|
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart';
|
import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/disable_multi_select_button.dart';
|
|
||||||
import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
|
import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart';
|
import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
@ -20,12 +19,9 @@ class HomePage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||||
|
|
||||||
var renderList = ref.watch(renderListProvider);
|
var renderList = ref.watch(renderListProvider);
|
||||||
|
|
||||||
var isMultiSelectEnable =
|
final multiselectEnabled = useState(false);
|
||||||
ref.watch(homePageStateProvider).isMultiSelectEnable;
|
|
||||||
var homePageState = ref.watch(homePageStateProvider);
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
@ -41,16 +37,9 @@ class HomePage extends HookConsumerWidget {
|
|||||||
ref.read(assetProvider.notifier).getAllAsset();
|
ref.read(assetProvider.notifier).getAllAsset();
|
||||||
}
|
}
|
||||||
|
|
||||||
buildSelectedItemCountIndicator() {
|
|
||||||
return DisableMultiSelectButton(
|
|
||||||
onPressed: ref.watch(homePageStateProvider.notifier).disableMultiSelect,
|
|
||||||
selectedItemCount: homePageState.selectedItems.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildBody() {
|
Widget buildBody() {
|
||||||
buildSliverAppBar() {
|
buildSliverAppBar() {
|
||||||
return isMultiSelectEnable
|
return multiselectEnabled.value
|
||||||
? const SliverToBoxAdapter(
|
? const SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 70,
|
height: 70,
|
||||||
@ -62,9 +51,13 @@ class HomePage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void selectionListener(bool multiselect) {
|
||||||
|
multiselectEnabled.value = multiselect;
|
||||||
|
}
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
bottom: !isMultiSelectEnable,
|
bottom: !multiselectEnabled.value,
|
||||||
top: !isMultiSelectEnable,
|
top: !multiselectEnabled.value,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
CustomScrollView(
|
CustomScrollView(
|
||||||
@ -80,10 +73,10 @@ class HomePage extends HookConsumerWidget {
|
|||||||
appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
|
appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
|
||||||
showStorageIndicator: appSettingService
|
showStorageIndicator: appSettingService
|
||||||
.getSetting(AppSettingsEnum.storageIndicator),
|
.getSetting(AppSettingsEnum.storageIndicator),
|
||||||
|
listener: selectionListener,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isMultiSelectEnable) ...[
|
if (multiselectEnabled.value) ...[
|
||||||
buildSelectedItemCountIndicator(),
|
|
||||||
const ControlBottomAppBar(),
|
const ControlBottomAppBar(),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user