Files
xianyan/lib/features/onboarding/onboarding_provider.dart
Developer 83720002e6 feat: 新增工作台模式、系统托盘,修复多平台兼容性问题
1. 新增工作台三栏布局模式,适配宽屏设备
2. 添加跨平台系统托盘支持,新增托盘图标资源
3. 修复工作台模式下导航返回异常问题
4. 统一JSON类型安全解析,替换硬类型转换
5. 增加macOS深度链接支持,统一渠道分发信息
6. 优化部分页面生命周期和状态加载逻辑
7. 移除废弃的nearby_connections依赖
2026-06-19 06:43:55 +08:00

206 lines
6.6 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 — 引导页状态管理
// 创建时间: 2026-05-21
// 更新时间: 2026-06-12
// 作用: 管理引导页流程状态(页面切换、协议勾选、个性化设置)
// 上次更新: 从providers/子目录提升至onboarding根目录
// ============================================================
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/services/post_agreement_initializer.dart';
import '../../../core/storage/hive_safe_access.dart';
import '../../../core/storage/kv_storage.dart';
import '../../../core/utils/logger.dart';
import '../../../core/constants/default_settings.dart';
class OnboardingState {
const OnboardingState({
this.currentPage = 0,
this.totalPages = 3,
this.selectedLocale = 'zh',
this.privacyAgreed = false,
this.termsAgreed = false,
this.permissionRead = false,
this.showOnNextLaunch = false,
this.isCompleting = false,
this.agreementTabIndex = 0,
this.soundEnabled = DefaultSettings.onboardingSoundEnabled,
this.shaderBackground = false,
this.knowNewFeatures = false,
});
final int currentPage;
final int totalPages;
final String selectedLocale;
final bool privacyAgreed;
final bool termsAgreed;
final bool permissionRead;
final bool showOnNextLaunch;
final bool isCompleting;
final int agreementTabIndex;
final bool soundEnabled;
final bool shaderBackground;
final bool knowNewFeatures;
bool get canProceedAgreement =>
privacyAgreed && termsAgreed && permissionRead;
bool get isLastPage => currentPage >= totalPages - 1;
OnboardingState copyWith({
int? currentPage,
int? totalPages,
String? selectedLocale,
bool? privacyAgreed,
bool? termsAgreed,
bool? permissionRead,
bool? showOnNextLaunch,
bool? isCompleting,
int? agreementTabIndex,
bool? soundEnabled,
bool? shaderBackground,
bool? knowNewFeatures,
}) {
return OnboardingState(
currentPage: currentPage ?? this.currentPage,
totalPages: totalPages ?? this.totalPages,
selectedLocale: selectedLocale ?? this.selectedLocale,
privacyAgreed: privacyAgreed ?? this.privacyAgreed,
termsAgreed: termsAgreed ?? this.termsAgreed,
permissionRead: permissionRead ?? this.permissionRead,
showOnNextLaunch: showOnNextLaunch ?? this.showOnNextLaunch,
isCompleting: isCompleting ?? this.isCompleting,
agreementTabIndex: agreementTabIndex ?? this.agreementTabIndex,
soundEnabled: soundEnabled ?? this.soundEnabled,
shaderBackground: shaderBackground ?? this.shaderBackground,
knowNewFeatures: knowNewFeatures ?? this.knowNewFeatures,
);
}
}
class OnboardingNotifier extends Notifier<OnboardingState> {
@override
OnboardingState build() => const OnboardingState();
void nextPage() {
if (state.currentPage < state.totalPages - 1) {
state = state.copyWith(currentPage: state.currentPage + 1);
Log.i('Onboarding: nextPage → ${state.currentPage}');
}
}
void previousPage() {
if (state.currentPage > 0) {
state = state.copyWith(currentPage: state.currentPage - 1);
Log.i('Onboarding: previousPage → ${state.currentPage}');
}
}
void setPage(int page) {
if (page >= 0 && page < state.totalPages) {
state = state.copyWith(currentPage: page);
Log.i('Onboarding: setPage → $page');
}
}
/// 设置引导页总页数根据平台和skipAgreement动态调整
void setTotalPages(int pages) {
state = state.copyWith(totalPages: pages);
Log.i('Onboarding: setTotalPages → $pages');
}
void togglePrivacy() {
state = state.copyWith(privacyAgreed: !state.privacyAgreed);
}
void toggleTerms() {
state = state.copyWith(termsAgreed: !state.termsAgreed);
}
void togglePermissionRead() {
state = state.copyWith(permissionRead: !state.permissionRead);
}
void toggleShowOnNextLaunch() {
state = state.copyWith(showOnNextLaunch: !state.showOnNextLaunch);
}
void toggleSound() {
state = state.copyWith(soundEnabled: !state.soundEnabled);
}
void toggleShaderBackground() {
state = state.copyWith(shaderBackground: !state.shaderBackground);
}
/// 切换"了解新版本功能"开关
void toggleKnowNewFeatures() {
state = state.copyWith(knowNewFeatures: !state.knowNewFeatures);
}
void setLocale(String locale) {
state = state.copyWith(selectedLocale: locale);
Log.i('Onboarding: setLocale → $locale');
}
void setAgreementTabIndex(int index) {
state = state.copyWith(agreementTabIndex: index);
}
Future<void> completeOnboarding() async {
state = state.copyWith(isCompleting: true);
try {
await KvStorage.markLaunched();
await KvStorage.setBool('onboarding_completed', true);
await KvStorage.setShowOnboarding(state.showOnNextLaunch);
await KvStorage.setString('locale', state.selectedLocale);
await KvStorage.setBool('sfx_enabled', state.soundEnabled);
await KvStorage.setBool(
'general_shader_background',
state.shaderBackground,
);
// 保存"了解新版本功能"开关状态
await KvStorage.setBool('know_new_features', state.knowNewFeatures);
// 立即将关键数据刷入磁盘防止App被杀后数据丢失导致重复显示引导页
try {
final appBox = HiveSafeAccess.tryGetBox<dynamic>(HiveBoxNames.app);
await appBox?.flush();
} catch (_) {}
// 通知原生端协议已同意,注册之前跳过的敏感插件(传感器/麦克风/位置等)
await _notifyNativeAgreementAccepted();
Log.i('Onboarding: completeOnboarding ✓ 用户已同意协议,开始初始化权限敏感服务');
await PostAgreementInitializer.init();
Log.i('Onboarding: 权限敏感服务初始化完成 ✓');
} catch (e, st) {
Log.e('Onboarding: completeOnboarding error', e, st);
} finally {
state = state.copyWith(isCompleting: false);
}
}
/// 通知原生端协议已同意Android端补充注册敏感插件
static const _agreementChannel = MethodChannel('apps.xy.xianyan/agreement');
Future<void> _notifyNativeAgreementAccepted() async {
if (!Platform.isAndroid) return;
try {
await _agreementChannel.invokeMethod<bool>('markAgreementAccepted');
Log.i('Onboarding: 原生端协议同意通知成功 ✓');
} catch (e) {
Log.e('Onboarding: 原生端协议同意通知失败', e);
}
}
}
final onboardingProvider =
NotifierProvider<OnboardingNotifier, OnboardingState>(
OnboardingNotifier.new,
);