Skip to content

Commit

Permalink
feat(mobile): Added "jump to date" functionality to the memory view (#…
Browse files Browse the repository at this point in the history
…7323)

* implemented jump to date from memory

* Changed implementation to a ValueNotifier & fixes

* remove debug code

* feat(mobile):
- Added index bound checks
- Handled edge cases when scrolling to the very bottom of the grid-view
- removing the listener on dispose

* feat(mobile): fixed debug index offset & added debug toast for scroll errors

* feat(mobile): added more debug toasts...

* feat(mobile): scroll to month, if timeline is not grouped by days

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
  • Loading branch information
arnolicious and alextran1502 committed Apr 24, 2024
1 parent 0dbe44c commit dc9b51a
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:flutter/material.dart';

final scrollToDateNotifierProvider = ScrollToDateNotifier(null);

class ScrollToDateNotifier extends ValueNotifier<DateTime?> {
ScrollToDateNotifier(super.value);

void scrollToDate(DateTime date) {
value = date;

// Manually notify listeners to trigger the scroll, even if the value hasn't changed
notifyListeners();
}
}
65 changes: 65 additions & 0 deletions mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.pro
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_drag_region.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_to_date_notifier.provider.dart';
import 'package:immich_mobile/shared/providers/haptic_feedback.provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';

Expand Down Expand Up @@ -150,6 +153,23 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
assets.firstWhereOrNull((e) => !_selectedAssets.contains(e)) == null;
}

Future<void> _scrollToIndex(int index) async {
// if the index is so far down, that the end of the list is reached on the screen
// the scroll_position widget crashes. This is a workaround to prevent this.
// If the index is within the last 10 elements, we jump instead of scrolling.
if (widget.renderList.elements.length <= index + 10) {
_itemScrollController.jumpTo(
index: index,
);
return;
}
await _itemScrollController.scrollTo(
index: index,
alignment: 0,
duration: const Duration(milliseconds: 500),
);
}

Widget _itemBuilder(BuildContext c, int position) {
int index = position;
if (widget.topWidget != null) {
Expand Down Expand Up @@ -247,6 +267,48 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
: RefreshIndicator(onRefresh: widget.onRefresh!, child: child);
}

void _scrollToDate() {
final date = scrollToDateNotifierProvider.value;
if (date == null) {
ImmichToast.show(
context: context,
msg: "Scroll To Date failed, date is null.",
gravity: ToastGravity.BOTTOM,
toastType: ToastType.error,
);
return;
}

// Search for the index of the exact date in the list
var index = widget.renderList.elements.indexWhere(
(e) =>
e.date.year == date.year &&
e.date.month == date.month &&
e.date.day == date.day,
);

// If the exact date is not found, the timeline is grouped by month,
// thus we search for the month
if (index == -1) {
index = widget.renderList.elements.indexWhere(
(e) => e.date.year == date.year && e.date.month == date.month,
);
}

if (index != -1 && index < widget.renderList.elements.length) {
// Not sure why the index is shifted, but it works. :3
_scrollToIndex(index + 1);
} else {
ImmichToast.show(
context: context,
msg:
"The date (${DateFormat.yMd().format(date)}) could not be found in the timeline.",
gravity: ToastGravity.BOTTOM,
toastType: ToastType.error,
);
}
}

@override
void didUpdateWidget(ImmichAssetGridView oldWidget) {
super.didUpdateWidget(oldWidget);
Expand All @@ -261,6 +323,8 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
void initState() {
super.initState();
scrollToTopNotifierProvider.addListener(_scrollToTop);
scrollToDateNotifierProvider.addListener(_scrollToDate);

if (widget.visibleItemsListener != null) {
_itemPositionsListener.itemPositions.addListener(_positionListener);
}
Expand All @@ -274,6 +338,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
@override
void dispose() {
scrollToTopNotifierProvider.removeListener(_scrollToTop);
scrollToDateNotifierProvider.removeListener(_scrollToDate);
if (widget.visibleItemsListener != null) {
_itemPositionsListener.itemPositions.removeListener(_positionListener);
}
Expand Down
65 changes: 41 additions & 24 deletions mobile/lib/modules/memories/ui/memory_bottom_info.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// ignore_for_file: require_trailing_commas

import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/modules/memories/models/memory.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_to_date_notifier.provider.dart';

class MemoryBottomInfo extends StatelessWidget {
final Memory memory;
Expand All @@ -12,33 +16,46 @@ class MemoryBottomInfo extends StatelessWidget {
final df = DateFormat.yMMMMd();
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
memory.title,
style: TextStyle(
color: Colors.grey[400],
fontSize: 13.0,
fontWeight: FontWeight.w500,
),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
memory.title,
style: TextStyle(
color: Colors.grey[400],
fontSize: 13.0,
fontWeight: FontWeight.w500,
),
),
Text(
df.format(
memory.assets[0].fileCreatedAt,
),
Text(
df.format(
memory.assets[0].fileCreatedAt,
),
style: const TextStyle(
color: Colors.white,
fontSize: 15.0,
fontWeight: FontWeight.w500,
),
style: const TextStyle(
color: Colors.white,
fontSize: 15.0,
fontWeight: FontWeight.w500,
),
],
),
],
),
MaterialButton(
minWidth: 0,
onPressed: () {
context.popRoute();
scrollToDateNotifierProvider
.scrollToDate(memory.assets[0].fileCreatedAt);
},
shape: const CircleBorder(),
color: Colors.white.withOpacity(0.2),
elevation: 0,
child: const Icon(
Icons.open_in_new,
color: Colors.white,
),
],
),
),
]),
);
}
}

0 comments on commit dc9b51a

Please sign in to comment.