mirror of
https://github.com/immich-app/immich.git
synced 2025-12-17 01:11:13 +03:00
chore: bump dart sdk to 3.8 (#20355)
* chore: bump dart sdk to 3.8 * chore: make build * make pigeon * chore: format files --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@@ -16,11 +16,7 @@ class FixedTimelineRow extends MultiChildRenderObjectWidget {
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderFixedRow(
|
||||
dimension: dimension,
|
||||
spacing: spacing,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
return RenderFixedRow(dimension: dimension, spacing: spacing, textDirection: textDirection);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -50,9 +46,9 @@ class RenderFixedRow extends RenderBox
|
||||
required double dimension,
|
||||
required double spacing,
|
||||
required TextDirection textDirection,
|
||||
}) : _dimension = dimension,
|
||||
_spacing = spacing,
|
||||
_textDirection = textDirection {
|
||||
}) : _dimension = dimension,
|
||||
_spacing = spacing,
|
||||
_textDirection = textDirection {
|
||||
addAll(children);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ class FixedSegment extends Segment {
|
||||
required super.headerExtent,
|
||||
required super.spacing,
|
||||
required super.header,
|
||||
}) : assert(tileHeight != 0),
|
||||
mainAxisExtend = tileHeight + spacing;
|
||||
}) : assert(tileHeight != 0),
|
||||
mainAxisExtend = tileHeight + spacing;
|
||||
|
||||
@override
|
||||
double indexToLayoutOffset(int index) {
|
||||
@@ -64,12 +64,7 @@ class FixedSegment extends Segment {
|
||||
final numberOfAssets = math.min(columnCount, assetCount - assetIndex);
|
||||
|
||||
if (index == firstIndex) {
|
||||
return TimelineHeader(
|
||||
bucket: bucket,
|
||||
header: header,
|
||||
height: headerExtent,
|
||||
assetOffset: firstAssetIndex,
|
||||
);
|
||||
return TimelineHeader(bucket: bucket, header: header, height: headerExtent, assetOffset: firstAssetIndex);
|
||||
}
|
||||
|
||||
return _FixedSegmentRow(
|
||||
@@ -104,10 +99,7 @@ class _FixedSegmentRow extends ConsumerWidget {
|
||||
}
|
||||
|
||||
if (timelineService.hasRange(assetIndex, assetCount)) {
|
||||
return _buildAssetRow(
|
||||
context,
|
||||
timelineService.getAssets(assetIndex, assetCount),
|
||||
);
|
||||
return _buildAssetRow(context, timelineService.getAssets(assetIndex, assetCount));
|
||||
}
|
||||
|
||||
return FutureBuilder<List<BaseAsset>>(
|
||||
@@ -122,12 +114,7 @@ class _FixedSegmentRow extends ConsumerWidget {
|
||||
}
|
||||
|
||||
Widget _buildPlaceholder(BuildContext context) {
|
||||
return SegmentBuilder.buildPlaceholder(
|
||||
context,
|
||||
assetCount,
|
||||
size: Size.square(tileHeight),
|
||||
spacing: spacing,
|
||||
);
|
||||
return SegmentBuilder.buildPlaceholder(context, assetCount, size: Size.square(tileHeight), spacing: spacing);
|
||||
}
|
||||
|
||||
Widget _buildAssetRow(BuildContext context, List<BaseAsset> assets) {
|
||||
@@ -137,11 +124,7 @@ class _FixedSegmentRow extends ConsumerWidget {
|
||||
textDirection: Directionality.of(context),
|
||||
children: [
|
||||
for (int i = 0; i < assets.length; i++)
|
||||
_AssetTileWidget(
|
||||
key: ValueKey(assets[i].heroTag),
|
||||
asset: assets[i],
|
||||
assetIndex: assetIndex + i,
|
||||
),
|
||||
_AssetTileWidget(key: ValueKey(assets[i].heroTag), asset: assets[i], assetIndex: assetIndex + i),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -151,19 +134,9 @@ class _AssetTileWidget extends ConsumerWidget {
|
||||
final BaseAsset asset;
|
||||
final int assetIndex;
|
||||
|
||||
const _AssetTileWidget({
|
||||
super.key,
|
||||
required this.asset,
|
||||
required this.assetIndex,
|
||||
});
|
||||
const _AssetTileWidget({super.key, required this.asset, required this.assetIndex});
|
||||
|
||||
Future _handleOnTap(
|
||||
BuildContext ctx,
|
||||
WidgetRef ref,
|
||||
int assetIndex,
|
||||
BaseAsset asset,
|
||||
int? heroOffset,
|
||||
) async {
|
||||
Future _handleOnTap(BuildContext ctx, WidgetRef ref, int assetIndex, BaseAsset asset, int? heroOffset) async {
|
||||
final multiSelectState = ref.read(multiSelectProvider);
|
||||
|
||||
if (multiSelectState.forceEnable || multiSelectState.isEnabled) {
|
||||
@@ -192,11 +165,7 @@ class _AssetTileWidget extends ConsumerWidget {
|
||||
}
|
||||
|
||||
bool _getLockSelectionStatus(WidgetRef ref) {
|
||||
final lockSelectionAssets = ref.read(
|
||||
multiSelectProvider.select(
|
||||
(state) => state.lockedSelectionAssets,
|
||||
),
|
||||
);
|
||||
final lockSelectionAssets = ref.read(multiSelectProvider.select((state) => state.lockedSelectionAssets));
|
||||
|
||||
if (lockSelectionAssets.isEmpty) {
|
||||
return false;
|
||||
@@ -210,9 +179,7 @@ class _AssetTileWidget extends ConsumerWidget {
|
||||
final heroOffset = TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
|
||||
|
||||
final lockSelection = _getLockSelectionStatus(ref);
|
||||
final showStorageIndicator = ref.watch(
|
||||
timelineArgsProvider.select((args) => args.showStorageIndicator),
|
||||
);
|
||||
final showStorageIndicator = ref.watch(timelineArgsProvider.select((args) => args.showStorageIndicator));
|
||||
|
||||
return RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
|
||||
@@ -35,8 +35,7 @@ class FixedSegmentBuilder extends SegmentBuilder {
|
||||
|
||||
final timelineHeader = switch (groupBy) {
|
||||
GroupAssetsBy.month => HeaderType.month,
|
||||
GroupAssetsBy.day ||
|
||||
GroupAssetsBy.auto =>
|
||||
GroupAssetsBy.day || GroupAssetsBy.auto =>
|
||||
bucket is TimeBucket && bucket.date.month != previousDate?.month ? HeaderType.monthAndDay : HeaderType.day,
|
||||
GroupAssetsBy.none => HeaderType.none,
|
||||
};
|
||||
|
||||
@@ -47,11 +47,7 @@ class TimelineHeader extends StatelessWidget {
|
||||
final isDayHeader = header == HeaderType.day || header == HeaderType.monthAndDay;
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: isMonthHeader ? 8.0 : 0.0,
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
),
|
||||
padding: EdgeInsets.only(top: isMonthHeader ? 8.0 : 0.0, left: 12.0, right: 12.0),
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: Column(
|
||||
@@ -61,32 +57,17 @@ class TimelineHeader extends StatelessWidget {
|
||||
if (isMonthHeader)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
_formatMonth(context, date),
|
||||
style: context.textTheme.labelLarge?.copyWith(fontSize: 24),
|
||||
),
|
||||
Text(_formatMonth(context, date), style: context.textTheme.labelLarge?.copyWith(fontSize: 24)),
|
||||
const Spacer(),
|
||||
if (header != HeaderType.monthAndDay)
|
||||
_BulkSelectIconButton(
|
||||
bucket: bucket,
|
||||
assetOffset: assetOffset,
|
||||
),
|
||||
if (header != HeaderType.monthAndDay) _BulkSelectIconButton(bucket: bucket, assetOffset: assetOffset),
|
||||
],
|
||||
),
|
||||
if (isDayHeader)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
_formatDay(context, date),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
Text(_formatDay(context, date), style: context.textTheme.labelLarge?.copyWith(fontSize: 15)),
|
||||
const Spacer(),
|
||||
_BulkSelectIconButton(
|
||||
bucket: bucket,
|
||||
assetOffset: assetOffset,
|
||||
),
|
||||
_BulkSelectIconButton(bucket: bucket, assetOffset: assetOffset),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -100,10 +81,7 @@ class _BulkSelectIconButton extends ConsumerWidget {
|
||||
final Bucket bucket;
|
||||
final int assetOffset;
|
||||
|
||||
const _BulkSelectIconButton({
|
||||
required this.bucket,
|
||||
required this.assetOffset,
|
||||
});
|
||||
const _BulkSelectIconButton({required this.bucket, required this.assetOffset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -118,23 +96,12 @@ class _BulkSelectIconButton extends ConsumerWidget {
|
||||
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
ref.read(multiSelectProvider.notifier).toggleBucketSelection(
|
||||
assetOffset,
|
||||
bucket.assetCount,
|
||||
);
|
||||
ref.read(multiSelectProvider.notifier).toggleBucketSelection(assetOffset, bucket.assetCount);
|
||||
ref.read(hapticFeedbackProvider.notifier).heavyImpact();
|
||||
},
|
||||
icon: isAllSelected
|
||||
? Icon(
|
||||
Icons.check_circle_rounded,
|
||||
size: 26,
|
||||
color: context.primaryColor,
|
||||
)
|
||||
: Icon(
|
||||
Icons.check_circle_outline_rounded,
|
||||
size: 26,
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
? Icon(Icons.check_circle_rounded, size: 26, color: context.primaryColor)
|
||||
: Icon(Icons.check_circle_outline_rounded, size: 26, color: context.colorScheme.onSurfaceSecondary),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,7 @@ class Scrubber extends ConsumerStatefulWidget {
|
||||
ConsumerState createState() => ScrubberState();
|
||||
}
|
||||
|
||||
List<_Segment> _buildSegments({
|
||||
required List<Segment> layoutSegments,
|
||||
required double timelineHeight,
|
||||
}) {
|
||||
List<_Segment> _buildSegments({required List<Segment> layoutSegments, required double timelineHeight}) {
|
||||
const double offsetThreshold = 20.0;
|
||||
|
||||
final segments = <_Segment>[];
|
||||
@@ -66,14 +63,7 @@ List<_Segment> _buildSegments({
|
||||
|
||||
final showSegment = lastOffset + offsetThreshold <= startOffset && (lastDate == null || date.year != lastDate.year);
|
||||
|
||||
segments.add(
|
||||
_Segment(
|
||||
date: date,
|
||||
startOffset: startOffset,
|
||||
scrollLabel: label,
|
||||
showSegment: showSegment,
|
||||
),
|
||||
);
|
||||
segments.add(_Segment(date: date, startOffset: startOffset, scrollLabel: label, showSegment: showSegment));
|
||||
lastDate = date;
|
||||
if (showSegment) {
|
||||
lastOffset = startOffset;
|
||||
@@ -109,27 +99,12 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isDragging = false;
|
||||
_segments = _buildSegments(
|
||||
layoutSegments: widget.layoutSegments,
|
||||
timelineHeight: _scrubberHeight,
|
||||
);
|
||||
_thumbAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: kTimelineScrubberFadeInDuration,
|
||||
);
|
||||
_thumbAnimation = CurvedAnimation(
|
||||
parent: _thumbAnimationController,
|
||||
curve: Curves.fastEaseInToSlowEaseOut,
|
||||
);
|
||||
_labelAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: kTimelineScrubberFadeInDuration,
|
||||
);
|
||||
_segments = _buildSegments(layoutSegments: widget.layoutSegments, timelineHeight: _scrubberHeight);
|
||||
_thumbAnimationController = AnimationController(vsync: this, duration: kTimelineScrubberFadeInDuration);
|
||||
_thumbAnimation = CurvedAnimation(parent: _thumbAnimationController, curve: Curves.fastEaseInToSlowEaseOut);
|
||||
_labelAnimationController = AnimationController(vsync: this, duration: kTimelineScrubberFadeInDuration);
|
||||
|
||||
_labelAnimation = CurvedAnimation(
|
||||
parent: _labelAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
_labelAnimation = CurvedAnimation(parent: _labelAnimationController, curve: Curves.fastOutSlowIn);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -143,10 +118,7 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.layoutSegments.lastOrNull?.endOffset != widget.layoutSegments.lastOrNull?.endOffset) {
|
||||
_segments = _buildSegments(
|
||||
layoutSegments: widget.layoutSegments,
|
||||
timelineHeight: _scrubberHeight,
|
||||
);
|
||||
_segments = _buildSegments(layoutSegments: widget.layoutSegments, timelineHeight: _scrubberHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,12 +248,10 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
}
|
||||
|
||||
int _findLayoutSegmentIndex(_Segment segment) {
|
||||
return widget.layoutSegments.indexWhere(
|
||||
(layoutSegment) {
|
||||
final bucket = layoutSegment.bucket as TimeBucket;
|
||||
return bucket.date.year == segment.date.year && bucket.date.month == segment.date.month;
|
||||
},
|
||||
);
|
||||
return widget.layoutSegments.indexWhere((layoutSegment) {
|
||||
final bucket = layoutSegment.bucket as TimeBucket;
|
||||
return bucket.date.year == segment.date.year && bucket.date.month == segment.date.month;
|
||||
});
|
||||
}
|
||||
|
||||
void _scrollToLayoutSegment(int layoutSegmentIndex) {
|
||||
@@ -311,19 +281,13 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
if (_scrollController.hasClients == true) {
|
||||
// Cache to avoid multiple calls to [_currentOffset]
|
||||
final scrollOffset = _currentOffset;
|
||||
final labelText = _segments
|
||||
.lastWhereOrNull(
|
||||
(segment) => segment.startOffset <= scrollOffset,
|
||||
)
|
||||
?.scrollLabel ??
|
||||
final labelText =
|
||||
_segments.lastWhereOrNull((segment) => segment.startOffset <= scrollOffset)?.scrollLabel ??
|
||||
_segments.firstOrNull?.scrollLabel;
|
||||
label = labelText != null
|
||||
? Text(
|
||||
labelText,
|
||||
style: ctx.textTheme.bodyLarge?.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: ctx.textTheme.bodyLarge?.copyWith(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
)
|
||||
: null;
|
||||
}
|
||||
@@ -351,11 +315,7 @@ class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixi
|
||||
onVerticalDragStart: _onDragStart,
|
||||
onVerticalDragUpdate: _onDragUpdate,
|
||||
onVerticalDragEnd: _onDragEnd,
|
||||
child: _Scrubber(
|
||||
thumbAnimation: _thumbAnimation,
|
||||
labelAnimation: _labelAnimation,
|
||||
label: label,
|
||||
),
|
||||
child: _Scrubber(thumbAnimation: _thumbAnimation, labelAnimation: _labelAnimation, label: label),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -370,12 +330,7 @@ class _SegmentsLayer extends StatelessWidget {
|
||||
final double topPadding;
|
||||
final bool isDragging;
|
||||
|
||||
const _SegmentsLayer({
|
||||
super.key,
|
||||
required this.segments,
|
||||
required this.topPadding,
|
||||
required this.isDragging,
|
||||
});
|
||||
const _SegmentsLayer({super.key, required this.segments, required this.topPadding, required this.isDragging});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -389,9 +344,7 @@ class _SegmentsLayer extends StatelessWidget {
|
||||
key: ValueKey('segment_${segment.date.millisecondsSinceEpoch}'),
|
||||
top: topPadding + segment.startOffset,
|
||||
end: 100,
|
||||
child: RepaintBoundary(
|
||||
child: _SegmentWidget(segment),
|
||||
),
|
||||
child: RepaintBoundary(child: _SegmentWidget(segment)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@@ -419,10 +372,7 @@ class _SegmentWidget extends StatelessWidget {
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_segment.date.year.toString(),
|
||||
style: context.textTheme.labelMedium?.copyWith(
|
||||
fontFamily: "OverpassMono",
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: context.textTheme.labelMedium?.copyWith(fontFamily: "OverpassMono", fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -436,11 +386,7 @@ class _ScrollLabel extends StatelessWidget {
|
||||
final Color backgroundColor;
|
||||
final Animation<double> animation;
|
||||
|
||||
const _ScrollLabel({
|
||||
required this.label,
|
||||
required this.backgroundColor,
|
||||
required this.animation,
|
||||
});
|
||||
const _ScrollLabel({required this.label, required this.backgroundColor, required this.animation});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -471,16 +417,13 @@ class _Scrubber extends StatelessWidget {
|
||||
final Animation<double> thumbAnimation;
|
||||
final Animation<double> labelAnimation;
|
||||
|
||||
const _Scrubber({
|
||||
this.label,
|
||||
required this.thumbAnimation,
|
||||
required this.labelAnimation,
|
||||
});
|
||||
const _Scrubber({this.label, required this.thumbAnimation, required this.labelAnimation});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundColor =
|
||||
context.isDarkTheme ? context.colorScheme.primary.darken(amount: .5) : context.colorScheme.primary;
|
||||
final backgroundColor = context.isDarkTheme
|
||||
? context.colorScheme.primary.darken(amount: .5)
|
||||
: context.colorScheme.primary;
|
||||
|
||||
return _SlideFadeTransition(
|
||||
animation: thumbAnimation,
|
||||
@@ -488,12 +431,7 @@ class _Scrubber extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (label != null)
|
||||
_ScrollLabel(
|
||||
label: label!,
|
||||
backgroundColor: backgroundColor,
|
||||
animation: labelAnimation,
|
||||
),
|
||||
if (label != null) _ScrollLabel(label: label!, backgroundColor: backgroundColor, animation: labelAnimation),
|
||||
_CircularThumb(backgroundColor),
|
||||
],
|
||||
),
|
||||
@@ -519,9 +457,7 @@ class _CircularThumb extends StatelessWidget {
|
||||
topRight: Radius.circular(4.0),
|
||||
bottomRight: Radius.circular(4.0),
|
||||
),
|
||||
child: Container(
|
||||
constraints: BoxConstraints.tight(const Size(48.0 * 0.6, 48.0)),
|
||||
),
|
||||
child: Container(constraints: BoxConstraints.tight(const Size(48.0 * 0.6, 48.0))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -543,14 +479,8 @@ class _ArrowPainter extends CustomPainter {
|
||||
final baseX = size.width / 2;
|
||||
final baseY = size.height / 2;
|
||||
|
||||
canvas.drawPath(
|
||||
_trianglePath(Offset(baseX, baseY - 2.0), width, height, true),
|
||||
paint,
|
||||
);
|
||||
canvas.drawPath(
|
||||
_trianglePath(Offset(baseX, baseY + 2.0), width, height, false),
|
||||
paint,
|
||||
);
|
||||
canvas.drawPath(_trianglePath(Offset(baseX, baseY - 2.0), width, height, true), paint);
|
||||
canvas.drawPath(_trianglePath(Offset(baseX, baseY + 2.0), width, height, false), paint);
|
||||
}
|
||||
|
||||
static Path _trianglePath(Offset o, double width, double height, bool isUp) {
|
||||
@@ -566,11 +496,9 @@ class _SlideFadeTransition extends StatelessWidget {
|
||||
final Animation<double> _animation;
|
||||
final Widget _child;
|
||||
|
||||
const _SlideFadeTransition({
|
||||
required Animation<double> animation,
|
||||
required Widget child,
|
||||
}) : _animation = animation,
|
||||
_child = child;
|
||||
const _SlideFadeTransition({required Animation<double> animation, required Widget child})
|
||||
: _animation = animation,
|
||||
_child = child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -578,14 +506,8 @@ class _SlideFadeTransition extends StatelessWidget {
|
||||
animation: _animation,
|
||||
builder: (context, child) => _animation.value == 0.0 ? const SizedBox() : 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,
|
||||
),
|
||||
position: Tween(begin: const Offset(0.3, 0.0), end: const Offset(0.0, 0.0)).animate(_animation),
|
||||
child: FadeTransition(opacity: _animation, child: _child),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -597,19 +519,9 @@ class _Segment {
|
||||
final String scrollLabel;
|
||||
final bool showSegment;
|
||||
|
||||
const _Segment({
|
||||
required this.date,
|
||||
required this.startOffset,
|
||||
required this.scrollLabel,
|
||||
this.showSegment = false,
|
||||
});
|
||||
const _Segment({required this.date, required this.startOffset, required this.scrollLabel, this.showSegment = false});
|
||||
|
||||
_Segment copyWith({
|
||||
DateTime? date,
|
||||
double? startOffset,
|
||||
String? scrollLabel,
|
||||
bool? showSegment,
|
||||
}) {
|
||||
_Segment copyWith({DateTime? date, double? startOffset, String? scrollLabel, bool? showSegment}) {
|
||||
return _Segment(
|
||||
date: date ?? this.date,
|
||||
startOffset: startOffset ?? this.startOffset,
|
||||
|
||||
@@ -37,8 +37,8 @@ abstract class Segment {
|
||||
required this.headerExtent,
|
||||
required this.spacing,
|
||||
required this.header,
|
||||
}) : gridIndex = firstIndex + 1,
|
||||
gridOffset = startOffset + headerExtent + spacing;
|
||||
}) : gridIndex = firstIndex + 1,
|
||||
gridOffset = startOffset + headerExtent + spacing;
|
||||
|
||||
bool containsIndex(int index) => firstIndex <= index && index <= lastIndex;
|
||||
|
||||
|
||||
@@ -9,34 +9,26 @@ abstract class SegmentBuilder {
|
||||
final double spacing;
|
||||
final GroupAssetsBy groupBy;
|
||||
|
||||
const SegmentBuilder({
|
||||
required this.buckets,
|
||||
this.spacing = kTimelineSpacing,
|
||||
this.groupBy = GroupAssetsBy.day,
|
||||
});
|
||||
const SegmentBuilder({required this.buckets, this.spacing = kTimelineSpacing, this.groupBy = GroupAssetsBy.day});
|
||||
|
||||
static double headerExtent(HeaderType header) => switch (header) {
|
||||
HeaderType.month => kTimelineHeaderExtent,
|
||||
HeaderType.day => kTimelineHeaderExtent * 0.90,
|
||||
HeaderType.monthAndDay => kTimelineHeaderExtent * 1.6,
|
||||
HeaderType.none => 0.0,
|
||||
};
|
||||
HeaderType.month => kTimelineHeaderExtent,
|
||||
HeaderType.day => kTimelineHeaderExtent * 0.90,
|
||||
HeaderType.monthAndDay => kTimelineHeaderExtent * 1.6,
|
||||
HeaderType.none => 0.0,
|
||||
};
|
||||
|
||||
static Widget buildPlaceholder(
|
||||
BuildContext context,
|
||||
int count, {
|
||||
Size size = const Size.square(kTimelineFixedTileExtent),
|
||||
double spacing = kTimelineSpacing,
|
||||
}) =>
|
||||
RepaintBoundary(
|
||||
child: FixedTimelineRow(
|
||||
dimension: size.height,
|
||||
spacing: spacing,
|
||||
textDirection: Directionality.of(context),
|
||||
children: List.generate(
|
||||
count,
|
||||
(_) => ThumbnailPlaceholder(width: size.width, height: size.height),
|
||||
),
|
||||
),
|
||||
);
|
||||
}) => RepaintBoundary(
|
||||
child: FixedTimelineRow(
|
||||
dimension: size.height,
|
||||
spacing: spacing,
|
||||
textDirection: Directionality.of(context),
|
||||
children: List.generate(count, (_) => ThumbnailPlaceholder(width: size.width, height: size.height)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,10 +54,7 @@ class TimelineState {
|
||||
final bool isScrubbing;
|
||||
final bool isScrolling;
|
||||
|
||||
const TimelineState({
|
||||
this.isScrubbing = false,
|
||||
this.isScrolling = false,
|
||||
});
|
||||
const TimelineState({this.isScrubbing = false, this.isScrolling = false});
|
||||
|
||||
bool get isInteracting => isScrubbing || isScrolling;
|
||||
|
||||
@@ -70,10 +67,7 @@ class TimelineState {
|
||||
int get hashCode => isScrubbing.hashCode ^ isScrolling.hashCode;
|
||||
|
||||
TimelineState copyWith({bool? isScrubbing, bool? isScrolling}) {
|
||||
return TimelineState(
|
||||
isScrubbing: isScrubbing ?? this.isScrubbing,
|
||||
isScrolling: isScrolling ?? this.isScrolling,
|
||||
);
|
||||
return TimelineState(isScrubbing: isScrubbing ?? this.isScrubbing, isScrolling: isScrolling ?? this.isScrolling);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,38 +83,30 @@ class TimelineStateNotifier extends Notifier<TimelineState> {
|
||||
}
|
||||
|
||||
@override
|
||||
TimelineState build() => const TimelineState(
|
||||
isScrubbing: false,
|
||||
isScrolling: false,
|
||||
);
|
||||
TimelineState build() => const TimelineState(isScrubbing: false, isScrolling: false);
|
||||
}
|
||||
|
||||
// This provider watches the buckets from the timeline service & args and serves the segments.
|
||||
// It should be used only after the timeline service and timeline args provider is overridden
|
||||
final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>(
|
||||
(ref) async* {
|
||||
final args = ref.watch(timelineArgsProvider);
|
||||
final columnCount = args.columnCount;
|
||||
final spacing = args.spacing;
|
||||
final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1));
|
||||
final tileExtent = math.max(0, availableTileWidth) / columnCount;
|
||||
final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>((ref) async* {
|
||||
final args = ref.watch(timelineArgsProvider);
|
||||
final columnCount = args.columnCount;
|
||||
final spacing = args.spacing;
|
||||
final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1));
|
||||
final tileExtent = math.max(0, availableTileWidth) / columnCount;
|
||||
|
||||
final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)];
|
||||
final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)];
|
||||
|
||||
final timelineService = ref.watch(timelineServiceProvider);
|
||||
yield* timelineService.watchBuckets().map((buckets) {
|
||||
return FixedSegmentBuilder(
|
||||
buckets: buckets,
|
||||
tileHeight: tileExtent,
|
||||
columnCount: columnCount,
|
||||
spacing: spacing,
|
||||
groupBy: groupBy,
|
||||
).generate();
|
||||
});
|
||||
},
|
||||
dependencies: [timelineServiceProvider, timelineArgsProvider],
|
||||
);
|
||||
final timelineService = ref.watch(timelineServiceProvider);
|
||||
yield* timelineService.watchBuckets().map((buckets) {
|
||||
return FixedSegmentBuilder(
|
||||
buckets: buckets,
|
||||
tileHeight: tileExtent,
|
||||
columnCount: columnCount,
|
||||
spacing: spacing,
|
||||
groupBy: groupBy,
|
||||
).generate();
|
||||
});
|
||||
}, dependencies: [timelineServiceProvider, timelineArgsProvider]);
|
||||
|
||||
final timelineStateProvider = NotifierProvider<TimelineStateNotifier, TimelineState>(
|
||||
TimelineStateNotifier.new,
|
||||
);
|
||||
final timelineStateProvider = NotifierProvider<TimelineStateNotifier, TimelineState>(TimelineStateNotifier.new);
|
||||
|
||||
@@ -29,11 +29,7 @@ class Timeline extends StatelessWidget {
|
||||
this.topSliverWidgetHeight,
|
||||
this.showStorageIndicator = false,
|
||||
this.withStack = false,
|
||||
this.appBar = const ImmichSliverAppBar(
|
||||
floating: true,
|
||||
pinned: false,
|
||||
snap: false,
|
||||
),
|
||||
this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false),
|
||||
this.bottomSheet = const GeneralBottomSheet(),
|
||||
this.groupBy,
|
||||
});
|
||||
@@ -57,9 +53,7 @@ class Timeline extends StatelessWidget {
|
||||
(ref) => TimelineArgs(
|
||||
maxWidth: constraints.maxWidth,
|
||||
maxHeight: constraints.maxHeight,
|
||||
columnCount: ref.watch(
|
||||
settingsProvider.select((s) => s.get(Setting.tilesPerRow)),
|
||||
),
|
||||
columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))),
|
||||
showStorageIndicator: showStorageIndicator,
|
||||
withStack: withStack,
|
||||
groupBy: groupBy,
|
||||
@@ -79,12 +73,7 @@ class Timeline extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
const _SliverTimeline({
|
||||
this.topSliverWidget,
|
||||
this.topSliverWidgetHeight,
|
||||
this.appBar,
|
||||
this.bottomSheet,
|
||||
});
|
||||
const _SliverTimeline({this.topSliverWidget, this.topSliverWidgetHeight, this.appBar, this.bottomSheet});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
final double? topSliverWidgetHeight;
|
||||
@@ -108,11 +97,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
void _onEvent(Event event) {
|
||||
switch (event) {
|
||||
case ScrollToTopEvent():
|
||||
_scrollController.animateTo(
|
||||
0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
_scrollController.animateTo(0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut);
|
||||
case ScrollToDateEvent scrollToDateEvent:
|
||||
_scrollToDate(scrollToDateEvent.date);
|
||||
case TimelineReloadEvent():
|
||||
@@ -143,7 +128,8 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
});
|
||||
|
||||
// If exact date not found, try to find the closest month
|
||||
final fallbackSegment = targetSegment ??
|
||||
final fallbackSegment =
|
||||
targetSegment ??
|
||||
segments.firstWhereOrNull((segment) {
|
||||
if (segment.bucket is TimeBucket) {
|
||||
final segmentDate = (segment.bucket as TimeBucket).date;
|
||||
@@ -168,9 +154,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
Widget build(BuildContext _) {
|
||||
final asyncSegments = ref.watch(timelineSegmentProvider);
|
||||
final maxHeight = ref.watch(timelineArgsProvider.select((args) => args.maxHeight));
|
||||
final isSelectionMode = ref.watch(
|
||||
multiSelectProvider.select((s) => s.forceEnable),
|
||||
);
|
||||
final isSelectionMode = ref.watch(multiSelectProvider.select((s) => s.forceEnable));
|
||||
|
||||
return asyncSegments.widgetWhen(
|
||||
onData: (segments) {
|
||||
@@ -211,42 +195,26 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
addRepaintBoundaries: false,
|
||||
),
|
||||
),
|
||||
const SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: scrubberBottomPadding,
|
||||
),
|
||||
),
|
||||
const SliverPadding(padding: EdgeInsets.only(bottom: scrubberBottomPadding)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!isSelectionMode) ...[
|
||||
Consumer(
|
||||
builder: (_, consumerRef, child) {
|
||||
final isMultiSelectEnabled = consumerRef.watch(
|
||||
multiSelectProvider.select(
|
||||
(s) => s.isEnabled,
|
||||
),
|
||||
);
|
||||
final isMultiSelectEnabled = consumerRef.watch(multiSelectProvider.select((s) => s.isEnabled));
|
||||
|
||||
if (isMultiSelectEnabled) {
|
||||
return child!;
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
child: const Positioned(
|
||||
top: 60,
|
||||
left: 25,
|
||||
child: _MultiSelectStatusButton(),
|
||||
),
|
||||
child: const Positioned(top: 60, left: 25, child: _MultiSelectStatusButton()),
|
||||
),
|
||||
if (widget.bottomSheet != null)
|
||||
Consumer(
|
||||
builder: (_, consumerRef, child) {
|
||||
final isMultiSelectEnabled = consumerRef.watch(
|
||||
multiSelectProvider.select(
|
||||
(s) => s.isEnabled,
|
||||
),
|
||||
);
|
||||
final isMultiSelectEnabled = consumerRef.watch(multiSelectProvider.select((s) => s.isEnabled));
|
||||
|
||||
if (isMultiSelectEnabled) {
|
||||
return child!;
|
||||
@@ -267,22 +235,14 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
class _SliverSegmentedList extends SliverMultiBoxAdaptorWidget {
|
||||
final List<Segment> _segments;
|
||||
|
||||
const _SliverSegmentedList({
|
||||
required List<Segment> segments,
|
||||
required super.delegate,
|
||||
}) : _segments = segments;
|
||||
const _SliverSegmentedList({required List<Segment> segments, required super.delegate}) : _segments = segments;
|
||||
|
||||
@override
|
||||
_RenderSliverTimelineBoxAdaptor createRenderObject(BuildContext context) => _RenderSliverTimelineBoxAdaptor(
|
||||
childManager: context as SliverMultiBoxAdaptorElement,
|
||||
segments: _segments,
|
||||
);
|
||||
_RenderSliverTimelineBoxAdaptor createRenderObject(BuildContext context) =>
|
||||
_RenderSliverTimelineBoxAdaptor(childManager: context as SliverMultiBoxAdaptorElement, segments: _segments);
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
_RenderSliverTimelineBoxAdaptor renderObject,
|
||||
) {
|
||||
void updateRenderObject(BuildContext context, _RenderSliverTimelineBoxAdaptor renderObject) {
|
||||
renderObject.segments = _segments;
|
||||
}
|
||||
}
|
||||
@@ -299,10 +259,8 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
_RenderSliverTimelineBoxAdaptor({
|
||||
required super.childManager,
|
||||
required List<Segment> segments,
|
||||
}) : _segments = segments;
|
||||
_RenderSliverTimelineBoxAdaptor({required super.childManager, required List<Segment> segments})
|
||||
: _segments = segments;
|
||||
|
||||
int getMinChildIndexForScrollOffset(double offset) =>
|
||||
_segments.findByOffset(offset)?.getMinChildIndexForScrollOffset(offset) ?? 0;
|
||||
@@ -335,16 +293,18 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
||||
final int firstRequiredChildIndex = getMinChildIndexForScrollOffset(scrollOffset);
|
||||
|
||||
// Find the index of the last child that should be visible or in the trailing cache area.
|
||||
final int? lastRequiredChildIndex =
|
||||
targetScrollOffset.isFinite ? getMaxChildIndexForScrollOffset(targetScrollOffset) : null;
|
||||
final int? lastRequiredChildIndex = targetScrollOffset.isFinite
|
||||
? getMaxChildIndexForScrollOffset(targetScrollOffset)
|
||||
: null;
|
||||
|
||||
// Remove children that are no longer visible or within the cache area.
|
||||
if (firstChild == null) {
|
||||
collectGarbage(0, 0);
|
||||
} else {
|
||||
final int leadingChildrenToRemove = calculateLeadingGarbage(firstIndex: firstRequiredChildIndex);
|
||||
final int trailingChildrenToRemove =
|
||||
lastRequiredChildIndex == null ? 0 : calculateTrailingGarbage(lastIndex: lastRequiredChildIndex);
|
||||
final int trailingChildrenToRemove = lastRequiredChildIndex == null
|
||||
? 0
|
||||
: calculateTrailingGarbage(lastIndex: lastRequiredChildIndex);
|
||||
collectGarbage(leadingChildrenToRemove, trailingChildrenToRemove);
|
||||
}
|
||||
|
||||
@@ -352,10 +312,7 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
||||
// try to add the first child needed for the current scroll offset.
|
||||
if (firstChild == null) {
|
||||
final double firstChildLayoutOffset = indexToLayoutOffset(firstRequiredChildIndex);
|
||||
final bool childAdded = addInitialChild(
|
||||
index: firstRequiredChildIndex,
|
||||
layoutOffset: firstChildLayoutOffset,
|
||||
);
|
||||
final bool childAdded = addInitialChild(index: firstRequiredChildIndex, layoutOffset: firstChildLayoutOffset);
|
||||
|
||||
if (!childAdded) {
|
||||
// There are either no children, or we are past the end of all our children.
|
||||
@@ -408,16 +365,15 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
||||
// until we reach the [lastRequiredChildIndex] or run out of children.
|
||||
double calculatedMaxScrollOffset = double.infinity;
|
||||
|
||||
for (int currentIndex = indexOf(mostRecentlyLaidOutChild!) + 1;
|
||||
lastRequiredChildIndex == null || currentIndex <= lastRequiredChildIndex;
|
||||
++currentIndex) {
|
||||
for (
|
||||
int currentIndex = indexOf(mostRecentlyLaidOutChild!) + 1;
|
||||
lastRequiredChildIndex == null || currentIndex <= lastRequiredChildIndex;
|
||||
++currentIndex
|
||||
) {
|
||||
RenderBox? child = childAfter(mostRecentlyLaidOutChild!);
|
||||
|
||||
if (child == null || indexOf(child) != currentIndex) {
|
||||
child = insertAndLayoutChild(
|
||||
childConstraints,
|
||||
after: mostRecentlyLaidOutChild,
|
||||
);
|
||||
child = insertAndLayoutChild(childConstraints, after: mostRecentlyLaidOutChild);
|
||||
if (child == null) {
|
||||
final Segment? segment = _segments.findByIndex(currentIndex) ?? _segments.lastOrNull;
|
||||
calculatedMaxScrollOffset = segment?.indexToLayoutOffset(currentIndex) ?? computeMaxScrollOffset();
|
||||
@@ -443,30 +399,18 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
||||
);
|
||||
assert(debugAssertChildListIsNonEmptyAndContiguous());
|
||||
assert(indexOf(firstChild!) == firstRequiredChildIndex);
|
||||
assert(
|
||||
lastRequiredChildIndex == null || lastLaidOutChildIndex <= lastRequiredChildIndex,
|
||||
);
|
||||
assert(lastRequiredChildIndex == null || lastLaidOutChildIndex <= lastRequiredChildIndex);
|
||||
|
||||
calculatedMaxScrollOffset = math.min(
|
||||
calculatedMaxScrollOffset,
|
||||
estimateMaxScrollOffset(),
|
||||
);
|
||||
calculatedMaxScrollOffset = math.min(calculatedMaxScrollOffset, estimateMaxScrollOffset());
|
||||
|
||||
final double paintExtent = calculatePaintOffset(
|
||||
constraints,
|
||||
from: leadingScrollOffset,
|
||||
to: trailingScrollOffset,
|
||||
);
|
||||
final double paintExtent = calculatePaintOffset(constraints, from: leadingScrollOffset, to: trailingScrollOffset);
|
||||
|
||||
final double cacheExtent = calculateCacheOffset(
|
||||
constraints,
|
||||
from: leadingScrollOffset,
|
||||
to: trailingScrollOffset,
|
||||
);
|
||||
final double cacheExtent = calculateCacheOffset(constraints, from: leadingScrollOffset, to: trailingScrollOffset);
|
||||
|
||||
final double targetEndScrollOffsetForPaint = constraints.scrollOffset + constraints.remainingPaintExtent;
|
||||
final int? targetLastIndexForPaint =
|
||||
targetEndScrollOffsetForPaint.isFinite ? getMaxChildIndexForScrollOffset(targetEndScrollOffsetForPaint) : null;
|
||||
final int? targetLastIndexForPaint = targetEndScrollOffsetForPaint.isFinite
|
||||
? getMaxChildIndexForScrollOffset(targetEndScrollOffsetForPaint)
|
||||
: null;
|
||||
|
||||
final maxPaintExtent = math.max(paintExtent, calculatedMaxScrollOffset);
|
||||
|
||||
@@ -477,7 +421,8 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
||||
// Indicates if there's content scrolled off-screen.
|
||||
// This is true if the last child needed for painting is actually laid out,
|
||||
// or if the first child is partially visible.
|
||||
hasVisualOverflow: (targetLastIndexForPaint != null && lastLaidOutChildIndex >= targetLastIndexForPaint) ||
|
||||
hasVisualOverflow:
|
||||
(targetLastIndexForPaint != null && lastLaidOutChildIndex >= targetLastIndexForPaint) ||
|
||||
constraints.scrollOffset > 0.0,
|
||||
cacheExtent: cacheExtent,
|
||||
);
|
||||
@@ -500,16 +445,10 @@ class _MultiSelectStatusButton extends ConsumerWidget {
|
||||
final selectCount = ref.watch(multiSelectProvider.select((s) => s.selectedAssets.length));
|
||||
return ElevatedButton.icon(
|
||||
onPressed: () => ref.read(multiSelectProvider.notifier).reset(),
|
||||
icon: Icon(
|
||||
Icons.close_rounded,
|
||||
color: context.colorScheme.onPrimary,
|
||||
),
|
||||
icon: Icon(Icons.close_rounded, color: context.colorScheme.onPrimary),
|
||||
label: Text(
|
||||
selectCount.toString(),
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
height: 2.5,
|
||||
color: context.colorScheme.onPrimary,
|
||||
),
|
||||
style: context.textTheme.titleMedium?.copyWith(height: 2.5, color: context.colorScheme.onPrimary),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user