优化项目结构2

This commit is contained in:
DelLevin-Home
2026-07-05 13:58:37 +08:00
parent de180eee47
commit 6dd2014f1b
43 changed files with 2235 additions and 2221 deletions

View File

@@ -9,13 +9,13 @@ import 'package:dynamic_color/dynamic_color.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'pages/home_page.dart';
import 'pages/home/home_page.dart';
import 'utils/theme/app_theme.dart';
import 'utils/app_router.dart';
import 'utils/user_prefs.dart';
import 'utils/changelog_service.dart';
import 'utils/sync/auto_backup_service.dart';
import 'utils/usage_stats_service.dart';
import 'services/changelog_service.dart';
import 'services/sync/auto_backup_service.dart';
import 'services/usage_stats_service.dart';
import 'providers/app_provider.dart';
final RouteObserver<ModalRoute<void>> routeObserver = RouteObserver<ModalRoute<void>>();

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View 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),
],
),
),
);
}
}

View File

@@ -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';
/// 字体选择页面
///

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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});

View File

@@ -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';
/// 本地备份页面

View File

@@ -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 备份页面

View File

@@ -1,19 +1,19 @@
import 'dart:collection';
import 'package:flutter/material.dart';
import '../models/data_models.dart';
import '../utils/movie/movie_dao.dart';
import '../utils/book/book_dao.dart';
import '../utils/note/note_dao.dart';
import '../utils/movie/movie_review_dao.dart';
import '../utils/movie/movie_poster_dao.dart';
import '../utils/book/book_review_dao.dart';
import '../utils/book/book_excerpt_dao.dart';
import '../utils/tag/tag_dao.dart';
import '../utils/database_helper.dart';
import '../data/movie/movie_dao.dart';
import '../data/book/book_dao.dart';
import '../data/note/note_dao.dart';
import '../data/movie/movie_review_dao.dart';
import '../data/movie/movie_poster_dao.dart';
import '../data/book/book_review_dao.dart';
import '../data/book/book_excerpt_dao.dart';
import '../data/tag/tag_dao.dart';
import '../data/database_helper.dart';
import '../utils/image_path_helper.dart';
import '../utils/user_prefs.dart';
import '../utils/theme/app_theme.dart';
import '../utils/font_download_manager.dart';
import '../services/font_download_manager.dart';
/// 应用全局状态管理
class AppProvider extends ChangeNotifier {

View File

@@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:package_info_plus/package_info_plus.dart';
import 'server_config.dart';
import '../utils/server_config.dart';
/// 更新日志数据模型
class ChangelogItem {

View File

@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/foundation.dart';
import 'package:xml/xml.dart';
import 'reader_models.dart';
import '../../data/epub/reader_models.dart';
/// EPUB 解析器 - 从 ZIP 归档中解析 EPUB 结构
class EpubParser {

View File

@@ -4,8 +4,8 @@ import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:uuid/uuid.dart';
import 'epub_parser.dart';
import 'reader_dao.dart';
import 'reader_models.dart';
import '../../data/epub/reader_dao.dart';
import '../../data/epub/reader_models.dart';
/// EPUB 服务层 - 管理导入、解压、删除
class EpubService {

View File

@@ -8,8 +8,8 @@ import 'package:path/path.dart' as path;
import 'package:share_plus/share_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sqflite/sqflite.dart';
import '../database_helper.dart';
import '../user_prefs.dart';
import '../../data/database_helper.dart';
import '../../utils/user_prefs.dart';
/// 数据备份服务 - 支持导出和导入数据(包含图片)
class BackupService {

View File

@@ -6,12 +6,12 @@ import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:package_info_plus/package_info_plus.dart';
import 'user_prefs.dart';
import 'server_config.dart';
import '../utils/user_prefs.dart';
import '../utils/server_config.dart';
/// 匿名用户统计服务(静默运行,对用户不可见)
///
/// App 启动后每 5 分钟向统计服务器发送匿名心跳。
/// App 启动后每 1 分钟向统计服务器发送匿名心跳。
/// 统计数据在服务端管理后台查看App 内无入口。
class UsageStatsService with WidgetsBindingObserver {
static final UsageStatsService instance = UsageStatsService._();

View File

@@ -3,13 +3,13 @@ import 'package:provider/provider.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../providers/app_provider.dart';
import '../utils/user_prefs.dart';
import '../pages/encounter_page.dart';
import '../pages/stroll_page.dart';
import '../pages/media_calendar_page.dart';
import '../pages/person_list_page.dart';
import '../pages/explore/encounter_page.dart';
import '../pages/explore/stroll_page.dart';
import '../pages/explore/media_calendar_page.dart';
import '../pages/explore/person_list_page.dart';
import '../pages/markdown_reader/md_reader_tab_page.dart';
import '../pages/epub_reader/epub_library_page.dart';
import '../pages/tag_management_page.dart';
import '../pages/settings/tag_management_page.dart';
import '../pages/movies/movie_detail_page.dart';
import '../pages/book/book_detail_page.dart';
import '../pages/note/note_detail_page.dart';