flutter架 构项目基础
This commit is contained in:
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user