Files
App.K12.Student.Miniapp_Flu…/lib/pages/demo/share_demo.dart
T
2026-09-04 15:22:20 +08:00

526 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
),
),
),
);
}
}