鸿蒙 白屏问题

This commit is contained in:
Developer
2026-05-18 03:07:50 +08:00
parent 702b41c29f
commit 1bca322600
36 changed files with 8094 additions and 974 deletions

View File

@@ -54,13 +54,12 @@ class AuthState {
class AuthNotifier extends Notifier<AuthState> {
@override
AuthState build() => _loadInitialState();
AuthState build() {
Future.microtask(_init);
return _loadInitialState();
}
static const String _userCacheKey = 'cached_user_info';
AuthNotifier() {
_init();
}
static AuthState _loadInitialState() {
if (!AppKVStore.isReady) return const AuthState();
final cached = AppKVStore.getString(_userCacheKey);

View File

@@ -1,4 +1,4 @@
// ============================================================
// ============================================================
// 闲言APP — 协作画布Riverpod状态管理
// 创建时间: 2026-05-14
// 更新时间: 2026-05-15
@@ -72,26 +72,22 @@ class CanvasNotifier extends Notifier<CanvasState> {
@override
CanvasState build() {
ref.onDispose(_onDispose);
return const CanvasState();
}
late final SignalingService _signaling = ref.read(sharedSignalingProvider);
late final CanvasEngine _engine;
late final CanvasSyncService _syncService;
CanvasNotifier() {
_signaling = ref.read(sharedSignalingProvider);
_engine = CanvasEngine();
_syncService = CanvasSyncService(signaling: _signaling);
_engine.addListener(_onEngineChanged);
_syncService.onRemoteStroke = _handleRemoteStroke;
_syncService.onRemoteSnapshot = _handleRemoteSnapshot;
_syncService.onRemoteCursor = _handleRemoteCursor;
_syncService.onParticipantsChanged = _handleParticipantsChanged;
_syncService.onSnapshotRequest = _handleSnapshotRequest;
return const CanvasState();
}
late SignalingService _signaling;
late CanvasEngine _engine;
late CanvasSyncService _syncService;
CanvasEngine get engine => _engine;
void setUserId(String userId) {

View File

@@ -87,13 +87,10 @@ class DeviceDiscoveryNotifier extends Notifier<DeviceDiscoveryState> {
@override
DeviceDiscoveryState build() {
ref.onDispose(_onDispose);
Future.microtask(_init);
return const DeviceDiscoveryState();
}
DeviceDiscoveryNotifier() {
Future.microtask(() => _init());
}
final LanDiscoveryService _lanService = LanDiscoveryService();
final BluetoothPairingService _bleService = BluetoothPairingService();
final NfcPairingService _nfcService = NfcPairingService();

View File

@@ -1,9 +1,9 @@
// ============================================================
// ============================================================
// 闲言APP — 首页每日推荐卡片(滑动版)
// 创建时间: 2026-04-27
// 更新时间: 2026-05-14
// 更新时间: 2026-05-18
// 作用: 首页每日推荐句子卡片,支持左右滑动切换,由内向外推送动画
// 上次更新: 增强加载卡片动画(旋转图标+呼吸文字+微光+浮动圆点)
// 上次更新: v6.0 OHOS端跳过加载动画(rotate/fadeIn/shimmer)防白屏
// ============================================================
import 'package:xianyan/core/utils/pattern_utils.dart';
@@ -25,6 +25,7 @@ import 'package:xianyan/core/theme/app_typography.dart';
import 'package:xianyan/shared/widgets/glass_container.dart';
import 'package:xianyan/core/utils/interaction_animations.dart';
import 'package:xianyan/core/utils/logger.dart';
import 'package:xianyan/core/utils/platform_utils.dart' as pu;
import 'package:xianyan/features/home/providers/home_provider.dart';
import 'package:xianyan/features/home/providers/daily_card_style_provider.dart';
import 'package:xianyan/features/home/models/feed_model.dart';
@@ -394,7 +395,7 @@ class _DailyCardState extends ConsumerState<DailyCard>
duration: const Duration(milliseconds: 1500),
curve: Curves.easeInOut,
begin: 0,
end: 1,
end: pu.isOhos ? 0 : 1,
),
const SizedBox(height: AppSpacing.sm),
Text(
@@ -406,13 +407,13 @@ class _DailyCardState extends ConsumerState<DailyCard>
.animate(onPlay: (c) => c.repeat(reverse: true))
.fadeIn(
duration: const Duration(milliseconds: 1200),
begin: 0.4,
begin: pu.isOhos ? 1.0 : 0.4,
curve: Curves.easeInOut,
)
.moveY(
duration: const Duration(milliseconds: 1200),
begin: 2,
end: -2,
begin: pu.isOhos ? 0 : 2,
end: pu.isOhos ? 0 : -2,
curve: Curves.easeInOut,
),
const SizedBox(height: AppSpacing.sm),
@@ -434,14 +435,16 @@ class _DailyCardState extends ConsumerState<DailyCard>
.scale(
duration: const Duration(milliseconds: 600),
delay: Duration(milliseconds: index * 200),
begin: const Offset(0.6, 0.6),
begin: pu.isOhos
? const Offset(1.0, 1.0)
: const Offset(0.6, 0.6),
end: const Offset(1.0, 1.0),
curve: Curves.easeInOut,
)
.fadeIn(
duration: const Duration(milliseconds: 600),
delay: Duration(milliseconds: index * 200),
begin: 0.3,
begin: pu.isOhos ? 1.0 : 0.3,
curve: Curves.easeInOut,
);
}),
@@ -457,7 +460,9 @@ class _DailyCardState extends ConsumerState<DailyCard>
.animate(onPlay: (c) => c.repeat(reverse: true))
.shimmer(
duration: const Duration(milliseconds: 2000),
color: ext.accent.withValues(alpha: 0.08),
color: pu.isOhos
? Colors.transparent
: ext.accent.withValues(alpha: 0.08),
angle: -15,
);
}

View File

@@ -31,6 +31,8 @@ import '../../../shared/widgets/skeleton.dart';
import '../../../core/utils/interaction_animations.dart';
import '../../../core/utils/sheet_animation_notifier.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/platform_feature_guard.dart';
import '../../../core/utils/platform_utils.dart' as pu;
import '../providers/home_provider.dart';
import '../../../features/source/providers/source_provider.dart';
import 'home_daily_card.dart';
@@ -141,6 +143,31 @@ class _HomePageState extends ConsumerState<HomePage> {
? const NeverScrollableScrollPhysics()
: const BouncingScrollPhysics(),
slivers: [
// OHOS 诊断横幅
if (pu.isOhos)
SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.xs,
),
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: CupertinoColors.systemRed.withValues(alpha: 0.15),
borderRadius: AppRadius.mdBorder,
),
child: Text(
'🔧 OHOS 诊断模式 | sentences=${state.sentences.length} isLoading=${state.isLoading} isDark=${ext.isDark}',
style: AppTypography.caption1.copyWith(
color: CupertinoColors.systemRed,
),
),
),
),
// 离线提示
if (state.isOffline)
SliverToBoxAdapter(
@@ -216,7 +243,7 @@ class _HomePageState extends ConsumerState<HomePage> {
),
],
),
).animate().fadeIn(duration: 300.ms),
).ohosSafeFadeIn(duration: 300.ms),
),
// 每日推荐卡片
@@ -224,121 +251,113 @@ class _HomePageState extends ConsumerState<HomePage> {
child: (state.isForceLoading && state.dailySentence == null)
? const DailyCardSkeleton()
: Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
child: DailyCard(
ext: ext,
state: state,
onScrollLockChanged: (locked) {
setState(() => _scrollLocked = locked);
},
onLike: (sentence) => ref
.read(homeProvider.notifier)
.toggleLike(sentence.id),
onFavorite: (sentence) => ref
.read(homeProvider.notifier)
.toggleFavorite(sentence.id),
onLoadMore: () => ref
.read(homeProvider.notifier)
.refreshDailySentences(),
onTap: (sentence) =>
_showDailyDetailSheet(sentence, ext, ref),
),
)
.animate()
.fadeIn(duration: 300.ms, delay: 100.ms)
.slideY(
begin: 0.05,
end: 0,
curve: Curves.easeOutCubic,
),
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
child: DailyCard(
ext: ext,
state: state,
onScrollLockChanged: (locked) {
setState(() => _scrollLocked = locked);
},
onLike: (sentence) => ref
.read(homeProvider.notifier)
.toggleLike(sentence.id),
onFavorite: (sentence) => ref
.read(homeProvider.notifier)
.toggleFavorite(sentence.id),
onLoadMore: () => ref
.read(homeProvider.notifier)
.refreshDailySentences(),
onTap: (sentence) =>
_showDailyDetailSheet(sentence, ext, ref),
),
).ohosSafeFadeInSlideY(
duration: 300.ms,
delay: 100.ms,
slideBegin: 0.05,
),
),
// 操作按钮行:创作卡片 / 编辑此句
SliverToBoxAdapter(
child:
Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
child: Row(
children: [
Expanded(
child: BounceButton(
onTap: () {
final text =
state.dailySentence?.text ??
'生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。';
_showQuickCard(context, text);
},
child: GlassContainer(
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Text(
'🎨',
style: TextStyle(fontSize: 20),
),
const SizedBox(width: AppSpacing.xs),
Text(
'创作卡片',
style: AppTypography.subhead.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
],
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
child: Row(
children: [
Expanded(
child: BounceButton(
onTap: () {
final text =
state.dailySentence?.text ??
'生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。';
_showQuickCard(context, text);
},
child: GlassContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'🎨',
style: TextStyle(fontSize: 20),
),
),
const SizedBox(width: AppSpacing.xs),
Text(
'创作卡片',
style: AppTypography.subhead.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
],
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: BounceButton(
onTap: () {
final text =
state.dailySentence?.text ??
'生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。';
context.push(
'${AppRoutes.editor}?text=${Uri.encodeComponent(text)}',
);
},
child: GlassContainer(
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Text(
'📝',
style: TextStyle(fontSize: 20),
),
const SizedBox(width: AppSpacing.xs),
Text(
'编辑此句',
style: AppTypography.subhead.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
],
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: BounceButton(
onTap: () {
final text =
state.dailySentence?.text ??
'生活不是等待暴风雨过去,而是学会在雨中翩翩起舞。';
context.push(
'${AppRoutes.editor}?text=${Uri.encodeComponent(text)}',
);
},
child: GlassContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'📝',
style: TextStyle(fontSize: 20),
),
),
const SizedBox(width: AppSpacing.xs),
Text(
'编辑此句',
style: AppTypography.subhead.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
),
)
.animate()
.fadeIn(duration: 300.ms, delay: 200.ms)
.slideY(
begin: 0.05,
end: 0,
curve: Curves.easeOutCubic,
),
],
),
).ohosSafeFadeInSlideY(
duration: 300.ms,
delay: 200.ms,
slideBegin: 0.05,
),
),
// 句子广场吸顶 Header — Feed频道
@@ -584,107 +603,119 @@ class _LoadingSkeletonCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
child: SkeletonBox(
child: Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: ext.bgCard,
borderRadius: AppRadius.lgBorder,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
child: SkeletonBox(
child: Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: ext.bgCard,
borderRadius: AppRadius.lgBorder,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Row(
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
shape: BoxShape.circle,
),
),
const SizedBox(width: AppSpacing.xs),
Container(
width: 48,
height: 10,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
const Spacer(),
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
shape: BoxShape.circle,
),
),
],
),
const SizedBox(height: AppSpacing.sm),
Container(
width: double.infinity,
height: 16,
width: 20,
height: 20,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
shape: BoxShape.circle,
),
),
const SizedBox(width: AppSpacing.xs),
Container(
width: 48,
height: 10,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
const SizedBox(height: AppSpacing.xs),
const Spacer(),
Container(
width: [0.7, 0.85, 0.6][index.clamp(0, 2)],
height: 14,
width: 24,
height: 24,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
shape: BoxShape.circle,
),
),
const SizedBox(height: AppSpacing.sm),
Row(
children: [
Container(
width: 60,
height: 10,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
const Spacer(),
Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
const SizedBox(width: AppSpacing.sm),
Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
],
),
],
),
),
const SizedBox(height: AppSpacing.sm),
Container(
width: double.infinity,
height: 16,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
const SizedBox(height: AppSpacing.xs),
Container(
width: [0.7, 0.85, 0.6][index.clamp(0, 2)],
height: 14,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
const SizedBox(height: AppSpacing.sm),
Row(
children: [
Container(
width: 60,
height: 10,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
const Spacer(),
Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
const SizedBox(width: AppSpacing.sm),
Container(
width: 28,
height: 20,
decoration: BoxDecoration(
color: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
],
),
],
),
)
.animate(onPlay: (c) => c.forward())
.shimmer(
duration: const Duration(milliseconds: 1000),
color: ext.accent.withValues(alpha: 0.2),
angle: -15,
);
),
),
).ohosSafeShimmer(
duration: const Duration(milliseconds: 1000),
color: ext.accent.withValues(alpha: 0.2),
angle: -15,
);
}
}
// ─── OHOS安全shimmer扩展 ───
extension _OhosSafeAnimate on Widget {
Widget ohosSafeShimmer({
required Duration duration,
required Color color,
double angle = 0,
}) {
if (pu.isOhos) return this;
return animate(
onPlay: (c) => c.forward(),
).shimmer(duration: duration, color: color, angle: angle);
}
}
@@ -834,26 +865,28 @@ class _SentenceCardWithSkeletonState extends State<_SentenceCardWithSkeleton>
: Duration.zero;
final animatedCard = shouldAnimate
? card
.animate(onPlay: (c) => c.forward(), delay: staggerDelay)
.fadeIn(duration: const Duration(milliseconds: 500))
.slideY(
begin: 0.12,
end: 0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutCubic,
)
.scale(
begin: const Offset(0.92, 0.92),
end: const Offset(1.0, 1.0),
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutCubic,
)
.shimmer(
duration: const Duration(milliseconds: 1200),
color: widget.accent.withValues(alpha: 0.25),
angle: -15,
)
? (pu.isOhos
? card
: card
.animate(onPlay: (c) => c.forward(), delay: staggerDelay)
.fadeIn(duration: const Duration(milliseconds: 500))
.slideY(
begin: 0.12,
end: 0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutCubic,
)
.scale(
begin: const Offset(0.92, 0.92),
end: const Offset(1.0, 1.0),
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutCubic,
)
.shimmer(
duration: const Duration(milliseconds: 1200),
color: widget.accent.withValues(alpha: 0.25),
angle: -15,
))
: card;
if (!_skeletonController.isAnimating && !_wasShowingSkeleton) {

View File

@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — 快速卡片创作 Sheet
// 创建时间: 2026-04-26
// 更新时间: 2026-04-30
// 更新时间: 2026-05-18
// 作用: 从首页底部弹出,快速调整文字样式/背景/预设,一键分享保存
// 上次更新: 增加"应用到主页卡片"按钮支持将创作样式同步到主页DailyCard
// 上次更新: v6.0 OHOS端跳过BackdropFilter防渲染崩溃
// ============================================================
import 'dart:ui' as ui;
@@ -19,6 +19,7 @@ import 'package:xianyan/core/theme/app_radius.dart';
import 'package:xianyan/core/theme/app_spacing.dart';
import 'package:xianyan/core/router/app_router.dart';
import 'package:xianyan/core/utils/logger.dart';
import 'package:xianyan/core/utils/platform_utils.dart' as pu;
import 'package:xianyan/editor/models/editor_models.dart';
import 'package:xianyan/editor/services/export/export_service.dart';
import 'package:xianyan/features/home/providers/quick_card_provider.dart';
@@ -266,80 +267,7 @@ class _QuickCardSheetState extends ConsumerState<QuickCardSheet> {
left: 0,
right: 0,
height: navBarH,
child: ClipRRect(
child: BackdropFilter(
filter: ui.ImageFilter.blur(
sigmaX: 20, // 加大模糊强度
sigmaY: 20,
),
child: Container(
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
// 大幅降低alpha让底层色彩透出来
color: ext.glassColor.withValues(
alpha: ext.isDark ? 0.45 : 0.38,
),
border: Border(
bottom: BorderSide(
color: const Color(
0xFF000000,
).withValues(alpha: ext.isDark ? 0.15 : 0.05),
width: 0.5,
),
),
),
padding: const EdgeInsets.only(
left: AppSpacing.md,
right: AppSpacing.md,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'✨ Slight editor mini',
style: AppTypography.title1.copyWith(
color: ext.textPrimary,
height: 1.0,
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: _handleShare,
child: Icon(
CupertinoIcons.share,
size: 20,
color: ext.iconSecondary,
),
),
const SizedBox(width: 4),
GestureDetector(
onTap: _handleSave,
child: Icon(
CupertinoIcons.arrow_down_doc,
size: 20,
color: ext.iconSecondary,
),
),
const SizedBox(width: 4),
CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () =>
Navigator.of(context).pop(),
child: Icon(
CupertinoIcons.xmark_circle_fill,
size: 28,
color: ext.iconSecondary,
),
),
],
),
],
),
),
),
),
child: ClipRRect(child: _buildNavBar(ext, navBarH)),
),
],
),
@@ -361,6 +289,96 @@ class _QuickCardSheetState extends ConsumerState<QuickCardSheet> {
);
}
Widget _buildNavBar(AppThemeExtension ext, double navBarH) {
final navBarContent = Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: AppSpacing.md, right: AppSpacing.md),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'✨ Slight editor mini',
style: AppTypography.title1.copyWith(
color: ext.textPrimary,
height: 1.0,
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: _handleShare,
child: Icon(
CupertinoIcons.share,
size: 20,
color: ext.iconSecondary,
),
),
const SizedBox(width: 4),
GestureDetector(
onTap: _handleSave,
child: Icon(
CupertinoIcons.arrow_down_doc,
size: 20,
color: ext.iconSecondary,
),
),
const SizedBox(width: 4),
CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () => Navigator.of(context).pop(),
child: Icon(
CupertinoIcons.xmark_circle_fill,
size: 28,
color: ext.iconSecondary,
),
),
],
),
],
),
);
if (pu.isOhos) {
return Container(
height: navBarH,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: ext.glassColor.withValues(alpha: ext.isDark ? 0.85 : 0.9),
border: Border(
bottom: BorderSide(
color: const Color(
0xFF000000,
).withValues(alpha: ext.isDark ? 0.15 : 0.05),
width: 0.5,
),
),
),
child: navBarContent,
);
}
return BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(
height: navBarH,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: ext.glassColor.withValues(alpha: ext.isDark ? 0.45 : 0.38),
border: Border(
bottom: BorderSide(
color: const Color(
0xFF000000,
).withValues(alpha: ext.isDark ? 0.15 : 0.05),
width: 0.5,
),
),
),
child: navBarContent,
),
);
}
Widget _sectionLabel(String label, AppThemeExtension ext) {
return Padding(
padding: const EdgeInsets.only(bottom: 8, top: 4),

View File

@@ -80,11 +80,13 @@ class ChatAttachmentState {
class ChatAttachmentNotifier extends Notifier<ChatAttachmentState> {
@override
ChatAttachmentState build() => const ChatAttachmentState();
ChatAttachmentNotifier(this.conversationId) {
_init();
ChatAttachmentState build() {
Future.microtask(_init);
return const ChatAttachmentState();
}
ChatAttachmentNotifier(this.conversationId);
final String conversationId;
Future<void> _init() async {

View File

@@ -100,10 +100,10 @@ class ChatSessionState {
class ChatSessionNotifier extends Notifier<ChatSessionState> {
@override
ChatSessionState build() => const ChatSessionState();
ChatSessionNotifier() {
_init();
}
ChatSessionState build() {
Future.microtask(_init);
return const ChatSessionState();
}
static const _sessionsKey = 'chat_sessions_remarks';

View File

@@ -131,14 +131,14 @@ class ToolCenterState {
/// 工具中心 Notifier
class ToolCenterNotifier extends Notifier<ToolCenterState> {
@override
ToolCenterState build() {
ref.onDispose(_onDispose);
return const ToolCenterState();
}
ToolCenterNotifier() {
_loadPersistedData();
_initConnectivity();
@override
ToolCenterState build() {
ref.onDispose(_onDispose);
Future.microtask(() {
_loadPersistedData();
_initConnectivity();
});
return const ToolCenterState();
}
static const _keyPrefix = 'tool_';

View File

@@ -72,11 +72,9 @@ class PomodoroNotifier extends Notifier<PomodoroState> {
@override
PomodoroState build() {
ref.onDispose(_onDispose);
Future.microtask(_loadRecords);
return const PomodoroState();
}
PomodoroNotifier() {
_loadRecords();
}
Timer? _timer;
static const _recordsKey = 'pomodoro_records';

View File

@@ -151,11 +151,9 @@ class SearchNotifier extends Notifier<SearchState> {
@override
SearchState build() {
ref.onDispose(_onDispose);
Future.microtask(_init);
return const SearchState();
}
SearchNotifier() {
_init();
}
StreamSubscription<NetworkType>? _networkSub;
final List<String> _pendingCloudSync = [];

View File

@@ -0,0 +1,91 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/storage/kv_storage.dart';
import '../general_settings_provider.dart';
class DeveloperSettingsState {
const DeveloperSettingsState({
this.debugInfoEnabled = false,
this.developerModeEnabled = false,
this.apiEnvironmentId = 'production',
this.apiCustomHost = '',
this.compatibilityMode = false,
});
final bool debugInfoEnabled;
final bool developerModeEnabled;
final String apiEnvironmentId;
final String apiCustomHost;
final bool compatibilityMode;
ApiEnvironment get apiEnvironment => ApiEnvironment.fromId(apiEnvironmentId);
DeveloperSettingsState copyWith({
bool? debugInfoEnabled,
bool? developerModeEnabled,
String? apiEnvironmentId,
String? apiCustomHost,
bool? compatibilityMode,
}) {
return DeveloperSettingsState(
debugInfoEnabled: debugInfoEnabled ?? this.debugInfoEnabled,
developerModeEnabled: developerModeEnabled ?? this.developerModeEnabled,
apiEnvironmentId: apiEnvironmentId ?? this.apiEnvironmentId,
apiCustomHost: apiCustomHost ?? this.apiCustomHost,
compatibilityMode: compatibilityMode ?? this.compatibilityMode,
);
}
static DeveloperSettingsState fromStorage() {
return DeveloperSettingsState(
debugInfoEnabled: KvStorage.getBool('general_debug') ?? false,
developerModeEnabled: KvStorage.getBool('general_developer_mode') ?? false,
apiEnvironmentId: KvStorage.getString('general_api_environment') ?? 'production',
apiCustomHost: KvStorage.getString('general_api_custom_host') ?? '',
compatibilityMode: KvStorage.getBool('general_compat') ?? false,
);
}
void saveToStorage() {
KvStorage.setBool('general_debug', debugInfoEnabled);
KvStorage.setBool('general_developer_mode', developerModeEnabled);
KvStorage.setString('general_api_environment', apiEnvironmentId);
KvStorage.setString('general_api_custom_host', apiCustomHost);
KvStorage.setBool('general_compat', compatibilityMode);
}
}
class DeveloperSettingsNotifier extends Notifier<DeveloperSettingsState> {
@override
DeveloperSettingsState build() => DeveloperSettingsState.fromStorage();
void setDebugInfoEnabled(bool v) {
state = state.copyWith(debugInfoEnabled: v);
KvStorage.setBool('general_debug', v);
}
void setDeveloperModeEnabled(bool v) {
state = state.copyWith(developerModeEnabled: v);
KvStorage.setBool('general_developer_mode', v);
}
void setApiEnvironment(String id) {
state = state.copyWith(apiEnvironmentId: id);
KvStorage.setString('general_api_environment', id);
}
void setApiCustomHost(String host) {
state = state.copyWith(apiCustomHost: host);
KvStorage.setString('general_api_custom_host', host);
}
void setCompatibilityMode(bool v) {
state = state.copyWith(compatibilityMode: v);
KvStorage.setBool('general_compat', v);
}
}
final developerSettingsProvider =
NotifierProvider<DeveloperSettingsNotifier, DeveloperSettingsState>(
DeveloperSettingsNotifier.new,
);

View File

@@ -0,0 +1,151 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/storage/kv_storage.dart';
import '../../../../core/services/device/screen_wake_service.dart';
import '../general_settings_provider.dart';
class DisplaySettingsState {
const DisplaySettingsState({
this.screenTimeoutId = '2m',
this.immersiveStatusBar = false,
this.reduceAnimations = false,
this.darkModeId = 'system',
this.fontScaleId = 'md',
this.highContrastEnabled = false,
this.colorWeakTypeId = 'none',
this.boldTextEnabled = false,
});
final String screenTimeoutId;
final bool immersiveStatusBar;
final bool reduceAnimations;
final String darkModeId;
final String fontScaleId;
final bool highContrastEnabled;
final String colorWeakTypeId;
final bool boldTextEnabled;
ScreenTimeoutOption get screenTimeout => screenTimeoutOptions.firstWhere(
(o) => o.id == screenTimeoutId,
orElse: () => screenTimeoutOptions[2],
);
DarkModeType get darkMode => DarkModeType.fromId(darkModeId);
FontScaleOption get fontScale => fontScaleOptions.firstWhere(
(o) => o.id == fontScaleId,
orElse: () => fontScaleOptions[2],
);
ColorWeakType get colorWeakType => ColorWeakType.fromId(colorWeakTypeId);
DisplaySettingsState copyWith({
String? screenTimeoutId,
bool? immersiveStatusBar,
bool? reduceAnimations,
String? darkModeId,
String? fontScaleId,
bool? highContrastEnabled,
String? colorWeakTypeId,
bool? boldTextEnabled,
}) {
return DisplaySettingsState(
screenTimeoutId: screenTimeoutId ?? this.screenTimeoutId,
immersiveStatusBar: immersiveStatusBar ?? this.immersiveStatusBar,
reduceAnimations: reduceAnimations ?? this.reduceAnimations,
darkModeId: darkModeId ?? this.darkModeId,
fontScaleId: fontScaleId ?? this.fontScaleId,
highContrastEnabled: highContrastEnabled ?? this.highContrastEnabled,
colorWeakTypeId: colorWeakTypeId ?? this.colorWeakTypeId,
boldTextEnabled: boldTextEnabled ?? this.boldTextEnabled,
);
}
static DisplaySettingsState fromStorage() {
const prefix = 'general_';
return DisplaySettingsState(
screenTimeoutId:
KvStorage.getString('${prefix}screen_timeout') ?? '2m',
immersiveStatusBar:
KvStorage.getBool('${prefix}immersive_status') ?? false,
reduceAnimations:
KvStorage.getBool('${prefix}reduce_animations') ?? false,
darkModeId: KvStorage.getString('${prefix}dark_mode') ?? 'system',
fontScaleId: KvStorage.getString('${prefix}font_scale') ?? 'md',
highContrastEnabled:
KvStorage.getBool('${prefix}high_contrast') ?? false,
colorWeakTypeId:
KvStorage.getString('${prefix}color_weak_type') ?? 'none',
boldTextEnabled: KvStorage.getBool('${prefix}bold_text') ?? false,
);
}
void saveToStorage() {
const prefix = 'general_';
KvStorage.setString('${prefix}screen_timeout', screenTimeoutId);
KvStorage.setBool('${prefix}immersive_status', immersiveStatusBar);
KvStorage.setBool('${prefix}reduce_animations', reduceAnimations);
KvStorage.setString('${prefix}dark_mode', darkModeId);
KvStorage.setString('${prefix}font_scale', fontScaleId);
KvStorage.setBool('${prefix}high_contrast', highContrastEnabled);
KvStorage.setString('${prefix}color_weak_type', colorWeakTypeId);
KvStorage.setBool('${prefix}bold_text', boldTextEnabled);
}
}
class DisplaySettingsNotifier extends Notifier<DisplaySettingsState> {
@override
DisplaySettingsState build() {
final loaded = DisplaySettingsState.fromStorage();
ScreenWakeService.setMode(
ScreenTimeoutMode.fromId(loaded.screenTimeoutId),
);
return loaded;
}
void setScreenTimeout(String id) {
state = state.copyWith(screenTimeoutId: id);
KvStorage.setString('general_screen_timeout', id);
ScreenWakeService.setMode(ScreenTimeoutMode.fromId(id));
}
void setImmersiveStatusBar(bool v) {
state = state.copyWith(immersiveStatusBar: v);
KvStorage.setBool('general_immersive_status', v);
}
void setReduceAnimations(bool v) {
state = state.copyWith(reduceAnimations: v);
KvStorage.setBool('general_reduce_animations', v);
}
void setDarkMode(String id) {
state = state.copyWith(darkModeId: id);
KvStorage.setString('general_dark_mode', id);
}
void setFontScale(String id) {
state = state.copyWith(fontScaleId: id);
KvStorage.setString('general_font_scale', id);
}
void setHighContrastEnabled(bool v) {
state = state.copyWith(highContrastEnabled: v);
KvStorage.setBool('general_high_contrast', v);
}
void setColorWeakType(String id) {
state = state.copyWith(colorWeakTypeId: id);
KvStorage.setString('general_color_weak_type', id);
}
void setBoldTextEnabled(bool v) {
state = state.copyWith(boldTextEnabled: v);
KvStorage.setBool('general_bold_text', v);
}
}
final displaySettingsProvider =
NotifierProvider<DisplaySettingsNotifier, DisplaySettingsState>(
DisplaySettingsNotifier.new,
);

View File

@@ -0,0 +1,290 @@
/// ============================================================
/// 闲言APP — 通用杂项设置状态管理
/// 创建时间: 2026-05-18
/// 更新时间: 2026-05-18
/// 作用: 管理同步/更新/语言/启动页/页面转场/预测返回/长按预览/搜索引擎/内容密度/自动清理/缓存等通用杂项设置
/// 上次更新: 初始创建,从 GeneralSettingsNotifier 拆分
/// ============================================================
import 'dart:io';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
import '../../../../core/storage/kv_storage.dart';
import '../../../../core/utils/logger.dart';
import '../general_settings_provider.dart';
class GeneralFieldsState {
const GeneralFieldsState({
this.syncEnabled = false,
this.autoCheckUpdate = true,
this.languageId = 'zh_CN',
this.startupPageId = 'home',
this.pageTransitionModeId = 'navigate',
this.predictiveBackEnabled = true,
this.longPressPreviewEnabled = true,
this.searchEngineId = 'internal',
this.contentDensityId = 'standard',
this.autoClearCacheId = 'never',
this.cacheSize = 0,
});
final bool syncEnabled;
final bool autoCheckUpdate;
final String languageId;
final String startupPageId;
final String pageTransitionModeId;
final bool predictiveBackEnabled;
final bool longPressPreviewEnabled;
final String searchEngineId;
final String contentDensityId;
final String autoClearCacheId;
final int cacheSize;
StartupPageOption get startupPage => startupPageOptions.firstWhere(
(o) => o.id == startupPageId,
orElse: () => startupPageOptions[0],
);
SearchEngineOption get searchEngine => searchEngineOptions.firstWhere(
(o) => o.id == searchEngineId,
orElse: () => searchEngineOptions[0],
);
PageTransitionMode get pageTransitionMode =>
PageTransitionMode.fromId(pageTransitionModeId);
ContentDensity get contentDensity => ContentDensity.fromId(contentDensityId);
AutoClearCacheOption get autoClearCache => autoClearCacheOptions.firstWhere(
(o) => o.id == autoClearCacheId,
orElse: () => autoClearCacheOptions[0],
);
String get cacheSizeText {
if (cacheSize <= 0) return '0 B';
if (cacheSize < 1024) return '$cacheSize B';
if (cacheSize < 1024 * 1024) {
return '${(cacheSize / 1024).toStringAsFixed(1)} KB';
}
if (cacheSize < 1024 * 1024 * 1024) {
return '${(cacheSize / (1024 * 1024)).toStringAsFixed(1)} MB';
}
return '${(cacheSize / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
}
GeneralFieldsState copyWith({
bool? syncEnabled,
bool? autoCheckUpdate,
String? languageId,
String? startupPageId,
String? pageTransitionModeId,
bool? predictiveBackEnabled,
bool? longPressPreviewEnabled,
String? searchEngineId,
String? contentDensityId,
String? autoClearCacheId,
int? cacheSize,
}) {
return GeneralFieldsState(
syncEnabled: syncEnabled ?? this.syncEnabled,
autoCheckUpdate: autoCheckUpdate ?? this.autoCheckUpdate,
languageId: languageId ?? this.languageId,
startupPageId: startupPageId ?? this.startupPageId,
pageTransitionModeId: pageTransitionModeId ?? this.pageTransitionModeId,
predictiveBackEnabled: predictiveBackEnabled ?? this.predictiveBackEnabled,
longPressPreviewEnabled: longPressPreviewEnabled ?? this.longPressPreviewEnabled,
searchEngineId: searchEngineId ?? this.searchEngineId,
contentDensityId: contentDensityId ?? this.contentDensityId,
autoClearCacheId: autoClearCacheId ?? this.autoClearCacheId,
cacheSize: cacheSize ?? this.cacheSize,
);
}
static const _keyPrefix = 'general_';
static String _migrateSwipeBack() {
final oldSwipe = KvStorage.getBool('${_keyPrefix}swipe_back');
if (oldSwipe == false) return 'sheet';
return 'navigate';
}
static GeneralFieldsState fromStorage() {
final storedTransitionMode =
KvStorage.getString('${_keyPrefix}page_transition_mode');
final resolvedTransitionMode =
storedTransitionMode ?? _migrateSwipeBack();
return GeneralFieldsState(
syncEnabled: KvStorage.getBool('${_keyPrefix}sync') ?? false,
autoCheckUpdate: KvStorage.getBool('${_keyPrefix}auto_update') ?? true,
languageId: KvStorage.getString('${_keyPrefix}language') ?? 'zh_CN',
startupPageId:
KvStorage.getString('${_keyPrefix}startup_page') ?? 'home',
pageTransitionModeId: resolvedTransitionMode,
predictiveBackEnabled:
KvStorage.getBool('${_keyPrefix}predictive_back') ?? true,
longPressPreviewEnabled:
KvStorage.getBool('${_keyPrefix}long_press_preview') ?? true,
searchEngineId:
KvStorage.getString('${_keyPrefix}search_engine') ?? 'internal',
contentDensityId:
KvStorage.getString('${_keyPrefix}content_density') ?? 'standard',
autoClearCacheId:
KvStorage.getString('${_keyPrefix}auto_clear_cache') ?? 'never',
);
}
void saveToStorage() {
KvStorage.setBool('${_keyPrefix}sync', syncEnabled);
KvStorage.setBool('${_keyPrefix}auto_update', autoCheckUpdate);
KvStorage.setString('${_keyPrefix}language', languageId);
KvStorage.setString('${_keyPrefix}startup_page', startupPageId);
KvStorage.setString('${_keyPrefix}page_transition_mode', pageTransitionModeId);
KvStorage.setBool('${_keyPrefix}predictive_back', predictiveBackEnabled);
KvStorage.setBool('${_keyPrefix}long_press_preview', longPressPreviewEnabled);
KvStorage.setString('${_keyPrefix}search_engine', searchEngineId);
KvStorage.setString('${_keyPrefix}content_density', contentDensityId);
KvStorage.setString('${_keyPrefix}auto_clear_cache', autoClearCacheId);
}
}
class GeneralFieldsNotifier extends Notifier<GeneralFieldsState> {
@override
GeneralFieldsState build() {
_loadFromStorage();
return const GeneralFieldsState();
}
static const _keyPrefix = 'general_';
Future<void> _loadFromStorage() async {
try {
state = GeneralFieldsState.fromStorage();
Log.i('通用杂项设置加载完成');
} catch (e) {
Log.e('通用杂项设置加载失败', e);
}
}
void setSyncEnabled(bool v) {
state = state.copyWith(syncEnabled: v);
KvStorage.setBool('${_keyPrefix}sync', v);
}
void setAutoCheckUpdate(bool v) {
state = state.copyWith(autoCheckUpdate: v);
KvStorage.setBool('${_keyPrefix}auto_update', v);
}
void setLanguage(String id) {
state = state.copyWith(languageId: id);
KvStorage.setString('${_keyPrefix}language', id);
}
void setStartupPage(String id) {
state = state.copyWith(startupPageId: id);
KvStorage.setString('${_keyPrefix}startup_page', id);
}
void setPageTransitionMode(String id) {
state = state.copyWith(pageTransitionModeId: id);
KvStorage.setString('${_keyPrefix}page_transition_mode', id);
}
void setPredictiveBackEnabled(bool v) {
state = state.copyWith(predictiveBackEnabled: v);
KvStorage.setBool('${_keyPrefix}predictive_back', v);
}
void setLongPressPreviewEnabled(bool v) {
state = state.copyWith(longPressPreviewEnabled: v);
KvStorage.setBool('${_keyPrefix}long_press_preview', v);
}
void setSearchEngine(String id) {
state = state.copyWith(searchEngineId: id);
KvStorage.setString('${_keyPrefix}search_engine', id);
}
void setContentDensity(String id) {
state = state.copyWith(contentDensityId: id);
KvStorage.setString('${_keyPrefix}content_density', id);
}
void setAutoClearCache(String id) {
state = state.copyWith(autoClearCacheId: id);
KvStorage.setString('${_keyPrefix}auto_clear_cache', id);
}
void setCacheSize(int size) {
state = state.copyWith(cacheSize: size);
}
Future<void> calculateCacheSize() async {
try {
int totalSize = 0;
final tempDir = await getTemporaryDirectory();
totalSize += await _dirSize(tempDir);
try {
final appDir = await getApplicationDocumentsDirectory();
totalSize += await _dirSize(appDir);
} catch (_) {}
try {
final supportDir = await getApplicationSupportDirectory();
totalSize += await _dirSize(supportDir);
} catch (_) {}
state = state.copyWith(cacheSize: totalSize);
} catch (e) {
Log.e('缓存大小计算失败', e);
}
}
Future<int> _dirSize(Directory dir) async {
if (!await dir.exists()) return 0;
int size = 0;
try {
await for (final entity in dir.list(
recursive: true,
followLinks: false,
)) {
if (entity is File) {
try {
size += await entity.length();
} catch (_) {}
}
}
} catch (_) {}
return size;
}
Future<void> clearCache() async {
try {
final tempDir = await getTemporaryDirectory();
if (await tempDir.exists()) {
await for (final entity in tempDir.list()) {
try {
if (entity is File) {
await entity.delete();
} else if (entity is Directory) {
await entity.delete(recursive: true);
}
} catch (_) {}
}
}
state = state.copyWith(cacheSize: 0);
Log.i('缓存已清除');
} catch (e) {
Log.e('缓存清除失败', e);
}
}
}
final generalFieldsProvider =
NotifierProvider<GeneralFieldsNotifier, GeneralFieldsState>(
GeneralFieldsNotifier.new,
);

View File

@@ -0,0 +1,103 @@
/// ============================================================
/// 闲言APP — 网络设置状态管理
/// 创建时间: 2026-05-18
/// 更新时间: 2026-05-18
/// 作用: 管理代理/缓存策略/自动播放WiFi等网络相关设置
/// 上次更新: 初始创建从general_settings_provider拆分
/// ============================================================
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/services/network/network_proxy_service.dart';
import '../../../../core/storage/kv_storage.dart';
import '../general_settings_provider.dart';
class NetworkSettingsState {
const NetworkSettingsState({
this.proxyEnabled = false,
this.proxyHost = '',
this.proxyPort = 0,
this.cacheStrategyId = 'smart',
this.autoPlayWifiOnly = false,
});
final bool proxyEnabled;
final String proxyHost;
final int proxyPort;
final String cacheStrategyId;
final bool autoPlayWifiOnly;
CacheStrategy get cacheStrategy => CacheStrategy.fromId(cacheStrategyId);
NetworkSettingsState copyWith({
bool? proxyEnabled,
String? proxyHost,
int? proxyPort,
String? cacheStrategyId,
bool? autoPlayWifiOnly,
}) {
return NetworkSettingsState(
proxyEnabled: proxyEnabled ?? this.proxyEnabled,
proxyHost: proxyHost ?? this.proxyHost,
proxyPort: proxyPort ?? this.proxyPort,
cacheStrategyId: cacheStrategyId ?? this.cacheStrategyId,
autoPlayWifiOnly: autoPlayWifiOnly ?? this.autoPlayWifiOnly,
);
}
static NetworkSettingsState fromStorage() {
return NetworkSettingsState(
proxyEnabled: KvStorage.getBool('general_proxy_enabled') ?? false,
proxyHost: KvStorage.getString('general_proxy_host') ?? '',
proxyPort: KvStorage.getInt('general_proxy_port') ?? 0,
cacheStrategyId: KvStorage.getString('general_cache_strategy') ?? 'smart',
autoPlayWifiOnly: KvStorage.getBool('general_auto_play_wifi_only') ?? false,
);
}
void saveToStorage() {
KvStorage.setBool('general_proxy_enabled', proxyEnabled);
KvStorage.setString('general_proxy_host', proxyHost);
KvStorage.setInt('general_proxy_port', proxyPort);
KvStorage.setString('general_cache_strategy', cacheStrategyId);
KvStorage.setBool('general_auto_play_wifi_only', autoPlayWifiOnly);
}
}
class NetworkSettingsNotifier extends Notifier<NetworkSettingsState> {
@override
NetworkSettingsState build() => NetworkSettingsState.fromStorage();
void setProxyEnabled(bool v) {
state = state.copyWith(proxyEnabled: v);
KvStorage.setBool('general_proxy_enabled', v);
NetworkProxyService.setEnabled(v);
}
void setProxyHost(String host) {
state = state.copyWith(proxyHost: host);
KvStorage.setString('general_proxy_host', host);
NetworkProxyService.setHost(host);
}
void setProxyPort(int port) {
state = state.copyWith(proxyPort: port);
KvStorage.setInt('general_proxy_port', port);
NetworkProxyService.setPort(port);
}
void setCacheStrategy(String id) {
state = state.copyWith(cacheStrategyId: id);
KvStorage.setString('general_cache_strategy', id);
}
void setAutoPlayWifiOnly(bool v) {
state = state.copyWith(autoPlayWifiOnly: v);
KvStorage.setBool('general_auto_play_wifi_only', v);
}
}
final networkSettingsProvider =
NotifierProvider<NetworkSettingsNotifier, NetworkSettingsState>(
NetworkSettingsNotifier.new,
);

View File

@@ -0,0 +1,108 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/services/device/battery_optimization_service.dart';
import '../../../../core/storage/kv_storage.dart';
import '../general_settings_provider.dart';
class PerformanceSettingsState {
const PerformanceSettingsState({
this.preloadEnabled = true,
this.dataSaverMode = false,
this.imageQualityId = 'high',
this.autoPlayEnabled = true,
this.batteryOptimizationEnabled = false,
this.backgroundRefreshEnabled = true,
});
final bool preloadEnabled;
final bool dataSaverMode;
final String imageQualityId;
final bool autoPlayEnabled;
final bool batteryOptimizationEnabled;
final bool backgroundRefreshEnabled;
ImageQuality get imageQuality => ImageQuality.fromId(imageQualityId);
PerformanceSettingsState copyWith({
bool? preloadEnabled,
bool? dataSaverMode,
String? imageQualityId,
bool? autoPlayEnabled,
bool? batteryOptimizationEnabled,
bool? backgroundRefreshEnabled,
}) {
return PerformanceSettingsState(
preloadEnabled: preloadEnabled ?? this.preloadEnabled,
dataSaverMode: dataSaverMode ?? this.dataSaverMode,
imageQualityId: imageQualityId ?? this.imageQualityId,
autoPlayEnabled: autoPlayEnabled ?? this.autoPlayEnabled,
batteryOptimizationEnabled:
batteryOptimizationEnabled ?? this.batteryOptimizationEnabled,
backgroundRefreshEnabled:
backgroundRefreshEnabled ?? this.backgroundRefreshEnabled,
);
}
static PerformanceSettingsState fromStorage() {
return PerformanceSettingsState(
preloadEnabled: KvStorage.getBool('general_preload') ?? true,
dataSaverMode: KvStorage.getBool('general_data_saver') ?? false,
imageQualityId: KvStorage.getString('general_image_quality') ?? 'high',
autoPlayEnabled: KvStorage.getBool('general_auto_play') ?? true,
batteryOptimizationEnabled:
KvStorage.getBool('general_battery_optimization') ?? false,
backgroundRefreshEnabled:
KvStorage.getBool('general_background_refresh') ?? true,
);
}
void saveToStorage() {
KvStorage.setBool('general_preload', preloadEnabled);
KvStorage.setBool('general_data_saver', dataSaverMode);
KvStorage.setString('general_image_quality', imageQualityId);
KvStorage.setBool('general_auto_play', autoPlayEnabled);
KvStorage.setBool('general_battery_optimization', batteryOptimizationEnabled);
KvStorage.setBool('general_background_refresh', backgroundRefreshEnabled);
}
}
class PerformanceSettingsNotifier extends Notifier<PerformanceSettingsState> {
@override
PerformanceSettingsState build() => PerformanceSettingsState.fromStorage();
void setPreloadEnabled(bool v) {
state = state.copyWith(preloadEnabled: v);
KvStorage.setBool('general_preload', v);
}
void setDataSaverMode(bool v) {
state = state.copyWith(dataSaverMode: v);
KvStorage.setBool('general_data_saver', v);
}
void setImageQuality(String id) {
state = state.copyWith(imageQualityId: id);
KvStorage.setString('general_image_quality', id);
}
void setAutoPlayEnabled(bool v) {
state = state.copyWith(autoPlayEnabled: v);
KvStorage.setBool('general_auto_play', v);
}
void setBatteryOptimizationEnabled(bool v) {
state = state.copyWith(batteryOptimizationEnabled: v);
KvStorage.setBool('general_battery_optimization', v);
BatteryOptimizationService.setEnabled(v);
}
void setBackgroundRefreshEnabled(bool v) {
state = state.copyWith(backgroundRefreshEnabled: v);
KvStorage.setBool('general_background_refresh', v);
}
}
final performanceSettingsProvider =
NotifierProvider<PerformanceSettingsNotifier, PerformanceSettingsState>(
PerformanceSettingsNotifier.new,
);

View File

@@ -0,0 +1,76 @@
/// ============================================================
/// 闲言APP — 隐私设置状态管理
/// 创建时间: 2026-05-18
/// 更新时间: 2026-05-18
/// 作用: 管理应用锁/剪贴板读取/搜索历史等隐私相关设置
/// 上次更新: 初始创建从general_settings_provider拆分
/// ============================================================
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/services/device/app_lock_service.dart';
import '../../../../core/storage/kv_storage.dart';
class PrivacySettingsState {
const PrivacySettingsState({
this.appLockEnabled = false,
this.clipboardReadEnabled = true,
this.searchHistoryEnabled = true,
});
final bool appLockEnabled;
final bool clipboardReadEnabled;
final bool searchHistoryEnabled;
PrivacySettingsState copyWith({
bool? appLockEnabled,
bool? clipboardReadEnabled,
bool? searchHistoryEnabled,
}) {
return PrivacySettingsState(
appLockEnabled: appLockEnabled ?? this.appLockEnabled,
clipboardReadEnabled: clipboardReadEnabled ?? this.clipboardReadEnabled,
searchHistoryEnabled: searchHistoryEnabled ?? this.searchHistoryEnabled,
);
}
static PrivacySettingsState fromStorage() {
return PrivacySettingsState(
appLockEnabled: KvStorage.getBool('general_app_lock') ?? false,
clipboardReadEnabled: KvStorage.getBool('general_clipboard_read') ?? true,
searchHistoryEnabled: KvStorage.getBool('general_search_history') ?? true,
);
}
void saveToStorage() {
KvStorage.setBool('general_app_lock', appLockEnabled);
KvStorage.setBool('general_clipboard_read', clipboardReadEnabled);
KvStorage.setBool('general_search_history', searchHistoryEnabled);
}
}
class PrivacySettingsNotifier extends Notifier<PrivacySettingsState> {
@override
PrivacySettingsState build() => PrivacySettingsState.fromStorage();
void setAppLockEnabled(bool v) {
state = state.copyWith(appLockEnabled: v);
KvStorage.setBool('general_app_lock', v);
AppLockService.setEnabled(v);
}
void setClipboardReadEnabled(bool v) {
state = state.copyWith(clipboardReadEnabled: v);
KvStorage.setBool('general_clipboard_read', v);
}
void setSearchHistoryEnabled(bool v) {
state = state.copyWith(searchHistoryEnabled: v);
KvStorage.setBool('general_search_history', v);
}
}
final privacySettingsProvider =
NotifierProvider<PrivacySettingsNotifier, PrivacySettingsState>(
PrivacySettingsNotifier.new,
);

View File

@@ -0,0 +1,102 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/storage/kv_storage.dart';
import '../../../../core/services/device/haptic_service.dart';
import '../../../../core/services/sound_service.dart' as svc;
class SoundSettingsState {
const SoundSettingsState({
this.soundEnabled = true,
this.vibrationLevelId = 'medium',
this.soundEffectTypeId = 'default',
});
final bool soundEnabled;
final String vibrationLevelId;
final String soundEffectTypeId;
VibrationLevel get vibrationLevel => VibrationLevel.fromId(vibrationLevelId);
svc.SoundEffectType get soundEffectType =>
svc.SoundEffectType.fromId(soundEffectTypeId);
bool get vibrationEnabled => vibrationLevelId != 'off';
SoundSettingsState copyWith({
bool? soundEnabled,
String? vibrationLevelId,
String? soundEffectTypeId,
}) {
return SoundSettingsState(
soundEnabled: soundEnabled ?? this.soundEnabled,
vibrationLevelId: vibrationLevelId ?? this.vibrationLevelId,
soundEffectTypeId: soundEffectTypeId ?? this.soundEffectTypeId,
);
}
static SoundSettingsState fromStorage() {
const prefix = 'general_';
final vibrationId = KvStorage.getString('${prefix}vibration_level');
final compatVibration = KvStorage.getBool('${prefix}vibration');
String resolvedVibrationId;
if (vibrationId != null) {
resolvedVibrationId = vibrationId;
} else if (compatVibration != null) {
resolvedVibrationId = compatVibration ? 'medium' : 'off';
} else {
resolvedVibrationId = 'medium';
}
return SoundSettingsState(
soundEnabled: KvStorage.getBool('${prefix}sound') ?? true,
vibrationLevelId: resolvedVibrationId,
soundEffectTypeId:
KvStorage.getString('${prefix}sound_effect') ?? 'default',
);
}
void saveToStorage() {
const prefix = 'general_';
KvStorage.setBool('${prefix}sound', soundEnabled);
KvStorage.setString('${prefix}vibration_level', vibrationLevelId);
KvStorage.setString('${prefix}sound_effect', soundEffectTypeId);
}
}
class SoundSettingsNotifier extends Notifier<SoundSettingsState> {
@override
SoundSettingsState build() {
final loaded = SoundSettingsState.fromStorage();
HapticService.setLevel(loaded.vibrationLevel);
svc.SoundService.setEnabled(loaded.soundEnabled);
svc.SoundService.setEffectType(loaded.soundEffectType);
return loaded;
}
void setSoundEnabled(bool v) {
state = state.copyWith(soundEnabled: v);
KvStorage.setBool('general_sound', v);
svc.SoundService.setEnabled(v);
}
void setVibrationLevel(String id) {
final level = VibrationLevel.fromId(id);
state = state.copyWith(vibrationLevelId: id);
KvStorage.setString('general_vibration_level', id);
HapticService.setLevel(level);
}
void setSoundEffectType(String id) {
final type = svc.SoundEffectType.fromId(id);
state = state.copyWith(soundEffectTypeId: id);
KvStorage.setString('general_sound_effect', id);
svc.SoundService.setEffectType(type);
}
}
final soundSettingsProvider =
NotifierProvider<SoundSettingsNotifier, SoundSettingsState>(
SoundSettingsNotifier.new,
);