鸿蒙 白屏问题
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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_';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
);
|
||||
@@ -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,
|
||||
);
|
||||
290
lib/features/settings/providers/sub/general_fields_provider.dart
Normal file
290
lib/features/settings/providers/sub/general_fields_provider.dart
Normal 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,
|
||||
);
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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,
|
||||
);
|
||||
102
lib/features/settings/providers/sub/sound_settings_provider.dart
Normal file
102
lib/features/settings/providers/sub/sound_settings_provider.dart
Normal 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,
|
||||
);
|
||||
Reference in New Issue
Block a user