Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace scrollable_positioned_list with super_sliver_list #1361

Merged
merged 2 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions lib/post/pages/post_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:lemmy_api_client/v3.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:super_sliver_list/super_sliver_list.dart';

// Project imports
import 'package:thunder/comment/utils/navigate_comment.dart';
Expand Down Expand Up @@ -54,8 +54,9 @@ class PostPage extends StatefulWidget {
}

class _PostPageState extends State<PostPage> {
final ItemScrollController _itemScrollController = ItemScrollController();
final ItemPositionsListener _itemPositionsListener = ItemPositionsListener.create();
final ScrollController _scrollController = ScrollController();
final ListController _listController = ListController();

bool hasScrolledToBottom = false;
bool resetFailureMessage = true;
bool _previousIsFabOpen = false;
Expand Down Expand Up @@ -246,7 +247,10 @@ class _PostPageState extends State<PostPage> {
child: Align(
alignment: Alignment.bottomCenter,
child: CommentNavigatorFab(
itemPositionsListener: _itemPositionsListener,
initialIndex: 0,
maxIndex: state.comments.length,
scrollController: _scrollController,
listController: _listController,
),
),
),
Expand Down Expand Up @@ -280,11 +284,13 @@ class _PostPageState extends State<PostPage> {
selectedCommentPath: state.selectedCommentPath,
override: singlePressAction == PostFabAction.backToTop
? () => {
_itemScrollController.scrollTo(
_listController.animateToItem(
index: 0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
)
scrollController: _scrollController,
alignment: 0,
duration: (estimatedDistance) => const Duration(milliseconds: 250),
curve: (estimatedDistance) => Curves.easeInOutCubicEmphasized,
),
}
: singlePressAction == PostFabAction.changeSort
? () => showSortBottomSheet(context, state)
Expand All @@ -301,11 +307,13 @@ class _PostPageState extends State<PostPage> {
selectedCommentPath: state.selectedCommentPath,
override: longPressAction == PostFabAction.backToTop
? () => {
_itemScrollController.scrollTo(
_listController.animateToItem(
index: 0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
)
scrollController: _scrollController,
alignment: 0,
duration: (estimatedDistance) => const Duration(milliseconds: 250),
curve: (estimatedDistance) => Curves.easeInOutCubicEmphasized,
),
}
: longPressAction == PostFabAction.changeSort
? () => showSortBottomSheet(context, state)
Expand Down Expand Up @@ -365,11 +373,13 @@ class _PostPageState extends State<PostPage> {
onPressed: () {
PostFabAction.backToTop.execute(
override: () => {
_itemScrollController.scrollTo(
_listController.animateToItem(
index: 0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
)
scrollController: _scrollController,
alignment: 0,
duration: (estimatedDistance) => const Duration(milliseconds: 250),
curve: (estimatedDistance) => Curves.easeInOutCubicEmphasized,
),
});
},
title: PostFabAction.backToTop.getTitle(context),
Expand Down Expand Up @@ -449,8 +459,8 @@ class _PostPageState extends State<PostPage> {
newlyCreatedCommentId: state.newlyCreatedCommentId,
moddingCommentId: state.moddingCommentId,
viewFullCommentsRefreshing: state.viewAllCommentsRefresh,
itemScrollController: _itemScrollController,
itemPositionsListener: _itemPositionsListener,
scrollController: _scrollController,
listController: _listController,
hasReachedCommentEnd: state.hasReachedCommentEnd,
moderators: state.moderators,
crossPosts: state.crossPosts,
Expand Down
18 changes: 9 additions & 9 deletions lib/post/pages/post_page_success.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
// Package imports
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lemmy_api_client/v3.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:super_sliver_list/super_sliver_list.dart';

// Project imports
import 'package:thunder/comment/utils/navigate_comment.dart';
Expand All @@ -22,8 +22,8 @@ class PostPageSuccess extends StatefulWidget {
final int? newlyCreatedCommentId;
final int? moddingCommentId;

final ItemScrollController itemScrollController;
final ItemPositionsListener itemPositionsListener;
final ScrollController scrollController;
final ListController listController;
final bool hasReachedCommentEnd;

final bool viewFullCommentsRefreshing;
Expand All @@ -36,8 +36,8 @@ class PostPageSuccess extends StatefulWidget {
super.key,
required this.postView,
this.comments = const [],
required this.itemScrollController,
required this.itemPositionsListener,
required this.scrollController,
required this.listController,
this.hasReachedCommentEnd = false,
this.selectedCommentId,
this.selectedCommentPath,
Expand All @@ -57,7 +57,7 @@ class _PostPageSuccessState extends State<PostPageSuccess> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => widget.itemScrollController.primaryScrollController?.addListener(_onScroll));
WidgetsBinding.instance.addPostFrameCallback((_) => widget.scrollController.addListener(_onScroll));
}

void _onScroll() {
Expand All @@ -67,7 +67,7 @@ class _PostPageSuccessState extends State<PostPageSuccess> {
if (widget.selectedCommentId != null || widget.hasReachedCommentEnd) {
return;
}
if ((widget.itemScrollController.primaryScrollController?.position.pixels ?? 0) >= (widget.itemScrollController.primaryScrollController?.position.maxScrollExtent ?? 0) * 0.6) {
if (widget.scrollController.position.pixels >= widget.scrollController.position.maxScrollExtent * 0.6) {
context.read<PostBloc>().add(const GetPostCommentsEvent());
}
}
Expand All @@ -84,8 +84,8 @@ class _PostPageSuccessState extends State<PostPageSuccess> {
selectedCommentPath: widget.selectedCommentPath,
newlyCreatedCommentId: widget.newlyCreatedCommentId,
now: DateTime.now().toUtc(),
itemScrollController: widget.itemScrollController,
itemPositionsListener: widget.itemPositionsListener,
scrollController: widget.scrollController,
listController: widget.listController,
postViewMedia: widget.postView,
comments: widget.comments,
hasReachedCommentEnd: widget.hasReachedCommentEnd,
Expand Down
42 changes: 26 additions & 16 deletions lib/post/widgets/comment_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:lemmy_api_client/v3.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:super_sliver_list/super_sliver_list.dart';

import 'package:thunder/core/models/post_view_media.dart';
import 'package:thunder/post/bloc/post_bloc.dart';
Expand All @@ -30,8 +30,8 @@ class CommentSubview extends StatefulWidget {
final String? selectedCommentPath;
final int? newlyCreatedCommentId;
final int? moddingCommentId;
final ItemScrollController itemScrollController;
final ItemPositionsListener itemPositionsListener;
final ScrollController scrollController;
final ListController listController;

final bool hasReachedCommentEnd;
final bool viewFullCommentsRefreshing;
Expand All @@ -55,8 +55,8 @@ class CommentSubview extends StatefulWidget {
this.selectedCommentPath,
this.newlyCreatedCommentId,
this.moddingCommentId,
required this.itemScrollController,
required this.itemPositionsListener,
required this.scrollController,
required this.listController,
this.hasReachedCommentEnd = false,
this.viewFullCommentsRefreshing = false,
required this.now,
Expand Down Expand Up @@ -108,7 +108,7 @@ class _CommentSubviewState extends State<CommentSubview> with SingleTickerProvid
// This must be run some time after the layout has been rendered so we can measure everything.
// It also must be run after there is something to scroll, and the easiest way to do this is to do it in a scroll listener.
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.itemScrollController.primaryScrollController?.addListener(() {
widget.scrollController.addListener(() {
if (_bottomSpacerHeight == null && _lastCommentKey.currentContext != null) {
final double? lastCommentHeight = (_lastCommentKey.currentContext!.findRenderObject() as RenderBox?)?.size.height;
final double? listHeight = (_listKey.currentContext?.findRenderObject() as RenderBox?)?.size.height;
Expand All @@ -133,7 +133,13 @@ class _CommentSubviewState extends State<CommentSubview> with SingleTickerProvid
// If we are looking at a comment context, scroll to the first comment.
// The delay is purely for aesthetics and is not required for the logic to work.
Future.delayed(const Duration(milliseconds: 250), () {
widget.itemScrollController.scrollTo(index: 1, duration: const Duration(milliseconds: 250));
widget.listController.animateToItem(
index: 1,
scrollController: widget.scrollController,
alignment: 0,
duration: (estimatedDistance) => const Duration(milliseconds: 250),
curve: (estimatedDistance) => Curves.easeInOutCubicEmphasized,
);
});
}

Expand All @@ -148,25 +154,29 @@ class _CommentSubviewState extends State<CommentSubview> with SingleTickerProvid
return BlocListener<PostBloc, PostState>(
listener: (context, state) {
if (state.navigateCommentId > 0) {
widget.itemScrollController.scrollTo(
widget.listController.animateToItem(
index: state.navigateCommentIndex,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
scrollController: widget.scrollController,
alignment: 0,
duration: (estimatedDistance) => const Duration(milliseconds: 250),
curve: (estimatedDistance) => Curves.easeInOutCubicEmphasized,
);
} else if (state.newlyCreatedCommentId != null && state.comments.first.commentView?.comment.id == state.newlyCreatedCommentId) {
// Only scroll for top level comments since you can comment from anywhere in the comment section.
widget.itemScrollController.scrollTo(
widget.listController.animateToItem(
index: 1,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
scrollController: widget.scrollController,
alignment: 0,
duration: (estimatedDistance) => const Duration(milliseconds: 250),
curve: (estimatedDistance) => Curves.easeInOutCubicEmphasized,
);
}
},
child: ScrollablePositionedList.builder(
child: SuperListView.builder(
key: _listKey,
addSemanticIndexes: false,
itemScrollController: widget.itemScrollController,
itemPositionsListener: widget.itemPositionsListener,
listController: widget.listController,
controller: widget.scrollController,
itemCount: getCommentsListLength(),
itemBuilder: (context, index) {
if (widget.postViewMedia != null && index == 0) {
Expand Down
84 changes: 61 additions & 23 deletions lib/shared/comment_navigator_fab.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,53 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:thunder/post/bloc/post_bloc.dart';
import 'package:collection/collection.dart';

import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class CommentNavigatorFab extends StatefulWidget {
final ItemPositionsListener itemPositionsListener;
/// The [ScrollController] for the scrollable list
final ScrollController scrollController;

/// The [ListController] for the scrollable list. This is used to navigate up and down
final ListController listController;

/// The initial index
final int initialIndex;

/// The maximum index that can be scrolled to
final int maxIndex;

const CommentNavigatorFab({
super.key,
required this.itemPositionsListener,
this.initialIndex = 0,
this.maxIndex = 0,
required this.scrollController,
required this.listController,
});

@override
State<CommentNavigatorFab> createState() => _CommentNavigatorFabState();
}

class _CommentNavigatorFabState extends State<CommentNavigatorFab> {
int currentIndex = 0;

@override
void initState() {
super.initState();

currentIndex = widget.initialIndex;
}

@override
void didUpdateWidget(covariant CommentNavigatorFab oldWidget) {
super.didUpdateWidget(oldWidget);

if (oldWidget.initialIndex != widget.initialIndex) {
// Reset the index if it ever changes. This could be due to an external event
currentIndex = widget.initialIndex;
}
}

@override
Widget build(BuildContext context) {
return SizedBox(
Expand Down Expand Up @@ -84,26 +114,34 @@ class _CommentNavigatorFabState extends State<CommentNavigatorFab> {
}

void navigateUp() {
int? currentIndex = widget.itemPositionsListener.itemPositions.value.firstWhereOrNull((item) => item.itemLeadingEdge < 0.0)?.index;

if (currentIndex != null) {
currentIndex;
} else {
currentIndex = widget.itemPositionsListener.itemPositions.value.firstWhereOrNull((item) => item.itemLeadingEdge <= 0.0)?.index;
if (currentIndex != null) {
currentIndex -= 1;
}
}
if (currentIndex == 0) return;

if (currentIndex != null) {
context.read<PostBloc>().add(NavigateCommentEvent(targetIndex: currentIndex, direction: NavigateCommentDirection.up));
}
widget.listController.animateToItem(
index: currentIndex - 1,
scrollController: widget.scrollController,
alignment: 0,
duration: (estimatedDistance) => const Duration(milliseconds: 250),
curve: (estimatedDistance) => Curves.easeInOutCubicEmphasized,
);

setState(() {
currentIndex = currentIndex - 1;
});
}

void navigateDown() {
final int? currentIndex = widget.itemPositionsListener.itemPositions.value.lastWhereOrNull((item) => item.itemLeadingEdge <= 0.01)?.index;
if (currentIndex != null) {
context.read<PostBloc>().add(NavigateCommentEvent(targetIndex: currentIndex + 1, direction: NavigateCommentDirection.down));
}
if (currentIndex == widget.maxIndex) return;

widget.listController.animateToItem(
index: currentIndex + 1,
scrollController: widget.scrollController,
alignment: 0,
duration: (estimatedDistance) => const Duration(milliseconds: 250),
curve: (estimatedDistance) => Curves.easeInOutCubicEmphasized,
);

setState(() {
currentIndex = currentIndex + 1;
});
}
}
17 changes: 8 additions & 9 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1467,15 +1467,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
scrollable_positioned_list:
dependency: "direct main"
description:
path: "packages/scrollable_positioned_list"
ref: master
resolved-ref: "7e0cd2f967727f0a027d53b2f3cd8f3331f3495d"
url: "https://github.com/bitstackapp/flutter.widgets.git"
source: git
version: "0.2.3"
share_plus:
dependency: "direct main"
description:
Expand Down Expand Up @@ -1697,6 +1688,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
super_sliver_list:
dependency: "direct main"
description:
name: super_sliver_list
sha256: b1e1e64d08ce40e459b9bb5d9f8e361617c26b8c9f3bb967760b0f436b6e3f56
url: "https://pub.dev"
source: hosted
version: "0.4.1"
swipeable_page_route:
dependency: "direct main"
description:
Expand Down
Loading
Loading