feat(app,viewer): WeChat card share + 'Share to...' label + site og tags
- 按钮文字「分享到微信/其他应用」→「分享到...」(用户需求) - 微信卡片分享(fluwx 6):WECHAT_APPID 经 --dart-define 注入;已配置且 装微信 → WXWebpage 卡片(标题=相册名、描述=日期·地点、缩略图=封面压缩 240px PNG ≤32KB、点击进观众链接);未配置/未装 → 回退系统分享面板 - viewer index.html 加站点级 og 标签(微信/QQ 抓取链接预览用;每相册动态 og 需服务端注入,登记后续增强) - 397/397 全绿、analyze 0 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -10,10 +10,12 @@ library;
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:fluwx/fluwx.dart';
|
||||
import 'package:gal/gal.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -47,17 +49,72 @@ final urlLauncherProvider = Provider<UrlLauncher>(
|
||||
(url) => launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication),
|
||||
);
|
||||
|
||||
/// 系统分享注入点(生产=share_plus;测试 override)。
|
||||
typedef ShareLauncher = Future<void> Function(String text);
|
||||
/// 分享请求(卡片字段:微信 SDK 用全集;系统分享回退用 title+url)。
|
||||
class ShareCardRequest {
|
||||
const ShareCardRequest({
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.url,
|
||||
this.thumbnailUrl,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final String url;
|
||||
|
||||
/// 卡片缩略图(封面 URL;null=无图,微信显示默认图标)。
|
||||
final String? thumbnailUrl;
|
||||
}
|
||||
|
||||
/// 分享注入点(生产:微信已配置且安装→WXWebpage 卡片;否则系统分享面板。
|
||||
/// 测试 override)。
|
||||
typedef ShareLauncher = Future<void> Function(ShareCardRequest card);
|
||||
|
||||
final shareLauncherProvider = Provider<ShareLauncher>(
|
||||
// share_plus 11:SharePlus.instance.share(旧 Share.share 已废弃)。
|
||||
(ref) =>
|
||||
(text) async => SharePlus.instance.share(
|
||||
ShareParams(text: text, subject: text.split('\n').first),
|
||||
),
|
||||
(ref) => (card) async {
|
||||
const wechatAppId = String.fromEnvironment('WECHAT_APPID');
|
||||
if (wechatAppId.isNotEmpty && await Fluwx().isWeChatInstalled) {
|
||||
final thumb = await compressShareThumbnail(card.thumbnailUrl);
|
||||
await Fluwx().share(
|
||||
WeChatShareWebPageModel(
|
||||
card.url,
|
||||
title: card.title,
|
||||
description: card.description,
|
||||
thumbData: thumb,
|
||||
scene: WeChatScene.session,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 回退:系统分享面板(文本=标题+链接;接收应用自行渲染链接)。
|
||||
// share_plus 11:SharePlus.instance.share(旧 Share.share 已废弃)。
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(text: '${card.title}\n${card.url}', subject: card.title),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/// 微信卡片缩略图:下载→缩到 240px→PNG(微信 thumbData 限制 ≤32KB)。
|
||||
Future<Uint8List?> compressShareThumbnail(String? url) async {
|
||||
if (url == null) return null;
|
||||
try {
|
||||
final resp = await Dio().get<List<int>>(
|
||||
url,
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
final data = Uint8List.fromList(resp.data!);
|
||||
final codec = await ui.instantiateImageCodec(data, targetWidth: 240);
|
||||
final frame = await codec.getNextFrame();
|
||||
final resized = await frame.image.toByteData(
|
||||
format: ui.ImageByteFormat.png,
|
||||
);
|
||||
return resized?.buffer.asUint8List();
|
||||
} on Object {
|
||||
// 缩略图失败不阻塞分享(无图卡片仍可发)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 海报保存相册注入点(生产=gal;测试 override)。
|
||||
typedef PosterSaver = Future<void> Function(ui.Image image);
|
||||
|
||||
@@ -89,7 +146,28 @@ class ShareQrPage extends ConsumerWidget {
|
||||
String url,
|
||||
String title,
|
||||
) async {
|
||||
await ref.read(shareLauncherProvider)('$title\n$url');
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final localeName = l10n.localeName;
|
||||
final start = formatAlbumDateTime(
|
||||
album.startAt,
|
||||
album.timezone,
|
||||
localeName,
|
||||
);
|
||||
final end = album.endAt == null
|
||||
? null
|
||||
: formatAlbumDateTime(album.endAt!, album.timezone, localeName);
|
||||
final dateRange = end == null ? start : '$start ~ $end';
|
||||
final venue = albumDisplayVenue(album, localeName);
|
||||
final description = venue == null ? dateRange : '$dateRange · $venue';
|
||||
// 缩略图用封面(列表即有签名 URL,减少一次串行请求)。
|
||||
await ref.read(shareLauncherProvider)(
|
||||
ShareCardRequest(
|
||||
title: title,
|
||||
description: description,
|
||||
url: url,
|
||||
thumbnailUrl: album.cover?.url,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user