提示词优化器V2.1.1-新增AI应用一键跳转功能
用户将优化后的提示词应用到目标 AI 应用时,传统流程需要:
- 复制优化结果
- 手动切换到目标 AI 应用
- 粘贴并发送
所以我新增了一个功能 URL Scheme 跨平台跳转 + 剪贴板联动实现一键直达的技术方案,将上述三步操作压缩为单次点击。
支持平台
| 平台 | Scheme 跳转 | 包名兜底 | 应用商店引导 |
|---|---|---|---|
| Android | ✅ | ✅ | ✅(market:// + 豌豆荚) |
| iOS | ✅ | ❌ | ✅(App Store) |
| Web/Desktop | ✅ | ❌ | ❌ |
展开关闭动画效果图
分层设计
采用 Clean Architecture 分层,确保业务逻辑与 Flutter 框架解耦:
┌─────────────────────────────────────────┐
│ Presentation Layer │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ AIAppButton │ │ AIAppLauncher │ │
│ │ Animated │ │ Section │ │
│ └─────────────┘ └──────────────────┘ │
│ ┌─────────────────────────────────────┐│
│ │ AIAppManager (Notifier) ││
│ └─────────────────────────────────────┘│
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Domain Layer │
│ ┌─────────────────┐ ┌────────────────┐ │
│ │ LaunchAIAppUC │ │ OpenAppStoreUC │ │
│ └─────────────────┘ └────────────────┘ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Data Layer │
│ ┌─────────────────────────────────────┐│
│ │ AIAppRepository (Drift + Hive) ││
│ └─────────────────────────────────────┘│
│ ┌─────────────────────────────────────┐│
│ │ AIAppConfigModel (freezed) ││
│ └─────────────────────────────────────┘│
└─────────────────────────────────────────┘
核心组件职责
| 组件 | 职责 |
|---|---|
AIAppLauncherSection | 可折叠区域 UI,监听已启用应用列表 |
AIAppManager | 状态管理:切换启用、排序、增删、跳转 |
LaunchAIAppUseCase | 领域服务:剪贴板 + Scheme 跳转 + 降级处理 |
OpenAppStoreUseCase | 领域服务:跨平台应用商店跳转 |
AIAppRepository | 数据持久化:Drift 数据库 + 内置常量合并 |
技术实现详解
1. 跳转策略:优先 Scheme,降级包名
LaunchAIAppUseCase 实现了分层降级策略:
class LaunchAIAppUseCase {
Future<bool> call({
required String scheme,
required String promptText,
String? packageName,
}) async {
// 1. 复制提示词到剪贴板
await Clipboard.setData(ClipboardData(text: promptText));
// 2. 优先尝试 Scheme 跳转
final schemeUrl = Uri.parse(scheme);
final canLaunchScheme = await canLaunchUrl(schemeUrl);
if (canLaunchScheme) {
final launched = await launchUrl(
schemeUrl,
mode: LaunchMode.externalApplication,
);
if (launched) return true;
}
// 3. Scheme 失败,用包名兜底(仅 Android)
if (Platform.isAndroid && packageName != null) {
final packageUrl = Uri.parse('package:$packageName');
final canLaunchPackage = await canLaunchUrl(packageUrl);
if (canLaunchPackage) {
return await launchUrl(packageUrl, mode: LaunchMode.externalApplication);
}
}
return false; // 跳转失败,触发应用未安装提示
}
}
关键设计:
- 剪贴板优先于参数传递:部分 AI 应用不支持通过 Scheme 参数传递文本(如
doubao://?prompt=xxx),统一使用剪贴板作为数据交换媒介 canLaunchUrl预检查:避免直接launchUrl抛出PlatformException- 包名降级:Android 端
package:com.bytedance.stable_diffusion可直达应用详情页,比 Scheme 更可靠
2. 应用商店跨平台跳转
OpenAppStoreUseCase 处理平台差异:
class OpenAppStoreUseCase {
Future<bool> call(String appName) async {
Uri? url;
if (Platform.isAndroid) {
// 优先 market:// 协议(调用系统应用商店)
url = Uri.parse('market://search?q=$appName');
final canLaunch = await canLaunchUrl(url);
if (!canLaunch) {
// 降级到豌豆荚网页版
url = Uri.parse('https://www.wandoujia.com/search?key=$appName');
}
} else if (Platform.isIOS) {
// App Store 搜索页
url = Uri.parse('https://apps.apple.com/cn/search?term=$appName');
} else {
return false;
}
return await launchUrl(url, mode: LaunchMode.externalApplication);
}
}
异常处理:
- Android 设备无 Google Play 时,
market://会失败,自动降级到豌豆荚 - iOS 使用 HTTPS 链接而非
itms://,兼容性更好
3. 应用配置数据模型
内置应用与自定义应用采用统一模型:
@freezed
class AIAppConfigModel with _$AIAppConfigModel {
const factory AIAppConfigModel({
required String id,
required String name,
required String scheme,
required String iconPath,
@Default(true) bool isEnabled,
@Default(0) int position,
@Default(false) bool isBuiltin,
required DateTime createdAt,
}) = _AIAppConfigModel;
// 内置应用从 AppConstants 获取实际配置
Map<String, String>? get _builtinConfig {
if (!isBuiltin) return null;
return AppConstants.builtInAIApps.firstWhere(
(app) => app['name'] == name,
);
}
String get actualScheme => _builtinConfig?['scheme'] ?? scheme;
String? get actualPackageName => _builtinConfig?['packageName'];
}
设计优势:
- 内置应用热更新:修改
AppConstants.builtInAIApps即可更新所有内置应用配置,无需数据库迁移 - 自定义应用持久化:用户添加的应用存储到 Drift 数据库,独立于内置配置
- 统一接口:UI 层无需区分内置/自定义,通过
actualScheme等 getter 透明访问
4. Riverpod 状态管理
/// AI 应用管理 Notifier
class AIAppManager extends AsyncNotifier<void> {
late AIAppRepository _repository;
late LaunchAIAppUseCase _launchUseCase;
late OpenAppStoreUseCase _openStoreUseCase;
@override
Future<void> build() async {
_repository = ref.watch(aiAppRepositoryProvider);
_launchUseCase = LaunchAIAppUseCase();
_openStoreUseCase = OpenAppStoreUseCase();
}
/// 启动 AI 应用
Future<void> launchApp(String id, String promptText) async {
final app = await _repository.getAppById(id);
if (app == null) {
_showErrorToast('应用不存在');
return;
}
final success = await _launchUseCase(
scheme: app.actualScheme,
promptText: promptText,
packageName: app.actualPackageName,
);
if (success) {
_showSuccessToast('已复制到剪贴板并跳转');
} else {
_showAppNotInstalledToast(app.name);
}
}
}
状态监听模式:
// 在 UI 中监听
final appsAsync = ref.watch(enabledAIAppListProvider);
appsAsync.when(
data: (apps) => _buildAppButtons(apps),
loading: () => const CircularProgressIndicator(),
error: (e, s) => Text('加载失败:$e'),
)
用户体验优化
1. 应用未安装检测与引导
当跳转失败时,显示带操作按钮的 Toast:
void _showAppNotInstalledToast(String appName) {
ref.read(toastProvider.notifier).showAction(
message: '该应用未安装,是否前往应用商店下载?',
type: ToastType.warning,
primaryAction: ToastAction(
label: '去下载',
onPressed: () async {
final success = await _openStoreUseCase(appName);
if (!success) {
_showErrorToast('打开应用商店失败');
}
},
),
duration: const Duration(seconds: 5),
);
}
[应用未安装 Toast 截图-黄色警告背景,右侧"去下载"按钮为重点]
2. 拖拽排序与持久化
已启用应用支持拖拽重排序,位置持久化到数据库:
class _EnabledAppsSectionState extends ConsumerState<_EnabledAppsSection> {
late List _orderedApps;
int? _draggingIndex;
void _saveOrder() {
final positions = <String, int>{};
for (int i = 0; i < _orderedApps.length; i++) {
positions[_orderedApps[i].id] = i;
}
ref.read(aIAppManagerProvider.notifier)
.updateAppPositions(positions);
}
}
实现细节:
- 使用
LongPressDraggable+DragTarget实现拖拽 - 拖拽时触发
HapticFeedback.mediumImpact()提供触觉反馈 - 松手时立即更新 UI,异步保存到数据库
[拖拽排序 GIF 截图-手指长按豆包按钮拖动到通义千问左侧]
边界处理
1. 异常捕获策略
try {
// 跳转逻辑
} on PlatformException catch (_) {
// 平台异常:应用未安装、系统拒绝打开
return false;
} catch (_) {
// 其他异常:链接格式错误、超时
return false;
}
2. Scheme 验证
用户添加自定义应用时,前端验证 Scheme 格式:
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '请输入 URL Scheme';
}
if (!value.contains('://')) {
return 'URL Scheme 格式错误,应包含 ://';
}
return null;
}
3. 内置应用白名单
内置应用配置在 AppConstants 中预定义,防止恶意注入:
static const List<Map<String, String>> builtInAIApps = [
{
'name': '豆包',
'scheme': 'doubao://',
'packageName': 'com.ss.android.ugc.alive',
'iconPath': 'assets/icon/doubao.svg',
},
{
'name': '通义千问',
'scheme': 'alibabacloudqwen://',
'packageName': 'com.alibaba.cloud.qwen',
'iconPath': 'assets/icon/qwen.svg',
},
// ...
];
开源地址
🌐Github开源地址:JIULANG9/PromptOptimizer:
如果这个项目对您有帮助,恳请点个 ⭐ Star 支持一下——您的认可是我持续维护和更新的重要动力。