马良AI写作初始化仓库

This commit is contained in:
邓滨杰
2025-09-10 00:07:52 +08:00
parent 3c06bb1a03
commit 39c0f8840f
1309 changed files with 318528 additions and 0 deletions

View File

@@ -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,
),
),
],
);
}
}