Source Code added
This commit is contained in:
parent
800376eafd
commit
9efa9bc6dd
3912 changed files with 754770 additions and 2 deletions
|
|
@ -0,0 +1,215 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail_tile.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/fixed/row.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/header.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/segment_builder.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class FixedSegment extends Segment {
|
||||
final double tileHeight;
|
||||
final int columnCount;
|
||||
final double mainAxisExtend;
|
||||
|
||||
const FixedSegment({
|
||||
required super.firstIndex,
|
||||
required super.lastIndex,
|
||||
required super.startOffset,
|
||||
required super.endOffset,
|
||||
required super.firstAssetIndex,
|
||||
required super.bucket,
|
||||
required this.tileHeight,
|
||||
required this.columnCount,
|
||||
required super.headerExtent,
|
||||
required super.spacing,
|
||||
required super.header,
|
||||
}) : assert(tileHeight != 0),
|
||||
mainAxisExtend = tileHeight + spacing;
|
||||
|
||||
@override
|
||||
double indexToLayoutOffset(int index) {
|
||||
final relativeIndex = index - gridIndex;
|
||||
return relativeIndex < 0 ? startOffset : gridOffset + (mainAxisExtend * relativeIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
int getMinChildIndexForScrollOffset(double scrollOffset) {
|
||||
final adjustedOffset = scrollOffset - gridOffset;
|
||||
if (!adjustedOffset.isFinite || adjustedOffset < 0) return firstIndex;
|
||||
return gridIndex + (adjustedOffset / mainAxisExtend).floor();
|
||||
}
|
||||
|
||||
@override
|
||||
int getMaxChildIndexForScrollOffset(double scrollOffset) {
|
||||
final adjustedOffset = scrollOffset - gridOffset;
|
||||
if (!adjustedOffset.isFinite || adjustedOffset < 0) return firstIndex;
|
||||
return gridIndex + (adjustedOffset / mainAxisExtend).ceil() - 1;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget builder(BuildContext context, int index) {
|
||||
final rowIndexInSegment = index - (firstIndex + 1);
|
||||
final assetIndex = rowIndexInSegment * columnCount;
|
||||
final assetCount = bucket.assetCount;
|
||||
final numberOfAssets = math.min(columnCount, assetCount - assetIndex);
|
||||
|
||||
if (index == firstIndex) {
|
||||
return TimelineHeader(bucket: bucket, header: header, height: headerExtent, assetOffset: firstAssetIndex);
|
||||
}
|
||||
|
||||
return _FixedSegmentRow(
|
||||
assetIndex: firstAssetIndex + assetIndex,
|
||||
assetCount: numberOfAssets,
|
||||
tileHeight: tileHeight,
|
||||
spacing: spacing,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FixedSegmentRow extends ConsumerWidget {
|
||||
final int assetIndex;
|
||||
final int assetCount;
|
||||
final double tileHeight;
|
||||
final double spacing;
|
||||
|
||||
const _FixedSegmentRow({
|
||||
required this.assetIndex,
|
||||
required this.assetCount,
|
||||
required this.tileHeight,
|
||||
required this.spacing,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isScrubbing = ref.watch(timelineStateProvider.select((s) => s.isScrubbing));
|
||||
final timelineService = ref.read(timelineServiceProvider);
|
||||
|
||||
if (isScrubbing) {
|
||||
return _buildPlaceholder(context);
|
||||
}
|
||||
if (timelineService.hasRange(assetIndex, assetCount)) {
|
||||
return _buildAssetRow(context, timelineService.getAssets(assetIndex, assetCount), timelineService);
|
||||
}
|
||||
|
||||
return FutureBuilder<List<BaseAsset>>(
|
||||
future: timelineService.loadAssets(assetIndex, assetCount),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return _buildPlaceholder(context);
|
||||
}
|
||||
return _buildAssetRow(context, snapshot.requireData, timelineService);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlaceholder(BuildContext context) {
|
||||
return SegmentBuilder.buildPlaceholder(context, assetCount, size: Size.square(tileHeight), spacing: spacing);
|
||||
}
|
||||
|
||||
Widget _buildAssetRow(BuildContext context, List<BaseAsset> assets, TimelineService timelineService) {
|
||||
return FixedTimelineRow(
|
||||
dimension: tileHeight,
|
||||
spacing: spacing,
|
||||
textDirection: Directionality.of(context),
|
||||
children: [
|
||||
for (int i = 0; i < assets.length; i++)
|
||||
TimelineAssetIndexWrapper(
|
||||
assetIndex: assetIndex + i,
|
||||
segmentIndex: 0, // For simplicity, using 0 for now
|
||||
child: _AssetTileWidget(
|
||||
key: ValueKey(Object.hash(assets[i].heroTag, assetIndex + i, timelineService.hashCode)),
|
||||
asset: assets[i],
|
||||
assetIndex: assetIndex + i,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetTileWidget extends ConsumerWidget {
|
||||
final BaseAsset asset;
|
||||
final int assetIndex;
|
||||
|
||||
const _AssetTileWidget({super.key, required this.asset, required this.assetIndex});
|
||||
|
||||
Future _handleOnTap(BuildContext ctx, WidgetRef ref, int assetIndex, BaseAsset asset, int? heroOffset) async {
|
||||
final multiSelectState = ref.read(multiSelectProvider);
|
||||
|
||||
if (multiSelectState.forceEnable || multiSelectState.isEnabled) {
|
||||
ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset);
|
||||
} else {
|
||||
await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1);
|
||||
ref.read(isPlayingMotionVideoProvider.notifier).playing = false;
|
||||
AssetViewer.setAsset(ref, asset);
|
||||
unawaited(
|
||||
ctx.pushRoute(
|
||||
AssetViewerRoute(
|
||||
initialIndex: assetIndex,
|
||||
timelineService: ref.read(timelineServiceProvider),
|
||||
heroOffset: heroOffset,
|
||||
currentAlbum: ref.read(currentRemoteAlbumProvider),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleOnLongPress(WidgetRef ref, BaseAsset asset) {
|
||||
final multiSelectState = ref.read(multiSelectProvider);
|
||||
if (multiSelectState.isEnabled || multiSelectState.forceEnable) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(hapticFeedbackProvider.notifier).heavyImpact();
|
||||
ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset);
|
||||
}
|
||||
|
||||
bool _getLockSelectionStatus(WidgetRef ref) {
|
||||
final lockSelectionAssets = ref.read(multiSelectProvider.select((state) => state.lockedSelectionAssets));
|
||||
|
||||
if (lockSelectionAssets.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lockSelectionAssets.contains(asset);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final heroOffset = TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
|
||||
|
||||
final lockSelection = _getLockSelectionStatus(ref);
|
||||
final showStorageIndicator = ref.watch(timelineArgsProvider.select((args) => args.showStorageIndicator));
|
||||
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
|
||||
|
||||
return RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
onTap: () => lockSelection ? null : _handleOnTap(context, ref, assetIndex, asset, heroOffset),
|
||||
onLongPress: () => lockSelection || isReadonlyModeEnabled ? null : _handleOnLongPress(ref, asset),
|
||||
child: ThumbnailTile(
|
||||
asset,
|
||||
lockSelection: lockSelection,
|
||||
showStorageIndicator: showStorageIndicator,
|
||||
heroOffset: heroOffset,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue