diff --git a/lib/utils/screenshot_util.dart b/lib/utils/screenshot_util.dart index 414b3af..6adef12 100644 --- a/lib/utils/screenshot_util.dart +++ b/lib/utils/screenshot_util.dart @@ -9,22 +9,85 @@ import 'package:share_plus/share_plus.dart'; /// 截图工具类 class ScreenshotUtil { + static double pixelRatio = 3.0; + static Duration delay = const Duration(milliseconds: 50); + static String watermarkText = '来自 食光集'; + // 截图功能 - static Future captureRecipeImage({ - required GlobalKey globalKey, - double pixelRatio = 3.0, - Duration delay = const Duration(milliseconds: 50), - }) async { + static Future captureImage({required GlobalKey globalKey}) async { // 等待一帧确保UI已渲染 await Future.delayed(delay); final RenderObject? render = globalKey.currentContext!.findRenderObject(); final RenderRepaintBoundary boundary = render as RenderRepaintBoundary; final ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); - final ByteData? byteData = await image.toByteData( + + // 添加水印 + return await _addWatermarkToImage(image); + } + + // 添加水印到图片 + static Future _addWatermarkToImage(ui.Image image) async { + // 创建画布 + final recorder = ui.PictureRecorder(); + final canvas = Canvas(recorder); + + // 绘制原始图片 + canvas.drawImage(image, Offset.zero, Paint()); + + // 添加文字水印 + _drawTextWatermark(canvas, image); + + // 生成最终图片 + final picture = recorder.endRecording(); + final watermarkedImage = await picture.toImage(image.width, image.height); + final ByteData? byteData = await watermarkedImage.toByteData( format: ui.ImageByteFormat.png, ); - return byteData?.buffer.asUint8List(); + return byteData!.buffer.asUint8List(); + } + + // 绘制文字水印 + static void _drawTextWatermark(Canvas canvas, ui.Image image) { + final textPainter = TextPainter( + text: TextSpan( + text: watermarkText, + style: TextStyle( + color: Color(0x99FFFFFF), + fontSize: 28, + fontFamily: 'CustomFont', + fontWeight: FontWeight.w600, + ), + ), + textDirection: TextDirection.ltr, + ); + + textPainter.layout(maxWidth: image.width.toDouble()); + + // 计算水印位置(右下角,带边距) + final double x = image.width - textPainter.width - 40; + final double y = image.height - textPainter.height - 40; + + // 绘制文字背景 + final backgroundPaint = + Paint() + ..color = const ui.Color(0x4D000000) + ..style = PaintingStyle.fill; + + // 绘制圆角矩形背景 + final backgroundRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + x - 12, + y - 6, + textPainter.width + 24, + textPainter.height + 12, + ), + Radius.circular(6), + ); + canvas.drawRRect(backgroundRect, backgroundPaint); + + // 绘制文字 + textPainter.paint(canvas, Offset(x, y)); } // 保存图片到临时文件 @@ -41,7 +104,7 @@ class ScreenshotUtil { required String shareText, }) async { try { - final imageBytes = await captureRecipeImage(globalKey: globalKey); + final imageBytes = await captureImage(globalKey: globalKey); if (imageBytes != null) { final imageFile = await saveImageToFile(imageBytes, fileName); diff --git a/lib/views/login.dart b/lib/views/login.dart index f2e0ad7..f9f2d74 100644 --- a/lib/views/login.dart +++ b/lib/views/login.dart @@ -5,7 +5,6 @@ import 'package:food_hub_app/models/session.dart'; import 'package:food_hub_app/utils/sp_util.dart'; import 'package:food_hub_app/widgets/common/index.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; -import 'package:tdesign_flutter/tdesign_flutter.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @@ -20,6 +19,7 @@ class _LoginPage extends State { String _username = "Cxx0822", _password = "19940822Cxx"; bool _isRemember = false; bool _isObscure = true; + bool _isLoading = false; Color _eyeColor = Colors.grey; final List _loginMethod = [ {"title": "phone", "icon": Icons.phone_android}, @@ -34,7 +34,7 @@ class _LoginPage extends State { } Future _loadRememberState() async { - bool? isRemember = await SPUtil.getBool('isRemember'); + bool? isRemember = SPUtil.getBool('isRemember'); if (isRemember) { setState(() { _isRemember = true; @@ -60,7 +60,11 @@ class _LoginPage extends State { SPUtil.set('token', token); } - void loginClick(BuildContext context) async { + void loginClick() async { + setState(() { + _isLoading = true; + }); + // 表单校验通过才会继续执行 if ((_formKey.currentState as FormState).validate()) { (_formKey.currentState as FormState).save(); @@ -70,9 +74,11 @@ class _LoginPage extends State { handleRememberState(); handleTokenState(session.saToken.tokenValue); Navigator.pushNamed(context, '/home'); - } else { - showErrorToast('请先输入信息'); } + + setState(() { + _isLoading = false; + }); } @override @@ -81,13 +87,9 @@ class _LoginPage extends State { backgroundColor: Colors.grey[100], body: Form( key: _formKey, - // autovalidateMode: AutovalidateMode.onUserInteraction, child: Column( children: [ - // 可滚动的主要内容区域 Expanded(child: buildLoginForm()), - - // 固定在底部的其他登录选项区域 Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 50), child: Column( @@ -123,18 +125,11 @@ class _LoginPage extends State { const SizedBox(height: 60), buildUsernameTextField(), const SizedBox(height: 20), - buildPasswordTextField(context), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - buildRememberPasswordCheckbox(context), // 记住密码(左对齐) - buildForgetPasswordText(context), - ], - ), - - const SizedBox(height: 20), // 调整与登录按钮的间距 - buildLoginButton(context), + buildPasswordTextField(), + const SizedBox(height: 10), + buildRememberPasswordCheckbox(), + const SizedBox(height: 20), + buildLoginButton(), ], ); } @@ -160,7 +155,7 @@ class _LoginPage extends State { ); } - Widget buildPasswordTextField(BuildContext context) { + Widget buildPasswordTextField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -198,10 +193,12 @@ class _LoginPage extends State { ); } - Widget buildRememberPasswordCheckbox(BuildContext context) { + Widget buildRememberPasswordCheckbox() { return Row( children: [ Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: VisualDensity.compact, value: _isRemember, onChanged: (bool? value) { setState(() { @@ -214,40 +211,40 @@ class _LoginPage extends State { ); } - Widget buildForgetPasswordText(BuildContext context) { - return Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: () { - print("忘记密码"); - }, - child: const Text( - "忘记密码?", - style: TextStyle(fontSize: 14, color: Colors.grey), + Widget buildLoginButton() { + return SizedBox( + height: 45, + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : loginClick, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Colors.white, + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), - ), - ); - } - - Widget buildLoginButton(BuildContext context) { - return Align( - child: SizedBox( - height: 45, - width: double.infinity, - child: TDButton( - text: '登录', - size: TDButtonSize.large, - type: TDButtonType.fill, - shape: TDButtonShape.rectangle, - theme: TDButtonTheme.primary, - onTap: () => loginClick(context), + child: const Text( + '登录', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), ), ); } Widget buildOtherLoginText() { - return TDDivider(text: '其他方式登录', alignment: TextAlignment.center); + return Row( + children: [ + Expanded(child: Divider(color: Colors.grey[300], thickness: 1)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + '其他方式登录', + style: TextStyle(color: Colors.grey, fontSize: 14), + ), + ), + Expanded(child: Divider(color: Colors.grey[300], thickness: 1)), + ], + ); } Widget buildOtherMethod(context) { diff --git a/lib/widgets/moment/card.dart b/lib/widgets/moment/card.dart index c87e477..dc3cfe0 100644 --- a/lib/widgets/moment/card.dart +++ b/lib/widgets/moment/card.dart @@ -33,6 +33,10 @@ class MomentCard extends StatelessWidget { _buildTime(), const SizedBox(height: 5), _buildActionButtons(), + if (moment.commentList!.isNotEmpty) ...[ + const SizedBox(height: 8), + _buildCommentList(), + ], ], ), ), @@ -189,4 +193,55 @@ class MomentCard extends StatelessWidget { ], ); } + + Widget _buildCommentItem(Comment comment) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + text: TextSpan( + children: [ + TextSpan( + text: comment.username, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + fontFamily: 'CustomFont', + color: Colors.black, + ), + ), + const TextSpan(text: ': ', style: TextStyle(color: Colors.black)), + TextSpan( + text: comment.content, + style: const TextStyle( + fontSize: 14, + fontFamily: 'CustomFont', + color: Colors.black87, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildCommentList() { + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: moment.commentList!.length, + separatorBuilder: (context, index) => SizedBox(height: 8), + itemBuilder: (context, index) { + return _buildCommentItem(moment.commentList![index]); + }, + ), + ); + } } diff --git a/lib/widgets/recipe/list.dart b/lib/widgets/recipe/list.dart index 8f129b2..5cdddd8 100644 --- a/lib/widgets/recipe/list.dart +++ b/lib/widgets/recipe/list.dart @@ -27,19 +27,18 @@ class _RecipeListState extends State { final colors = Theme.of(context).colorScheme; return Container( - width: 150, + width: 140, decoration: BoxDecoration( - color: Colors.white, + color: colors.surfaceContainer, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), padding: EdgeInsets.symmetric(horizontal: 16), child: DropdownButton( value: provider.queryCategory, - hint: Text('请选择菜谱类别', style: TextStyle(color: Colors.grey[500])), isExpanded: true, borderRadius: BorderRadius.circular(12), - dropdownColor: Colors.white, + dropdownColor: colors.surfaceContainer, elevation: 6, underline: Container(), items: