diff --git a/lib/controls/notifications_control.dart b/lib/controls/notifications_control.dart index 51e9338..4d80080 100644 --- a/lib/controls/notifications_control.dart +++ b/lib/controls/notifications_control.dart @@ -25,12 +25,13 @@ class NotificationControl extends StatelessWidget { final manager = getIt(); final existingPostData = manager.getPostTreeEntryBy(notification.iid); if (existingPostData.isSuccess) { - context.push('/post/view/${existingPostData.value.id}'); + context + .push('/post/view/${existingPostData.value.id}/${notification.iid}'); return; } final loadedPost = await manager.refreshStatusChain(notification.iid); if (loadedPost.isSuccess) { - context.push('/post/view/${loadedPost.value.id}'); + context.push('/post/view/${loadedPost.value.id}/${notification.iid}'); return; } buildSnackbar( diff --git a/lib/controls/timeline/post_control.dart b/lib/controls/timeline/post_control.dart index 208ec5c..b0041e4 100644 --- a/lib/controls/timeline/post_control.dart +++ b/lib/controls/timeline/post_control.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import '../../models/entry_tree_item.dart'; +import '../../models/flattened_tree_item.dart'; import '../../models/timeline_entry.dart'; import '../../services/timeline_manager.dart'; import '../../utils/entry_tree_item_flattening.dart'; @@ -10,14 +12,19 @@ import 'flattened_tree_entry_control.dart'; class PostControl extends StatefulWidget { final EntryTreeItem originalItem; + final String scrollToId; final bool openRemote; final bool showStatusOpenButton; + final bool isRoot; - const PostControl( - {super.key, - required this.originalItem, - required this.openRemote, - required this.showStatusOpenButton}); + const PostControl({ + super.key, + required this.originalItem, + required this.scrollToId, + required this.openRemote, + required this.showStatusOpenButton, + required this.isRoot, + }); @override State createState() => _PostControlState(); @@ -26,6 +33,10 @@ class PostControl extends StatefulWidget { class _PostControlState extends State { static final _logger = Logger('$PostControl'); + final ItemScrollController itemScrollController = ItemScrollController(); + final ItemPositionsListener itemPositionsListener = + ItemPositionsListener.create(); + var showContent = true; var showComments = false; @@ -41,6 +52,7 @@ class _PostControlState extends State { @override void initState() { showContent = entry.spoilerText.isEmpty; + showComments = widget.scrollToId != item.id; } @override @@ -48,6 +60,19 @@ class _PostControlState extends State { final manager = context.watch(); _logger.finest('Building ${item.entry.toShortString()}'); 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 items, + TimelineManager manager, + ) { final widgets = []; for (var i = 0; i < items.length; i++) { final itemWidget = FlattenedTreeEntryControl( @@ -76,4 +101,61 @@ class _PostControlState extends State { } return Column(children: widgets); } + + Widget buildListView( + BuildContext context, + List 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, + ); + }); + } } diff --git a/lib/controls/timeline/timeline_panel.dart b/lib/controls/timeline/timeline_panel.dart index 683fc3a..8d58ae4 100644 --- a/lib/controls/timeline/timeline_panel.dart +++ b/lib/controls/timeline/timeline_panel.dart @@ -45,8 +45,10 @@ class TimelinePanel extends StatelessWidget { 'Building item: $itemIndex: ${item.entry.toShortString()}'); return PostControl( originalItem: item, + scrollToId: item.id, openRemote: false, showStatusOpenButton: true, + isRoot: false, ); }, separatorBuilder: (context, index) => Divider(), diff --git a/lib/routes.dart b/lib/routes.dart index 191c4d4..ded5485 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -137,9 +137,11 @@ final appRouter = GoRouter( EditorScreen(id: state.params['id'] ?? 'Not Found'), ), GoRoute( - path: 'view/:id', - builder: (context, state) => - PostScreen(id: state.params['id'] ?? 'Not Found'), + path: 'view/:id/:goto_id', + builder: (context, state) => PostScreen( + id: state.params['id'] ?? 'Not Found', + goToId: state.params['goto_id'] ?? 'Not Found', + ), ), ]), GoRoute( diff --git a/lib/screens/post_screen.dart b/lib/screens/post_screen.dart index 0da9ff0..f9548b6 100644 --- a/lib/screens/post_screen.dart +++ b/lib/screens/post_screen.dart @@ -7,7 +7,13 @@ import '../services/timeline_manager.dart'; class PostScreen extends StatelessWidget { final String id; - const PostScreen({super.key, required this.id}); + final String goToId; + + const PostScreen({ + super.key, + required this.id, + required this.goToId, + }); @override Widget build(BuildContext context) { @@ -17,13 +23,12 @@ class PostScreen extends StatelessWidget { onRefresh: () async { await manager.refreshStatusChain(id); }, - child: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: PostControl( - originalItem: post, - openRemote: true, - showStatusOpenButton: true, - ), + child: PostControl( + originalItem: post, + scrollToId: goToId, + openRemote: true, + showStatusOpenButton: true, + isRoot: true, ), ), onError: (error) => Text('Error getting post: $error')); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index fd12375..808f3fb 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,7 @@ import Foundation import desktop_window import flutter_secure_storage_macos import path_provider_macos -import shared_preferences_macos +import shared_preferences_foundation import sqflite import url_launcher_macos diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 308da92..4b4a807 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -9,7 +9,8 @@ PODS: - FMDB/standard (2.7.5) - path_provider_macos (0.0.1): - FlutterMacOS - - shared_preferences_macos (0.0.1): + - shared_preferences_foundation (0.0.1): + - Flutter - FlutterMacOS - sqflite (0.0.2): - FlutterMacOS @@ -22,7 +23,7 @@ DEPENDENCIES: - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - 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`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) @@ -39,8 +40,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral path_provider_macos: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos - shared_preferences_macos: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos sqflite: :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos url_launcher_macos: @@ -51,8 +52,8 @@ SPEC CHECKSUMS: flutter_secure_storage_macos: 75c8cadfdba05ca007c0fa4ea0c16e5cf85e521b FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 - shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727 + path_provider_macos: 05fb0ef0cedf3e5bd179b9e41a638682b37133ea + shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 diff --git a/pubspec.lock b/pubspec.lock index d9675d4..50f7094 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -147,7 +147,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "5.2.4" + version: "5.2.5" flutter: dependency: "direct main" description: flutter @@ -260,7 +260,7 @@ packages: name: flutter_widget_from_html_core url: "https://pub.dartlang.org" source: hosted - version: "0.9.0+2" + version: "0.9.1" functional_listener: dependency: transitive description: @@ -323,7 +323,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "3.2.2" + version: "3.3.0" image_picker: dependency: "direct main" description: @@ -351,7 +351,7 @@ packages: name: image_picker_ios url: "https://pub.dartlang.org" source: hosted - version: "0.8.6+4" + version: "0.8.6+5" image_picker_platform_interface: dependency: transitive description: @@ -484,7 +484,7 @@ packages: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.7" path_provider_platform_interface: dependency: transitive description: @@ -562,13 +562,20 @@ packages: url: "https://pub.dartlang.org" source: hosted 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: dependency: "direct main" description: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.15" + version: "2.0.16" shared_preferences_android: dependency: transitive description: @@ -576,10 +583,10 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.14" - shared_preferences_ios: + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_ios + name: shared_preferences_foundation url: "https://pub.dartlang.org" source: hosted version: "2.1.1" @@ -590,13 +597,6 @@ packages: url: "https://pub.dartlang.org" source: hosted 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: dependency: transitive description: @@ -636,14 +636,14 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "2.2.2" + version: "2.2.3" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "2.4.0+2" + version: "2.4.1" stack_trace: dependency: transitive description: @@ -678,7 +678,7 @@ packages: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "3.0.0+3" + version: "3.0.1" term_glyph: dependency: transitive description: @@ -825,7 +825,7 @@ packages: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.2.0+2" + version: "0.2.0+3" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dc3caa4..6ce8244 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: flutter_file_dialog: ^2.3.2 multi_trigger_autocomplete: ^0.1.1 video_player: ^2.4.10 + scrollable_positioned_list: ^0.3.5 dev_dependencies: flutter_test: