Delete old gallery files
This commit is contained in:
@@ -1,217 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:photos/ui/huge_listview/scroll_bar_thumb.dart';
|
||||
|
||||
class DraggableScrollbar extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Color backgroundColor;
|
||||
final Color drawColor;
|
||||
final double heightScrollThumb;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final int totalCount;
|
||||
final int initialScrollIndex;
|
||||
final double bottomSafeArea;
|
||||
final int currentFirstIndex;
|
||||
final ValueChanged<double>? onChange;
|
||||
final String Function(int) labelTextBuilder;
|
||||
final bool isEnabled;
|
||||
|
||||
const DraggableScrollbar({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.backgroundColor = Colors.white,
|
||||
this.drawColor = Colors.grey,
|
||||
this.heightScrollThumb = 80.0,
|
||||
this.bottomSafeArea = 120,
|
||||
this.padding,
|
||||
this.totalCount = 1,
|
||||
this.initialScrollIndex = 0,
|
||||
this.currentFirstIndex = 0,
|
||||
required this.labelTextBuilder,
|
||||
this.onChange,
|
||||
this.isEnabled = true,
|
||||
});
|
||||
|
||||
@override
|
||||
DraggableScrollbarState createState() => DraggableScrollbarState();
|
||||
}
|
||||
|
||||
class DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
with TickerProviderStateMixin {
|
||||
static const thumbAnimationDuration = Duration(milliseconds: 1000);
|
||||
static const labelAnimationDuration = Duration(milliseconds: 1000);
|
||||
double thumbOffset = 0.0;
|
||||
bool isDragging = false;
|
||||
late int currentFirstIndex;
|
||||
|
||||
double get thumbMin => 0.0;
|
||||
|
||||
double get thumbMax =>
|
||||
context.size!.height - widget.heightScrollThumb - widget.bottomSafeArea;
|
||||
|
||||
late AnimationController _thumbAnimationController;
|
||||
Animation<double>? _thumbAnimation;
|
||||
late AnimationController _labelAnimationController;
|
||||
Animation<double>? _labelAnimation;
|
||||
Timer? _fadeoutTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
currentFirstIndex = widget.currentFirstIndex;
|
||||
|
||||
///Where will this be true on init?
|
||||
if (widget.initialScrollIndex > 0 && widget.totalCount > 1) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(
|
||||
() => thumbOffset = (widget.initialScrollIndex / widget.totalCount) *
|
||||
(thumbMax - thumbMin),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_thumbAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: thumbAnimationDuration,
|
||||
animationBehavior: AnimationBehavior.preserve,
|
||||
);
|
||||
|
||||
_thumbAnimation = CurvedAnimation(
|
||||
parent: _thumbAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
|
||||
_labelAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: labelAnimationDuration,
|
||||
animationBehavior: AnimationBehavior.preserve,
|
||||
);
|
||||
|
||||
_labelAnimation = CurvedAnimation(
|
||||
parent: _labelAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_thumbAnimationController.dispose();
|
||||
_labelAnimationController.dispose();
|
||||
_fadeoutTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
RepaintBoundary(child: widget.child),
|
||||
widget.isEnabled
|
||||
? RepaintBoundary(child: buildThumb())
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildThumb() => Padding(
|
||||
padding: widget.padding!,
|
||||
child: Container(
|
||||
alignment: Alignment.topRight,
|
||||
margin: EdgeInsets.only(top: thumbOffset),
|
||||
child: ScrollBarThumb(
|
||||
widget.backgroundColor,
|
||||
widget.drawColor,
|
||||
widget.heightScrollThumb,
|
||||
widget.labelTextBuilder.call(currentFirstIndex),
|
||||
_labelAnimation,
|
||||
_thumbAnimation,
|
||||
onDragStart,
|
||||
onDragUpdate,
|
||||
onDragEnd,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
void setPosition(double position, int currentFirstIndex) {
|
||||
setState(() {
|
||||
this.currentFirstIndex = currentFirstIndex;
|
||||
thumbOffset = position * (thumbMax - thumbMin);
|
||||
if (_thumbAnimationController.status != AnimationStatus.forward) {
|
||||
_thumbAnimationController.forward();
|
||||
}
|
||||
_fadeoutTimer?.cancel();
|
||||
_fadeoutTimer = Timer(thumbAnimationDuration, () {
|
||||
_thumbAnimationController.reverse();
|
||||
_labelAnimationController.reverse();
|
||||
_fadeoutTimer = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void onDragStart(DragStartDetails details) {
|
||||
setState(() {
|
||||
isDragging = true;
|
||||
_labelAnimationController.forward();
|
||||
_fadeoutTimer?.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
void onDragUpdate(DragUpdateDetails details) {
|
||||
setState(() {
|
||||
if (_thumbAnimationController.status != AnimationStatus.forward) {
|
||||
_thumbAnimationController.forward();
|
||||
}
|
||||
if (isDragging && details.delta.dy != 0) {
|
||||
thumbOffset += details.delta.dy;
|
||||
thumbOffset = thumbOffset.clamp(thumbMin, thumbMax);
|
||||
final double position = thumbOffset / (thumbMax - thumbMin);
|
||||
widget.onChange?.call(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void onDragEnd(DragEndDetails details) {
|
||||
_fadeoutTimer = Timer(thumbAnimationDuration, () {
|
||||
_thumbAnimationController.reverse();
|
||||
_labelAnimationController.reverse();
|
||||
_fadeoutTimer = null;
|
||||
});
|
||||
setState(() => isDragging = false);
|
||||
}
|
||||
|
||||
void keyHandler(KeyEvent value) {
|
||||
if (value.runtimeType == KeyDownEvent) {
|
||||
if (value.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||
onDragUpdate(
|
||||
DragUpdateDetails(
|
||||
globalPosition: Offset.zero,
|
||||
delta: const Offset(0, 2),
|
||||
),
|
||||
);
|
||||
} else if (value.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||
onDragUpdate(
|
||||
DragUpdateDetails(
|
||||
globalPosition: Offset.zero,
|
||||
delta: const Offset(0, -2),
|
||||
),
|
||||
);
|
||||
} else if (value.logicalKey == LogicalKeyboardKey.pageDown) {
|
||||
onDragUpdate(
|
||||
DragUpdateDetails(
|
||||
globalPosition: Offset.zero,
|
||||
delta: const Offset(0, 25),
|
||||
),
|
||||
);
|
||||
} else if (value.logicalKey == LogicalKeyboardKey.pageUp) {
|
||||
onDragUpdate(
|
||||
DragUpdateDetails(
|
||||
globalPosition: Offset.zero,
|
||||
delta: const Offset(0, -25),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
import 'dart:math' show max;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/ui/huge_listview/draggable_scrollbar.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
typedef HugeListViewItemBuilder<T> = Widget Function(
|
||||
BuildContext context,
|
||||
int index,
|
||||
);
|
||||
typedef HugeListViewErrorBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
dynamic error,
|
||||
);
|
||||
|
||||
class HugeListView<T> extends StatefulWidget {
|
||||
/// A [ScrollablePositionedList] controller for jumping or scrolling to an item.
|
||||
final ItemScrollController? controller;
|
||||
|
||||
/// Index of an item to initially align within the viewport.
|
||||
final int startIndex;
|
||||
|
||||
/// Total number of items in the list.
|
||||
final int totalCount;
|
||||
|
||||
/// Called to build the thumb. One of [DraggableScrollbarThumbs.RoundedRectThumb], [DraggableScrollbarThumbs.ArrowThumb]
|
||||
/// or [DraggableScrollbarThumbs.SemicircleThumb], or build your own.
|
||||
final String Function(int) labelTextBuilder;
|
||||
|
||||
/// Background color of scroll thumb, defaults to white.
|
||||
final Color thumbBackgroundColor;
|
||||
|
||||
/// Drawing color of scroll thumb, defaults to gray.
|
||||
final Color thumbDrawColor;
|
||||
|
||||
/// Height of scroll thumb, defaults to 48.
|
||||
final double thumbHeight;
|
||||
|
||||
/// Height of bottomSafeArea so that scroll thumb does not become hidden
|
||||
/// or un-clickable due to footer elements. Default value is 120
|
||||
final double bottomSafeArea;
|
||||
|
||||
/// Called to build an individual item with the specified [index].
|
||||
final HugeListViewItemBuilder<T> itemBuilder;
|
||||
|
||||
/// Called to build a progress widget while the whole list is initialized.
|
||||
final WidgetBuilder? waitBuilder;
|
||||
|
||||
/// Called to build a widget when the list is empty.
|
||||
final WidgetBuilder? emptyResultBuilder;
|
||||
|
||||
/// Called to build a widget when there is an error.
|
||||
final HugeListViewErrorBuilder? errorBuilder;
|
||||
|
||||
/// Event to call with the index of the topmost visible item in the viewport while scrolling.
|
||||
/// Can be used to display the current letter of an alphabetically sorted list, for instance.
|
||||
final ValueChanged<int>? firstShown;
|
||||
|
||||
final bool isDraggableScrollbarEnabled;
|
||||
|
||||
final EdgeInsetsGeometry? thumbPadding;
|
||||
|
||||
final bool disableScroll;
|
||||
|
||||
final bool isScrollablePositionedList;
|
||||
|
||||
const HugeListView({
|
||||
super.key,
|
||||
this.controller,
|
||||
required this.startIndex,
|
||||
required this.totalCount,
|
||||
required this.labelTextBuilder,
|
||||
required this.itemBuilder,
|
||||
this.waitBuilder,
|
||||
this.emptyResultBuilder,
|
||||
this.errorBuilder,
|
||||
this.firstShown,
|
||||
this.thumbBackgroundColor = Colors.red, // Colors.white,
|
||||
this.thumbDrawColor = Colors.yellow, //Colors.grey,
|
||||
this.thumbHeight = 48.0,
|
||||
this.bottomSafeArea = 120.0,
|
||||
this.isDraggableScrollbarEnabled = true,
|
||||
this.thumbPadding,
|
||||
this.disableScroll = false,
|
||||
this.isScrollablePositionedList = true,
|
||||
});
|
||||
|
||||
@override
|
||||
HugeListViewState<T> createState() => HugeListViewState<T>();
|
||||
}
|
||||
|
||||
class HugeListViewState<T> extends State<HugeListView<T>> {
|
||||
final scrollKey = GlobalKey<DraggableScrollbarState>();
|
||||
final listener = ItemPositionsListener.create();
|
||||
int lastIndexJump = -1;
|
||||
dynamic error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.isScrollablePositionedList
|
||||
? listener.itemPositions.addListener(_sendScroll)
|
||||
: null;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
listener.itemPositions.removeListener(_sendScroll);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _sendScroll() {
|
||||
final int current = _currentFirst();
|
||||
widget.firstShown?.call(current);
|
||||
scrollKey.currentState?.setPosition(current / widget.totalCount, current);
|
||||
}
|
||||
|
||||
int _currentFirst() {
|
||||
try {
|
||||
return listener.itemPositions.value.first.index;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (error != null && widget.errorBuilder != null) {
|
||||
return widget.errorBuilder!(context, error);
|
||||
}
|
||||
if (widget.totalCount == -1 && widget.waitBuilder != null) {
|
||||
return widget.waitBuilder!(context);
|
||||
}
|
||||
if (widget.totalCount == 0 && widget.emptyResultBuilder != null) {
|
||||
return widget.emptyResultBuilder!(context);
|
||||
}
|
||||
|
||||
return widget.isScrollablePositionedList
|
||||
? DraggableScrollbar(
|
||||
key: scrollKey,
|
||||
totalCount: widget.totalCount,
|
||||
initialScrollIndex: widget.startIndex,
|
||||
onChange: (position) {
|
||||
final int currentIndex = _currentFirst();
|
||||
final int floorIndex = (position * widget.totalCount).floor();
|
||||
final int cielIndex = (position * widget.totalCount).ceil();
|
||||
int nextIndexToJump;
|
||||
if (floorIndex != currentIndex && floorIndex > currentIndex) {
|
||||
nextIndexToJump = floorIndex;
|
||||
} else if (cielIndex != currentIndex &&
|
||||
cielIndex < currentIndex) {
|
||||
nextIndexToJump = floorIndex;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (lastIndexJump != nextIndexToJump) {
|
||||
lastIndexJump = nextIndexToJump;
|
||||
widget.controller?.jumpTo(index: nextIndexToJump);
|
||||
}
|
||||
},
|
||||
labelTextBuilder: widget.labelTextBuilder,
|
||||
backgroundColor: widget.thumbBackgroundColor,
|
||||
drawColor: widget.thumbDrawColor,
|
||||
heightScrollThumb: widget.thumbHeight,
|
||||
bottomSafeArea: widget.bottomSafeArea,
|
||||
currentFirstIndex: _currentFirst(),
|
||||
isEnabled: widget.isDraggableScrollbarEnabled,
|
||||
padding: widget.thumbPadding,
|
||||
child: ScrollablePositionedList.builder(
|
||||
physics: widget.disableScroll
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: const BouncingScrollPhysics(),
|
||||
itemScrollController: widget.controller,
|
||||
itemPositionsListener: listener,
|
||||
initialScrollIndex: widget.startIndex,
|
||||
itemCount: max(widget.totalCount, 0),
|
||||
itemBuilder: (context, index) {
|
||||
return ExcludeSemantics(
|
||||
child: widget.itemBuilder(context, index),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: max(widget.totalCount, 0),
|
||||
itemBuilder: (context, index) {
|
||||
return ExcludeSemantics(
|
||||
child: widget.itemBuilder(context, index),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Jump to the [position] in the list. [position] is between 0.0 (first item) and 1.0 (last item), practically currentIndex / totalCount.
|
||||
/// To jump to a specific item, use [ItemScrollController.jumpTo] or [ItemScrollController.scrollTo].
|
||||
void setPosition(double position) {
|
||||
scrollKey.currentState?.setPosition(position, _currentFirst());
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ScrollBarThumb extends StatelessWidget {
|
||||
final Color backgroundColor;
|
||||
final Color drawColor;
|
||||
final double height;
|
||||
final String title;
|
||||
final Animation? labelAnimation;
|
||||
final Animation? thumbAnimation;
|
||||
final Function(DragStartDetails details) onDragStart;
|
||||
final Function(DragUpdateDetails details) onDragUpdate;
|
||||
final Function(DragEndDetails details) onDragEnd;
|
||||
|
||||
const ScrollBarThumb(
|
||||
this.backgroundColor,
|
||||
this.drawColor,
|
||||
this.height,
|
||||
this.title,
|
||||
this.labelAnimation,
|
||||
this.thumbAnimation,
|
||||
this.onDragStart,
|
||||
this.onDragUpdate,
|
||||
this.onDragEnd, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
IgnorePointer(
|
||||
child: FadeTransition(
|
||||
opacity: labelAnimation as Animation<double>,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: backgroundColor,
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: drawColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
backgroundColor: Colors.transparent,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
),
|
||||
GestureDetector(
|
||||
onVerticalDragStart: onDragStart,
|
||||
onVerticalDragUpdate: onDragUpdate,
|
||||
onVerticalDragEnd: onDragEnd,
|
||||
child: SlideFadeTransition(
|
||||
animation: thumbAnimation as Animation<double>?,
|
||||
child: CustomPaint(
|
||||
foregroundPainter: _ArrowCustomPainter(drawColor),
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(height),
|
||||
bottomLeft: Radius.circular(height),
|
||||
topRight: const Radius.circular(4.0),
|
||||
bottomRight: const Radius.circular(4.0),
|
||||
),
|
||||
child: Container(
|
||||
constraints: BoxConstraints.tight(Size(height * 0.6, height)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ArrowCustomPainter extends CustomPainter {
|
||||
final Color drawColor;
|
||||
|
||||
_ArrowCustomPainter(this.drawColor);
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..isAntiAlias = true
|
||||
..style = PaintingStyle.fill
|
||||
..color = drawColor;
|
||||
const width = 10.0;
|
||||
const height = 8.0;
|
||||
final baseX = size.width / 2;
|
||||
final baseY = size.height / 2;
|
||||
|
||||
canvas.drawPath(
|
||||
trianglePath(Offset(baseX - 2.0, baseY - 2.0), width, height, true),
|
||||
paint,
|
||||
);
|
||||
canvas.drawPath(
|
||||
trianglePath(Offset(baseX - 2.0, baseY + 2.0), width, height, false),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
static Path trianglePath(
|
||||
Offset offset,
|
||||
double width,
|
||||
double height,
|
||||
bool isUp,
|
||||
) {
|
||||
return Path()
|
||||
..moveTo(offset.dx, offset.dy)
|
||||
..lineTo(offset.dx + width, offset.dy)
|
||||
..lineTo(
|
||||
offset.dx + (width / 2),
|
||||
isUp ? offset.dy - height : offset.dy + height,
|
||||
)
|
||||
..close();
|
||||
}
|
||||
}
|
||||
|
||||
class SlideFadeTransition extends StatelessWidget {
|
||||
final Animation<double>? animation;
|
||||
final Widget child;
|
||||
|
||||
const SlideFadeTransition({
|
||||
super.key,
|
||||
required this.animation,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation!,
|
||||
builder: (context, child) =>
|
||||
animation!.value == 0.0 ? const SizedBox.shrink() : child!,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0.3, 0.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
).animate(animation!),
|
||||
child: FadeTransition(
|
||||
opacity: animation!,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import "package:flutter/widgets.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/models/selected_files.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/gallery_file_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
|
||||
class GalleryGridViewWidget extends StatelessWidget {
|
||||
final List<EnteFile> filesInGroup;
|
||||
final int photoGridSize;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final bool limitSelectionToOne;
|
||||
final String tag;
|
||||
final int? currentUserID;
|
||||
final GalleryLoader asyncLoader;
|
||||
const GalleryGridViewWidget({
|
||||
required this.filesInGroup,
|
||||
required this.photoGridSize,
|
||||
this.selectedFiles,
|
||||
required this.limitSelectionToOne,
|
||||
required this.tag,
|
||||
super.key,
|
||||
this.currentUserID,
|
||||
required this.asyncLoader,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
// to disable GridView's scrolling
|
||||
itemBuilder: (context, index) {
|
||||
return GalleryFileWidget(
|
||||
file: filesInGroup[index],
|
||||
selectedFiles: selectedFiles,
|
||||
limitSelectionToOne: limitSelectionToOne,
|
||||
tag: tag,
|
||||
photoGridSize: photoGridSize,
|
||||
currentUserID: currentUserID,
|
||||
);
|
||||
},
|
||||
itemCount: filesInGroup.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisSpacing: 2,
|
||||
mainAxisSpacing: 2,
|
||||
crossAxisCount: photoGridSize,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: (galleryGridSpacing / 2)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/clear_selections_event.dart";
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/models/selected_files.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/grid/non_recyclable_grid_view_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/grid/recyclable_grid_view_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
|
||||
class LazyGridView extends StatefulWidget {
|
||||
final String tag;
|
||||
final List<EnteFile> filesInGroup;
|
||||
final GalleryLoader asyncLoader;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final bool shouldRender;
|
||||
final bool shouldRecycle;
|
||||
final int? photoGridSize;
|
||||
final bool limitSelectionToOne;
|
||||
|
||||
const LazyGridView(
|
||||
this.tag,
|
||||
this.filesInGroup,
|
||||
this.asyncLoader,
|
||||
this.selectedFiles,
|
||||
this.shouldRender,
|
||||
this.shouldRecycle,
|
||||
this.photoGridSize, {
|
||||
this.limitSelectionToOne = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LazyGridView> createState() => _LazyGridViewState();
|
||||
}
|
||||
|
||||
class _LazyGridViewState extends State<LazyGridView> {
|
||||
late bool _shouldRender;
|
||||
int? _currentUserID;
|
||||
late StreamSubscription<ClearSelectionsEvent> _clearSelectionsEvent;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_shouldRender = widget.shouldRender;
|
||||
_currentUserID = Configuration.instance.getUserID();
|
||||
widget.selectedFiles?.addListener(_selectedFilesListener);
|
||||
_clearSelectionsEvent =
|
||||
Bus.instance.on<ClearSelectionsEvent>().listen((event) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.selectedFiles?.removeListener(_selectedFilesListener);
|
||||
_clearSelectionsEvent.cancel();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(LazyGridView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!listEquals(widget.filesInGroup, oldWidget.filesInGroup)) {
|
||||
_shouldRender = widget.shouldRender;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.shouldRecycle) {
|
||||
return RecyclableGridViewWidget(
|
||||
shouldRender: _shouldRender,
|
||||
filesInGroup: widget.filesInGroup,
|
||||
photoGridSize: widget.photoGridSize!,
|
||||
limitSelectionToOne: widget.limitSelectionToOne,
|
||||
tag: widget.tag,
|
||||
asyncLoader: widget.asyncLoader,
|
||||
selectedFiles: widget.selectedFiles,
|
||||
currentUserID: _currentUserID,
|
||||
);
|
||||
} else {
|
||||
return NonRecyclableGridViewWidget(
|
||||
shouldRender: _shouldRender,
|
||||
filesInGroup: widget.filesInGroup,
|
||||
photoGridSize: widget.photoGridSize!,
|
||||
limitSelectionToOne: widget.limitSelectionToOne,
|
||||
tag: widget.tag,
|
||||
asyncLoader: widget.asyncLoader,
|
||||
selectedFiles: widget.selectedFiles,
|
||||
currentUserID: _currentUserID,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _selectedFilesListener() {
|
||||
bool shouldRefresh = false;
|
||||
for (final file in widget.filesInGroup) {
|
||||
if (widget.selectedFiles!.isPartOfLastSelected(file)) {
|
||||
shouldRefresh = true;
|
||||
}
|
||||
}
|
||||
if (shouldRefresh && mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/models/selected_files.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/grid/gallery_grid_view_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
import "package:visibility_detector/visibility_detector.dart";
|
||||
|
||||
class NonRecyclableGridViewWidget extends StatefulWidget {
|
||||
final bool shouldRender;
|
||||
final List<EnteFile> filesInGroup;
|
||||
final int photoGridSize;
|
||||
final bool limitSelectionToOne;
|
||||
final String tag;
|
||||
final GalleryLoader asyncLoader;
|
||||
final int? currentUserID;
|
||||
final SelectedFiles? selectedFiles;
|
||||
const NonRecyclableGridViewWidget({
|
||||
required this.shouldRender,
|
||||
required this.filesInGroup,
|
||||
required this.photoGridSize,
|
||||
required this.limitSelectionToOne,
|
||||
required this.tag,
|
||||
required this.asyncLoader,
|
||||
this.currentUserID,
|
||||
this.selectedFiles,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<NonRecyclableGridViewWidget> createState() =>
|
||||
_NonRecyclableGridViewWidgetState();
|
||||
}
|
||||
|
||||
class _NonRecyclableGridViewWidgetState
|
||||
extends State<NonRecyclableGridViewWidget> {
|
||||
late bool _shouldRender;
|
||||
@override
|
||||
void initState() {
|
||||
_shouldRender = widget.shouldRender;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_shouldRender) {
|
||||
return VisibilityDetector(
|
||||
key: Key("gallery" + widget.filesInGroup.first.tag),
|
||||
onVisibilityChanged: (visibility) {
|
||||
if (mounted && visibility.visibleFraction > 0 && !_shouldRender) {
|
||||
setState(() {
|
||||
_shouldRender = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: PlaceHolderGridViewWidget(
|
||||
widget.filesInGroup.length,
|
||||
widget.photoGridSize,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return GalleryGridViewWidget(
|
||||
filesInGroup: widget.filesInGroup,
|
||||
photoGridSize: widget.photoGridSize,
|
||||
limitSelectionToOne: widget.limitSelectionToOne,
|
||||
tag: widget.tag,
|
||||
asyncLoader: widget.asyncLoader,
|
||||
selectedFiles: widget.selectedFiles,
|
||||
currentUserID: widget.currentUserID,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import "dart:math";
|
||||
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
|
||||
class PlaceHolderGridViewWidget extends StatelessWidget {
|
||||
const PlaceHolderGridViewWidget(
|
||||
this.count,
|
||||
this.columns, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
final int count, columns;
|
||||
|
||||
static Widget? _placeHolderCache;
|
||||
static final _gridViewCache = <String, GridView>{};
|
||||
static const crossAxisSpacing = 2.0; // as per your code
|
||||
static const mainAxisSpacing = 2.0; // as per your code
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color faintColor = getEnteColorScheme(context).fillFaint;
|
||||
int limitCount = count;
|
||||
if (kDebugMode) {
|
||||
limitCount = min(count, columns * 5);
|
||||
}
|
||||
|
||||
final key = '$limitCount:$columns';
|
||||
if (!_gridViewCache.containsKey(key)) {
|
||||
_gridViewCache[key] = GridView.builder(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return PlaceHolderGridViewWidget._placeHolderCache ??=
|
||||
Container(color: faintColor);
|
||||
},
|
||||
itemCount: limitCount,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
crossAxisSpacing: crossAxisSpacing,
|
||||
mainAxisSpacing: mainAxisSpacing,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _gridViewCache[key]!;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/models/selected_files.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/grid/gallery_grid_view_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
import "package:visibility_detector/visibility_detector.dart";
|
||||
|
||||
class RecyclableGridViewWidget extends StatefulWidget {
|
||||
final bool shouldRender;
|
||||
final List<EnteFile> filesInGroup;
|
||||
final int photoGridSize;
|
||||
final bool limitSelectionToOne;
|
||||
final String tag;
|
||||
final GalleryLoader asyncLoader;
|
||||
final int? currentUserID;
|
||||
final SelectedFiles? selectedFiles;
|
||||
const RecyclableGridViewWidget({
|
||||
required this.shouldRender,
|
||||
required this.filesInGroup,
|
||||
required this.photoGridSize,
|
||||
required this.limitSelectionToOne,
|
||||
required this.tag,
|
||||
required this.asyncLoader,
|
||||
this.currentUserID,
|
||||
this.selectedFiles,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RecyclableGridViewWidget> createState() =>
|
||||
_RecyclableGridViewWidgetState();
|
||||
}
|
||||
|
||||
class _RecyclableGridViewWidgetState extends State<RecyclableGridViewWidget> {
|
||||
late bool _shouldRender;
|
||||
@override
|
||||
void initState() {
|
||||
_shouldRender = widget.shouldRender;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return VisibilityDetector(
|
||||
key: Key("gallery" + widget.filesInGroup.first.tag),
|
||||
onVisibilityChanged: (visibility) {
|
||||
final shouldRender = visibility.visibleFraction > 0;
|
||||
if (mounted && shouldRender != _shouldRender) {
|
||||
setState(() {
|
||||
_shouldRender = shouldRender;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: _shouldRender
|
||||
? GalleryGridViewWidget(
|
||||
filesInGroup: widget.filesInGroup,
|
||||
photoGridSize: widget.photoGridSize,
|
||||
limitSelectionToOne: widget.limitSelectionToOne,
|
||||
tag: widget.tag,
|
||||
asyncLoader: widget.asyncLoader,
|
||||
selectedFiles: widget.selectedFiles,
|
||||
currentUserID: widget.currentUserID,
|
||||
)
|
||||
: PlaceHolderGridViewWidget(
|
||||
widget.filesInGroup.length,
|
||||
widget.photoGridSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/constants.dart';
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
import "package:photos/ui/viewer/gallery/component/grid/lazy_grid_view.dart";
|
||||
import 'package:photos/ui/viewer/gallery/gallery.dart';
|
||||
|
||||
class GroupGallery extends StatelessWidget {
|
||||
final int photoGridSize;
|
||||
final List<EnteFile> files;
|
||||
final String tag;
|
||||
final GalleryLoader asyncLoader;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final bool limitSelectionToOne;
|
||||
|
||||
const GroupGallery({
|
||||
required this.photoGridSize,
|
||||
required this.files,
|
||||
required this.tag,
|
||||
required this.asyncLoader,
|
||||
required this.selectedFiles,
|
||||
required this.limitSelectionToOne,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const kRecycleLimit = 400;
|
||||
final List<Widget> childGalleries = [];
|
||||
final subGalleryItemLimit = photoGridSize * subGalleryMultiplier;
|
||||
|
||||
for (int index = 0; index < files.length; index += subGalleryItemLimit) {
|
||||
childGalleries.add(
|
||||
LazyGridView(
|
||||
tag,
|
||||
files.sublist(
|
||||
index,
|
||||
min(index + subGalleryItemLimit, files.length),
|
||||
),
|
||||
asyncLoader,
|
||||
selectedFiles,
|
||||
index == 0,
|
||||
files.length > kRecycleLimit,
|
||||
photoGridSize,
|
||||
limitSelectionToOne: limitSelectionToOne,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: childGalleries,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/constants.dart';
|
||||
import 'package:photos/events/files_updated_event.dart';
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import 'package:photos/models/selected_files.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import "package:photos/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/group/group_gallery.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/group/group_header_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/group/type.dart";
|
||||
import 'package:photos/ui/viewer/gallery/gallery.dart';
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
|
||||
|
||||
class LazyGroupGallery extends StatefulWidget {
|
||||
final List<EnteFile> files;
|
||||
final int index;
|
||||
final Stream<FilesUpdatedEvent>? reloadEvent;
|
||||
final Set<EventType> removalEventTypes;
|
||||
final GalleryLoader asyncLoader;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final String tag;
|
||||
final String? logTag;
|
||||
final Stream<int> currentIndexStream;
|
||||
final int photoGridSize;
|
||||
final bool enableFileGrouping;
|
||||
final bool limitSelectionToOne;
|
||||
final bool showSelectAllByDefault;
|
||||
const LazyGroupGallery(
|
||||
this.files,
|
||||
this.index,
|
||||
this.reloadEvent,
|
||||
this.removalEventTypes,
|
||||
this.asyncLoader,
|
||||
this.selectedFiles,
|
||||
this.tag,
|
||||
this.currentIndexStream,
|
||||
this.enableFileGrouping,
|
||||
this.showSelectAllByDefault, {
|
||||
this.logTag = "",
|
||||
this.photoGridSize = photoGridSizeDefault,
|
||||
this.limitSelectionToOne = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<LazyGroupGallery> createState() => _LazyGroupGalleryState();
|
||||
}
|
||||
|
||||
class _LazyGroupGalleryState extends State<LazyGroupGallery> {
|
||||
static const numberOfGroupsToRenderBeforeAndAfter = 8;
|
||||
late final ValueNotifier<bool> _showSelectAllButtonNotifier;
|
||||
late final ValueNotifier<bool> _areAllFromGroupSelectedNotifier;
|
||||
|
||||
late Logger _logger;
|
||||
|
||||
late List<EnteFile> _filesInGroup;
|
||||
late StreamSubscription<FilesUpdatedEvent>? _reloadEventSubscription;
|
||||
late StreamSubscription<int> _currentIndexSubscription;
|
||||
bool? _shouldRender;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_areAllFromGroupSelectedNotifier =
|
||||
ValueNotifier(_areAllFromGroupSelected());
|
||||
|
||||
widget.selectedFiles?.addListener(_selectedFilesListener);
|
||||
_showSelectAllButtonNotifier = ValueNotifier(widget.showSelectAllByDefault);
|
||||
_init();
|
||||
}
|
||||
|
||||
void _init() {
|
||||
_logger = Logger("LazyLoading_${widget.logTag}");
|
||||
_shouldRender = true;
|
||||
_filesInGroup = widget.files;
|
||||
_areAllFromGroupSelectedNotifier.value = _areAllFromGroupSelected();
|
||||
_reloadEventSubscription = widget.reloadEvent?.listen((e) => _onReload(e));
|
||||
|
||||
_currentIndexSubscription =
|
||||
widget.currentIndexStream.listen((currentIndex) {
|
||||
final bool shouldRender = (currentIndex - widget.index).abs() <
|
||||
numberOfGroupsToRenderBeforeAndAfter;
|
||||
if (mounted && shouldRender != _shouldRender) {
|
||||
setState(() {
|
||||
_shouldRender = shouldRender;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _areAllFromGroupSelected() {
|
||||
if (widget.selectedFiles != null &&
|
||||
widget.selectedFiles!.files.length >= widget.files.length) {
|
||||
return widget.selectedFiles!.files.containsAll(widget.files);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future _onReload(FilesUpdatedEvent event) async {
|
||||
if (_filesInGroup.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final galleryState = context.findAncestorStateOfType<GalleryState>();
|
||||
final groupType = GalleryContextState.of(context)!.type;
|
||||
|
||||
// iterate over files and check if any of the belongs to this group
|
||||
final anyCandidateForGroup = groupType.areModifiedFilesPartOfGroup(
|
||||
event.updatedFiles,
|
||||
_filesInGroup[0],
|
||||
lastFile: _filesInGroup.last,
|
||||
);
|
||||
if (anyCandidateForGroup) {
|
||||
late int startRange, endRange;
|
||||
(startRange, endRange) = groupType.getGroupRange(_filesInGroup[0]);
|
||||
if (kDebugMode) {
|
||||
_logger.info(
|
||||
" files were updated due to ${event.reason} on type ${groupType.name} from ${DateTime.fromMicrosecondsSinceEpoch(startRange).toIso8601String()}"
|
||||
" to ${DateTime.fromMicrosecondsSinceEpoch(endRange).toIso8601String()}",
|
||||
);
|
||||
}
|
||||
if (event.type == EventType.addedOrUpdated ||
|
||||
widget.removalEventTypes.contains(event.type)) {
|
||||
// We are reloading the whole group
|
||||
final result = await widget.asyncLoader(
|
||||
startRange,
|
||||
endRange,
|
||||
asc: GalleryContextState.of(context)!.sortOrderAsc,
|
||||
);
|
||||
|
||||
//When items are updated in a LazyGroupGallery, only it rebuilds with the
|
||||
//new state of _files which is a state variable in it's state object.
|
||||
//widget.files is not updated. Calling setState from it's ancestor
|
||||
//state object 'Gallery' creates a new LazyLoadingGallery widget with
|
||||
//updated widget.files
|
||||
|
||||
//If widget.files is kept in it's old state, the old state will come
|
||||
//up when scrolled down and back up to the group.
|
||||
|
||||
//[galleryState] will never be null except when LazyLoadingGallery is
|
||||
//used without Gallery as an ancestor.
|
||||
|
||||
if (galleryState?.mounted ?? false) {
|
||||
galleryState!.setState(() {});
|
||||
_filesInGroup = result.files;
|
||||
}
|
||||
} else if (kDebugMode) {
|
||||
debugPrint("Unexpected event ${event.type.name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_reloadEventSubscription?.cancel();
|
||||
_currentIndexSubscription.cancel();
|
||||
_areAllFromGroupSelectedNotifier.dispose();
|
||||
widget.selectedFiles?.removeListener(_selectedFilesListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(LazyGroupGallery oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!listEquals(_filesInGroup, widget.files)) {
|
||||
_reloadEventSubscription?.cancel();
|
||||
_init();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_filesInGroup.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final groupType = GalleryContextState.of(context)!.type;
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (widget.enableFileGrouping)
|
||||
GroupHeaderWidget(
|
||||
title: groupType.getTitle(
|
||||
context,
|
||||
_filesInGroup[0],
|
||||
lastFile: _filesInGroup.last,
|
||||
),
|
||||
gridSize: widget.photoGridSize,
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
widget.limitSelectionToOne
|
||||
? const SizedBox.shrink()
|
||||
: ValueListenableBuilder(
|
||||
valueListenable: _showSelectAllButtonNotifier,
|
||||
builder: (context, dynamic value, _) {
|
||||
return !value
|
||||
? const SizedBox.shrink()
|
||||
: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: SizedBox(
|
||||
width: 48,
|
||||
height: 44,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable:
|
||||
_areAllFromGroupSelectedNotifier,
|
||||
builder: (context, dynamic value, _) {
|
||||
return value
|
||||
? const Icon(
|
||||
Icons.check_circle,
|
||||
size: 18,
|
||||
)
|
||||
: Icon(
|
||||
Icons.check_circle_outlined,
|
||||
color: getEnteColorScheme(context)
|
||||
.strokeMuted,
|
||||
size: 18,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
widget.selectedFiles?.toggleGroupSelection(
|
||||
_filesInGroup.toSet(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
_shouldRender!
|
||||
? GroupGallery(
|
||||
photoGridSize: widget.photoGridSize,
|
||||
files: _filesInGroup,
|
||||
tag: widget.tag,
|
||||
asyncLoader: widget.asyncLoader,
|
||||
selectedFiles: widget.selectedFiles,
|
||||
limitSelectionToOne: widget.limitSelectionToOne,
|
||||
)
|
||||
// todo: perf eval should we have separate PlaceHolder for Groups
|
||||
// instead of creating a large cached view
|
||||
: PlaceHolderGridViewWidget(
|
||||
_filesInGroup.length,
|
||||
widget.photoGridSize,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _selectedFilesListener() {
|
||||
if (widget.selectedFiles == null) return;
|
||||
_areAllFromGroupSelectedNotifier.value =
|
||||
widget.selectedFiles!.files.containsAll(_filesInGroup.toSet());
|
||||
|
||||
//Can remove this if we decide to show select all by default for all galleries
|
||||
if (widget.selectedFiles!.files.isEmpty && !widget.showSelectAllByDefault) {
|
||||
_showSelectAllButtonNotifier.value = false;
|
||||
} else {
|
||||
_showSelectAllButtonNotifier.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/ente_theme_data.dart";
|
||||
import "package:photos/events/files_updated_event.dart";
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/models/selected_files.dart";
|
||||
import "package:photos/service_locator.dart";
|
||||
import "package:photos/ui/common/loading_widget.dart";
|
||||
import "package:photos/ui/huge_listview/huge_listview.dart";
|
||||
import 'package:photos/ui/viewer/gallery/component/group/lazy_group_gallery.dart';
|
||||
import "package:photos/ui/viewer/gallery/component/group/type.dart";
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
|
||||
import "package:photos/utils/standalone/data.dart";
|
||||
import "package:scrollable_positioned_list/scrollable_positioned_list.dart";
|
||||
|
||||
/*
|
||||
MultipleGroupsGalleryView is a widget that displays a list of grouped/collated
|
||||
files when grouping is enabled.
|
||||
For each group, it displays a header and use LazyGroupGallery to display a
|
||||
particular group of files.
|
||||
If a group has more than 400 files, LazyGroupGallery internally divides the
|
||||
group into multiple grid views during rendering.
|
||||
*/
|
||||
class MultipleGroupsGalleryView extends StatelessWidget {
|
||||
final ItemScrollController itemScroller;
|
||||
final List<List<EnteFile>> groupedFiles;
|
||||
final bool disableScroll;
|
||||
final Widget? header;
|
||||
final Widget? footer;
|
||||
final Widget emptyState;
|
||||
final GalleryLoader asyncLoader;
|
||||
final Stream<FilesUpdatedEvent>? reloadEvent;
|
||||
final Set<EventType> removalEventTypes;
|
||||
final String tagPrefix;
|
||||
final double scrollBottomSafeArea;
|
||||
final bool limitSelectionToOne;
|
||||
final SelectedFiles? selectedFiles;
|
||||
final bool enableFileGrouping;
|
||||
final String logTag;
|
||||
final Logger logger;
|
||||
final bool showSelectAllByDefault;
|
||||
final bool isScrollablePositionedList;
|
||||
|
||||
const MultipleGroupsGalleryView({
|
||||
required this.itemScroller,
|
||||
required this.groupedFiles,
|
||||
required this.disableScroll,
|
||||
this.header,
|
||||
this.footer,
|
||||
required this.emptyState,
|
||||
required this.asyncLoader,
|
||||
this.reloadEvent,
|
||||
required this.removalEventTypes,
|
||||
required this.tagPrefix,
|
||||
required this.scrollBottomSafeArea,
|
||||
required this.limitSelectionToOne,
|
||||
this.selectedFiles,
|
||||
required this.enableFileGrouping,
|
||||
required this.logTag,
|
||||
required this.logger,
|
||||
required this.showSelectAllByDefault,
|
||||
required this.isScrollablePositionedList,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gType = GalleryContextState.of(context)!.type;
|
||||
return HugeListView<List<EnteFile>>(
|
||||
controller: itemScroller,
|
||||
startIndex: 0,
|
||||
totalCount: groupedFiles.length,
|
||||
isDraggableScrollbarEnabled: groupedFiles.length > 10,
|
||||
disableScroll: disableScroll,
|
||||
isScrollablePositionedList: isScrollablePositionedList,
|
||||
waitBuilder: (_) {
|
||||
return const EnteLoadingWidget();
|
||||
},
|
||||
emptyResultBuilder: (_) {
|
||||
final List<Widget> children = [];
|
||||
if (header != null) {
|
||||
children.add(header!);
|
||||
}
|
||||
children.add(
|
||||
Expanded(
|
||||
child: emptyState,
|
||||
),
|
||||
);
|
||||
if (footer != null) {
|
||||
children.add(footer!);
|
||||
}
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
Widget gallery;
|
||||
gallery = LazyGroupGallery(
|
||||
groupedFiles[index],
|
||||
index,
|
||||
reloadEvent,
|
||||
removalEventTypes,
|
||||
asyncLoader,
|
||||
selectedFiles,
|
||||
tagPrefix,
|
||||
Bus.instance
|
||||
.on<GalleryIndexUpdatedEvent>()
|
||||
.where((event) => event.tag == tagPrefix)
|
||||
.map((event) => event.index),
|
||||
enableFileGrouping,
|
||||
showSelectAllByDefault,
|
||||
logTag: logTag,
|
||||
photoGridSize: localSettings.getPhotoGridSize(),
|
||||
limitSelectionToOne: limitSelectionToOne,
|
||||
);
|
||||
if (header != null && index == 0) {
|
||||
gallery = Column(children: [header!, gallery]);
|
||||
}
|
||||
if (footer != null && index == groupedFiles.length - 1) {
|
||||
gallery = Column(children: [gallery, footer!]);
|
||||
}
|
||||
return gallery;
|
||||
},
|
||||
labelTextBuilder: (int index) {
|
||||
try {
|
||||
final EnteFile file = groupedFiles[index][0];
|
||||
if (gType == GroupType.size) {
|
||||
return file.fileSize != null
|
||||
? convertBytesToReadableFormat(file.fileSize!)
|
||||
: "";
|
||||
}
|
||||
|
||||
return DateFormat.yMMM(Localizations.localeOf(context).languageCode)
|
||||
.format(
|
||||
DateTime.fromMicrosecondsSinceEpoch(
|
||||
file.creationTime!,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
logger.severe("label text builder failed", e);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
thumbBackgroundColor:
|
||||
Theme.of(context).colorScheme.galleryThumbBackgroundColor,
|
||||
thumbDrawColor: Theme.of(context).colorScheme.galleryThumbDrawColor,
|
||||
thumbPadding: header != null
|
||||
? const EdgeInsets.only(top: 60)
|
||||
: const EdgeInsets.all(0),
|
||||
bottomSafeArea: scrollBottomSafeArea,
|
||||
firstShown: (int firstIndex) {
|
||||
Bus.instance.fire(GalleryIndexUpdatedEvent(tagPrefix, firstIndex));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -555,29 +555,6 @@ class GalleryState extends State<Gallery> {
|
||||
sortOrderAsc: _sortOrderAsc,
|
||||
inSelectionMode: widget.inSelectionMode,
|
||||
type: _groupType,
|
||||
// Replace this with the new gallery and use `_allGalleryFiles`
|
||||
// child: MultipleGroupsGalleryView(
|
||||
// groupedFiles: currentGroupedFiles,
|
||||
// disableScroll: widget.disableScroll,
|
||||
// emptyState: widget.emptyState,
|
||||
// asyncLoader: widget.asyncLoader,
|
||||
// removalEventTypes: widget.removalEventTypes,
|
||||
// tagPrefix: widget.tagPrefix,
|
||||
// scrollBottomSafeArea: widget.scrollBottomSafeArea,
|
||||
// limitSelectionToOne: widget.limitSelectionToOne,
|
||||
// enableFileGrouping:
|
||||
// widget.enableFileGrouping && widget.groupType.showGroupHeader(),
|
||||
// logTag: _logTag,
|
||||
// logger: _logger,
|
||||
// reloadEvent: widget.reloadEvent,
|
||||
// header: widget.header,
|
||||
// footer: widget.footer,
|
||||
// selectedFiles: widget.selectedFiles,
|
||||
// showSelectAllByDefault:
|
||||
// widget.showSelectAllByDefault && widget.groupType.showGroupHeader(),
|
||||
// isScrollablePositionedList: widget.isScrollablePositionedList,
|
||||
// ),
|
||||
|
||||
child: _allGalleryFiles.isEmpty
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
||||
Reference in New Issue
Block a user