### 详细变更:
1. **文档与配置**:更新AGENTS.md添加命令超时约束,升级Rive依赖至0.14.7并替换平台插件引用
2. **UI优化**:重构AppInfo页面布局、移除图表冗余配置、锁定部分系统设置项
3. **功能增强**:
- 新增工具面板拖拽状态管理与介绍弹窗
- 新增进度页面编辑/重排/清空用户进度功能
- 新增摇一摇路由作用域拦截逻辑
4. **体验优化**:
- 统一外部链接跳转弹窗,添加文件打开确认逻辑
- 修复设备卡片IP溢出、Android权限声明问题
- 后台任务初始化增加协议校验
5. **代码重构**:拆分工具面板配置、拖拽逻辑与动画参数,优化状态管理代码
6. **工具脚本**:新增协议文件上传脚本
167 lines
5.0 KiB
Dart
167 lines
5.0 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 宽屏分屏状态管理
|
||
/// 创建时间: 2026-05-29
|
||
/// 更新时间: 2026-05-29
|
||
/// 作用: 管理分屏比例、右侧面板内容、导航栏位置、分屏开关
|
||
/// 上次更新: 新增三栏布局状态(thirdPanelContent/thirdPanelArgs/setThirdPanel)
|
||
/// ============================================================
|
||
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||
import 'package:xianyan/core/storage/kv_storage.dart';
|
||
|
||
part 'split_view_provider.freezed.dart';
|
||
|
||
/// 导航栏停靠位置
|
||
enum NavBarPosition {
|
||
left,
|
||
right,
|
||
top,
|
||
bottom;
|
||
|
||
String get label => switch (this) {
|
||
left => '左侧',
|
||
right => '右侧',
|
||
top => '顶部',
|
||
bottom => '底部',
|
||
};
|
||
|
||
String get emoji => switch (this) {
|
||
left => '⬅️',
|
||
right => '➡️',
|
||
top => '⬆️',
|
||
bottom => '⬇️',
|
||
};
|
||
}
|
||
|
||
/// 分屏比例选项
|
||
class SplitRatioOption {
|
||
const SplitRatioOption(this.value, this.label);
|
||
final double value;
|
||
final String label;
|
||
|
||
static const List<SplitRatioOption> options = [
|
||
SplitRatioOption(0.30, '30:70'),
|
||
SplitRatioOption(0.35, '35:65'),
|
||
SplitRatioOption(0.40, '40:60'),
|
||
SplitRatioOption(0.45, '45:55'),
|
||
SplitRatioOption(0.50, '50:50'),
|
||
SplitRatioOption(0.55, '55:45'),
|
||
SplitRatioOption(0.60, '60:40'),
|
||
];
|
||
|
||
static SplitRatioOption? findByValue(double v) =>
|
||
options.where((o) => o.value == v).firstOrNull;
|
||
|
||
static int indexOfValue(double v) {
|
||
final idx = options.indexWhere((o) => o.value == v);
|
||
return idx >= 0 ? idx : 2;
|
||
}
|
||
}
|
||
|
||
const String _kSplitRatio = 'split_view_ratio';
|
||
const String _kNavBarPosition = 'nav_bar_position';
|
||
const String _kSplitViewEnabled = 'split_view_enabled';
|
||
|
||
/// 三栏布局断点:宽度 >= 1400px 进入三栏模式
|
||
const double kTripleColumnBreakpoint = 1400.0;
|
||
|
||
/// 分屏视图状态(freezed不可变数据类)
|
||
@freezed
|
||
sealed class SplitViewState with _$SplitViewState {
|
||
const SplitViewState._();
|
||
const factory SplitViewState({
|
||
@Default(0.4) double splitRatio,
|
||
String? rightPanelContent,
|
||
Map<String, dynamic>? rightPanelArgs,
|
||
@Default(NavBarPosition.left) NavBarPosition navBarPosition,
|
||
@Default(true) bool splitViewEnabled,
|
||
String? homeRightPanel,
|
||
String? discoverRightPanel,
|
||
String? profileRightPanel,
|
||
@Default(0) int currentTab,
|
||
String? thirdPanelContent,
|
||
Map<String, dynamic>? thirdPanelArgs,
|
||
}) = _SplitViewState;
|
||
|
||
String? get activeRightPanel => switch (currentTab) {
|
||
0 => homeRightPanel,
|
||
1 => discoverRightPanel,
|
||
2 => profileRightPanel,
|
||
_ => null,
|
||
};
|
||
}
|
||
|
||
/// 分屏视图状态管理Notifier
|
||
class SplitViewNotifier extends Notifier<SplitViewState> {
|
||
@override
|
||
SplitViewState build() {
|
||
final savedIndex = KvStorage.getInt(_kNavBarPosition) ?? 0;
|
||
const positions = NavBarPosition.values;
|
||
return SplitViewState(
|
||
splitRatio: KvStorage.getDouble(_kSplitRatio) ?? 0.4,
|
||
navBarPosition: (savedIndex >= 0 && savedIndex < positions.length)
|
||
? positions[savedIndex]
|
||
: NavBarPosition.left,
|
||
splitViewEnabled: KvStorage.getBool(_kSplitViewEnabled) ?? true,
|
||
);
|
||
}
|
||
|
||
void setSplitRatio(double value) {
|
||
KvStorage.setDouble(_kSplitRatio, value);
|
||
state = state.copyWith(splitRatio: value);
|
||
}
|
||
|
||
void setRightPanelContent(String? content, {Map<String, dynamic>? args}) {
|
||
state = state.copyWith(rightPanelContent: content, rightPanelArgs: args);
|
||
}
|
||
|
||
void setNavBarPosition(NavBarPosition position) {
|
||
KvStorage.setInt(_kNavBarPosition, position.index);
|
||
state = state.copyWith(navBarPosition: position);
|
||
}
|
||
|
||
void setSplitViewEnabled(bool enabled) {
|
||
KvStorage.setBool(_kSplitViewEnabled, enabled);
|
||
state = state.copyWith(splitViewEnabled: enabled);
|
||
}
|
||
|
||
void setHomeRightPanel(String? panelId, {Map<String, dynamic>? args}) {
|
||
state = state.copyWith(homeRightPanel: panelId, rightPanelArgs: args);
|
||
}
|
||
|
||
void setDiscoverRightPanel(String? panelId, {Map<String, dynamic>? args}) {
|
||
state = state.copyWith(discoverRightPanel: panelId, rightPanelArgs: args);
|
||
}
|
||
|
||
void setProfileRightPanel(String? panelId, {Map<String, dynamic>? args}) {
|
||
state = state.copyWith(profileRightPanel: panelId, rightPanelArgs: args);
|
||
}
|
||
|
||
void setCurrentTab(int index) {
|
||
state = state.copyWith(currentTab: index);
|
||
}
|
||
|
||
/// 设置第三栏面板内容
|
||
void setThirdPanel(String? panelId, {Map<String, dynamic>? args}) {
|
||
state = state.copyWith(thirdPanelContent: panelId, thirdPanelArgs: args);
|
||
}
|
||
|
||
void clearActivePanel() {
|
||
final tab = state.currentTab;
|
||
switch (tab) {
|
||
case 0:
|
||
state = state.copyWith(homeRightPanel: null);
|
||
case 1:
|
||
state = state.copyWith(discoverRightPanel: null);
|
||
case 2:
|
||
state = state.copyWith(profileRightPanel: null);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 分屏视图Provider
|
||
final splitViewProvider = NotifierProvider<SplitViewNotifier, SplitViewState>(
|
||
SplitViewNotifier.new,
|
||
);
|