Files
xianyan/lib/core/services/sound_service.dart
Developer 794da27193 refactor: 完成存储层迁移,替换AppKVStore为KvStorage
主要变更:
1. 重构存储层导入路径,将app_kv_store替换为kv_storage
2. 移除AppKVStore初始化代码,统一使用KvStorage
3. 修复壁纸健康检测逻辑,使用最新检查时间判断检测间隔
4. 调整主页头部容器高度与裁剪行为
5. 新增引导页下次显示开关与Riverpod提供者
6. 修复API响应List类型转换崩溃问题
7. 优化部分文件头注释格式
2026-05-24 05:54:14 +08:00

220 lines
6.7 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.
/// ============================================================
/// 闲言APP — 音效服务 (Riverpod Notifier)
/// 创建时间: 2026-05-07
/// 更新时间: 2026-05-24
/// 作用: 统一管理操作音效,使用 Riverpod Notifier 管理状态
/// 上次更新: 从静态方法重构为 Riverpod Notifier 模式,保留废弃兼容层
/// ============================================================
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../storage/kv_storage.dart';
import '../utils/logger.dart';
/// 音效类型枚举
enum SoundEffectType {
standard('标准', 'standard'),
crisp('清脆', 'crisp'),
soft('柔和', 'soft');
const SoundEffectType(this.label, this.id);
final String label;
final String id;
static SoundEffectType fromId(String id) {
return SoundEffectType.values.firstWhere(
(e) => e.id == id,
orElse: () => SoundEffectType.standard,
);
}
}
/// 音效状态数据类
class SoundState {
const SoundState({
this.enabled = true,
this.effectType = SoundEffectType.standard,
this.volume = 0.5,
});
final bool enabled;
final SoundEffectType effectType;
final double volume;
SoundState copyWith({
bool? enabled,
SoundEffectType? effectType,
double? volume,
}) {
return SoundState(
enabled: enabled ?? this.enabled,
effectType: effectType ?? this.effectType,
volume: volume ?? this.volume,
);
}
}
/// AudioPlayer 提供者,自动管理生命周期
final audioPlayerProvider = Provider<AudioPlayer>((ref) {
final player = AudioPlayer();
ref.onDispose(() => player.dispose());
return player;
});
/// 音效状态 Notifier
class SoundNotifier extends Notifier<SoundState> {
@override
SoundState build() {
final savedEnabled = KvStorage.getBool('general_sound');
final savedType = KvStorage.getString('general_sound_effect');
final initialState = SoundState(
enabled: savedEnabled ?? true,
effectType: savedType != null
? SoundEffectType.fromId(savedType)
: SoundEffectType.standard,
);
final player = ref.read(audioPlayerProvider);
player.setVolume(initialState.volume);
Log.i(
'SoundNotifier 初始化: enabled=${initialState.enabled}, '
'type=${initialState.effectType.label}',
);
return initialState;
}
/// 设置音效开关
void setEnabled(bool v) {
state = state.copyWith(enabled: v);
KvStorage.setBool('general_sound', v);
}
/// 设置音效类型
void setEffectType(SoundEffectType type) {
state = state.copyWith(effectType: type);
KvStorage.setString('general_sound_effect', type.id);
}
/// 设置音量
void setVolume(double v) {
final clamped = v.clamp(0.0, 1.0);
state = state.copyWith(volume: clamped);
final player = ref.read(audioPlayerProvider);
player.setVolume(clamped);
}
/// 播放音效内部方法
Future<void> _play(String action) async {
if (!state.enabled) return;
try {
final path = 'assets/sounds/${state.effectType.id}_$action.mp3';
final player = ref.read(audioPlayerProvider);
await player.stop();
await player.play(AssetSource(path));
} catch (e) {
Log.w('音效播放失败($action): $e');
}
}
/// 点击音效
Future<void> playClick() => _play('click');
/// 切换音效
Future<void> playToggle() => _play('toggle');
/// 成功音效
Future<void> playSuccess() => _play('success');
/// 错误音效
Future<void> playError() => _play('error');
/// 提醒音效
Future<void> playAlert() => _play('alert');
/// 删除音效
Future<void> playDelete() => _play('delete');
/// 发送音效
Future<void> playSend() => _play('send');
/// 释放资源(由 Provider 体系自动管理,此方法保留兼容)
Future<void> dispose() async {
final player = ref.read(audioPlayerProvider);
await player.dispose();
}
}
/// 音效状态 Provider
final soundProvider = NotifierProvider<SoundNotifier, SoundState>(
SoundNotifier.new,
);
/// ============================================================
/// 废弃兼容层:旧 SoundService 静态方法委托到 soundProvider
/// 仅用于过渡期,新代码请直接使用 ref.read(soundProvider.notifier)
/// ============================================================
@Deprecated('请使用 ref.read(soundProvider.notifier) 替代 SoundService')
class SoundService {
SoundService._();
static ProviderContainer? _container;
/// 设置全局 ProviderContainer由 main.dart 调用一次)
static void setContainer(ProviderContainer container) {
_container = container;
}
static SoundNotifier get _notifier {
assert(_container != null, 'SoundService: 未设置 ProviderContainer请先调用 setContainer');
return _container!.read(soundProvider.notifier);
}
static bool get enabled => _container?.read(soundProvider).enabled ?? true;
static SoundEffectType get effectType =>
_container?.read(soundProvider).effectType ?? SoundEffectType.standard;
static double get volume => _container?.read(soundProvider).volume ?? 0.5;
@Deprecated('初始化已由 SoundNotifier.build() 自动完成,无需手动调用')
static Future<void> init() async {}
@Deprecated('请使用 ref.read(soundProvider.notifier).setEnabled(v)')
static void setEnabled(bool v) => _notifier.setEnabled(v);
@Deprecated('请使用 ref.read(soundProvider.notifier).setEffectType(type)')
static void setEffectType(SoundEffectType type) => _notifier.setEffectType(type);
@Deprecated('请使用 ref.read(soundProvider.notifier).setVolume(v)')
static void setVolume(double v) => _notifier.setVolume(v);
@Deprecated('请使用 ref.read(soundProvider.notifier).playClick()')
static Future<void> playClick() => _notifier.playClick();
@Deprecated('请使用 ref.read(soundProvider.notifier).playToggle()')
static Future<void> playToggle() => _notifier.playToggle();
@Deprecated('请使用 ref.read(soundProvider.notifier).playSuccess()')
static Future<void> playSuccess() => _notifier.playSuccess();
@Deprecated('请使用 ref.read(soundProvider.notifier).playError()')
static Future<void> playError() => _notifier.playError();
@Deprecated('请使用 ref.read(soundProvider.notifier).playAlert()')
static Future<void> playAlert() => _notifier.playAlert();
@Deprecated('请使用 ref.read(soundProvider.notifier).playDelete()')
static Future<void> playDelete() => _notifier.playDelete();
@Deprecated('请使用 ref.read(soundProvider.notifier).playSend()')
static Future<void> playSend() => _notifier.playSend();
@Deprecated('资源由 Provider 体系自动管理,无需手动 dispose')
static Future<void> dispose() async => _notifier.dispose();
}