From 674b6c70aa7d5f116c06033ee0d014c16b8d1e94 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 20 Mar 2023 16:26:31 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat=EF=BC=9A=E6=B6=88=E6=81=AF=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E5=AF=B9=E5=BA=94=E6=A5=BC=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/components/message/notice_item.dart | 17 ++-- lib/components/topic/reply_item.dart | 105 ++++++++++++++++++++---- lib/pages/t/:topicId.dart | 104 ++++++++++++++++++----- pubspec.lock | 8 ++ pubspec.yaml | 1 + 5 files changed, 191 insertions(+), 44 deletions(-) diff --git a/lib/components/message/notice_item.dart b/lib/components/message/notice_item.dart index 76b4476..6b1a6e8 100644 --- a/lib/components/message/notice_item.dart +++ b/lib/components/message/notice_item.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_v2ex/utils/string.dart'; import 'package:flutter_v2ex/components/common/avatar.dart'; import 'package:flutter_v2ex/components/topic/html_render.dart'; import 'package:flutter_v2ex/models/web/item_member_notice.dart'; @@ -59,11 +60,17 @@ class _NoticeItemState extends State { color: Theme.of(context).colorScheme.onInverseSurface, child: InkWell( onTap: () { - String replyCount = widget.noticeItem.topicHref.split('#reply')[1]; - Get.toNamed('/t/${widget.noticeItem.topicId}', parameters: { - 'source': 'notice', - 'floorNumber': replyCount - }); + String floorNumber = + widget.noticeItem.topicHref.split('#reply')[1]; + NoticeType noticeType = widget.noticeItem.noticeType; + Map parameters = {}; + if (noticeType.name == NoticeType.reply.name || + noticeType.name == NoticeType.thanksReply.name) { + // 回复 or 感谢回复 + parameters = {'source': 'notice', 'floorNumber': floorNumber}; + } + Get.toNamed('/t/${widget.noticeItem.topicId}', + parameters: parameters); }, child: Ink( padding: const EdgeInsets.fromLTRB(15, 15, 5, 15), diff --git a/lib/components/topic/reply_item.dart b/lib/components/topic/reply_item.dart index e39b4f6..49e8985 100644 --- a/lib/components/topic/reply_item.dart +++ b/lib/components/topic/reply_item.dart @@ -13,6 +13,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'dart:math' as math; class ReplyListItem extends StatefulWidget { ReplyListItem({ @@ -22,6 +23,7 @@ class ReplyListItem extends StatefulWidget { this.totalPage, this.source, this.replyList, + this.floorNumber, Key? key, }) : super(key: key); @@ -31,12 +33,16 @@ class ReplyListItem extends StatefulWidget { int? totalPage; String? source; List? replyList; + int? floorNumber; + + @override State createState() => _ReplyListItemState(); } -class _ReplyListItemState extends State { +class _ReplyListItemState extends State + with TickerProviderStateMixin { // bool isChoose = false; List> sheetMenu = [ { @@ -80,6 +86,10 @@ class _ReplyListItemState extends State { String? loginUserName; bool highLightOp = GStorage().getHighlightOp(); + late AnimationController _controller; + int _animationCount = 0; + final int _maxAnimationCount = 2; + @override void initState() { // TODO: implement initState @@ -97,6 +107,24 @@ class _ReplyListItemState extends State { setState(() { reply = widget.reply; }); + + _controller = AnimationController( + lowerBound: 0.95, + duration: const Duration(milliseconds: 700), + vsync: this, + )..addListener(() { + if (_controller.status == AnimationStatus.completed) { + _animationCount++; + if (_animationCount >= _maxAnimationCount) { + _controller.stop(); + } else { + _controller.reverse(); + } + } else if (_controller.status == AnimationStatus.dismissed) { + _controller.forward(); + } + }); + _controller.forward(); } void menuAction(id) { @@ -238,6 +266,13 @@ class _ReplyListItemState extends State { } } + @override + void dispose() { + // TODO: implement dispose + _controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return AnimatedSize( @@ -414,26 +449,62 @@ class _ReplyListItemState extends State { } Widget replyItemTopic(context, child) { - return RepaintBoundary( - key: repaintKey, - child: Material( - color: reply.isOwner && highLightOp - ? Theme.of(context).colorScheme.onInverseSurface - : null, - child: InkWell( - onTap: () async { - /// 增加200毫秒延迟 水波纹动画 - await Future.delayed(const Duration(milliseconds: 200)); - replyComment(); - }, - onLongPress: () {}, - child: Ink( - padding: const EdgeInsets.fromLTRB(14, 12, 12, 0), - child: child, + return AnimatedBuilder( + animation: _controller, + child: RepaintBoundary( + key: repaintKey, + child: Material( + color: reply.isOwner + ? Theme.of(context).colorScheme.onInverseSurface + : null, + child: InkWell( + onTap: () async { + /// 增加200毫秒延迟 水波纹动画 + await Future.delayed(const Duration(milliseconds: 200)); + replyComment(); + }, + onLongPress: () {}, + child: Ink( + padding: const EdgeInsets.fromLTRB(14, 12, 12, 0), + child: child, + ), ), ), ), + builder: (BuildContext context, Widget? child) { + return Transform.scale( + scale: widget.floorNumber! > 0 && + reply.floorNumber == widget.floorNumber! + ? _controller.value + : 1, + child: child, + ); + }, ); + // return + // RepaintBoundary( + // key: repaintKey, + // child: Material( + // color: + // widget.floorNumber! > 0 && reply.floorNumber == widget.floorNumber! + // ? Theme.of(context).colorScheme.errorContainer.withOpacity(0.5) + // : reply.isOwner + // ? Theme.of(context).colorScheme.onInverseSurface + // : null, + // child: InkWell( + // onTap: () async { + // /// 增加200毫秒延迟 水波纹动画 + // await Future.delayed(const Duration(milliseconds: 200)); + // replyComment(); + // }, + // onLongPress: () {}, + // child: Ink( + // padding: const EdgeInsets.fromLTRB(14, 12, 12, 0), + // child: child, + // ), + // ), + // ), + // ); } Widget replyItemSheet(context, child) { diff --git a/lib/pages/t/:topicId.dart b/lib/pages/t/:topicId.dart index 7a39206..69b073f 100644 --- a/lib/pages/t/:topicId.dart +++ b/lib/pages/t/:topicId.dart @@ -26,6 +26,7 @@ import 'package:flutter_v2ex/components/topic/reply_sheet.dart'; import 'package:share_plus/share_plus.dart'; import 'package:flutter_v2ex/http/topic.dart'; import 'package:flutter_v2ex/service/read.dart'; +import 'package:scroll_to_index/scroll_to_index.dart'; enum SampleItem { ignore, share, report, browse } @@ -48,7 +49,7 @@ class _TopicDetailState extends State String heroTag = ''; // 监听页面滚动 - final ScrollController _scrollController = ScrollController(); + // final ScrollController _scrollController = ScrollController(); TopicDetailModel? _detailModel; // 主题详情 late List _replyList = []; // 回复列表 int _totalPage = 1; // 总页数 @@ -74,17 +75,34 @@ class _TopicDetailState extends State late AnimationController animationController; bool _visibleTitle = false; double? pinScrollHeight; + late AutoScrollController autoScrollController; + // 消息页面进入 + String routerSource = ''; + int noticeFloorNumber = 0; @override void initState() { super.initState(); + autoScrollController = AutoScrollController( + viewportBoundaryGetter: () => + Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom), + axis: Axis.vertical); + // setState(() { topicId = Get.parameters['topicId']!; if (Get.arguments != null) { topicDetail = Get.arguments['topic']; heroTag = Get.arguments['heroTag']; } + var keys = Get.parameters.keys; + // 从消息页面进入 跳转至指定楼层 + if (keys.contains('floorNumber')) { + routerSource = Get.parameters['source']! ?? ''; + noticeFloorNumber = int.parse(Get.parameters['floorNumber']!) ?? 0; + _currentPage = (noticeFloorNumber / 100).ceil() - 1; + // noticeReplyCount 小于等于100 直接请求第一页 大于100 请求 + } myUserName = GStorage().getUserInfo().isNotEmpty ? GStorage().getUserInfo()['userName'] : ''; @@ -96,7 +114,7 @@ class _TopicDetailState extends State ); // TODO build优化 - _scrollController.addListener(_listen); + autoScrollController.addListener(_listen); getDetailInit(); eventBus.on('topicReply', (status) { print('eventON: $status'); @@ -140,7 +158,7 @@ class _TopicDetailState extends State } Future getDetail({type}) async { - if (type == 'init') { + if (type == 'init' && routerSource == '') { // 初始化加载 正序首页为0 倒序首页为最后一页 setState(() { _currentPage = !reverseSort ? 0 : _totalPage; @@ -173,6 +191,9 @@ class _TopicDetailState extends State }); } }); + if(noticeFloorNumber > 0) { + _scrollToCounter(); + } } if (!topicDetailModel.isAuth) { SmartDialog.dismiss(); @@ -204,7 +225,7 @@ class _TopicDetailState extends State print('---_totalPage---:$_totalPage'); }); if (type == 'init') { - _scrollController.animateTo(pinScrollHeight!, + autoScrollController.animateTo(pinScrollHeight!, duration: const Duration(milliseconds: 1000), curve: Curves.easeInOut); } @@ -213,24 +234,24 @@ class _TopicDetailState extends State // 返回顶部并 todo 刷新 Future onRefreshBtm() async { - await _scrollController.animateTo(0, + await autoScrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.ease); _controller.callRefresh(); } void _listen() { final ScrollDirection direction = - _scrollController.position.userScrollDirection; + autoScrollController.position.userScrollDirection; if (direction == ScrollDirection.forward) { _show(); } else if (direction == ScrollDirection.reverse) { _hide(); } - if (_scrollController.offset > 100 && !_visibleTitle) { + if (autoScrollController.offset > 100 && !_visibleTitle) { _visibleTitle = true; titleStreamC.add(true); - } else if (_scrollController.offset <= 100 && _visibleTitle) { + } else if (autoScrollController.offset <= 100 && _visibleTitle) { _visibleTitle = false; titleStreamC.add(false); } @@ -489,11 +510,18 @@ class _TopicDetailState extends State } } + Future _scrollToCounter() async { + print('line 502: _scrollToCounter'); + await autoScrollController.scrollToIndex((noticeFloorNumber%100)-1, + preferPosition: AutoScrollPosition.begin); + // autoScrollController.highlight(5); + } + @override void dispose() { _controller.dispose(); - _scrollController.removeListener(_listen); - _scrollController.dispose(); + autoScrollController.removeListener(_listen); + autoScrollController.dispose(); eventBus.off('topicReply'); super.dispose(); } @@ -527,7 +555,7 @@ class _TopicDetailState extends State ? showLoading() : Scrollbar( radius: const Radius.circular(10), - controller: _scrollController, + controller: autoScrollController, child: PullRefresh( key: _globalKey, onChildRefresh: getDetailInit, @@ -633,7 +661,7 @@ class _TopicDetailState extends State Widget showRes() { return CustomScrollView( - controller: _scrollController, + controller: autoScrollController, // key: listGlobalKey, slivers: [ if (expendAppBar) ...[ @@ -809,23 +837,55 @@ class _TopicDetailState extends State ), pinned: true, ), + if(noticeFloorNumber > 0 && _currentPage > 1) + SliverToBoxAdapter( + child: Container( + width: double.infinity, + height: 60, + color: Theme.of(context).colorScheme.onInverseSurface, + child: Center( + child: Text('前 ${_currentPage-1} 页已隐藏'), + ), + ), + ), SliverList( delegate: SliverChildBuilderDelegate( + childCount: _replyList.length, (context, index) { - return ReplyListItem( - reply: _replyList[index], - topicId: _detailModel!.topicId, - totalPage: _totalPage, - key: UniqueKey(), - queryReplyList: - (replyMemberList, floorNumber, resultList, totalPage) => + return AutoScrollTag( + key: ValueKey(index), + controller: autoScrollController, + index: index, + child: + // noticeFloorNumber > 0 && index == 0 ? Text('123') : + ReplyListItem( + reply: _replyList[index], + topicId: _detailModel!.topicId, + totalPage: _totalPage, + key: UniqueKey(), + queryReplyList: + (replyMemberList, floorNumber, resultList, totalPage) => queryReplyList(replyMemberList, floorNumber, resultList, _totalPage), - source: 'topic', - replyList: _replyList, + source: 'topic', + replyList: _replyList, + floorNumber: noticeFloorNumber, + ) ); + // return ReplyListItem( + // reply: _replyList[index], + // topicId: _detailModel!.topicId, + // totalPage: _totalPage, + // key: UniqueKey(), + // queryReplyList: + // (replyMemberList, floorNumber, resultList, totalPage) => + // queryReplyList(replyMemberList, floorNumber, + // resultList, _totalPage), + // source: 'topic', + // replyList: _replyList, + // ); }, - childCount: _replyList.length, + // childCount: _replyList.length, ), ), ], diff --git a/pubspec.lock b/pubspec.lock index 8d1580a..4fbcb03 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -929,6 +929,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.27.7" + scroll_to_index: + dependency: "direct main" + description: + name: scroll_to_index + sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" share_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index abdc59b..75c52b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,7 @@ dependencies: sticky_headers: ^0.3.0+2 hive: ^2.2.3 hive_flutter: ^1.1.0 + scroll_to_index: ^3.0.1 dev_dependencies: flutter_test: From d041b211ffd5a6a4199b02c3d3f6d750496c5129 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 20 Mar 2023 16:49:50 +0800 Subject: [PATCH 2/3] =?UTF-8?q?mod:=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/components/topic/reply_item.dart | 4 +-- lib/pages/t/:topicId.dart | 50 +++++++++++++++------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/lib/components/topic/reply_item.dart b/lib/components/topic/reply_item.dart index 49e8985..852a326 100644 --- a/lib/components/topic/reply_item.dart +++ b/lib/components/topic/reply_item.dart @@ -88,7 +88,7 @@ class _ReplyListItemState extends State late AnimationController _controller; int _animationCount = 0; - final int _maxAnimationCount = 2; + final int _maxAnimationCount = 3; @override void initState() { @@ -110,7 +110,7 @@ class _ReplyListItemState extends State _controller = AnimationController( lowerBound: 0.95, - duration: const Duration(milliseconds: 700), + duration: const Duration(milliseconds: 600), vsync: this, )..addListener(() { if (_controller.status == AnimationStatus.completed) { diff --git a/lib/pages/t/:topicId.dart b/lib/pages/t/:topicId.dart index 69b073f..96c3953 100644 --- a/lib/pages/t/:topicId.dart +++ b/lib/pages/t/:topicId.dart @@ -76,6 +76,7 @@ class _TopicDetailState extends State bool _visibleTitle = false; double? pinScrollHeight; late AutoScrollController autoScrollController; + // 消息页面进入 String routerSource = ''; int noticeFloorNumber = 0; @@ -182,17 +183,19 @@ class _TopicDetailState extends State if (pinScrollHeight == null) { WidgetsBinding.instance.addPostFrameCallback((_) { - if(listGlobalKey.currentContext != null){ + if (listGlobalKey.currentContext != null) { final pinBox = - listGlobalKey.currentContext?.findRenderObject() as RenderBox; + listGlobalKey.currentContext?.findRenderObject() as RenderBox; final pinPosition = pinBox.localToGlobal(Offset.zero).dy - 100; setState(() { pinScrollHeight = pinPosition; }); } }); - if(noticeFloorNumber > 0) { - _scrollToCounter(); + if (noticeFloorNumber > 0) { + SmartDialog.showLoading(msg: '前往楼层'); + await _scrollToCounter(); + SmartDialog.dismiss(); } } if (!topicDetailModel.isAuth) { @@ -511,9 +514,11 @@ class _TopicDetailState extends State } Future _scrollToCounter() async { - print('line 502: _scrollToCounter'); - await autoScrollController.scrollToIndex((noticeFloorNumber%100)-1, - preferPosition: AutoScrollPosition.begin); + await autoScrollController.scrollToIndex( + (noticeFloorNumber % 100) - 1, + preferPosition: AutoScrollPosition.begin, + duration: const Duration(milliseconds: 100), + ); // autoScrollController.highlight(5); } @@ -837,41 +842,40 @@ class _TopicDetailState extends State ), pinned: true, ), - if(noticeFloorNumber > 0 && _currentPage > 1) - SliverToBoxAdapter( - child: Container( - width: double.infinity, - height: 60, - color: Theme.of(context).colorScheme.onInverseSurface, - child: Center( - child: Text('前 ${_currentPage-1} 页已隐藏'), + if (noticeFloorNumber > 0 && _currentPage > 1) + SliverToBoxAdapter( + child: Container( + width: double.infinity, + height: 60, + color: Theme.of(context).colorScheme.onInverseSurface, + child: Center( + child: Text('前 ${_currentPage - 1} 页已隐藏'), + ), ), ), - ), SliverList( delegate: SliverChildBuilderDelegate( - childCount: _replyList.length, + childCount: _replyList.length, (context, index) { return AutoScrollTag( key: ValueKey(index), controller: autoScrollController, index: index, child: - // noticeFloorNumber > 0 && index == 0 ? Text('123') : - ReplyListItem( + // noticeFloorNumber > 0 && index == 0 ? Text('123') : + ReplyListItem( reply: _replyList[index], topicId: _detailModel!.topicId, totalPage: _totalPage, key: UniqueKey(), - queryReplyList: - (replyMemberList, floorNumber, resultList, totalPage) => + queryReplyList: (replyMemberList, floorNumber, + resultList, totalPage) => queryReplyList(replyMemberList, floorNumber, resultList, _totalPage), source: 'topic', replyList: _replyList, floorNumber: noticeFloorNumber, - ) - ); + )); // return ReplyListItem( // reply: _replyList[index], // topicId: _detailModel!.topicId, From 22dcecc718fabed051ddf2f71ca7c829800288f8 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 24 Mar 2023 17:49:38 +0800 Subject: [PATCH 3/3] =?UTF-8?q?mod:=20=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/t/:topicId.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pages/t/:topicId.dart b/lib/pages/t/:topicId.dart index 96c3953..3f7c5d9 100644 --- a/lib/pages/t/:topicId.dart +++ b/lib/pages/t/:topicId.dart @@ -849,7 +849,14 @@ class _TopicDetailState extends State height: 60, color: Theme.of(context).colorScheme.onInverseSurface, child: Center( - child: Text('前 ${_currentPage - 1} 页已隐藏'), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.commit, color: Theme.of(context).colorScheme.outline,), + const SizedBox(width: 6), + Text('前 ${_currentPage - 1} 页已隐藏') + ], + ), ), ), ),