feat:增加分享图片功能
This commit is contained in:
61
lib/utils/screenshot_util.dart
Normal file
61
lib/utils/screenshot_util.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:food_hub_app/widgets/common/index.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
/// 截图工具类
|
||||||
|
class ScreenshotUtil {
|
||||||
|
// 截图功能
|
||||||
|
static Future<Uint8List?> captureRecipeImage({
|
||||||
|
required GlobalKey globalKey,
|
||||||
|
double pixelRatio = 3.0,
|
||||||
|
Duration delay = const Duration(milliseconds: 50),
|
||||||
|
}) 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(
|
||||||
|
format: ui.ImageByteFormat.png,
|
||||||
|
);
|
||||||
|
|
||||||
|
return byteData?.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存图片到临时文件
|
||||||
|
static Future<File> saveImageToFile(Uint8List bytes, String fileName) async {
|
||||||
|
final directory = await getTemporaryDirectory();
|
||||||
|
final File imageFile = File('${directory.path}/$fileName');
|
||||||
|
await imageFile.writeAsBytes(bytes);
|
||||||
|
return imageFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> captureAndShare({
|
||||||
|
required GlobalKey globalKey,
|
||||||
|
required String fileName,
|
||||||
|
required String shareText,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final imageBytes = await captureRecipeImage(globalKey: globalKey);
|
||||||
|
|
||||||
|
if (imageBytes != null) {
|
||||||
|
final imageFile = await saveImageToFile(imageBytes, fileName);
|
||||||
|
await SharePlus.instance.share(
|
||||||
|
ShareParams(files: [XFile(imageFile.path)], text: shareText),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 分享后删除临时文件
|
||||||
|
await imageFile.delete();
|
||||||
|
} else {
|
||||||
|
showErrorToast('生成图片失败,请重试');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorToast('分享失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,14 +44,17 @@ class _HomePage extends State<HomePage> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
buildBottomSheet(context);
|
buildBottomSheet(context);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// backgroundColor: Color(0xFFF5F5F5),
|
// backgroundColor: Color(0xFFF5F5F5),
|
||||||
drawer: SettingsDrawer(),
|
drawer: SettingsDrawer(),
|
||||||
body: Padding(padding: EdgeInsets.all(10), child: tabPages[_currentIndex]),
|
body: Padding(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
child: tabPages[_currentIndex],
|
||||||
|
),
|
||||||
bottomNavigationBar: buildBottomNavBar(
|
bottomNavigationBar: buildBottomNavBar(
|
||||||
currentIndex: _currentIndex,
|
currentIndex: _currentIndex,
|
||||||
onTap: (index) {
|
onTap: (index) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:food_hub_app/apis/recipe.dart';
|
import 'package:food_hub_app/apis/recipe.dart';
|
||||||
import 'package:food_hub_app/models/recipe.dart';
|
import 'package:food_hub_app/models/recipe.dart';
|
||||||
|
import 'package:food_hub_app/utils/screenshot_util.dart';
|
||||||
import 'package:food_hub_app/widgets/common/index.dart';
|
import 'package:food_hub_app/widgets/common/index.dart';
|
||||||
|
|
||||||
class RecipeDetailPage extends StatefulWidget {
|
class RecipeDetailPage extends StatefulWidget {
|
||||||
@@ -12,6 +13,8 @@ class RecipeDetailPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _RecipeDetailState extends State<RecipeDetailPage> {
|
class _RecipeDetailState extends State<RecipeDetailPage> {
|
||||||
RecipeDetail recipe = RecipeDetail.getEmpty();
|
RecipeDetail recipe = RecipeDetail.getEmpty();
|
||||||
|
final GlobalKey _recipeDetailKey = GlobalKey();
|
||||||
|
bool _isSharing = false;
|
||||||
|
|
||||||
// 定义三个分类列表
|
// 定义三个分类列表
|
||||||
List<RecipeMaterial> mainMaterialList = []; // 主料
|
List<RecipeMaterial> mainMaterialList = []; // 主料
|
||||||
@@ -36,6 +39,10 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void getRecipeMaterial() {
|
void getRecipeMaterial() {
|
||||||
|
mainMaterialList.clear();
|
||||||
|
auxiliaryMaterialList.clear();
|
||||||
|
accessoryMaterialList.clear();
|
||||||
|
|
||||||
for (var material in recipe.materialList) {
|
for (var material in recipe.materialList) {
|
||||||
switch (material.type) {
|
switch (material.type) {
|
||||||
case '主料':
|
case '主料':
|
||||||
@@ -51,12 +58,45 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分享图片
|
||||||
|
Future<void> _shareRecipeAsImage() async {
|
||||||
|
if (_isSharing) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isSharing = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final imageName = '${recipe.name}_$timestamp.png';
|
||||||
|
await ScreenshotUtil.captureAndShare(
|
||||||
|
globalKey: _recipeDetailKey,
|
||||||
|
fileName: imageName,
|
||||||
|
shareText: '分享菜谱: ${recipe.name}',
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isSharing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('菜谱信息')),
|
appBar: AppBar(title: Text('菜谱信息')),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Padding(padding: EdgeInsets.all(10), child: _buildRecipeDetail()),
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// 用RepaintBoundary包裹要截图的部分
|
||||||
|
RepaintBoundary(
|
||||||
|
key: _recipeDetailKey,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
child: _buildRecipeDetail(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildButtons(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -206,4 +246,17 @@ class _RecipeDetailState extends State<RecipeDetailPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildButtons() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
circleIconButton(
|
||||||
|
context: context,
|
||||||
|
icon: _isSharing ? Icons.hourglass_top : Icons.share,
|
||||||
|
onPressed: _shareRecipeAsImage,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,13 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_linux
|
file_selector_linux
|
||||||
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|||||||
@@ -7,10 +7,14 @@ import Foundation
|
|||||||
|
|
||||||
import file_picker
|
import file_picker
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
|
import path_provider_foundation
|
||||||
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
80
pubspec.lock
80
pubspec.lock
@@ -661,6 +661,30 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
path_provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.19"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -749,6 +773,22 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.0"
|
||||||
|
share_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: share_plus
|
||||||
|
sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "11.1.0"
|
||||||
|
share_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus_platform_interface
|
||||||
|
sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -978,6 +1018,46 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.4"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.2"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ dependencies:
|
|||||||
minio: ^3.5.8
|
minio: ^3.5.8
|
||||||
crypto: ^3.0.7
|
crypto: ^3.0.7
|
||||||
file_picker: ^10.3.3
|
file_picker: ^10.3.3
|
||||||
|
share_plus: ^11.0.0
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
tdesign_flutter_adaptation: 3.16.0
|
tdesign_flutter_adaptation: 3.16.0
|
||||||
|
|||||||
@@ -7,8 +7,14 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||||
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
|
share_plus
|
||||||
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|||||||
Reference in New Issue
Block a user