From 1a9ee2d153250f15840f59389d4625a91bb4d66c Mon Sep 17 00:00:00 2001 From: orz12 Date: Thu, 11 Jul 2024 17:38:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8A=A8=E6=80=81=E4=B8=8E=E4=B8=93?= =?UTF-8?q?=E6=A0=8F=E8=AF=A6=E6=83=85=E9=80=82=E9=85=8D=E6=A8=AA=E5=B1=8F?= =?UTF-8?q?=E5=8F=8C=E5=88=97=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/html_render.dart | 6 +- lib/pages/dynamics/detail/view.dart | 337 ++++++++++++++------------ lib/pages/html/view.dart | 363 +++++++++++++++------------- 3 files changed, 386 insertions(+), 320 deletions(-) diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart index fed456a7..ee3f8b2f 100644 --- a/lib/common/widgets/html_render.dart +++ b/lib/common/widgets/html_render.dart @@ -1,3 +1,5 @@ +import 'dart:ffi'; + import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:get/get.dart'; @@ -10,12 +12,14 @@ class HtmlRender extends StatelessWidget { this.htmlContent, this.imgCount, this.imgList, + required this.constrainedWidth, super.key, }); final String? htmlContent; final int? imgCount; final List? imgList; + final double constrainedWidth; @override Widget build(BuildContext context) { @@ -57,7 +61,7 @@ class HtmlRender extends StatelessWidget { // height: isEmote ? 22 : null, // ); return NetworkImgLayer( - width: isEmote ? 22 : (Get.size.width - 23) / textScale, + width: isEmote ? 22 : (constrainedWidth - 23) / textScale, height: isEmote ? 22 : 200, src: imgUrl, ignoreHeight: !isEmote, diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index c66082ac..9545a1be 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; @@ -16,6 +17,7 @@ import 'package:PiliPalaX/pages/video/detail/reply_reply/index.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/id_utils.dart'; +import '../../../utils/grid.dart'; import '../widgets/dynamic_panel.dart'; class DynamicDetailPage extends StatefulWidget { @@ -212,158 +214,67 @@ class _DynamicDetailPageState extends State }, child: Stack( children: [ - CustomScrollView( - controller: scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - if (action != 'comment') - SliverToBoxAdapter( - child: DynamicPanel( - item: _dynamicDetailController.item, - source: 'detail', - ), - ), - SliverPersistentHeader( - delegate: _MySliverPersistentHeaderDelegate( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - border: Border( - top: BorderSide( - width: 0.6, - color: Theme.of(context) - .dividerColor - .withOpacity(0.05), - ), + OrientationBuilder( + builder: (context, orientation) { + double padding = max(context.width / 2 - Grid.maxRowWidth, 0); + if (orientation == Orientation.portrait) { + return CustomScrollView( + controller: scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverToBoxAdapter( + child: DynamicPanel( + item: _dynamicDetailController.item, + source: 'detail', ), ), - height: 45, - padding: const EdgeInsets.only(left: 12, right: 6), - child: Row( - children: [ - Obx( - () => AnimatedSwitcher( - duration: const Duration(milliseconds: 400), - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Text( - '${_dynamicDetailController.acount.value}条回复', - key: ValueKey( - _dynamicDetailController.acount.value), - ), - ), - ), - const Spacer(), - SizedBox( - height: 35, - child: TextButton.icon( - onPressed: () => - _dynamicDetailController.queryBySort(), - icon: const Icon(Icons.sort, size: 16), - label: Obx(() => Text( - _dynamicDetailController - .sortTypeLabel.value, - style: const TextStyle(fontSize: 13), + replyPersistentHeader(context), + replyList(), + ] + .map((e) => SliverPadding( + padding: EdgeInsets.symmetric(horizontal: padding), + sliver: e)) + .toList(), + ); + } else { + return Row( + children: [ + Expanded( + child: CustomScrollView( + controller: ScrollController(), + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only(left: padding / 2), + sliver: SliverToBoxAdapter( + child: DynamicPanel( + item: _dynamicDetailController.item, + source: 'detail', + ), )), - ), - ) - ], + ]), ), - ), - ), - pinned: true, - ), - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (snapshot.data['status']) { - // 请求成功 - return Obx( - () => _dynamicDetailController.replyList.isEmpty && - _dynamicDetailController.isLoadingMore - ? SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - return const VideoReplySkeleton(); - }, childCount: 8), - ) - : SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index == - _dynamicDetailController - .replyList.length) { - return Container( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context) - .padding - .bottom), - height: MediaQuery.of(context) - .padding - .bottom + - 100, - child: Center( - child: Obx( - () => Text( - _dynamicDetailController - .noMore.value, - style: TextStyle( - fontSize: 12, - color: Theme.of(context) - .colorScheme - .outline, - ), - ), - ), - ), - ); - } else { - return ReplyItem( - replyItem: _dynamicDetailController - .replyList[index], - showReplyRow: true, - replyLevel: '1', - replyReply: (replyItem) => - replyReply(replyItem), - replyType: - ReplyType.values[replyType], - addReply: (replyItem) { - _dynamicDetailController - .replyList[index].replies! - .add(replyItem); - }, - ); - } - }, - childCount: _dynamicDetailController - .replyList.length + - 1, - ), - ), - ); - } else { - // 请求错误 - return HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ); - } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoReplySkeleton(); - }, childCount: 8), - ); - } - }, - ) - ], + Expanded( + child: CustomScrollView( + controller: scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only(right: padding / 2), + sliver: replyPersistentHeader(context)), + SliverPadding( + padding: EdgeInsets.only(right: padding / 2), + sliver: replyList()), + ] + // .map( + // (e) => SliverPadding(padding: padding, sliver: e)) + // .toList(), + ), + ), + ], + ); + } + }, ), Positioned( bottom: MediaQuery.of(context).padding.bottom + 14, @@ -415,6 +326,136 @@ class _DynamicDetailPageState extends State ), ); } + + SliverPersistentHeader replyPersistentHeader(BuildContext context) { + return SliverPersistentHeader( + delegate: _MySliverPersistentHeaderDelegate( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + border: Border( + top: BorderSide( + width: 0.6, + color: Theme.of(context).dividerColor.withOpacity(0.05), + ), + ), + ), + height: 45, + padding: const EdgeInsets.only(left: 12, right: 6), + child: Row( + children: [ + Obx( + () => AnimatedSwitcher( + duration: const Duration(milliseconds: 400), + transitionBuilder: + (Widget child, Animation animation) { + return ScaleTransition(scale: animation, child: child); + }, + child: Text( + '${_dynamicDetailController.acount.value}条回复', + key: ValueKey(_dynamicDetailController.acount.value), + ), + ), + ), + const Spacer(), + SizedBox( + height: 35, + child: TextButton.icon( + onPressed: () => _dynamicDetailController.queryBySort(), + icon: const Icon(Icons.sort, size: 16), + label: Obx(() => Text( + _dynamicDetailController.sortTypeLabel.value, + style: const TextStyle(fontSize: 13), + )), + ), + ) + ], + ), + ), + ), + pinned: true, + ); + } + + FutureBuilder replyList() { + return FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (snapshot.data['status']) { + // 请求成功 + return Obx( + () => _dynamicDetailController.replyList.isEmpty && + _dynamicDetailController.isLoadingMore + ? SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoReplySkeleton(); + }, childCount: 8), + ) + : SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == + _dynamicDetailController.replyList.length) { + return Container( + padding: EdgeInsets.only( + bottom: + MediaQuery.of(context).padding.bottom), + height: + MediaQuery.of(context).padding.bottom + 100, + child: Center( + child: Obx( + () => Text( + _dynamicDetailController.noMore.value, + style: TextStyle( + fontSize: 12, + color: + Theme.of(context).colorScheme.outline, + ), + ), + ), + ), + ); + } else { + return ReplyItem( + replyItem: + _dynamicDetailController.replyList[index], + showReplyRow: true, + replyLevel: '1', + replyReply: (replyItem) => replyReply(replyItem), + replyType: ReplyType.values[replyType], + addReply: (replyItem) { + _dynamicDetailController + .replyList[index].replies! + .add(replyItem); + }, + ); + } + }, + childCount: + _dynamicDetailController.replyList.length + 1, + ), + ), + ); + } else { + // 请求错误 + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + ); + } + } else { + // 骨架屏 + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoReplySkeleton(); + }, childCount: 8), + ); + } + }, + ); + } } class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index d9b8fe20..d623c7d5 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -13,6 +15,7 @@ import 'package:PiliPalaX/pages/video/detail/reply_new/index.dart'; import 'package:PiliPalaX/pages/video/detail/reply_reply/index.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; +import '../../utils/grid.dart'; import 'controller.dart'; class HtmlRenderPage extends StatefulWidget { @@ -210,181 +213,123 @@ class _HtmlRenderPageState extends State ), body: Stack( children: [ - SingleChildScrollView( - controller: scrollController, - child: FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - var data = snapshot.data; - // fabAnimationCtr.forward(); - if (data != null && data['status']) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), - child: Row( - children: [ - NetworkImgLayer( - width: 40, - height: 40, - type: 'avatar', - src: _htmlRenderCtr.response['avatar']!, - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(_htmlRenderCtr.response['uname'], - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleSmall! - .fontSize, - )), - Text( - _htmlRenderCtr.response['updateTime'], - style: TextStyle( - color: - Theme.of(context).colorScheme.outline, - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - ), - ), - ], - ), - const Spacer(), - ], - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), - child: HtmlRender( - htmlContent: _htmlRenderCtr.response['content'], - ), - ), - Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 8, - color: Theme.of(context) - .dividerColor - .withOpacity(0.05), - ), - ), - ), - ), - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - border: Border( - top: BorderSide( - width: 0.6, - color: Theme.of(context) - .dividerColor - .withOpacity(0.05), - ), - ), - ), - height: 45, - padding: const EdgeInsets.only(left: 12, right: 6), - child: Row( - children: [ - const Text('回复'), - const Spacer(), - SizedBox( - height: 35, - child: TextButton.icon( - onPressed: () => _htmlRenderCtr.queryBySort(), - icon: const Icon(Icons.sort, size: 16), - label: Obx( - () => Text( - _htmlRenderCtr.sortTypeLabel.value, - style: const TextStyle(fontSize: 13), - ), - ), - ), - ) - ], - ), - ), - Obx( - () => _htmlRenderCtr.replyList.isEmpty && - _htmlRenderCtr.isLoadingMore - ? ListView.builder( - itemCount: 5, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return const VideoReplySkeleton(); - }, - ) - : ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: - _htmlRenderCtr.replyList.length + 1, - itemBuilder: (context, index) { - if (index == - _htmlRenderCtr.replyList.length) { - return Container( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context) - .padding - .bottom), - height: MediaQuery.of(context) - .padding - .bottom + - 100, - child: Center( - child: Obx( - () => Text( - _htmlRenderCtr.noMore.value, + OrientationBuilder(builder: (context, orientation) { + double padding = max(context.width / 2 - Grid.maxRowWidth, 0); + return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Expanded( + child: SingleChildScrollView( + controller: orientation == Orientation.portrait + ? scrollController + : ScrollController(), + child: Padding( + padding: orientation == Orientation.portrait + ? EdgeInsets.symmetric(horizontal: padding) + : EdgeInsets.only(left: padding / 2), + child: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + var data = snapshot.data; + // fabAnimationCtr.forward(); + if (data != null && data['status']) { + return Column( + children: [ + Padding( + padding: + const EdgeInsets.fromLTRB(12, 12, 12, 8), + child: Row( + children: [ + NetworkImgLayer( + width: 40, + height: 40, + type: 'avatar', + src: _htmlRenderCtr.response['avatar']!, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(_htmlRenderCtr.response['uname'], style: TextStyle( - fontSize: 12, - color: Theme.of(context) - .colorScheme - .outline, - ), + fontSize: Theme.of(context) + .textTheme + .titleSmall! + .fontSize, + )), + Text( + _htmlRenderCtr + .response['updateTime'], + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline, + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, ), ), - ), - ); - } else { - return ReplyItem( - replyItem: - _htmlRenderCtr.replyList[index], - showReplyRow: true, - replyLevel: '1', - replyReply: (replyItem) => - replyReply(replyItem), - replyType: ReplyType.values[type], - addReply: (replyItem) { - _htmlRenderCtr - .replyList[index].replies! - .add(replyItem); - }, - ); - } - }, + ], + ), + const Spacer(), + ], + ), ), - ), - ], - ); - } else { - return const Text('error'); - } - } else { - // 骨架屏 - return const SizedBox(); - } - }, - ), - ), + Padding( + padding: + const EdgeInsets.fromLTRB(12, 8, 12, 8), + child: LayoutBuilder( + builder: (context, boxConstraints) { + return HtmlRender( + htmlContent: + _htmlRenderCtr.response['content'], + constrainedWidth: + boxConstraints.maxWidth, + ); + }, + ), + ), + if (orientation == Orientation.portrait) ...[ + Divider( + thickness: 8, + color: Theme.of(context) + .dividerColor + .withOpacity(0.05)), + replyHeader(), + replyList(), + ] + ], + ); + } else { + return const Text('error'); + } + } else { + // 骨架屏 + return const SizedBox(); + } + }, + )), + )), + if (orientation == Orientation.landscape) ...[ + VerticalDivider( + thickness: 8, + color: Theme.of(context).dividerColor.withOpacity(0.05)), + Expanded( + child: SingleChildScrollView( + controller: scrollController, + child: Padding( + padding: EdgeInsets.only(right: padding / 2), + child: Column( + children: [ + replyHeader(), + replyList(), + ], + )))) + ] + ]); + }), Positioned( bottom: MediaQuery.of(context).padding.bottom + 14, right: 14, @@ -432,4 +377,80 @@ class _HtmlRenderPageState extends State ), ); } + + Obx replyList() { + return Obx( + () => _htmlRenderCtr.replyList.isEmpty && _htmlRenderCtr.isLoadingMore + ? ListView.builder( + itemCount: 5, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return const VideoReplySkeleton(); + }, + ) + : ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _htmlRenderCtr.replyList.length + 1, + itemBuilder: (context, index) { + if (index == _htmlRenderCtr.replyList.length) { + return Container( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom), + height: MediaQuery.of(context).padding.bottom + 100, + child: Center( + child: Obx( + () => Text( + _htmlRenderCtr.noMore.value, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + ), + ); + } else { + return ReplyItem( + replyItem: _htmlRenderCtr.replyList[index], + showReplyRow: true, + replyLevel: '1', + replyReply: (replyItem) => replyReply(replyItem), + replyType: ReplyType.values[type], + addReply: (replyItem) { + _htmlRenderCtr.replyList[index].replies!.add(replyItem); + }, + ); + } + }, + ), + ); + } + + Container replyHeader() { + return Container( + height: 45, + padding: const EdgeInsets.only(left: 12, right: 6), + child: Row( + children: [ + const Text('回复'), + const Spacer(), + SizedBox( + height: 35, + child: TextButton.icon( + onPressed: () => _htmlRenderCtr.queryBySort(), + icon: const Icon(Icons.sort, size: 16), + label: Obx( + () => Text( + _htmlRenderCtr.sortTypeLabel.value, + style: const TextStyle(fontSize: 13), + ), + ), + ), + ) + ], + ), + ); + } }