flutter架 构项目基础

This commit is contained in:
崔宝新
2026-09-04 15:22:20 +08:00
parent 70967229d0
commit 0c5aee59c0
265 changed files with 35484 additions and 0 deletions
+525
View File
@@ -0,0 +1,525 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:new_learn/core/services/share_service.dart';
import 'package:new_learn/core/services/picker_service.dart';
/// ============================================================
/// 【ShareDemoPage】分享功能演示页
///
/// 一、演示内容:
/// 1. 分享纯文本 / 链接
/// 2. 选图后分享(单张图片)
/// 3. Widget 截图分享(模拟“攻克海报”场景)
/// 4. Widget 截图预览(不分享,只截图看效果)
/// 5. 保存到相册(选图 / 截图 → 写入系统相册)
///
/// 二、核心能力对应:
/// ShareService.shareText → 场景 1
/// ShareService.shareImage → 场景 2(先 PickerService 选图)
/// ShareService.shareFromWidget → 场景 3
/// ShareService.captureWidget → 场景 4
/// ShareService.saveToGallery → 场景 5a
/// ShareService.saveToGalleryFromBytes → 场景 5b
///
/// 三、使用方式:
/// 从任务页 Demo 列表进入,或直接:
/// context.push('/share-demo'); (需先在 app_router.dart 注册)
/// ============================================================
class ShareDemoPage extends StatefulWidget {
const ShareDemoPage({super.key});
@override
State<ShareDemoPage> createState() => _ShareDemoPageState();
}
class _ShareDemoPageState extends State<ShareDemoPage> {
final TextEditingController _textController = TextEditingController(
text: '来看看这个超棒的学习 App!https://example.com',
);
bool _sharing = false;
String? _lastResult;
@override
void dispose() {
_textController.dispose();
super.dispose();
}
/// 统一结果展示
void _showResult(String msg) {
setState(() => _lastResult = msg);
}
// ==================== 场景 1:分享文本 ====================
Future<void> _shareText() async {
final text = _textController.text.trim();
if (text.isEmpty) {
_showResult('请输入要分享的内容');
return;
}
setState(() => _sharing = true);
try {
final result = await ShareService.instance.shareText(text: text);
_showResult('文本分享结果:${result.status.name}');
} catch (e) {
_showResult('分享失败:$e');
} finally {
if (mounted) setState(() => _sharing = false);
}
}
// ==================== 场景 2:选图 → 分享 ====================
Future<void> _pickAndShare() async {
setState(() => _sharing = true);
try {
final picked = await PickerService.instance.showPickSheet(
context,
maxCount: 1,
);
if (picked == null || picked.isEmpty) {
_showResult('未选择图片');
return;
}
final filePath = picked.first.path;
if (filePath == null) {
_showResult('Web 端暂不支持文件路径分享');
return;
}
final result = await ShareService.instance.shareImage(
filePath: filePath,
text: '看看这张图片!',
);
_showResult('图片分享结果:${result.status.name}');
} catch (e) {
_showResult('分享失败:$e');
} finally {
if (mounted) setState(() => _sharing = false);
}
}
// ==================== 场景 3:Widget 截图 → 分享(海报) ====================
Future<void> _sharePoster() async {
setState(() => _sharing = true);
try {
final result = await ShareService.instance.shareFromWidget(
context: context,
text: '我攻克了一道难题,快来挑战!',
builder: (_) => _buildPosterWidget(
userName: '张三同学',
taskName: '二次函数综合练习',
score: 98,
),
);
_showResult('海报分享结果:${result.status.name}');
} catch (e) {
_showResult('分享失败:$e');
} finally {
if (mounted) setState(() => _sharing = false);
}
}
// ==================== 场景 4:Widget 截图预览(不分享) ====================
Future<void> _previewPoster() async {
setState(() => _sharing = true);
try {
final bytes = await ShareService.captureWidget(
context: context,
builder: (_) => _buildPosterWidget(
userName: '张三同学',
taskName: '二次函数综合练习',
score: 98,
),
);
if (bytes == null) {
_showResult('截图失败');
return;
}
// 弹窗预览截图结果
if (!mounted) return;
showDialog(
context: context,
builder: (ctx) => Dialog(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(
padding: EdgeInsets.all(12),
child: Text('截图预览(海报效果)',
style: TextStyle(fontWeight: FontWeight.bold)),
),
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.memory(bytes, width: 280),
),
Padding(
padding: const EdgeInsets.all(12),
child: Text(
'图片大小:${(bytes.length / 1024).toStringAsFixed(1)} KB',
style: const TextStyle(color: Colors.grey),
),
),
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('关闭'),
),
],
),
),
);
_showResult('截图成功,可预览效果');
} catch (e) {
_showResult('截图失败:$e');
} finally {
if (mounted) setState(() => _sharing = false);
}
}
// ==================== 场景 5:保存到相册 ====================
/// 5a:选图 → 保存到相册
Future<void> _pickAndSaveToGallery() async {
setState(() => _sharing = true);
try {
final picked = await PickerService.instance.showPickSheet(
context,
maxCount: 1,
);
if (picked == null || picked.isEmpty) {
_showResult('未选择图片');
return;
}
final filePath = picked.first.path;
if (filePath == null) {
_showResult('Web 端暂不支持保存');
return;
}
final ok = await ShareService.saveToGallery(filePath: filePath);
_showResult(ok ? '✅ 已保存到相册' : '❌ 保存失败');
} catch (e) {
_showResult('保存失败:$e');
} finally {
if (mounted) setState(() => _sharing = false);
}
}
/// 5b:Widget 截图 → 保存到相册
Future<void> _captureAndSaveToGallery() async {
setState(() => _sharing = true);
try {
final bytes = await ShareService.captureWidget(
context: context,
builder: (_) => _buildPosterWidget(
userName: '张三同学',
taskName: '二次函数综合练习',
score: 98,
),
);
if (bytes == null) {
_showResult('截图失败');
return;
}
final ok = await ShareService.saveToGalleryFromBytes(bytes: bytes);
_showResult(ok ? '✅ 海报已保存到相册' : '❌ 保存失败');
} catch (e) {
_showResult('保存失败:$e');
} finally {
if (mounted) setState(() => _sharing = false);
}
}
// ==================== 海报 Widget(截图用) ====================
/// 模拟"攻克海报"布局(实际项目中从业务数据构建)
Widget _buildPosterWidget({
required String userName,
required String taskName,
required int score,
}) {
return Container(
width: 300,
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF667EEA), Color(0xFF764BA2)],
),
borderRadius: BorderRadius.all(Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Logo
const Icon(Icons.school, color: Colors.white, size: 40),
const SizedBox(height: 8),
const Text(
'爱学蝶变',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
// 用户信息
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(
userName,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
'攻克了 $taskName',
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 13,
),
),
],
),
),
const SizedBox(height: 16),
// 分数
Text(
'$score',
style: const TextStyle(
color: Colors.white,
fontSize: 48,
fontWeight: FontWeight.w800,
height: 1,
),
),
const Text(
'分',
style: TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
const SizedBox(height: 16),
// 底部提示
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'扫码一起来挑战',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
],
),
);
}
// ==================== UI ====================
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('分享功能演示')),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ---------- 场景 1:文本分享 ----------
_sectionTitle('① 分享文本 / 链接', Icons.text_fields),
SizedBox(height: 8.h),
TextField(
controller: _textController,
decoration: InputDecoration(
hintText: '输入要分享的文本或链接',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 12.h,
),
),
maxLines: 2,
),
SizedBox(height: 8.h),
_shareButton(
label: '分享文本',
icon: Icons.share,
onPressed: _shareText,
),
SizedBox(height: 24.h),
// ---------- 场景 2:选图分享 ----------
_sectionTitle('② 选图 → 分享', Icons.photo),
SizedBox(height: 8.h),
Text(
'先从相册/拍照选一张图片,再通过系统分享面板分享出去',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
SizedBox(height: 8.h),
_shareButton(
label: '选图并分享',
icon: Icons.image_outlined,
onPressed: _pickAndShare,
),
SizedBox(height: 24.h),
// ---------- 场景 3:Widget 截图分享 ----------
_sectionTitle('③ Widget 截图 → 分享(海报)', Icons.card_giftcard),
SizedBox(height: 8.h),
Text(
'把 Flutter Widget 截图成图片,再通过系统分享面板分享。\n'
'这就是"攻克海报分享"的核心能力。',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
SizedBox(height: 8.h),
// 海报预览(静态展示)
Center(child: _buildPosterWidget(
userName: '张三同学',
taskName: '二次函数综合练习',
score: 98,
)),
SizedBox(height: 12.h),
Row(
children: [
Expanded(
child: _shareButton(
label: '截图并分享',
icon: Icons.share,
onPressed: _sharePoster,
),
),
SizedBox(width: 8.w),
Expanded(
child: _shareButton(
label: '仅截图预览',
icon: Icons.preview,
onPressed: _previewPoster,
color: const Color(0xFF64748B),
),
),
],
),
SizedBox(height: 24.h),
// ---------- 场景 5:保存到相册 ----------
_sectionTitle('④ 保存到相册', Icons.save_alt),
SizedBox(height: 8.h),
Text(
'将图片或 Widget 截图写入系统相册(用 gal 插件实现)',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
SizedBox(height: 8.h),
Row(
children: [
Expanded(
child: _shareButton(
label: '选图→保存',
icon: Icons.photo_library,
onPressed: _pickAndSaveToGallery,
color: const Color(0xFF059669),
),
),
SizedBox(width: 8.w),
Expanded(
child: _shareButton(
label: '截图→保存',
icon: Icons.camera_alt,
onPressed: _captureAndSaveToGallery,
color: const Color(0xFF059669),
),
),
],
),
SizedBox(height: 24.h),
// ---------- 结果展示 ----------
if (_lastResult != null)
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: const Color(0xFFF0F9FF),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: const Color(0xFFBAE6FD)),
),
child: Row(
children: [
const Icon(Icons.info_outline,
size: 18, color: Color(0xFF0284C7)),
SizedBox(width: 8.w),
Expanded(
child: Text(
_lastResult!,
style: const TextStyle(
fontSize: 13,
color: Color(0xFF0C4A6E),
),
),
),
],
),
),
],
),
),
);
}
/// 分区标题
Widget _sectionTitle(String title, IconData icon) {
return Row(
children: [
Icon(icon, size: 20, color: const Color(0xFF3B82F6)),
SizedBox(width: 8.w),
Text(
title,
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w600,
color: const Color(0xFF1E293B),
),
),
],
);
}
/// 分享按钮
Widget _shareButton({
required String label,
required IconData icon,
required VoidCallback onPressed,
Color? color,
}) {
return SizedBox(
height: 44.h,
child: FilledButton.icon(
onPressed: _sharing ? null : onPressed,
icon: Icon(icon, size: 18),
label: Text(label, style: TextStyle(fontSize: 13.sp)),
style: FilledButton.styleFrom(
backgroundColor: color ?? const Color(0xFF3B82F6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
),
);
}
}