import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'shared/settings_persistence.dart'; const kLanguageNames = { 'chi': '中文', 'zho': '中文', 'eng': '英语', 'jpn': '日语', 'kor': '韩语', 'fre': '法语', 'fra': '法语', 'ger': '德语', 'deu': '德语', 'spa': '西班牙语', 'ita': '意大利语', 'por': '葡萄牙语', 'rus': '俄语', 'ara': '阿拉伯语', 'hin': '印地语', 'tha': '泰语', 'vie': '越南语', 'pol': '波兰语', 'tur': '土耳其语', 'dut': '荷兰语', 'nld': '荷兰语', 'dan': '丹麦语', 'fin': '芬兰语', 'swe': '瑞典语', 'nor': '挪威语', 'nob': '书面挪威语', 'cat': '加泰罗尼亚语', 'cze': '捷克语', 'ces': '捷克语', 'hun': '匈牙利语', 'gre': '希腊语', 'ell': '希腊语', 'heb': '希伯来语', 'ind': '印度尼西亚语', 'may': '马来语', 'msa': '马来语', 'hrv': '克罗地亚语', 'baq': '巴斯克语', 'eus': '巴斯克语', 'glg': '加利西亚语', 'fil': '菲律宾语', 'rum': '罗马尼亚语', 'ron': '罗马尼亚语', 'bul': '保加利亚语', 'ukr': '乌克兰语', 'srp': '塞尔维亚语', 'slk': '斯洛伐克语', 'slv': '斯洛文尼亚语', 'lit': '立陶宛语', 'lav': '拉脱维亚语', 'est': '爱沙尼亚语', }; class RememberedSubtitleTrack { final int streamIndex; final String? language; final String? title; final DateTime updatedAt; const RememberedSubtitleTrack({ required this.streamIndex, this.language, this.title, required this.updatedAt, }); bool get isNone => streamIndex == -1; Map toJson() => { 'streamIndex': streamIndex, if (language != null) 'language': language, if (title != null) 'title': title, 'updatedAt': updatedAt.toIso8601String(), }; factory RememberedSubtitleTrack.fromJson(Map json) { return RememberedSubtitleTrack( streamIndex: (json['streamIndex'] as num?)?.toInt() ?? -1, language: json['language'] as String?, title: json['title'] as String?, updatedAt: DateTime.tryParse(json['updatedAt'] as String? ?? '') ?? DateTime.now(), ); } } class SubtitleStyleSettings { final int position; final double fontSize; final double delay; final double bgOpacity; const SubtitleStyleSettings({ this.position = 100, this.fontSize = 55, this.delay = 0.0, this.bgOpacity = 0.0, }); SubtitleStyleSettings copyWith({ int? position, double? fontSize, double? delay, double? bgOpacity, }) => SubtitleStyleSettings( position: position ?? this.position, fontSize: fontSize ?? this.fontSize, delay: delay ?? this.delay, bgOpacity: bgOpacity ?? this.bgOpacity, ); } class SubtitleSettingsNotifier extends AsyncNotifier with SettingsPersistence { static const _keyPosition = 'smplayer.subtitle_position'; static const _keyFontSize = 'smplayer.subtitle_font_size'; static const _keyDelay = 'smplayer.subtitle_delay'; static const _keyBgOpacity = 'smplayer.subtitle_bg_opacity'; static const _keyLastTracks = 'smplayer.subtitle_last_tracks_primary'; static const _maxRememberedTracks = 200; @override Future build() async { final prefs = await getPrefs(); for (final stale in const [ 'smplayer.subtitle_default_policy', 'smplayer.subtitle_preferred_language', 'smplayer.subtitle_secondary_position', 'smplayer.subtitle_secondary_font_size', 'smplayer.subtitle_secondary_delay', 'smplayer.subtitle_secondary_bg_opacity', 'smplayer.subtitle_secondary_follow_primary', 'smplayer.subtitle_last_tracks_secondary', ]) { await prefs.remove(stale); } return SubtitleStyleSettings( position: prefs.getInt(_keyPosition) ?? 100, fontSize: prefs.getDouble(_keyFontSize) ?? 55, delay: prefs.getDouble(_keyDelay) ?? 0.0, bgOpacity: prefs.getDouble(_keyBgOpacity) ?? 0.0, ); } Future setSettings(SubtitleStyleSettings settings) async { state = AsyncValue.data(settings); await persistFields({ _keyPosition: settings.position, _keyFontSize: settings.fontSize, _keyDelay: settings.delay, _keyBgOpacity: settings.bgOpacity, }); } Future saveLastSubtitleTrack( String key, RememberedSubtitleTrack? track, ) async { if (track == null) return; await putRemembered( _keyLastTracks, key, track.toJson(), _maxRememberedTracks, ); } Future getLastSubtitleTrack(String key) async { final entry = await readRemembered(_keyLastTracks, key); return entry == null ? null : RememberedSubtitleTrack.fromJson(entry); } } final subtitleSettingsProvider = AsyncNotifierProvider( SubtitleSettingsNotifier.new, );