Add scroll to position of notification in post screen

This commit is contained in:
Hank Grabowski 2023-01-15 09:39:05 -06:00
parent 3dc6751281
commit a1ac7a009f
9 changed files with 138 additions and 44 deletions

View file

@ -25,12 +25,13 @@ class NotificationControl extends StatelessWidget {
final manager = getIt<TimelineManager>(); final manager = getIt<TimelineManager>();
final existingPostData = manager.getPostTreeEntryBy(notification.iid); final existingPostData = manager.getPostTreeEntryBy(notification.iid);
if (existingPostData.isSuccess) { if (existingPostData.isSuccess) {
context.push('/post/view/${existingPostData.value.id}'); context
.push('/post/view/${existingPostData.value.id}/${notification.iid}');
return; return;
} }
final loadedPost = await manager.refreshStatusChain(notification.iid); final loadedPost = await manager.refreshStatusChain(notification.iid);
if (loadedPost.isSuccess) { if (loadedPost.isSuccess) {
context.push('/post/view/${loadedPost.value.id}'); context.push('/post/view/${loadedPost.value.id}/${notification.iid}');
return; return;
} }
buildSnackbar( buildSnackbar(

View file

@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../../models/entry_tree_item.dart'; import '../../models/entry_tree_item.dart';
import '../../models/flattened_tree_item.dart';
import '../../models/timeline_entry.dart'; import '../../models/timeline_entry.dart';
import '../../services/timeline_manager.dart'; import '../../services/timeline_manager.dart';
import '../../utils/entry_tree_item_flattening.dart'; import '../../utils/entry_tree_item_flattening.dart';
@ -10,14 +12,19 @@ import 'flattened_tree_entry_control.dart';
class PostControl extends StatefulWidget { class PostControl extends StatefulWidget {
final EntryTreeItem originalItem; final EntryTreeItem originalItem;
final String scrollToId;
final bool openRemote; final bool openRemote;
final bool showStatusOpenButton; final bool showStatusOpenButton;
final bool isRoot;
const PostControl( const PostControl({
{super.key, super.key,
required this.originalItem, required this.originalItem,
required this.scrollToId,
required this.openRemote, required this.openRemote,
required this.showStatusOpenButton}); required this.showStatusOpenButton,
required this.isRoot,
});
@override @override
State<PostControl> createState() => _PostControlState(); State<PostControl> createState() => _PostControlState();
@ -26,6 +33,10 @@ class PostControl extends StatefulWidget {
class _PostControlState extends State<PostControl> { class _PostControlState extends State<PostControl> {
static final _logger = Logger('$PostControl'); static final _logger = Logger('$PostControl');
final ItemScrollController itemScrollController = ItemScrollController();
final ItemPositionsListener itemPositionsListener =
ItemPositionsListener.create();
var showContent = true; var showContent = true;
var showComments = false; var showComments = false;
@ -41,6 +52,7 @@ class _PostControlState extends State<PostControl> {
@override @override
void initState() { void initState() {
showContent = entry.spoilerText.isEmpty; showContent = entry.spoilerText.isEmpty;
showComments = widget.scrollToId != item.id;
} }
@override @override
@ -48,6 +60,19 @@ class _PostControlState extends State<PostControl> {
final manager = context.watch<TimelineManager>(); final manager = context.watch<TimelineManager>();
_logger.finest('Building ${item.entry.toShortString()}'); _logger.finest('Building ${item.entry.toShortString()}');
final items = widget.originalItem.flatten(topLevelOnly: !showComments); final items = widget.originalItem.flatten(topLevelOnly: !showComments);
if (widget.isRoot) {
return buildListView(context, items, manager);
} else {
return buildColumnView(context, items, manager);
}
}
Widget buildColumnView(
BuildContext context,
List<FlattenedTreeItem> items,
TimelineManager manager,
) {
final widgets = <Widget>[]; final widgets = <Widget>[];
for (var i = 0; i < items.length; i++) { for (var i = 0; i < items.length; i++) {
final itemWidget = FlattenedTreeEntryControl( final itemWidget = FlattenedTreeEntryControl(
@ -76,4 +101,61 @@ class _PostControlState extends State<PostControl> {
} }
return Column(children: widgets); return Column(children: widgets);
} }
Widget buildListView(
BuildContext context,
List<FlattenedTreeItem> items,
TimelineManager manager,
) {
final int count;
final int offset;
if (hasComments && showComments) {
count = items.length + 1;
offset = 1;
final scrollToIndex =
items.indexWhere((e) => e.timelineEntry.id == widget.scrollToId);
Future.delayed(
const Duration(seconds: 1),
() async => itemScrollController.jumpTo(index: scrollToIndex),
);
} else if (hasComments) {
count = 2;
offset = 0;
} else {
count = 1;
offset = 0;
}
return ScrollablePositionedList.builder(
itemCount: count,
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
itemBuilder: (context, index) {
if (index == 0) {
return FlattenedTreeEntryControl(
originalItem: items.first,
openRemote: widget.openRemote,
showStatusOpenButton: widget.showStatusOpenButton,
);
}
if (index == 1 && hasComments) {
return TextButton(
onPressed: () async {
setState(() {
showComments = !showComments;
});
if (showComments) {
await manager.refreshStatusChain(entry.id);
}
},
child:
Text(showComments ? 'Hide Comments' : 'Load & Show Comments'),
);
}
return FlattenedTreeEntryControl(
originalItem: items[index - offset],
openRemote: widget.openRemote,
showStatusOpenButton: widget.showStatusOpenButton,
);
});
}
} }

View file

@ -45,8 +45,10 @@ class TimelinePanel extends StatelessWidget {
'Building item: $itemIndex: ${item.entry.toShortString()}'); 'Building item: $itemIndex: ${item.entry.toShortString()}');
return PostControl( return PostControl(
originalItem: item, originalItem: item,
scrollToId: item.id,
openRemote: false, openRemote: false,
showStatusOpenButton: true, showStatusOpenButton: true,
isRoot: false,
); );
}, },
separatorBuilder: (context, index) => Divider(), separatorBuilder: (context, index) => Divider(),

View file

@ -137,9 +137,11 @@ final appRouter = GoRouter(
EditorScreen(id: state.params['id'] ?? 'Not Found'), EditorScreen(id: state.params['id'] ?? 'Not Found'),
), ),
GoRoute( GoRoute(
path: 'view/:id', path: 'view/:id/:goto_id',
builder: (context, state) => builder: (context, state) => PostScreen(
PostScreen(id: state.params['id'] ?? 'Not Found'), id: state.params['id'] ?? 'Not Found',
goToId: state.params['goto_id'] ?? 'Not Found',
),
), ),
]), ]),
GoRoute( GoRoute(

View file

@ -7,7 +7,13 @@ import '../services/timeline_manager.dart';
class PostScreen extends StatelessWidget { class PostScreen extends StatelessWidget {
final String id; final String id;
const PostScreen({super.key, required this.id}); final String goToId;
const PostScreen({
super.key,
required this.id,
required this.goToId,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -17,13 +23,12 @@ class PostScreen extends StatelessWidget {
onRefresh: () async { onRefresh: () async {
await manager.refreshStatusChain(id); await manager.refreshStatusChain(id);
}, },
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: PostControl( child: PostControl(
originalItem: post, originalItem: post,
scrollToId: goToId,
openRemote: true, openRemote: true,
showStatusOpenButton: true, showStatusOpenButton: true,
), isRoot: true,
), ),
), ),
onError: (error) => Text('Error getting post: $error')); onError: (error) => Text('Error getting post: $error'));

View file

@ -8,7 +8,7 @@ import Foundation
import desktop_window import desktop_window
import flutter_secure_storage_macos import flutter_secure_storage_macos
import path_provider_macos import path_provider_macos
import shared_preferences_macos import shared_preferences_foundation
import sqflite import sqflite
import url_launcher_macos import url_launcher_macos

View file

@ -9,7 +9,8 @@ PODS:
- FMDB/standard (2.7.5) - FMDB/standard (2.7.5)
- path_provider_macos (0.0.1): - path_provider_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- shared_preferences_macos (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS - FlutterMacOS
- sqflite (0.0.2): - sqflite (0.0.2):
- FlutterMacOS - FlutterMacOS
@ -22,7 +23,7 @@ DEPENDENCIES:
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
@ -39,8 +40,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral :path: Flutter/ephemeral
path_provider_macos: path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
shared_preferences_macos: shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos
sqflite: sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
url_launcher_macos: url_launcher_macos:
@ -51,8 +52,8 @@ SPEC CHECKSUMS:
flutter_secure_storage_macos: 75c8cadfdba05ca007c0fa4ea0c16e5cf85e521b flutter_secure_storage_macos: 75c8cadfdba05ca007c0fa4ea0c16e5cf85e521b
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 path_provider_macos: 05fb0ef0cedf3e5bd179b9e41a638682b37133ea
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727 shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3

View file

@ -147,7 +147,7 @@ packages:
name: file_picker name: file_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.2.4" version: "5.2.5"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -260,7 +260,7 @@ packages:
name: flutter_widget_from_html_core name: flutter_widget_from_html_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.0+2" version: "0.9.1"
functional_listener: functional_listener:
dependency: transitive dependency: transitive
description: description:
@ -323,7 +323,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.2" version: "3.3.0"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -351,7 +351,7 @@ packages:
name: image_picker_ios name: image_picker_ios
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.8.6+4" version: "0.8.6+5"
image_picker_platform_interface: image_picker_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -484,7 +484,7 @@ packages:
name: path_provider_macos name: path_provider_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.6" version: "2.0.7"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -562,13 +562,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.27.7" version: "0.27.7"
scrollable_positioned_list:
dependency: "direct main"
description:
name: scrollable_positioned_list
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.5"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.15" version: "2.0.16"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
@ -576,10 +583,10 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.14" version: "2.0.14"
shared_preferences_ios: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_ios name: shared_preferences_foundation
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
@ -590,13 +597,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -636,14 +636,14 @@ packages:
name: sqflite name: sqflite
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.0+2" version: "2.4.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -678,7 +678,7 @@ packages:
name: synchronized name: synchronized
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0+3" version: "3.0.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -825,7 +825,7 @@ packages:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0+2" version: "0.2.0+3"
xml: xml:
dependency: transitive dependency: transitive
description: description:

View file

@ -37,6 +37,7 @@ dependencies:
flutter_file_dialog: ^2.3.2 flutter_file_dialog: ^2.3.2
multi_trigger_autocomplete: ^0.1.1 multi_trigger_autocomplete: ^0.1.1
video_player: ^2.4.10 video_player: ^2.4.10
scrollable_positioned_list: ^0.3.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: