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:
shenlong
2025-07-29 00:34:03 +05:30
committed by GitHub
parent 9b3718120b
commit e52b9d15b5
643 changed files with 32561 additions and 35292 deletions

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -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,
};

View File

@@ -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),
);
}
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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)),
),
);
}

View File

@@ -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);

View File

@@ -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),
),
);
}