马良AI写作初始化仓库
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:ainoval/blocs/editor/editor_bloc.dart' as editor_bloc;
|
||||
import 'package:ainoval/utils/logger.dart';
|
||||
|
||||
/// 卷轴导航按钮组件
|
||||
/// 显示上一卷/下一卷/添加新卷按钮
|
||||
class VolumeNavigationButtons extends StatelessWidget {
|
||||
// 位置控制
|
||||
final bool isTop;
|
||||
|
||||
// 卷状态控制
|
||||
final bool isFirstAct;
|
||||
final bool isLastAct;
|
||||
final String? previousActTitle;
|
||||
final String? nextActTitle;
|
||||
|
||||
// 滚动状态
|
||||
final bool hasReachedStart;
|
||||
final bool hasReachedEnd;
|
||||
|
||||
// 加载状态
|
||||
final bool isLoadingMore;
|
||||
|
||||
// 回调
|
||||
final VoidCallback? onPreviousAct;
|
||||
final VoidCallback? onNextAct;
|
||||
final VoidCallback? onAddNewAct;
|
||||
|
||||
const VolumeNavigationButtons({
|
||||
Key? key,
|
||||
required this.isTop,
|
||||
required this.isFirstAct,
|
||||
required this.isLastAct,
|
||||
this.previousActTitle,
|
||||
this.nextActTitle,
|
||||
required this.hasReachedStart,
|
||||
required this.hasReachedEnd,
|
||||
this.isLoadingMore = false,
|
||||
this.onPreviousAct,
|
||||
this.onNextAct,
|
||||
this.onAddNewAct,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 始终记录底部按钮显示条件,方便调试
|
||||
if (!isTop) {
|
||||
AppLogger.i('VolumeNavigationButtons', '底部按钮条件: isLastAct=$isLastAct, hasReachedEnd=$hasReachedEnd');
|
||||
}
|
||||
|
||||
// 上方按钮显示条件:
|
||||
// 1. 是顶部按钮位置 (isTop)
|
||||
// 2. 不能是第一卷 (isFirstAct == false)
|
||||
final bool shouldShowTopButton = isTop && !isFirstAct;
|
||||
|
||||
// 下方按钮显示条件:
|
||||
// 1. 是底部按钮位置
|
||||
final bool shouldShowBottomButton = !isTop;
|
||||
|
||||
// 确定按钮类型
|
||||
// 顶部按钮永远是"上一卷"
|
||||
// 底部按钮在最后一卷时是"添加新卷",否则是"下一卷"
|
||||
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: Offset(0, isTop ? -0.5 : 0.5),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: (shouldShowTopButton || shouldShowBottomButton)
|
||||
? _buildButton(
|
||||
context,
|
||||
isTop: isTop,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildButton(
|
||||
BuildContext context, {
|
||||
required bool isTop,
|
||||
}) {
|
||||
final themeData = Theme.of(context);
|
||||
|
||||
// 安全地获取前一个和下一个卷的信息
|
||||
final String? prevVolumeName = isFirstAct ? null : previousActTitle;
|
||||
final String? nextVolumeName = this.isLastAct ? null : this.nextActTitle;
|
||||
|
||||
// 按钮文本
|
||||
late final String buttonText;
|
||||
late final IconData buttonIcon;
|
||||
late final VoidCallback? onPressed;
|
||||
|
||||
if (isTop) {
|
||||
// 顶部按钮:上一卷
|
||||
if (prevVolumeName == null) {
|
||||
buttonText = '返回首卷';
|
||||
} else {
|
||||
String displayName = prevVolumeName;
|
||||
if (displayName.length > 10) {
|
||||
displayName = displayName.substring(0, 10) + '...';
|
||||
}
|
||||
buttonText = '上一卷:$displayName';
|
||||
}
|
||||
buttonIcon = Icons.arrow_upward_rounded;
|
||||
onPressed = onPreviousAct;
|
||||
} else {
|
||||
if (this.isLastAct) {
|
||||
// 底部按钮:如果是最后一卷,则为"添加新卷"
|
||||
buttonText = '添加新卷';
|
||||
buttonIcon = Icons.add_rounded;
|
||||
onPressed = onAddNewAct;
|
||||
} else {
|
||||
// 底部按钮:如果不是最后一卷,则为"下一卷"
|
||||
if (nextVolumeName == null) {
|
||||
buttonText = '下一卷';
|
||||
} else {
|
||||
String displayName = nextVolumeName;
|
||||
if (displayName.length > 10) {
|
||||
displayName = displayName.substring(0, 10) + '...';
|
||||
}
|
||||
buttonText = '下一卷:$displayName';
|
||||
}
|
||||
buttonIcon = Icons.arrow_downward_rounded;
|
||||
onPressed = onNextAct;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建按钮
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: isTop ? 16.0 : 0.0,
|
||||
bottom: isTop ? 0.0 : 16.0,
|
||||
),
|
||||
child: Container(
|
||||
height: 60,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: themeData.cardColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
onTap: isLoadingMore ? null : onPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
isLoadingMore
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
buttonIcon,
|
||||
color: themeData.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
isLoadingMore ? '加载中...' : buttonText,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: themeData.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 构建加载指示器
|
||||
Widget _buildLoadingIndicator(ThemeData theme, String loadingText) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(theme.colorScheme.primary),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
loadingText,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user