- 通过 qemu-user-static 实现 ARM64 主机编译 Android APK (51MB) - 修复 Gradle: Aliyun 镜像 + PREFER_SETTINGS + JVM 内存 1536M - 部署 APK 到 configs/downloads/, Web 下载接口已验证 (HTTP 200) - 新增 Flutter TODO.md: 10项待优化 (P0/P1/P2 分级) - 新增 pm_soul.md, 更新 routes.rs APK 下载路由 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
195 lines
7.1 KiB
Dart
195 lines
7.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../providers/player_provider.dart';
|
|
import '../theme/app_colors.dart';
|
|
|
|
class TriggerScreen extends StatefulWidget {
|
|
const TriggerScreen({super.key});
|
|
|
|
@override
|
|
State<TriggerScreen> createState() => _TriggerScreenState();
|
|
}
|
|
|
|
class _TriggerScreenState extends State<TriggerScreen> {
|
|
final TextEditingController _triggerController = TextEditingController();
|
|
final TextEditingController _valueController = TextEditingController();
|
|
String? _selectedScene;
|
|
|
|
static const List<_PresetTrigger> _presets = <_PresetTrigger>[
|
|
_PresetTrigger(label: '语音唤醒', name: 'wake', icon: Icons.mic_rounded),
|
|
_PresetTrigger(label: '按钮 1', name: 'button1', icon: Icons.filter_1_rounded),
|
|
_PresetTrigger(label: '按钮 2', name: 'button2', icon: Icons.filter_2_rounded),
|
|
_PresetTrigger(label: '触摸传感器', name: 'touch', icon: Icons.touch_app_rounded),
|
|
];
|
|
|
|
@override
|
|
void dispose() {
|
|
_triggerController.dispose();
|
|
_valueController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final provider = context.watch<PlayerProvider>();
|
|
_selectedScene ??= provider.sceneOptions.isNotEmpty ? provider.sceneOptions.first : null;
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('状态机触发')),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
children: [
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('当前状态', style: Theme.of(context).textTheme.titleMedium),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Text(
|
|
provider.currentState,
|
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
color: AppColors.primary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
Text('预设触发器', style: Theme.of(context).textTheme.headlineSmall),
|
|
const SizedBox(height: AppSpacing.md),
|
|
GridView.count(
|
|
crossAxisCount: 2,
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
crossAxisSpacing: AppSpacing.md,
|
|
mainAxisSpacing: AppSpacing.md,
|
|
childAspectRatio: 1.35,
|
|
children: _presets.map((preset) {
|
|
return InkWell(
|
|
borderRadius: BorderRadius.circular(AppRadius.large),
|
|
onTap: provider.isLoading
|
|
? null
|
|
: () => context.read<PlayerProvider>().triggerEvent(preset.name),
|
|
child: Ink(
|
|
decoration: BoxDecoration(
|
|
color: AppColors.card,
|
|
borderRadius: BorderRadius.circular(AppRadius.large),
|
|
border: Border.all(color: AppColors.border),
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(preset.icon, color: AppColors.accent, size: 32),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Text(preset.label),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}).toList(growable: false),
|
|
),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('自定义触发器', style: Theme.of(context).textTheme.titleMedium),
|
|
const SizedBox(height: AppSpacing.md),
|
|
TextField(
|
|
controller: _triggerController,
|
|
decoration: const InputDecoration(labelText: '触发器名称'),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
TextField(
|
|
controller: _valueController,
|
|
decoration: const InputDecoration(labelText: '可选参数值'),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: FilledButton.icon(
|
|
onPressed: provider.isLoading ? null : _handleCustomTrigger,
|
|
icon: const Icon(Icons.send_rounded),
|
|
label: const Text('发送触发器'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('场景切换', style: Theme.of(context).textTheme.titleMedium),
|
|
const SizedBox(height: AppSpacing.md),
|
|
DropdownButtonFormField<String>(
|
|
initialValue: provider.sceneOptions.contains(_selectedScene) ? _selectedScene : null,
|
|
items: provider.sceneOptions
|
|
.map(
|
|
(scene) => DropdownMenuItem<String>(
|
|
value: scene,
|
|
child: Text(scene),
|
|
),
|
|
)
|
|
.toList(growable: false),
|
|
onChanged: (value) => setState(() => _selectedScene = value),
|
|
decoration: const InputDecoration(labelText: '选择场景'),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: FilledButton.tonal(
|
|
onPressed: provider.isLoading || _selectedScene == null
|
|
? null
|
|
: () => context.read<PlayerProvider>().switchScene(_selectedScene!),
|
|
child: const Text('切换场景'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _handleCustomTrigger() {
|
|
final name = _triggerController.text.trim();
|
|
final value = _valueController.text.trim();
|
|
if (name.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('请输入触发器名称')),
|
|
);
|
|
return;
|
|
}
|
|
|
|
context.read<PlayerProvider>().triggerEvent(
|
|
name,
|
|
value: value.isEmpty ? null : value,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _PresetTrigger {
|
|
const _PresetTrigger({
|
|
required this.label,
|
|
required this.name,
|
|
required this.icon,
|
|
});
|
|
|
|
final String label;
|
|
final String name;
|
|
final IconData icon;
|
|
}
|