主要变更: 1. 重构存储层导入路径,将app_kv_store替换为kv_storage 2. 移除AppKVStore初始化代码,统一使用KvStorage 3. 修复壁纸健康检测逻辑,使用最新检查时间判断检测间隔 4. 调整主页头部容器高度与裁剪行为 5. 新增引导页下次显示开关与Riverpod提供者 6. 修复API响应List类型转换崩溃问题 7. 优化部分文件头注释格式
220 lines
6.7 KiB
Dart
220 lines
6.7 KiB
Dart
/// ============================================================
|
||
/// 闲言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();
|
||
}
|