mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-28 06:20:12 +08:00
@@ -33,7 +33,7 @@ import 'package:PiliPlus/utils/storage_pref.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart' show HapticFeedback;
|
import 'package:flutter/services.dart' show HapticFeedback;
|
||||||
import 'package:get/get_core/src/get_main.dart';
|
import 'package:get/get_core/src/get_main.dart';
|
||||||
import 'package:get/get_navigation/get_navigation.dart';
|
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||||
|
|
||||||
class ImageModel {
|
class ImageModel {
|
||||||
ImageModel({
|
ImageModel({
|
||||||
@@ -74,14 +74,14 @@ class ImageGridView extends StatelessWidget {
|
|||||||
final bool fullScreen;
|
final bool fullScreen;
|
||||||
|
|
||||||
static bool horizontalPreview = Pref.horizontalPreview;
|
static bool horizontalPreview = Pref.horizontalPreview;
|
||||||
static const _routes = ['/videoV', '/dynamicDetail'];
|
static final _regex = RegExp(r'/videoV|/dynamicDetail$|/articlePage');
|
||||||
|
|
||||||
void _onTap(BuildContext context, int index) {
|
void _onTap(BuildContext context, int index) {
|
||||||
final imgList = picArr.map(
|
final imgList = picArr.map(
|
||||||
(item) {
|
(item) {
|
||||||
bool isLive = item.isLivePhoto;
|
bool isLive = item.isLivePhoto;
|
||||||
return SourceModel(
|
return SourceModel(
|
||||||
sourceType: isLive ? SourceType.livePhoto : SourceType.networkImage,
|
sourceType: isLive ? .livePhoto : .networkImage,
|
||||||
url: item.url,
|
url: item.url,
|
||||||
liveUrl: isLive ? item.liveUrl : null,
|
liveUrl: isLive ? item.liveUrl : null,
|
||||||
width: isLive ? item.width.toInt() : null,
|
width: isLive ? item.width.toInt() : null,
|
||||||
@@ -92,7 +92,7 @@ class ImageGridView extends StatelessWidget {
|
|||||||
).toList();
|
).toList();
|
||||||
if (horizontalPreview &&
|
if (horizontalPreview &&
|
||||||
!fullScreen &&
|
!fullScreen &&
|
||||||
_routes.contains(Get.currentRoute) &&
|
Get.currentRoute.startsWith(_regex) &&
|
||||||
!context.mediaQuerySize.isPortrait) {
|
!context.mediaQuerySize.isPortrait) {
|
||||||
final scaffoldState = Scaffold.maybeOf(context);
|
final scaffoldState = Scaffold.maybeOf(context);
|
||||||
if (scaffoldState != null) {
|
if (scaffoldState != null) {
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ class ArticleController extends CommonDynController {
|
|||||||
|
|
||||||
late final RxInt topIndex = 0.obs;
|
late final RxInt topIndex = 0.obs;
|
||||||
|
|
||||||
late final showDynActionBar = Pref.showDynActionBar;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
dynamic get sourceId => commentType == 12 ? 'cv$commentId' : id;
|
dynamic get sourceId => commentType == 12 ? 'cv$commentId' : id;
|
||||||
|
|
||||||
|
|||||||
@@ -68,13 +68,12 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
|||||||
appBar: _buildAppBar(),
|
appBar: _buildAppBar(),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: EdgeInsets.only(left: padding.left, right: padding.right),
|
padding: EdgeInsets.only(left: padding.left, right: padding.right),
|
||||||
child: Stack(
|
child: _buildPage(theme),
|
||||||
clipBehavior: Clip.none,
|
),
|
||||||
children: [
|
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||||
_buildPage(theme),
|
floatingActionButton: SlideTransition(
|
||||||
_buildBottom(theme),
|
position: fabAnimation,
|
||||||
],
|
child: _buildBottom(theme),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -494,177 +493,160 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildBottom(ThemeData theme) => Positioned(
|
Widget _buildBottom(ThemeData theme) {
|
||||||
left: 0,
|
if (!controller.showDynActionBar) {
|
||||||
bottom: 0,
|
return fabButton;
|
||||||
right: 0,
|
}
|
||||||
child: SlideTransition(
|
|
||||||
position: fabAnim,
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) {
|
|
||||||
if (!controller.showDynActionBar) {
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.bottomRight,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
right: kFloatingActionButtonMargin,
|
|
||||||
bottom: padding.bottom + kFloatingActionButtonMargin,
|
|
||||||
),
|
|
||||||
child: replyButton,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
late final primary = theme.colorScheme.primary;
|
late final primary = theme.colorScheme.primary;
|
||||||
late final outline = theme.colorScheme.outline;
|
late final outline = theme.colorScheme.outline;
|
||||||
late final btnStyle = TextButton.styleFrom(
|
late final btnStyle = TextButton.styleFrom(
|
||||||
tapTargetSize: .padded,
|
tapTargetSize: .padded,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
foregroundColor: outline,
|
foregroundColor: outline,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget textIconButton({
|
||||||
|
required IconData icon,
|
||||||
|
required String text,
|
||||||
|
required DynamicStat? stat,
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
IconData? activatedIcon,
|
||||||
|
}) {
|
||||||
|
final status = stat?.status == true;
|
||||||
|
final color = status ? primary : outline;
|
||||||
|
return TextButton.icon(
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: Icon(
|
||||||
|
status ? activatedIcon : icon,
|
||||||
|
size: 16,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
style: btnStyle,
|
||||||
|
label: Text(
|
||||||
|
stat?.count != null ? NumUtils.numFormat(stat!.count) : text,
|
||||||
|
style: TextStyle(color: color),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: .only(left: padding.left, right: padding.right),
|
||||||
|
child: Obx(() {
|
||||||
|
final stats = controller.stats.value;
|
||||||
|
|
||||||
|
Widget btn = Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
right: kFloatingActionButtonMargin,
|
||||||
|
bottom:
|
||||||
|
kFloatingActionButtonMargin +
|
||||||
|
(stats != null ? 0 : padding.bottom),
|
||||||
|
),
|
||||||
|
child: replyButton,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (stats == null) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
child: btn,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget textIconButton({
|
return Column(
|
||||||
required IconData icon,
|
mainAxisSize: MainAxisSize.min,
|
||||||
required String text,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
required DynamicStat? stat,
|
children: [
|
||||||
required VoidCallback onPressed,
|
btn,
|
||||||
IconData? activatedIcon,
|
Container(
|
||||||
}) {
|
decoration: BoxDecoration(
|
||||||
final status = stat?.status == true;
|
color: theme.colorScheme.surface,
|
||||||
final color = status ? primary : outline;
|
border: Border(
|
||||||
return TextButton.icon(
|
top: BorderSide(
|
||||||
onPressed: onPressed,
|
color: theme.colorScheme.outline.withValues(
|
||||||
icon: Icon(
|
alpha: 0.08,
|
||||||
status ? activatedIcon : icon,
|
|
||||||
size: 16,
|
|
||||||
color: color,
|
|
||||||
),
|
|
||||||
style: btnStyle,
|
|
||||||
label: Text(
|
|
||||||
stat?.count != null ? NumUtils.numFormat(stat!.count) : text,
|
|
||||||
style: TextStyle(color: color),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Obx(() {
|
|
||||||
final stats = controller.stats.value;
|
|
||||||
|
|
||||||
Widget btn = Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
right: kFloatingActionButtonMargin,
|
|
||||||
bottom:
|
|
||||||
kFloatingActionButtonMargin +
|
|
||||||
(stats != null ? 0 : padding.bottom),
|
|
||||||
),
|
|
||||||
child: replyButton,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stats == null) {
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: btn,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
btn,
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.surface,
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(
|
|
||||||
color: theme.colorScheme.outline.withValues(
|
|
||||||
alpha: 0.08,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.only(bottom: padding.bottom),
|
),
|
||||||
child: Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
padding: EdgeInsets.only(bottom: padding.bottom),
|
||||||
children: [
|
child: Row(
|
||||||
Expanded(
|
children: [
|
||||||
child: Builder(
|
Expanded(
|
||||||
builder: (btnContext) {
|
child: Builder(
|
||||||
final forward = stats.forward;
|
builder: (btnContext) {
|
||||||
return textIconButton(
|
final forward = stats.forward;
|
||||||
text: '转发',
|
return textIconButton(
|
||||||
icon: FontAwesomeIcons.shareFromSquare,
|
text: '转发',
|
||||||
stat: forward,
|
icon: FontAwesomeIcons.shareFromSquare,
|
||||||
onPressed: () {
|
stat: forward,
|
||||||
if (controller.opusData == null &&
|
onPressed: () {
|
||||||
controller.articleData?.dynIdStr == null) {
|
if (controller.opusData == null &&
|
||||||
SmartDialog.showToast(
|
controller.articleData?.dynIdStr == null) {
|
||||||
'err: ${controller.id}',
|
SmartDialog.showToast(
|
||||||
);
|
'err: ${controller.id}',
|
||||||
return;
|
);
|
||||||
}
|
return;
|
||||||
final summary = controller.summary;
|
}
|
||||||
showModalBottomSheet(
|
final summary = controller.summary;
|
||||||
context: context,
|
showModalBottomSheet(
|
||||||
isScrollControlled: true,
|
context: context,
|
||||||
useSafeArea: true,
|
isScrollControlled: true,
|
||||||
builder: (context) => RepostPanel(
|
useSafeArea: true,
|
||||||
item: controller.opusData,
|
builder: (context) => RepostPanel(
|
||||||
dynIdStr: controller.articleData?.dynIdStr,
|
item: controller.opusData,
|
||||||
pic: summary.cover,
|
dynIdStr: controller.articleData?.dynIdStr,
|
||||||
title: summary.title,
|
pic: summary.cover,
|
||||||
uname: summary.author?.name,
|
title: summary.title,
|
||||||
onSuccess: () {
|
uname: summary.author?.name,
|
||||||
if (forward != null) {
|
onSuccess: () {
|
||||||
int count = forward.count ?? 0;
|
if (forward != null) {
|
||||||
forward.count = count + 1;
|
int count = forward.count ?? 0;
|
||||||
if (btnContext.mounted) {
|
forward.count = count + 1;
|
||||||
(btnContext as Element?)
|
if (btnContext.mounted) {
|
||||||
?.markNeedsBuild();
|
(btnContext as Element?)
|
||||||
}
|
?.markNeedsBuild();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
Expanded(
|
),
|
||||||
child: textIconButton(
|
|
||||||
text: '分享',
|
|
||||||
icon: CustomIcons.share_node,
|
|
||||||
stat: null,
|
|
||||||
onPressed: () => Utils.shareText(controller.url),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: textIconButton(
|
|
||||||
icon: FontAwesomeIcons.star,
|
|
||||||
activatedIcon: FontAwesomeIcons.solidStar,
|
|
||||||
text: '收藏',
|
|
||||||
stat: stats.favorite,
|
|
||||||
onPressed: controller.onFav,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: textIconButton(
|
|
||||||
icon: FontAwesomeIcons.thumbsUp,
|
|
||||||
activatedIcon: FontAwesomeIcons.solidThumbsUp,
|
|
||||||
text: '点赞',
|
|
||||||
stat: stats.like,
|
|
||||||
onPressed: controller.onLike,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
],
|
child: textIconButton(
|
||||||
);
|
text: '分享',
|
||||||
});
|
icon: CustomIcons.share_node,
|
||||||
},
|
stat: null,
|
||||||
),
|
onPressed: () => Utils.shareText(controller.url),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
Expanded(
|
||||||
|
child: textIconButton(
|
||||||
|
icon: FontAwesomeIcons.star,
|
||||||
|
activatedIcon: FontAwesomeIcons.solidStar,
|
||||||
|
text: '收藏',
|
||||||
|
stat: stats.favorite,
|
||||||
|
onPressed: controller.onFav,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: textIconButton(
|
||||||
|
icon: FontAwesomeIcons.thumbsUp,
|
||||||
|
activatedIcon: FontAwesomeIcons.solidThumbsUp,
|
||||||
|
text: '点赞',
|
||||||
|
stat: stats.like,
|
||||||
|
onPressed: controller.onLike,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ abstract class CommonDynController extends ReplyController<MainListReply> {
|
|||||||
late final horizontalPreview = Pref.horizontalPreview;
|
late final horizontalPreview = Pref.horizontalPreview;
|
||||||
late final List<double> ratio = Pref.dynamicDetailRatio;
|
late final List<double> ratio = Pref.dynamicDetailRatio;
|
||||||
|
|
||||||
|
late final showDynActionBar = Pref.showDynActionBar;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
|
Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
|
||||||
type: replyType,
|
type: replyType,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
|||||||
show ReplyInfo;
|
show ReplyInfo;
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/common/fab_mixin.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
||||||
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
||||||
@@ -22,7 +23,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin, FabMixin {
|
||||||
CommonDynController get controller;
|
CommonDynController get controller;
|
||||||
|
|
||||||
late final ScrollController scrollController;
|
late final ScrollController scrollController;
|
||||||
@@ -36,26 +37,9 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
|||||||
late double maxWidth;
|
late double maxWidth;
|
||||||
late double maxHeight;
|
late double maxHeight;
|
||||||
|
|
||||||
bool _showFab = true;
|
|
||||||
|
|
||||||
final fabOffset = const Offset(0, 1);
|
|
||||||
|
|
||||||
late final AnimationController _fabAnimationCtr;
|
|
||||||
late final Animation<Offset> fabAnim;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fabAnimationCtr = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
)..forward();
|
|
||||||
fabAnim = _fabAnimationCtr.drive(
|
|
||||||
Tween<Offset>(
|
|
||||||
begin: fabOffset,
|
|
||||||
end: Offset.zero,
|
|
||||||
).chain(CurveTween(curve: Curves.easeInOut)),
|
|
||||||
);
|
|
||||||
scrollController = ScrollController()..addListener(listener);
|
scrollController = ScrollController()..addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,20 +53,6 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void showFab() {
|
|
||||||
if (!_showFab) {
|
|
||||||
_showFab = true;
|
|
||||||
_fabAnimationCtr.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void hideFab() {
|
|
||||||
if (_showFab) {
|
|
||||||
_showFab = false;
|
|
||||||
_fabAnimationCtr.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
@@ -98,7 +68,6 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
|||||||
scrollController
|
scrollController
|
||||||
..removeListener(listener)
|
..removeListener(listener)
|
||||||
..dispose();
|
..dispose();
|
||||||
_fabAnimationCtr.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +273,16 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
FloatingActionButtonLocation get floatingActionButtonLocation =>
|
||||||
|
controller.showDynActionBar
|
||||||
|
? const ActionBarLocation()
|
||||||
|
: const NoBottomPaddingFabLocation();
|
||||||
|
|
||||||
|
Widget get fabButton => Padding(
|
||||||
|
padding: .only(bottom: padding.bottom + kFloatingActionButtonMargin),
|
||||||
|
child: replyButton,
|
||||||
|
);
|
||||||
|
|
||||||
Widget get replyButton => FloatingActionButton(
|
Widget get replyButton => FloatingActionButton(
|
||||||
heroTag: null,
|
heroTag: null,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
78
lib/pages/common/fab_mixin.dart
Normal file
78
lib/pages/common/fab_mixin.dart
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
mixin FabMixin<T extends StatefulWidget> on State<T>, TickerProvider {
|
||||||
|
bool _isFabVisible = true;
|
||||||
|
late final AnimationController _fabAnimationCtr;
|
||||||
|
late final Animation<Offset> fabAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fabAnimationCtr = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
);
|
||||||
|
fabAnimation = _fabAnimationCtr.drive(
|
||||||
|
Tween<Offset>(
|
||||||
|
begin: Offset.zero,
|
||||||
|
end: const Offset(0.0, 1.0),
|
||||||
|
).chain(CurveTween(curve: Curves.easeInOut)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showFab() {
|
||||||
|
if (!_isFabVisible) {
|
||||||
|
_isFabVisible = true;
|
||||||
|
_fabAnimationCtr.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hideFab() {
|
||||||
|
if (_isFabVisible) {
|
||||||
|
_isFabVisible = false;
|
||||||
|
_fabAnimationCtr.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_fabAnimationCtr.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin _NoRightMarginMixin on StandardFabLocation {
|
||||||
|
@override
|
||||||
|
double getOffsetX(scaffoldGeometry, _) {
|
||||||
|
return scaffoldGeometry.scaffoldSize.width -
|
||||||
|
scaffoldGeometry.minInsets.right -
|
||||||
|
scaffoldGeometry.floatingActionButtonSize.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin _NoBottomPaddingMixin on StandardFabLocation {
|
||||||
|
@override
|
||||||
|
double getOffsetY(scaffoldGeometry, _) {
|
||||||
|
return scaffoldGeometry.contentBottom -
|
||||||
|
scaffoldGeometry.floatingActionButtonSize.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoRightMarginFabLocation extends StandardFabLocation
|
||||||
|
with FabFloatOffsetY, _NoRightMarginMixin {
|
||||||
|
const NoRightMarginFabLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoBottomPaddingFabLocation extends StandardFabLocation
|
||||||
|
with FabEndOffsetX, _NoBottomPaddingMixin {
|
||||||
|
const NoBottomPaddingFabLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActionBarLocation extends StandardFabLocation with _NoBottomPaddingMixin {
|
||||||
|
const ActionBarLocation();
|
||||||
|
|
||||||
|
@override
|
||||||
|
double getOffsetX(scaffoldGeometry, _) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import 'package:PiliPlus/http/reply.dart';
|
|||||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||||
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
||||||
import 'package:PiliPlus/utils/id_utils.dart';
|
import 'package:PiliPlus/utils/id_utils.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -15,8 +14,6 @@ class DynamicDetailController extends CommonDynController {
|
|||||||
late int replyType;
|
late int replyType;
|
||||||
late DynamicItemModel dynItem;
|
late DynamicItemModel dynItem;
|
||||||
|
|
||||||
late final showDynActionBar = Pref.showDynActionBar;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
dynamic get sourceId => replyType == 1 ? IdUtils.av2bv(oid) : oid;
|
dynamic get sourceId => replyType == 1 ? IdUtils.av2bv(oid) : oid;
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
)
|
)
|
||||||
: _buildBody(theme),
|
: _buildBody(theme),
|
||||||
),
|
),
|
||||||
|
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||||
|
floatingActionButton: SlideTransition(
|
||||||
|
position: fabAnimation,
|
||||||
|
child: _buildBottom(theme),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,166 +324,139 @@ class _DynamicDetailPageState extends CommonDynPageState<DynamicDetailPage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Stack(
|
return child;
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: [
|
|
||||||
child,
|
|
||||||
_buildBottom(theme),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBottom(ThemeData theme) {
|
Widget _buildBottom(ThemeData theme) {
|
||||||
return Positioned(
|
if (!controller.showDynActionBar) {
|
||||||
left: 0,
|
return fabButton;
|
||||||
right: 0,
|
}
|
||||||
bottom: 0,
|
|
||||||
child: SlideTransition(
|
final primary = theme.colorScheme.primary;
|
||||||
position: fabAnim,
|
final outline = theme.colorScheme.outline;
|
||||||
child: Builder(
|
final btnStyle = TextButton.styleFrom(
|
||||||
builder: (context) {
|
tapTargetSize: .padded,
|
||||||
if (!controller.showDynActionBar) {
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
return Align(
|
foregroundColor: outline,
|
||||||
alignment: Alignment.bottomRight,
|
);
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
Widget textIconButton({
|
||||||
right: kFloatingActionButtonMargin,
|
required IconData icon,
|
||||||
bottom: padding.bottom + kFloatingActionButtonMargin,
|
required String text,
|
||||||
|
required DynamicStat? stat,
|
||||||
|
required ValueChanged<Color> onPressed,
|
||||||
|
IconData? activatedIcon,
|
||||||
|
}) {
|
||||||
|
final status = stat?.status == true;
|
||||||
|
final color = status ? primary : outline;
|
||||||
|
final iconWidget = Icon(
|
||||||
|
status ? activatedIcon : icon,
|
||||||
|
size: 16,
|
||||||
|
color: color,
|
||||||
|
);
|
||||||
|
return TextButton.icon(
|
||||||
|
onPressed: () => onPressed(iconWidget.color!),
|
||||||
|
icon: iconWidget,
|
||||||
|
style: btnStyle,
|
||||||
|
label: Text(
|
||||||
|
stat?.count != null ? NumUtils.numFormat(stat!.count) : text,
|
||||||
|
style: TextStyle(color: color),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final moduleStat = controller.dynItem.modules.moduleStat;
|
||||||
|
return Padding(
|
||||||
|
padding: .only(left: padding.left, right: padding.right),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: kFloatingActionButtonMargin,
|
||||||
|
bottom: kFloatingActionButtonMargin,
|
||||||
|
),
|
||||||
|
child: replyButton,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.surface,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: theme.colorScheme.outline.withValues(
|
||||||
|
alpha: 0.08,
|
||||||
),
|
),
|
||||||
child: replyButton,
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
),
|
||||||
|
padding: EdgeInsets.only(bottom: padding.bottom),
|
||||||
final moduleStat = controller.dynItem.modules.moduleStat;
|
child: Row(
|
||||||
final primary = theme.colorScheme.primary;
|
|
||||||
final outline = theme.colorScheme.outline;
|
|
||||||
final btnStyle = TextButton.styleFrom(
|
|
||||||
tapTargetSize: .padded,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
|
||||||
foregroundColor: outline,
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget textIconButton({
|
|
||||||
required IconData icon,
|
|
||||||
required String text,
|
|
||||||
required DynamicStat? stat,
|
|
||||||
required ValueChanged<Color> onPressed,
|
|
||||||
IconData? activatedIcon,
|
|
||||||
}) {
|
|
||||||
final status = stat?.status == true;
|
|
||||||
final color = status ? primary : outline;
|
|
||||||
final iconWidget = Icon(
|
|
||||||
status ? activatedIcon : icon,
|
|
||||||
size: 16,
|
|
||||||
color: color,
|
|
||||||
);
|
|
||||||
return TextButton.icon(
|
|
||||||
onPressed: () => onPressed(iconWidget.color!),
|
|
||||||
icon: iconWidget,
|
|
||||||
style: btnStyle,
|
|
||||||
label: Text(
|
|
||||||
stat?.count != null ? NumUtils.numFormat(stat!.count) : text,
|
|
||||||
style: TextStyle(color: color),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Expanded(
|
||||||
padding: const EdgeInsets.only(
|
child: Builder(
|
||||||
right: kFloatingActionButtonMargin,
|
builder: (btnContext) {
|
||||||
bottom: kFloatingActionButtonMargin,
|
final forward = moduleStat?.forward;
|
||||||
),
|
return textIconButton(
|
||||||
child: replyButton,
|
icon: FontAwesomeIcons.shareFromSquare,
|
||||||
),
|
text: '转发',
|
||||||
Container(
|
stat: forward,
|
||||||
decoration: BoxDecoration(
|
onPressed: (_) => showModalBottomSheet(
|
||||||
color: theme.colorScheme.surface,
|
context: context,
|
||||||
border: Border(
|
isScrollControlled: true,
|
||||||
top: BorderSide(
|
useSafeArea: true,
|
||||||
color: theme.colorScheme.outline.withValues(
|
builder: (context) => RepostPanel(
|
||||||
alpha: 0.08,
|
item: controller.dynItem,
|
||||||
),
|
onSuccess: () {
|
||||||
),
|
if (forward != null) {
|
||||||
),
|
int count = forward.count ?? 0;
|
||||||
),
|
forward.count = count + 1;
|
||||||
padding: EdgeInsets.only(bottom: padding.bottom),
|
if (btnContext.mounted) {
|
||||||
child: Row(
|
(btnContext as Element).markNeedsBuild();
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
}
|
||||||
children: [
|
}
|
||||||
Expanded(
|
},
|
||||||
child: Builder(
|
|
||||||
builder: (btnContext) {
|
|
||||||
final forward = moduleStat?.forward;
|
|
||||||
return textIconButton(
|
|
||||||
icon: FontAwesomeIcons.shareFromSquare,
|
|
||||||
text: '转发',
|
|
||||||
stat: forward,
|
|
||||||
onPressed: (_) => showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
useSafeArea: true,
|
|
||||||
builder: (context) => RepostPanel(
|
|
||||||
item: controller.dynItem,
|
|
||||||
onSuccess: () {
|
|
||||||
if (forward != null) {
|
|
||||||
int count = forward.count ?? 0;
|
|
||||||
forward.count = count + 1;
|
|
||||||
if (btnContext.mounted) {
|
|
||||||
(btnContext as Element)
|
|
||||||
.markNeedsBuild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: textIconButton(
|
|
||||||
icon: CustomIcons.share_node,
|
|
||||||
text: '分享',
|
|
||||||
stat: null,
|
|
||||||
onPressed: (_) => Utils.shareText(
|
|
||||||
'${HttpString.dynamicShareBaseUrl}/${controller.dynItem.idStr}',
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
Expanded(
|
},
|
||||||
child: Builder(
|
),
|
||||||
builder: (context) {
|
),
|
||||||
return textIconButton(
|
Expanded(
|
||||||
icon: FontAwesomeIcons.thumbsUp,
|
child: textIconButton(
|
||||||
activatedIcon: FontAwesomeIcons.solidThumbsUp,
|
icon: CustomIcons.share_node,
|
||||||
text: '点赞',
|
text: '分享',
|
||||||
stat: moduleStat?.like,
|
stat: null,
|
||||||
onPressed: (iconColor) =>
|
onPressed: (_) => Utils.shareText(
|
||||||
RequestUtils.onLikeDynamic(
|
'${HttpString.dynamicShareBaseUrl}/${controller.dynItem.idStr}',
|
||||||
controller.dynItem,
|
),
|
||||||
iconColor == primary,
|
),
|
||||||
() {
|
),
|
||||||
if (context.mounted) {
|
Expanded(
|
||||||
(context as Element).markNeedsBuild();
|
child: Builder(
|
||||||
}
|
builder: (context) {
|
||||||
},
|
return textIconButton(
|
||||||
),
|
icon: FontAwesomeIcons.thumbsUp,
|
||||||
);
|
activatedIcon: FontAwesomeIcons.solidThumbsUp,
|
||||||
|
text: '点赞',
|
||||||
|
stat: moduleStat?.like,
|
||||||
|
onPressed: (iconColor) => RequestUtils.onLikeDynamic(
|
||||||
|
controller.dynItem,
|
||||||
|
iconColor == primary,
|
||||||
|
() {
|
||||||
|
if (context.mounted) {
|
||||||
|
(context as Element).markNeedsBuild();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import 'package:PiliPlus/http/loading_state.dart';
|
|||||||
import 'package:PiliPlus/models/common/fav_order_type.dart';
|
import 'package:PiliPlus/models/common/fav_order_type.dart';
|
||||||
import 'package:PiliPlus/models_new/fav/fav_detail/media.dart';
|
import 'package:PiliPlus/models_new/fav/fav_detail/media.dart';
|
||||||
import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
|
import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
|
||||||
|
import 'package:PiliPlus/pages/common/fab_mixin.dart'
|
||||||
|
show NoRightMarginFabLocation;
|
||||||
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
|
||||||
import 'package:PiliPlus/pages/fav_detail/controller.dart';
|
import 'package:PiliPlus/pages/fav_detail/controller.dart';
|
||||||
import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart';
|
import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart';
|
||||||
@@ -58,7 +60,7 @@ class _FavDetailPageState extends State<FavDetailPage> with GridMixin {
|
|||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
floatingActionButtonLocation: const CustomFabLocation(),
|
floatingActionButtonLocation: const NoRightMarginFabLocation(),
|
||||||
floatingActionButton: Padding(
|
floatingActionButton: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
right: kFloatingActionButtonMargin,
|
right: kFloatingActionButtonMargin,
|
||||||
@@ -507,18 +509,3 @@ class _FavDetailPageState extends State<FavDetailPage> with GridMixin {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CustomFabLocation extends StandardFabLocation with FabFloatOffsetY {
|
|
||||||
const CustomFabLocation();
|
|
||||||
|
|
||||||
@override
|
|
||||||
double getOffsetX(
|
|
||||||
ScaffoldPrelayoutGeometry scaffoldGeometry,
|
|
||||||
double adjustment,
|
|
||||||
) {
|
|
||||||
return scaffoldGeometry.scaffoldSize.width -
|
|
||||||
scaffoldGeometry.minInsets.right -
|
|
||||||
scaffoldGeometry.floatingActionButtonSize.width +
|
|
||||||
adjustment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/view_safe_area.dart';
|
import 'package:PiliPlus/common/widgets/view_safe_area.dart';
|
||||||
import 'package:PiliPlus/models/common/later_view_type.dart';
|
import 'package:PiliPlus/models/common/later_view_type.dart';
|
||||||
import 'package:PiliPlus/models_new/later/list.dart';
|
import 'package:PiliPlus/models_new/later/list.dart';
|
||||||
import 'package:PiliPlus/pages/fav_detail/view.dart';
|
import 'package:PiliPlus/pages/common/fab_mixin.dart'
|
||||||
|
show NoRightMarginFabLocation;
|
||||||
import 'package:PiliPlus/pages/later/base_controller.dart';
|
import 'package:PiliPlus/pages/later/base_controller.dart';
|
||||||
import 'package:PiliPlus/pages/later/controller.dart';
|
import 'package:PiliPlus/pages/later/controller.dart';
|
||||||
import 'package:PiliPlus/utils/accounts.dart';
|
import 'package:PiliPlus/utils/accounts.dart';
|
||||||
@@ -74,7 +75,7 @@ class _LaterPageState extends State<LaterPage>
|
|||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
appBar: _buildAppbar(enableMultiSelect),
|
appBar: _buildAppbar(enableMultiSelect),
|
||||||
floatingActionButtonLocation: const CustomFabLocation(),
|
floatingActionButtonLocation: const NoRightMarginFabLocation(),
|
||||||
floatingActionButton: Padding(
|
floatingActionButton: Padding(
|
||||||
padding: const .only(right: kFloatingActionButtonMargin),
|
padding: const .only(right: kFloatingActionButtonMargin),
|
||||||
child: Obx(
|
child: Obx(
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import 'package:PiliPlus/utils/video_utils.dart';
|
|||||||
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -373,9 +372,9 @@ class LiveRoomController extends GetxController {
|
|||||||
|
|
||||||
void listener() {
|
void listener() {
|
||||||
final userScrollDirection = scrollController.position.userScrollDirection;
|
final userScrollDirection = scrollController.position.userScrollDirection;
|
||||||
if (userScrollDirection == ScrollDirection.forward) {
|
if (userScrollDirection == .forward) {
|
||||||
disableAutoScroll.value = true;
|
disableAutoScroll.value = true;
|
||||||
} else if (userScrollDirection == ScrollDirection.reverse) {
|
} else if (userScrollDirection == .reverse) {
|
||||||
final pos = scrollController.position;
|
final pos = scrollController.position;
|
||||||
if (pos.maxScrollExtent - pos.pixels <= 100) {
|
if (pos.maxScrollExtent - pos.pixels <= 100) {
|
||||||
disableAutoScroll.value = false;
|
disableAutoScroll.value = false;
|
||||||
|
|||||||
@@ -3,35 +3,18 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
|||||||
import 'package:PiliPlus/grpc/reply.dart';
|
import 'package:PiliPlus/grpc/reply.dart';
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class MainReplyController extends ReplyController<MainListReply>
|
class MainReplyController extends ReplyController<MainListReply> {
|
||||||
with GetSingleTickerProviderStateMixin {
|
|
||||||
late final int oid;
|
late final int oid;
|
||||||
late final int replyType;
|
late final int replyType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get sourceId => oid;
|
int get sourceId => oid;
|
||||||
|
|
||||||
bool _showFab = true;
|
|
||||||
|
|
||||||
late final AnimationController _fabAnimationCtr;
|
|
||||||
late final Animation<Offset> fabAnim;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
_fabAnimationCtr = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
)..forward();
|
|
||||||
fabAnim = _fabAnimationCtr.drive(
|
|
||||||
Tween<Offset>(
|
|
||||||
begin: const Offset(0.0, 2.0),
|
|
||||||
end: Offset.zero,
|
|
||||||
).chain(CurveTween(curve: Curves.easeInOut)),
|
|
||||||
);
|
|
||||||
final args = Get.arguments;
|
final args = Get.arguments;
|
||||||
oid = args['oid'];
|
oid = args['oid'];
|
||||||
replyType = args['replyType'];
|
replyType = args['replyType'];
|
||||||
@@ -39,20 +22,6 @@ class MainReplyController extends ReplyController<MainListReply>
|
|||||||
queryData();
|
queryData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void showFab() {
|
|
||||||
if (!_showFab) {
|
|
||||||
_showFab = true;
|
|
||||||
_fabAnimationCtr.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void hideFab() {
|
|
||||||
if (_showFab) {
|
|
||||||
_showFab = false;
|
|
||||||
_fabAnimationCtr.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
|
Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
|
||||||
type: replyType,
|
type: replyType,
|
||||||
@@ -64,10 +33,4 @@ class MainReplyController extends ReplyController<MainListReply>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<ReplyInfo>? getDataList(MainListReply response) => response.replies;
|
List<ReplyInfo>? getDataList(MainListReply response) => response.replies;
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
_fabAnimationCtr.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:PiliPlus/common/widgets/view_safe_area.dart';
|
|||||||
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
||||||
show ReplyInfo;
|
show ReplyInfo;
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/pages/common/fab_mixin.dart';
|
||||||
import 'package:PiliPlus/pages/main_reply/controller.dart';
|
import 'package:PiliPlus/pages/main_reply/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
||||||
@@ -38,7 +39,8 @@ class MainReplyPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MainReplyPageState extends State<MainReplyPage> {
|
class _MainReplyPageState extends State<MainReplyPage>
|
||||||
|
with SingleTickerProviderStateMixin, FabMixin {
|
||||||
final _controller = Get.put(
|
final _controller = Get.put(
|
||||||
MainReplyController(),
|
MainReplyController(),
|
||||||
tag: Utils.generateRandomString(8),
|
tag: Utils.generateRandomString(8),
|
||||||
@@ -62,9 +64,9 @@ class _MainReplyPageState extends State<MainReplyPage> {
|
|||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
final direction = notification.direction;
|
final direction = notification.direction;
|
||||||
if (direction == .forward) {
|
if (direction == .forward) {
|
||||||
_controller.showFab();
|
showFab();
|
||||||
} else if (direction == .reverse) {
|
} else if (direction == .reverse) {
|
||||||
_controller.hideFab();
|
hideFab();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
@@ -87,22 +89,26 @@ class _MainReplyPageState extends State<MainReplyPage> {
|
|||||||
),
|
),
|
||||||
).constraintWidth(),
|
).constraintWidth(),
|
||||||
),
|
),
|
||||||
|
floatingActionButtonLocation: const NoBottomPaddingFabLocation(),
|
||||||
floatingActionButton: SlideTransition(
|
floatingActionButton: SlideTransition(
|
||||||
position: _controller.fabAnim,
|
position: fabAnimation,
|
||||||
child: FloatingActionButton(
|
child: Padding(
|
||||||
heroTag: null,
|
padding: .only(bottom: padding.bottom + kFloatingActionButtonMargin),
|
||||||
onPressed: () {
|
child: FloatingActionButton(
|
||||||
try {
|
heroTag: null,
|
||||||
feedBack();
|
onPressed: () {
|
||||||
_controller.onReply(
|
try {
|
||||||
null,
|
feedBack();
|
||||||
oid: _controller.oid,
|
_controller.onReply(
|
||||||
replyType: _controller.replyType,
|
null,
|
||||||
);
|
oid: _controller.oid,
|
||||||
} catch (_) {}
|
replyType: _controller.replyType,
|
||||||
},
|
);
|
||||||
tooltip: '评论',
|
} catch (_) {}
|
||||||
child: const Icon(Icons.reply),
|
},
|
||||||
|
tooltip: '评论',
|
||||||
|
child: const Icon(Icons.reply),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import 'package:PiliPlus/models/common/image_type.dart';
|
|||||||
import 'package:PiliPlus/models_new/match/match_info/contest.dart';
|
import 'package:PiliPlus/models_new/match/match_info/contest.dart';
|
||||||
import 'package:PiliPlus/models_new/match/match_info/team.dart';
|
import 'package:PiliPlus/models_new/match/match_info/team.dart';
|
||||||
import 'package:PiliPlus/pages/common/dyn/common_dyn_page.dart';
|
import 'package:PiliPlus/pages/common/dyn/common_dyn_page.dart';
|
||||||
|
import 'package:PiliPlus/pages/common/fab_mixin.dart'
|
||||||
|
show NoBottomPaddingFabLocation;
|
||||||
import 'package:PiliPlus/pages/match_info/controller.dart';
|
import 'package:PiliPlus/pages/match_info/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
||||||
import 'package:PiliPlus/utils/date_utils.dart';
|
import 'package:PiliPlus/utils/date_utils.dart';
|
||||||
@@ -36,9 +38,6 @@ class _MatchInfoPageState extends CommonDynPageState<MatchInfoPage> {
|
|||||||
@override
|
@override
|
||||||
dynamic get arguments => null;
|
dynamic get arguments => null;
|
||||||
|
|
||||||
@override
|
|
||||||
Offset get fabOffset => const Offset(0, 2);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
@@ -59,9 +58,10 @@ class _MatchInfoPageState extends CommonDynPageState<MatchInfoPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
).constraintWidth(),
|
).constraintWidth(),
|
||||||
|
floatingActionButtonLocation: const NoBottomPaddingFabLocation(),
|
||||||
floatingActionButton: SlideTransition(
|
floatingActionButton: SlideTransition(
|
||||||
position: fabAnim,
|
position: fabAnimation,
|
||||||
child: replyButton,
|
child: fabButton,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:PiliPlus/http/loading_state.dart';
|
|||||||
import 'package:PiliPlus/http/music.dart';
|
import 'package:PiliPlus/http/music.dart';
|
||||||
import 'package:PiliPlus/models_new/music/bgm_detail.dart';
|
import 'package:PiliPlus/models_new/music/bgm_detail.dart';
|
||||||
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class MusicDetailController extends CommonDynController {
|
class MusicDetailController extends CommonDynController {
|
||||||
@@ -18,8 +17,6 @@ class MusicDetailController extends CommonDynController {
|
|||||||
|
|
||||||
late final String musicId;
|
late final String musicId;
|
||||||
|
|
||||||
bool get showDynActionBar => Pref.showDynActionBar;
|
|
||||||
|
|
||||||
String get shareUrl =>
|
String get shareUrl =>
|
||||||
'https://music.bilibili.com/h5/music-detail?music_id=$musicId';
|
'https://music.bilibili.com/h5/music-detail?music_id=$musicId';
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,24 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Widget _buildBottom(ThemeData theme, MusicDetail item) {
|
Widget _buildBottom(ThemeData theme, MusicDetail item) {
|
||||||
|
if (!controller.showDynActionBar) {
|
||||||
|
return Positioned(
|
||||||
|
right: kFloatingActionButtonMargin,
|
||||||
|
bottom: 0,
|
||||||
|
child: SlideTransition(
|
||||||
|
position: fabAnimation,
|
||||||
|
child: fabButton,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final primary = theme.colorScheme.primary;
|
||||||
final outline = theme.colorScheme.outline;
|
final outline = theme.colorScheme.outline;
|
||||||
|
final style = TextButton.styleFrom(
|
||||||
|
tapTargetSize: .padded,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
foregroundColor: outline,
|
||||||
|
);
|
||||||
|
|
||||||
Widget textIconButton({
|
Widget textIconButton({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
@@ -214,7 +231,7 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
IconData? activatedIcon,
|
IconData? activatedIcon,
|
||||||
}) {
|
}) {
|
||||||
final color = status ? theme.colorScheme.primary : outline;
|
final color = status ? primary : outline;
|
||||||
return TextButton.icon(
|
return TextButton.icon(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
@@ -222,11 +239,7 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
size: 16,
|
size: 16,
|
||||||
color: color,
|
color: color,
|
||||||
),
|
),
|
||||||
style: TextButton.styleFrom(
|
style: style,
|
||||||
tapTargetSize: .padded,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
|
||||||
foregroundColor: outline,
|
|
||||||
),
|
|
||||||
label: Text(
|
label: Text(
|
||||||
count != null ? NumUtils.numFormat(count) : text,
|
count != null ? NumUtils.numFormat(count) : text,
|
||||||
style: TextStyle(color: color),
|
style: TextStyle(color: color),
|
||||||
@@ -239,116 +252,104 @@ class _MusicDetailPageState extends CommonDynPageState<MusicDetailPage> {
|
|||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
position: fabAnim,
|
position: fabAnimation,
|
||||||
child: controller.showDynActionBar
|
child: Column(
|
||||||
? Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
children: [
|
||||||
children: [
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.only(
|
||||||
padding: const EdgeInsets.only(
|
right: kFloatingActionButtonMargin,
|
||||||
right: kFloatingActionButtonMargin,
|
bottom: kFloatingActionButtonMargin,
|
||||||
bottom: kFloatingActionButtonMargin,
|
),
|
||||||
|
child: replyButton,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.surface,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: theme.colorScheme.outline.withValues(
|
||||||
|
alpha: 0.08,
|
||||||
),
|
),
|
||||||
child: replyButton,
|
|
||||||
),
|
),
|
||||||
Container(
|
),
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
color: theme.colorScheme.surface,
|
padding: EdgeInsets.only(bottom: padding.bottom),
|
||||||
border: Border(
|
child: Row(
|
||||||
top: BorderSide(
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
color: theme.colorScheme.outline.withValues(
|
children: [
|
||||||
alpha: 0.08,
|
// TODO
|
||||||
),
|
// Expanded(
|
||||||
),
|
// child: textIconButton(
|
||||||
),
|
// icon: FontAwesomeIcons.shareFromSquare,
|
||||||
|
// text: '转发',
|
||||||
|
// count: item.musicShares,
|
||||||
|
// onPressed: () {
|
||||||
|
// final data = controller.infoState.value.dataOrNull;
|
||||||
|
// if (data != null) {
|
||||||
|
// showModalBottomSheet(
|
||||||
|
// context: context,
|
||||||
|
// isScrollControlled: true,
|
||||||
|
// useSafeArea: true,
|
||||||
|
// builder: (context) => RepostPanel(
|
||||||
|
// rid: controller.oid,
|
||||||
|
// dynType: null,
|
||||||
|
// pic: data.mvCover,
|
||||||
|
// title: data.musicTitle,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
Expanded(
|
||||||
|
child: textIconButton(
|
||||||
|
icon: CustomIcons.share_node,
|
||||||
|
text: '分享',
|
||||||
|
onPressed: () => Utils.shareText(controller.shareUrl),
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.only(bottom: padding.bottom),
|
),
|
||||||
child: Row(
|
Expanded(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
child: Builder(
|
||||||
children: [
|
builder: (context) => textIconButton(
|
||||||
// TODO
|
icon: FontAwesomeIcons.thumbsUp,
|
||||||
// Expanded(
|
activatedIcon: FontAwesomeIcons.solidThumbsUp,
|
||||||
// child: textIconButton(
|
text: '点赞',
|
||||||
// icon: FontAwesomeIcons.shareFromSquare,
|
count: item.wishCount,
|
||||||
// text: '转发',
|
status: item.wishListen ?? false,
|
||||||
// count: item.musicShares,
|
onPressed: () async {
|
||||||
// onPressed: () {
|
if (!Accounts.main.isLogin) {
|
||||||
// final data = controller.infoState.value.dataOrNull;
|
SmartDialog.showToast('请先登录');
|
||||||
// if (data != null) {
|
return;
|
||||||
// showModalBottomSheet(
|
}
|
||||||
// context: context,
|
final hasLike = item.wishListen ?? false;
|
||||||
// isScrollControlled: true,
|
final res = await MusicHttp.wishUpdate(
|
||||||
// useSafeArea: true,
|
controller.musicId,
|
||||||
// builder: (context) => RepostPanel(
|
hasLike,
|
||||||
// rid: controller.oid,
|
);
|
||||||
// dynType: null,
|
if (res.isSuccess) {
|
||||||
// pic: data.mvCover,
|
if (hasLike) {
|
||||||
// title: data.musicTitle,
|
item.wishCount--;
|
||||||
// ),
|
} else {
|
||||||
// );
|
item.wishCount++;
|
||||||
// }
|
}
|
||||||
// },
|
item.wishListen = !hasLike;
|
||||||
// ),
|
if (context.mounted) {
|
||||||
// ),
|
(context as Element).markNeedsBuild();
|
||||||
Expanded(
|
}
|
||||||
child: textIconButton(
|
} else {
|
||||||
icon: CustomIcons.share_node,
|
res.toast();
|
||||||
text: '分享',
|
}
|
||||||
onPressed: () =>
|
},
|
||||||
Utils.shareText(controller.shareUrl),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) => textIconButton(
|
|
||||||
icon: FontAwesomeIcons.thumbsUp,
|
|
||||||
activatedIcon: FontAwesomeIcons.solidThumbsUp,
|
|
||||||
text: '点赞',
|
|
||||||
count: item.wishCount,
|
|
||||||
status: item.wishListen ?? false,
|
|
||||||
onPressed: () async {
|
|
||||||
if (!Accounts.main.isLogin) {
|
|
||||||
SmartDialog.showToast('请先登录');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final hasLike = item.wishListen ?? false;
|
|
||||||
final res = await MusicHttp.wishUpdate(
|
|
||||||
controller.musicId,
|
|
||||||
hasLike,
|
|
||||||
);
|
|
||||||
if (res.isSuccess) {
|
|
||||||
if (hasLike) {
|
|
||||||
item.wishCount--;
|
|
||||||
} else {
|
|
||||||
item.wishCount++;
|
|
||||||
}
|
|
||||||
item.wishListen = !hasLike;
|
|
||||||
if (context.mounted) {
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.toast();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
|
||||||
: Align(
|
|
||||||
alignment: Alignment.bottomRight,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
right: kFloatingActionButtonMargin,
|
|
||||||
bottom: padding.bottom + kFloatingActionButtonMargin,
|
|
||||||
),
|
|
||||||
child: replyButton,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import 'package:PiliPlus/models/common/video/video_type.dart';
|
|||||||
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/controller.dart';
|
import 'package:PiliPlus/pages/video/controller.dart';
|
||||||
import 'package:PiliPlus/utils/id_utils.dart';
|
import 'package:PiliPlus/utils/id_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class VideoReplyController extends ReplyController<MainListReply>
|
class VideoReplyController extends ReplyController<MainListReply> {
|
||||||
with GetSingleTickerProviderStateMixin {
|
|
||||||
VideoReplyController({
|
VideoReplyController({
|
||||||
required this.aid,
|
required this.aid,
|
||||||
required this.videoType,
|
required this.videoType,
|
||||||
@@ -26,39 +24,6 @@ class VideoReplyController extends ReplyController<MainListReply>
|
|||||||
@override
|
@override
|
||||||
dynamic get sourceId => IdUtils.av2bv(aid);
|
dynamic get sourceId => IdUtils.av2bv(aid);
|
||||||
|
|
||||||
bool _isFabVisible = true;
|
|
||||||
late final AnimationController _fabAnimationCtr;
|
|
||||||
late final Animation<Offset> animation;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
_fabAnimationCtr = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: const Duration(milliseconds: 100),
|
|
||||||
)..forward();
|
|
||||||
animation = _fabAnimationCtr.drive(
|
|
||||||
Tween<Offset>(
|
|
||||||
begin: const Offset(0.0, 2.0),
|
|
||||||
end: Offset.zero,
|
|
||||||
).chain(CurveTween(curve: Curves.easeInOut)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showFab() {
|
|
||||||
if (!_isFabVisible) {
|
|
||||||
_isFabVisible = true;
|
|
||||||
_fabAnimationCtr.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void hideFab() {
|
|
||||||
if (_isFabVisible) {
|
|
||||||
_isFabVisible = false;
|
|
||||||
_fabAnimationCtr.reverse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<ReplyInfo>? getDataList(MainListReply response) {
|
List<ReplyInfo>? getDataList(MainListReply response) {
|
||||||
return response.replies;
|
return response.replies;
|
||||||
@@ -72,10 +37,4 @@ class VideoReplyController extends ReplyController<MainListReply>
|
|||||||
cursorNext: cursorNext,
|
cursorNext: cursorNext,
|
||||||
offset: paginationReply?.nextOffset,
|
offset: paginationReply?.nextOffset,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
|
||||||
void onClose() {
|
|
||||||
_fabAnimationCtr.dispose();
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart';
|
|||||||
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
||||||
show ReplyInfo;
|
show ReplyInfo;
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/pages/common/fab_mixin.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply/controller.dart';
|
import 'package:PiliPlus/pages/video/reply/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
||||||
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
|
||||||
@@ -13,7 +14,6 @@ import 'package:PiliPlus/utils/feed_back.dart';
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class VideoReplyPanel extends StatefulWidget {
|
class VideoReplyPanel extends StatefulWidget {
|
||||||
@@ -33,7 +33,10 @@ class VideoReplyPanel extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _VideoReplyPanelState extends State<VideoReplyPanel>
|
class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with
|
||||||
|
AutomaticKeepAliveClientMixin,
|
||||||
|
SingleTickerProviderStateMixin,
|
||||||
|
FabMixin {
|
||||||
late VideoReplyController _videoReplyController;
|
late VideoReplyController _videoReplyController;
|
||||||
|
|
||||||
String get heroTag => widget.heroTag;
|
String get heroTag => widget.heroTag;
|
||||||
@@ -64,11 +67,12 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final child = NotificationListener<UserScrollNotification>(
|
final child = NotificationListener<UserScrollNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
final direction = notification.direction;
|
switch (notification.direction) {
|
||||||
if (direction == ScrollDirection.forward) {
|
case .forward:
|
||||||
_videoReplyController.showFab();
|
showFab();
|
||||||
} else if (direction == ScrollDirection.reverse) {
|
case .reverse:
|
||||||
_videoReplyController.hideFab();
|
hideFab();
|
||||||
|
case _:
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
@@ -129,22 +133,28 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: kFloatingActionButtonMargin,
|
right: 0,
|
||||||
bottom: kFloatingActionButtonMargin + bottom,
|
bottom: 0,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
position: _videoReplyController.animation,
|
position: fabAnimation,
|
||||||
child: FloatingActionButton(
|
child: Padding(
|
||||||
heroTag: null,
|
padding: .only(
|
||||||
onPressed: () {
|
right: kFloatingActionButtonMargin,
|
||||||
feedBack();
|
bottom: kFloatingActionButtonMargin + bottom,
|
||||||
_videoReplyController.onReply(
|
),
|
||||||
null,
|
child: FloatingActionButton(
|
||||||
oid: _videoReplyController.aid,
|
heroTag: null,
|
||||||
replyType: _videoReplyController.videoType.replyType,
|
onPressed: () {
|
||||||
);
|
feedBack();
|
||||||
},
|
_videoReplyController.onReply(
|
||||||
tooltip: '发表评论',
|
null,
|
||||||
child: const Icon(Icons.reply),
|
oid: _videoReplyController.aid,
|
||||||
|
replyType: _videoReplyController.videoType.replyType,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
tooltip: '发表评论',
|
||||||
|
child: const Icon(Icons.reply),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user