generated from dellevin/template
优化项目结构2
This commit is contained in:
@@ -11,7 +11,7 @@ import '../../utils/user_prefs.dart';
|
||||
import 'book_reviews_page.dart';
|
||||
import 'book_excerpts_page.dart';
|
||||
import 'book_share_page.dart';
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../epub_reader/epub_highlights_page.dart';
|
||||
import '../epub_reader/reader_screen.dart';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../models/data_models.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../../widgets/fade_in_local_image.dart';
|
||||
import 'book_excerpt_form_page.dart';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/book/book_dao.dart';
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../data/book/book_dao.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../models/data_models.dart';
|
||||
import '../../widgets/fade_in_local_image.dart';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import '../../utils/epub/epub_webview_handler.dart';
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../utils/epub/reader_models.dart';
|
||||
import '../../services/epub/epub_webview_handler.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../../data/epub/reader_models.dart';
|
||||
|
||||
/// Manages the current reading session including book data, TOC state, and
|
||||
/// progress tracking. Adapted from lumina's BookSession but uses mooknote's
|
||||
|
||||
@@ -3,11 +3,11 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../utils/epub/epub_parser.dart';
|
||||
import '../../utils/epub/reader_models.dart';
|
||||
import '../../utils/book/book_dao.dart';
|
||||
import '../../utils/book/book_excerpt_dao.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../../services/epub/epub_parser.dart';
|
||||
import '../../data/epub/reader_models.dart';
|
||||
import '../../data/book/book_dao.dart';
|
||||
import '../../data/book/book_excerpt_dao.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../models/data_models.dart';
|
||||
import '../book/book_detail_page.dart';
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../../widgets/genre_selector_page.dart';
|
||||
import '../../widgets/text_input_panel.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
import 'highlight_detail_sheet.dart';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../utils/epub/epub_service.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../../services/epub/epub_service.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
import '../../utils/responsive.dart';
|
||||
import 'epub_detail_page.dart';
|
||||
|
||||
@@ -4,9 +4,9 @@ import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/epub/epub_theme.dart';
|
||||
import '../../utils/epub/reader_settings.dart';
|
||||
import '../../utils/epub/epub_webview_handler.dart';
|
||||
import '../../services/epub/epub_theme.dart';
|
||||
import '../../services/epub/reader_settings.dart';
|
||||
import '../../services/epub/epub_webview_handler.dart';
|
||||
import 'book_session.dart';
|
||||
import 'reader_webview.dart';
|
||||
import 'page_turn/page_turn.dart';
|
||||
|
||||
@@ -4,14 +4,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../utils/database_helper.dart';
|
||||
import '../../utils/epub/epub_theme.dart';
|
||||
import '../../utils/epub/epub_webview_handler.dart';
|
||||
import '../../utils/epub/epub_stream_service.dart';
|
||||
import '../../utils/epub/epub_parser.dart';
|
||||
import '../../utils/epub/reader_settings.dart';
|
||||
import '../../utils/epub/reader_models.dart';
|
||||
import '../../utils/epub/reader_dao.dart';
|
||||
import '../../data/database_helper.dart';
|
||||
import '../../services/epub/epub_theme.dart';
|
||||
import '../../services/epub/epub_webview_handler.dart';
|
||||
import '../../services/epub/epub_stream_service.dart';
|
||||
import '../../services/epub/epub_parser.dart';
|
||||
import '../../services/epub/reader_settings.dart';
|
||||
import '../../data/epub/reader_models.dart';
|
||||
import '../../data/epub/reader_dao.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import 'book_session.dart';
|
||||
import 'reader_renderer.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/epub/epub_theme.dart';
|
||||
import '../../services/epub/epub_theme.dart';
|
||||
import 'widgets/integer_stepper.dart';
|
||||
import 'widgets/reader_scale_slider.dart';
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import '../../utils/epub/epub_theme.dart';
|
||||
import '../../services/epub/epub_theme.dart';
|
||||
import 'book_session.dart';
|
||||
import '../../utils/epub/epub_webview_handler.dart';
|
||||
import '../../utils/epub/reader_scripts.dart';
|
||||
import '../../utils/epub/web/webview_bridge.dart';
|
||||
import '../../utils/epub/web/reader_api.dart';
|
||||
import '../../services/epub/epub_webview_handler.dart';
|
||||
import '../../services/epub/reader_scripts.dart';
|
||||
import '../../services/epub/web/webview_bridge.dart';
|
||||
import '../../services/epub/web/reader_api.dart';
|
||||
|
||||
/// Controller for ReaderWebView that provides methods to control the WebView
|
||||
class ReaderWebViewController {
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
import '../../utils/epub/epub_stream_service.dart';
|
||||
import '../../services/epub/epub_stream_service.dart';
|
||||
import 'book_session.dart';
|
||||
|
||||
class SearchSheet extends StatefulWidget {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/epub/reader_models.dart';
|
||||
import '../../data/epub/reader_models.dart';
|
||||
|
||||
/// Helper class to represent a visible row in the flattened TOC list
|
||||
class _TocRowItem {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
|
||||
/// 相遇统计页
|
||||
class EncounterPage extends StatelessWidget {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import 'movies/movie_detail_page.dart';
|
||||
import 'movies/movie_form_page.dart';
|
||||
import 'book/book_detail_page.dart';
|
||||
import 'book/book_form_page.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../models/data_models.dart';
|
||||
import '../movies/movie_detail_page.dart';
|
||||
import '../movies/movie_form_page.dart';
|
||||
import '../book/book_detail_page.dart';
|
||||
import '../book/book_form_page.dart';
|
||||
|
||||
/// 书影日历 - 按月展示影视/书籍添加记录
|
||||
class MediaCalendarPage extends StatefulWidget {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import 'movies/movie_detail_page.dart';
|
||||
import 'book/book_detail_page.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../models/data_models.dart';
|
||||
import '../movies/movie_detail_page.dart';
|
||||
import '../book/book_detail_page.dart';
|
||||
|
||||
/// 角色信息页面 - 列出所有导演/主演/编剧/作者
|
||||
class PersonListPage extends StatefulWidget {
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../widgets/fade_in_local_image.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../models/data_models.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
import '../../widgets/fade_in_local_image.dart';
|
||||
|
||||
/// 数据统计页面 - 多维度数据分析
|
||||
class StatisticsPage extends StatefulWidget {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../widgets/fade_in_local_image.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
import 'movies/movie_detail_page.dart';
|
||||
import 'book/book_detail_page.dart';
|
||||
import 'note/note_detail_page.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../models/data_models.dart';
|
||||
import '../../widgets/fade_in_local_image.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../movies/movie_detail_page.dart';
|
||||
import '../book/book_detail_page.dart';
|
||||
import '../note/note_detail_page.dart';
|
||||
|
||||
/// 漫步页面 - 随机发现内容
|
||||
class StrollPage extends StatefulWidget {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../utils/responsive.dart';
|
||||
import '../widgets/custom_drawer.dart';
|
||||
import '../widgets/bottom_nav_bar.dart';
|
||||
import '../widgets/add_sheet.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
import '../../utils/responsive.dart';
|
||||
import '../../widgets/custom_drawer.dart';
|
||||
import '../../widgets/bottom_nav_bar.dart';
|
||||
import '../../widgets/add_sheet.dart';
|
||||
import 'main_content_page.dart';
|
||||
import 'profile_page.dart';
|
||||
import '../profile/profile_page.dart';
|
||||
|
||||
/// 主页 - 包含底部导航,可切换主页/我的
|
||||
class HomePage extends StatefulWidget {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../utils/sync/webdav_service.dart';
|
||||
import 'movies/movie_tab_page.dart';
|
||||
import 'book/book_tab_page.dart';
|
||||
import 'note/note_tab_page.dart';
|
||||
import 'online_search/search_page.dart';
|
||||
import 'online_search/online_search_page.dart';
|
||||
import 'sync/webdav_sync_page.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
import '../../services/sync/webdav_service.dart';
|
||||
import '../movies/movie_tab_page.dart';
|
||||
import '../book/book_tab_page.dart';
|
||||
import '../note/note_tab_page.dart';
|
||||
import '../online_search/search_page.dart';
|
||||
import '../online_search/online_search_page.dart';
|
||||
import '../sync/webdav_sync_page.dart';
|
||||
|
||||
/// 主内容页 - 观影/阅读/笔记标签页(PageView 滑动切换)
|
||||
class MainContentPage extends StatefulWidget {
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../../utils/user_prefs.dart';
|
||||
import '../../utils/server_config.dart';
|
||||
import '../legal_page.dart';
|
||||
import '../settings/legal_page.dart';
|
||||
|
||||
/// 增强搜索设置页面
|
||||
class EnhancedSearchSettingsPage extends StatefulWidget {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../utils/app_icon_channel.dart';
|
||||
import '../../services/app_icon_channel.dart';
|
||||
|
||||
/// 应用图标选择页面
|
||||
class AppIconPickerPage extends StatefulWidget {
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../utils/changelog_service.dart';
|
||||
import '../../services/changelog_service.dart';
|
||||
|
||||
/// 更新日志页面
|
||||
class ChangelogPage extends StatefulWidget {
|
||||
|
||||
415
lib/pages/profile/feature_settings_page.dart
Normal file
415
lib/pages/profile/feature_settings_page.dart
Normal file
@@ -0,0 +1,415 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
|
||||
class FeatureSettingsPage extends StatefulWidget {
|
||||
const FeatureSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
State<FeatureSettingsPage> createState() => _FeatureSettingsPageState();
|
||||
}
|
||||
|
||||
class _FeatureSettingsPageState extends State<FeatureSettingsPage> {
|
||||
final UserPrefs _userPrefs = UserPrefs();
|
||||
|
||||
// 主界面
|
||||
bool _showMovieTab = true;
|
||||
bool _showBookTab = true;
|
||||
bool _showNoteTab = true;
|
||||
int _defaultTabIndex = 0;
|
||||
|
||||
// 侧边栏
|
||||
bool _showHeatmap = true;
|
||||
bool _showRecent = true;
|
||||
bool _showEncounter = true;
|
||||
bool _showStroll = true;
|
||||
bool _showCalendar = true;
|
||||
bool _showPerson = true;
|
||||
bool _showTags = true;
|
||||
bool _showMdReader = true;
|
||||
bool _showEpub = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
void _loadSettings() {
|
||||
setState(() {
|
||||
_showMovieTab = _userPrefs.showMovieTab;
|
||||
_showBookTab = _userPrefs.showBookTab;
|
||||
_showNoteTab = _userPrefs.showNoteTab;
|
||||
_defaultTabIndex = _userPrefs.defaultMainTabIndex;
|
||||
_showHeatmap = _userPrefs.showSidebarHeatmap;
|
||||
_showRecent = _userPrefs.showSidebarRecent;
|
||||
_showEncounter = _userPrefs.showSidebarEncounter;
|
||||
_showStroll = _userPrefs.showSidebarStroll;
|
||||
_showCalendar = _userPrefs.showSidebarCalendar;
|
||||
_showPerson = _userPrefs.showSidebarPerson;
|
||||
_showTags = _userPrefs.showSidebarTags;
|
||||
_showMdReader = _userPrefs.showSidebarMdReader;
|
||||
_showEpub = _userPrefs.showSidebarEpub;
|
||||
});
|
||||
}
|
||||
|
||||
int get _enabledTabCount {
|
||||
int count = 0;
|
||||
if (_showMovieTab) count++;
|
||||
if (_showBookTab) count++;
|
||||
if (_showNoteTab) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
List<(int, String, IconData)> get _enabledTabs {
|
||||
final all = [
|
||||
(0, '影视', Icons.movie_outlined),
|
||||
(1, '阅读', Icons.menu_book_outlined),
|
||||
(2, '笔记', Icons.note_outlined),
|
||||
];
|
||||
return all.where((t) {
|
||||
return switch (t.$1) {
|
||||
0 => _showMovieTab,
|
||||
1 => _showBookTab,
|
||||
2 => _showNoteTab,
|
||||
_ => false,
|
||||
};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
void _fixDefaultTabIndex() {
|
||||
final enabled = _enabledTabs;
|
||||
if (!enabled.any((t) => t.$1 == _defaultTabIndex) && enabled.isNotEmpty) {
|
||||
_defaultTabIndex = enabled.first.$1;
|
||||
_userPrefs.setDefaultMainTabIndex(_defaultTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleMovieTab(bool value) async {
|
||||
if (!value && _enabledTabCount <= 1) {
|
||||
ToastUtil.show(context, '至少保留一个标签页');
|
||||
return;
|
||||
}
|
||||
await _userPrefs.setShowMovieTab(value);
|
||||
setState(() {
|
||||
_showMovieTab = value;
|
||||
_fixDefaultTabIndex();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _toggleBookTab(bool value) async {
|
||||
if (!value && _enabledTabCount <= 1) {
|
||||
ToastUtil.show(context, '至少保留一个标签页');
|
||||
return;
|
||||
}
|
||||
await _userPrefs.setShowBookTab(value);
|
||||
setState(() {
|
||||
_showBookTab = value;
|
||||
_fixDefaultTabIndex();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _toggleNoteTab(bool value) async {
|
||||
if (!value && _enabledTabCount <= 1) {
|
||||
ToastUtil.show(context, '至少保留一个标签页');
|
||||
return;
|
||||
}
|
||||
await _userPrefs.setShowNoteTab(value);
|
||||
setState(() {
|
||||
_showNoteTab = value;
|
||||
_fixDefaultTabIndex();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surface,
|
||||
appBar: AppBar(title: const Text('功能设置')),
|
||||
body: ListView(
|
||||
children: [
|
||||
// ── 启动设置 ──
|
||||
_buildSectionHeader('启动设置'),
|
||||
_buildDefaultTabSelector(),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
|
||||
// ── 模块开关 ──
|
||||
_buildSectionHeader('模块开关'),
|
||||
_buildSwitchItem(Icons.movie_outlined, '观影', '记录和管理观影记录',
|
||||
_showMovieTab, _toggleMovieTab),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.menu_book_outlined, '阅读', '记录和管理阅读记录',
|
||||
_showBookTab, _toggleBookTab),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.note_outlined, '笔记', '记录和管理笔记', _showNoteTab,
|
||||
_toggleNoteTab),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
// ── 侧边栏:信息模块 ──
|
||||
_buildSectionHeader('侧边栏 · 信息模块'),
|
||||
_buildSwitchItem(
|
||||
Icons.calendar_today, '热力图', '显示创作活跃度热力图', _showHeatmap,
|
||||
(v) async {
|
||||
await _userPrefs.setShowSidebarHeatmap(v);
|
||||
setState(() => _showHeatmap = v);
|
||||
}),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.schedule, '最近添加', '显示最近添加的记录', _showRecent,
|
||||
(v) async {
|
||||
await _userPrefs.setShowSidebarRecent(v);
|
||||
setState(() => _showRecent = v);
|
||||
}),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(
|
||||
Icons.favorite_border, '统计', '与应用相遇的天数和数据概览', _showEncounter,
|
||||
(v) async {
|
||||
await _userPrefs.setShowSidebarEncounter(v);
|
||||
setState(() => _showEncounter = v);
|
||||
}),
|
||||
|
||||
// ── 侧边栏:快捷功能 ──
|
||||
_buildSectionHeader('侧边栏 · 快捷功能'),
|
||||
_buildSwitchItem(Icons.explore_outlined, '漫步', '随机发现内容', _showStroll,
|
||||
(v) async {
|
||||
await _userPrefs.setShowSidebarStroll(v);
|
||||
setState(() => _showStroll = v);
|
||||
}),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(
|
||||
Icons.calendar_month_outlined, '书影日历', '按日历查看记录', _showCalendar,
|
||||
(v) async {
|
||||
await _userPrefs.setShowSidebarCalendar(v);
|
||||
setState(() => _showCalendar = v);
|
||||
}),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(
|
||||
Icons.people_outline, '角色信息', '管理影视和书籍中的角色', _showPerson,
|
||||
(v) async {
|
||||
await _userPrefs.setShowSidebarPerson(v);
|
||||
setState(() => _showPerson = v);
|
||||
}),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.label_outline, '标签管理', '管理所有标签', _showTags,
|
||||
(v) async {
|
||||
await _userPrefs.setShowSidebarTags(v);
|
||||
setState(() => _showTags = v);
|
||||
}),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(Icons.description_outlined, 'MD阅读', 'Markdown 文件阅读器',
|
||||
_showMdReader, (v) async {
|
||||
await _userPrefs.setShowSidebarMdReader(v);
|
||||
setState(() => _showMdReader = v);
|
||||
}),
|
||||
Divider(
|
||||
height: 0.5,
|
||||
indent: 24,
|
||||
endIndent: 24,
|
||||
color: colors.outlineVariant),
|
||||
_buildSwitchItem(
|
||||
Icons.auto_stories_outlined, 'EPUB阅读', 'EPUB 电子书阅读器', _showEpub,
|
||||
(v) async {
|
||||
await _userPrefs.setShowSidebarEpub(v);
|
||||
setState(() => _showEpub = v);
|
||||
}),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
child: Text('关闭后对应功能将从界面中隐藏。',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: colors.onSurface.withValues(alpha: 0.3))),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionHeader(String title) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 20, 24, 8),
|
||||
child: Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSwitchItem(IconData icon, String title, String subtitle,
|
||||
bool value, ValueChanged<bool> onChanged) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4),
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
child: Icon(icon,
|
||||
color: colors.onSurface.withValues(alpha: 0.6), size: 18)),
|
||||
title: Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colors.onSurface)),
|
||||
subtitle: Text(subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4))),
|
||||
trailing: Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: colors.primary,
|
||||
activeTrackColor: colors.primary.withValues(alpha: 0.3),
|
||||
inactiveThumbColor: colors.surface,
|
||||
inactiveTrackColor: colors.outline),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDefaultTabSelector() {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final enabled = _enabledTabs;
|
||||
final currentLabel = enabled
|
||||
.firstWhere((t) => t.$1 == _defaultTabIndex,
|
||||
orElse: () => enabled.first)
|
||||
.$2;
|
||||
|
||||
return InkWell(
|
||||
onTap: _showDefaultTabPicker,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
child: Icon(Icons.home_outlined,
|
||||
color: colors.onSurface.withValues(alpha: 0.6), size: 18)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text('默认启动标签',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colors.onSurface)),
|
||||
),
|
||||
Text(currentLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: colors.onSurface.withValues(alpha: 0.5))),
|
||||
const SizedBox(width: 4),
|
||||
Icon(Icons.chevron_right,
|
||||
size: 18, color: colors.onSurface.withValues(alpha: 0.25)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDefaultTabPicker() {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final enabled = _enabledTabs;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (ctx) => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
borderRadius:
|
||||
const BorderRadius.vertical(top: Radius.circular(16))),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.onSurface.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(2))),
|
||||
const SizedBox(height: 20),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text('默认启动标签',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface)))),
|
||||
const SizedBox(height: 16),
|
||||
for (final t in enabled)
|
||||
ListTile(
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
child: Icon(t.$3,
|
||||
color: colors.onSurface.withValues(alpha: 0.6))),
|
||||
title: Text(t.$2,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colors.onSurface)),
|
||||
trailing: _defaultTabIndex == t.$1
|
||||
? Icon(Icons.check, color: colors.onSurface, size: 20)
|
||||
: null,
|
||||
onTap: () async {
|
||||
await _userPrefs.setDefaultMainTabIndex(t.$1);
|
||||
setState(() => _defaultTabIndex = t.$1);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import '../utils/font_download_manager.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../../services/font_download_manager.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
|
||||
/// 字体选择页面
|
||||
///
|
||||
|
||||
276
lib/pages/profile/layout_settings_page.dart
Normal file
276
lib/pages/profile/layout_settings_page.dart
Normal file
@@ -0,0 +1,276 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
|
||||
class LayoutSettingsPage extends StatefulWidget {
|
||||
const LayoutSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
State<LayoutSettingsPage> createState() => _LayoutSettingsPageState();
|
||||
}
|
||||
|
||||
class _LayoutSettingsPageState extends State<LayoutSettingsPage> {
|
||||
final UserPrefs _userPrefs = UserPrefs();
|
||||
int _noteLayout = 0;
|
||||
int _movieLayout = 0;
|
||||
int _bookLayout = 0;
|
||||
bool _movieWallMode = false;
|
||||
bool _bookshelfMode = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_noteLayout = _userPrefs.noteLayoutStyle;
|
||||
_movieLayout = _userPrefs.movieLayoutStyle;
|
||||
_bookLayout = _userPrefs.bookLayoutStyle;
|
||||
_movieWallMode = _userPrefs.movieWallMode;
|
||||
_bookshelfMode = _userPrefs.bookshelfMode;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surface,
|
||||
appBar: AppBar(title: const Text('布局设置')),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
children: [
|
||||
// ── 影视 ──
|
||||
_buildCategoryHeader(Icons.movie_outlined, '影视', colors.primary),
|
||||
_buildWallSwitch(
|
||||
icon: Icons.wallpaper_outlined,
|
||||
title: '影视墙模式',
|
||||
subtitle: '显示全部影片,不区分状态',
|
||||
value: _movieWallMode,
|
||||
onChanged: (v) => _setWallMode('movie', v),
|
||||
),
|
||||
_buildLayoutSelector(
|
||||
selected: _movieLayout,
|
||||
options: [
|
||||
(0, Icons.grid_view_outlined, '海报网格'),
|
||||
(1, Icons.view_list_outlined, '列表'),
|
||||
(2, Icons.crop_landscape_outlined, '大图卡片'),
|
||||
],
|
||||
onChanged: (v) => _setLayout('movie', v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Divider(
|
||||
height: 1,
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
color: colors.outlineVariant.withValues(alpha: 0.5)),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ── 阅读 ──
|
||||
_buildCategoryHeader(Icons.menu_book_outlined, '阅读', colors.primary),
|
||||
_buildWallSwitch(
|
||||
icon: Icons.auto_stories_outlined,
|
||||
title: '书架模式',
|
||||
subtitle: '显示全部书籍,不区分状态',
|
||||
value: _bookshelfMode,
|
||||
onChanged: (v) => _setWallMode('book', v),
|
||||
),
|
||||
_buildLayoutSelector(
|
||||
selected: _bookLayout,
|
||||
options: [
|
||||
(0, Icons.grid_view_outlined, '海报网格'),
|
||||
(1, Icons.view_list_outlined, '列表'),
|
||||
],
|
||||
onChanged: (v) => _setLayout('book', v),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Divider(
|
||||
height: 1,
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
color: colors.outlineVariant.withValues(alpha: 0.5)),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ── 笔记 ──
|
||||
_buildCategoryHeader(Icons.note_outlined, '笔记', colors.primary),
|
||||
_buildLayoutSelector(
|
||||
selected: _noteLayout,
|
||||
options: [
|
||||
(0, Icons.view_list_outlined, '列表'),
|
||||
(1, Icons.grid_view_outlined, '瀑布流'),
|
||||
(2, Icons.timeline_outlined, '时间线'),
|
||||
],
|
||||
onChanged: (v) => _setLayout('note', v),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCategoryHeader(IconData icon, String title, Color color) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(icon, size: 16, color: color),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.w700, color: color)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWallSwitch({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required bool value,
|
||||
required ValueChanged<bool> onChanged,
|
||||
}) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.primary.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(icon,
|
||||
size: 20, color: colors.primary.withValues(alpha: 0.8)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface)),
|
||||
const SizedBox(height: 2),
|
||||
Text(subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colors.onSurface.withValues(alpha: 0.5))),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeThumbColor: colors.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _setWallMode(String type, bool value) async {
|
||||
switch (type) {
|
||||
case 'movie':
|
||||
await _userPrefs.setMovieWallMode(value);
|
||||
setState(() => _movieWallMode = value);
|
||||
if (mounted) context.read<AppProvider>().setMovieWallMode(value);
|
||||
case 'book':
|
||||
await _userPrefs.setBookshelfMode(value);
|
||||
setState(() => _bookshelfMode = value);
|
||||
if (mounted) context.read<AppProvider>().setBookshelfMode(value);
|
||||
}
|
||||
}
|
||||
|
||||
void _setLayout(String type, int value) async {
|
||||
switch (type) {
|
||||
case 'note':
|
||||
await _userPrefs.setNoteLayoutStyle(value);
|
||||
setState(() => _noteLayout = value);
|
||||
case 'movie':
|
||||
await _userPrefs.setMovieLayoutStyle(value);
|
||||
setState(() => _movieLayout = value);
|
||||
if (mounted) context.read<AppProvider>().setMovieLayoutStyle(value);
|
||||
case 'book':
|
||||
await _userPrefs.setBookLayoutStyle(value);
|
||||
setState(() => _bookLayout = value);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildLayoutSelector({
|
||||
required int selected,
|
||||
required List<(int, IconData, String)> options,
|
||||
required ValueChanged<int> onChanged,
|
||||
}) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
child: Row(
|
||||
children: options.map((opt) {
|
||||
final isSelected = selected == opt.$1;
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => onChanged(opt.$1),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? colors.primary
|
||||
: colors.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? colors.primary
|
||||
: colors.outlineVariant.withValues(alpha: 0.5),
|
||||
width: isSelected ? 0 : 0.5,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
opt.$2,
|
||||
size: 22,
|
||||
color: isSelected
|
||||
? colors.onPrimary
|
||||
: colors.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
opt.$3,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: isSelected
|
||||
? colors.onPrimary
|
||||
: colors.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
1414
lib/pages/profile/settings_page.dart
Normal file
1414
lib/pages/profile/settings_page.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../utils/server_config.dart';
|
||||
import '../../utils/server_config.dart';
|
||||
|
||||
/// 用户服务协议 / 隐私政策查看页面
|
||||
class LegalPage extends StatefulWidget {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../models/data_models.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
|
||||
/// 回收站页面
|
||||
class RecycleBinPage extends StatefulWidget {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
|
||||
class TagManagementPage extends StatefulWidget {
|
||||
const TagManagementPage({super.key});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import '../../utils/sync/backup_service.dart';
|
||||
import '../../utils/sync/auto_backup_service.dart';
|
||||
import '../../services/sync/backup_service.dart';
|
||||
import '../../services/sync/auto_backup_service.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
|
||||
/// 本地备份页面
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../utils/sync/webdav_service.dart';
|
||||
import '../../services/sync/webdav_service.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
|
||||
/// WebDAV 备份页面
|
||||
|
||||
Reference in New Issue
Block a user