generated from dellevin/template
新增标签自主控制功能
This commit is contained in:
@@ -121,3 +121,7 @@ flutter run
|
||||
- 目前图片同步是基于文件存在性判断,不是基于修改时间
|
||||
- 下载新数据库后,应用会自动重新加载数据(调用 Provider 的 load 方法)
|
||||
- 首次同步会创建远程目录结构
|
||||
|
||||
## 开源协议
|
||||
|
||||
本项目采用 [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html) 开源协议。
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'utils/user_prefs.dart';
|
||||
import 'utils/sync/webdav_service.dart';
|
||||
import 'utils/sync/auto_backup_service.dart';
|
||||
import 'providers/app_provider.dart';
|
||||
import 'package:flutter/widget_previews.dart';
|
||||
|
||||
void main() async {
|
||||
// 确保 Flutter 绑定初始化完成
|
||||
@@ -67,3 +68,16 @@ class MyApp extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 用于预览 MyApp 的 Widget
|
||||
@Preview(name: "MookNote App Preview") // 添加 @Preview 注解
|
||||
Widget previewMyApp() {
|
||||
final appProvider = AppProvider();
|
||||
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: appProvider),
|
||||
],
|
||||
child: MyApp(appProvider: appProvider),
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import 'movies/movie_tab_page.dart';
|
||||
import 'book/book_tab_page.dart';
|
||||
import 'note/note_tab_page.dart';
|
||||
import 'search_page.dart';
|
||||
|
||||
/// 主内容页 - 观影/阅读/笔记标签页
|
||||
class MainContentPage extends StatelessWidget {
|
||||
class MainContentPage extends StatefulWidget {
|
||||
const MainContentPage({super.key});
|
||||
|
||||
@override
|
||||
State<MainContentPage> createState() => _MainContentPageState();
|
||||
}
|
||||
|
||||
class _MainContentPageState extends State<MainContentPage> {
|
||||
final UserPrefs _userPrefs = UserPrefs();
|
||||
|
||||
bool _showMovieTab = true;
|
||||
bool _showBookTab = true;
|
||||
bool _showNoteTab = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadTabSettings();
|
||||
}
|
||||
|
||||
/// 加载标签显示设置
|
||||
void _loadTabSettings() {
|
||||
setState(() {
|
||||
_showMovieTab = _userPrefs.showMovieTab;
|
||||
_showBookTab = _userPrefs.showBookTab;
|
||||
_showNoteTab = _userPrefs.showNoteTab;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
// 当页面重新获得焦点时刷新设置
|
||||
_loadTabSettings();
|
||||
}
|
||||
|
||||
/// 获取启用的标签列表
|
||||
List<_TabItem> get _enabledTabs {
|
||||
final tabs = <_TabItem>[];
|
||||
if (_showMovieTab) tabs.add(_TabItem('观影', 0));
|
||||
if (_showBookTab) tabs.add(_TabItem('阅读', 1));
|
||||
if (_showNoteTab) tabs.add(_TabItem('笔记', 2));
|
||||
return tabs;
|
||||
}
|
||||
|
||||
/// 将原始索引映射到启用标签的索引
|
||||
int _mapToEnabledTabIndex(int originalIndex) {
|
||||
final tabs = _enabledTabs;
|
||||
for (int i = 0; i < tabs.length; i++) {
|
||||
if (tabs[i].originalIndex == originalIndex) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
@@ -71,6 +123,11 @@ class MainContentPage extends StatelessWidget {
|
||||
Widget _buildTabBar(BuildContext context) {
|
||||
return Consumer<AppProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final tabs = _enabledTabs;
|
||||
// 如果当前选中的标签被禁用了,切换到第一个启用的标签
|
||||
final currentEnabledIndex = _mapToEnabledTabIndex(provider.mainTabIndex);
|
||||
final safeIndex = currentEnabledIndex < tabs.length ? currentEnabledIndex : 0;
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
@@ -79,29 +136,17 @@ class MainContentPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildTabItem(
|
||||
children: tabs.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final tab = entry.value;
|
||||
return _buildTabItem(
|
||||
context,
|
||||
'观影',
|
||||
0,
|
||||
provider.mainTabIndex,
|
||||
() => provider.setMainTabIndex(0),
|
||||
),
|
||||
_buildTabItem(
|
||||
context,
|
||||
'阅读',
|
||||
1,
|
||||
provider.mainTabIndex,
|
||||
() => provider.setMainTabIndex(1),
|
||||
),
|
||||
_buildTabItem(
|
||||
context,
|
||||
'笔记',
|
||||
2,
|
||||
provider.mainTabIndex,
|
||||
() => provider.setMainTabIndex(2),
|
||||
),
|
||||
],
|
||||
tab.label,
|
||||
index,
|
||||
safeIndex,
|
||||
() => provider.setMainTabIndex(tab.originalIndex),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -154,7 +199,29 @@ class MainContentPage extends StatelessWidget {
|
||||
Widget _buildTabContent() {
|
||||
return Consumer<AppProvider>(
|
||||
builder: (context, provider, child) {
|
||||
switch (provider.mainTabIndex) {
|
||||
final tabs = _enabledTabs;
|
||||
// 找到当前应该显示的标签
|
||||
_TabItem? currentTab;
|
||||
for (final tab in tabs) {
|
||||
if (tab.originalIndex == provider.mainTabIndex) {
|
||||
currentTab = tab;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 如果当前标签被禁用了,显示第一个启用的标签
|
||||
if (currentTab == null && tabs.isNotEmpty) {
|
||||
currentTab = tabs.first;
|
||||
// 更新 provider 的索引
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
provider.setMainTabIndex(currentTab!.originalIndex);
|
||||
});
|
||||
}
|
||||
|
||||
if (currentTab == null) {
|
||||
return const Center(child: Text('请至少启用一个标签页'));
|
||||
}
|
||||
|
||||
switch (currentTab.originalIndex) {
|
||||
case 0:
|
||||
return const MovieTabPage();
|
||||
case 1:
|
||||
@@ -233,3 +300,11 @@ class MainContentPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 标签项数据类
|
||||
class _TabItem {
|
||||
final String label;
|
||||
final int originalIndex;
|
||||
|
||||
_TabItem(this.label, this.originalIndex);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:image_picker/image_picker.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
// import 'package:url_launcher/url_launcher.dart'; // 改为应用内打开
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
@@ -25,17 +26,27 @@ class ProfilePage extends StatefulWidget {
|
||||
class _ProfilePageState extends State<ProfilePage> {
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
final UserPrefs _userPrefs = UserPrefs();
|
||||
|
||||
|
||||
// 用户数据
|
||||
String _nickname = 'Mook';
|
||||
String _motto = '好运不会眷顾一无所有之人。';
|
||||
String? _avatarPath;
|
||||
String _version = '0.1.5';
|
||||
bool _isLoading = true;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadUserData();
|
||||
_loadVersionInfo();
|
||||
}
|
||||
|
||||
/// 加载版本信息
|
||||
Future<void> _loadVersionInfo() async {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
setState(() {
|
||||
_version = packageInfo.version;
|
||||
});
|
||||
}
|
||||
|
||||
/// 加载用户数据
|
||||
@@ -100,10 +111,10 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
const SizedBox(height: 48),
|
||||
|
||||
// 版本信息
|
||||
const Center(
|
||||
Center(
|
||||
child: Text(
|
||||
'MookNote v0.1.5',
|
||||
style: TextStyle(
|
||||
'MookNote v$_version',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
@@ -617,7 +628,25 @@ class SettingsPage extends StatelessWidget {
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
// 主界面功能显示入口
|
||||
_buildSectionHeader('主界面显示'),
|
||||
_buildNavigationItem(
|
||||
icon: Icons.view_list_outlined,
|
||||
title: '主界面功能显示',
|
||||
subtitle: '控制观影、阅读、笔记的显示',
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const MainContentSettingsPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(height: 0.5, indent: 24, endIndent: 24),
|
||||
|
||||
// 使用说明
|
||||
_buildSectionHeader('帮助'),
|
||||
_buildLinkItem(
|
||||
context: context,
|
||||
icon: Icons.help_outline,
|
||||
@@ -626,7 +655,7 @@ class SettingsPage extends StatelessWidget {
|
||||
url: 'https://mooknote.iletter.top/#/guide',
|
||||
),
|
||||
const Divider(height: 0.5, indent: 24, endIndent: 24),
|
||||
|
||||
|
||||
// 关于作者
|
||||
_buildLinkItem(
|
||||
context: context,
|
||||
@@ -635,13 +664,74 @@ class SettingsPage extends StatelessWidget {
|
||||
subtitle: '了解更多信息',
|
||||
url: 'https://www.iletter.top/',
|
||||
),
|
||||
|
||||
|
||||
const Divider(height: 0.5, indent: 24, endIndent: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建区块标题
|
||||
Widget _buildSectionHeader(String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||
child: Text(
|
||||
title.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF999999),
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建导航项
|
||||
Widget _buildNavigationItem({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F5F5),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: const Color(0xFF666666),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
color: Color(0xFF1A1A1A),
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
color: Color(0xFFCCCCCC),
|
||||
size: 20,
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建链接项
|
||||
Widget _buildLinkItem({
|
||||
required BuildContext context,
|
||||
@@ -699,6 +789,183 @@ class SettingsPage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 主界面功能显示设置页面
|
||||
class MainContentSettingsPage extends StatefulWidget {
|
||||
const MainContentSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
State<MainContentSettingsPage> createState() => _MainContentSettingsPageState();
|
||||
}
|
||||
|
||||
class _MainContentSettingsPageState extends State<MainContentSettingsPage> {
|
||||
final UserPrefs _userPrefs = UserPrefs();
|
||||
|
||||
bool _showMovieTab = true;
|
||||
bool _showBookTab = true;
|
||||
bool _showNoteTab = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
/// 加载设置
|
||||
void _loadSettings() {
|
||||
setState(() {
|
||||
_showMovieTab = _userPrefs.showMovieTab;
|
||||
_showBookTab = _userPrefs.showBookTab;
|
||||
_showNoteTab = _userPrefs.showNoteTab;
|
||||
});
|
||||
}
|
||||
|
||||
/// 获取已启用的标签数量
|
||||
int get _enabledTabCount {
|
||||
int count = 0;
|
||||
if (_showMovieTab) count++;
|
||||
if (_showBookTab) count++;
|
||||
if (_showNoteTab) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
/// 切换观影标签显示
|
||||
Future<void> _toggleMovieTab(bool value) async {
|
||||
if (!value && _enabledTabCount <= 1) {
|
||||
_showToast('至少保留一个标签页');
|
||||
return;
|
||||
}
|
||||
await _userPrefs.setShowMovieTab(value);
|
||||
setState(() => _showMovieTab = value);
|
||||
}
|
||||
|
||||
/// 切换阅读标签显示
|
||||
Future<void> _toggleBookTab(bool value) async {
|
||||
if (!value && _enabledTabCount <= 1) {
|
||||
_showToast('至少保留一个标签页');
|
||||
return;
|
||||
}
|
||||
await _userPrefs.setShowBookTab(value);
|
||||
setState(() => _showBookTab = value);
|
||||
}
|
||||
|
||||
/// 切换笔记标签显示
|
||||
Future<void> _toggleNoteTab(bool value) async {
|
||||
if (!value && _enabledTabCount <= 1) {
|
||||
_showToast('至少保留一个标签页');
|
||||
return;
|
||||
}
|
||||
await _userPrefs.setShowNoteTab(value);
|
||||
setState(() => _showNoteTab = value);
|
||||
}
|
||||
|
||||
/// 显示提示
|
||||
void _showToast(String message) {
|
||||
ToastUtil.show(context, message);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: const Text('主界面功能显示'),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
// 说明文字
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: const Text(
|
||||
'选择要在主界面显示的功能模块,至少保留一个。',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(height: 0.5, thickness: 0.5, color: Color(0xFFE5E5E5)),
|
||||
// 观影开关
|
||||
_buildSwitchItem(
|
||||
icon: Icons.movie_outlined,
|
||||
title: '观影',
|
||||
subtitle: '记录和管理观影记录',
|
||||
value: _showMovieTab,
|
||||
onChanged: _toggleMovieTab,
|
||||
),
|
||||
const Divider(height: 0.5, indent: 24, endIndent: 24),
|
||||
// 阅读开关
|
||||
_buildSwitchItem(
|
||||
icon: Icons.menu_book_outlined,
|
||||
title: '阅读',
|
||||
subtitle: '记录和管理阅读记录',
|
||||
value: _showBookTab,
|
||||
onChanged: _toggleBookTab,
|
||||
),
|
||||
const Divider(height: 0.5, indent: 24, endIndent: 24),
|
||||
// 笔记开关
|
||||
_buildSwitchItem(
|
||||
icon: Icons.note_outlined,
|
||||
title: '笔记',
|
||||
subtitle: '记录和管理笔记',
|
||||
value: _showNoteTab,
|
||||
onChanged: _toggleNoteTab,
|
||||
),
|
||||
const Divider(height: 0.5, indent: 24, endIndent: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建开关项
|
||||
Widget _buildSwitchItem({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required bool value,
|
||||
required ValueChanged<bool> onChanged,
|
||||
}) {
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
leading: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F5F5),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: const Color(0xFF666666),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF1A1A1A),
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
),
|
||||
trailing: Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: const Color(0xFF1A1A1A),
|
||||
activeTrackColor: const Color(0xFF1A1A1A).withOpacity(0.3),
|
||||
inactiveThumbColor: Colors.white,
|
||||
inactiveTrackColor: const Color(0xFFE5E5E5),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// WebView 页面
|
||||
class WebViewPage extends StatefulWidget {
|
||||
final String url;
|
||||
|
||||
@@ -45,4 +45,18 @@ class UserPrefs {
|
||||
/// 是否首次启动
|
||||
bool get isFirstLaunch => prefs.getBool('isFirstLaunch') ?? true;
|
||||
Future<bool> setFirstLaunch(bool value) => prefs.setBool('isFirstLaunch', value);
|
||||
|
||||
// ========== 主界面显示设置 ==========
|
||||
|
||||
/// 是否显示观影标签
|
||||
bool get showMovieTab => prefs.getBool('showMovieTab') ?? true;
|
||||
Future<bool> setShowMovieTab(bool value) => prefs.setBool('showMovieTab', value);
|
||||
|
||||
/// 是否显示阅读标签
|
||||
bool get showBookTab => prefs.getBool('showBookTab') ?? true;
|
||||
Future<bool> setShowBookTab(bool value) => prefs.setBool('showBookTab', value);
|
||||
|
||||
/// 是否显示笔记标签
|
||||
bool get showNoteTab => prefs.getBool('showNoteTab') ?? true;
|
||||
Future<bool> setShowNoteTab(bool value) => prefs.setBool('showNoteTab', value);
|
||||
}
|
||||
|
||||
@@ -42,26 +42,9 @@ class BookListItem extends StatelessWidget {
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// 评分
|
||||
// 评分 - 5星显示
|
||||
if (book.rating != null)
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.star,
|
||||
size: 12,
|
||||
color: Color(0xFFFFB800),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
book.rating!.toStringAsFixed(1),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
_buildStarRating(book.rating!)
|
||||
else
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
@@ -99,6 +82,50 @@ class BookListItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建5星评分显示(评分范围1-10,每星2分)
|
||||
Widget _buildStarRating(double rating) {
|
||||
// 将10分制转换为5星制
|
||||
final starValue = rating / 2;
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 5个星星
|
||||
...List.generate(5, (index) {
|
||||
final starIndex = index + 1;
|
||||
IconData iconData;
|
||||
|
||||
if (starValue >= starIndex) {
|
||||
// 满星
|
||||
iconData = Icons.star;
|
||||
} else if (starValue >= starIndex - 0.5) {
|
||||
// 半星
|
||||
iconData = Icons.star_half;
|
||||
} else {
|
||||
// 空星
|
||||
iconData = Icons.star_border;
|
||||
}
|
||||
|
||||
return Icon(
|
||||
iconData,
|
||||
size: 12,
|
||||
color: const Color(0xFFFFB800),
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 4),
|
||||
// 评分数字
|
||||
Text(
|
||||
rating.toStringAsFixed(1),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示删除确认对话框
|
||||
void _showDeleteDialog(BuildContext context) {
|
||||
showDialog(
|
||||
|
||||
@@ -2,14 +2,36 @@ import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
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 '../utils/toast_util.dart';
|
||||
import '../models/data_models.dart';
|
||||
|
||||
/// 自定义左侧弹出菜单 - 极简主义设计
|
||||
class CustomDrawer extends StatelessWidget {
|
||||
CustomDrawer({super.key});
|
||||
class CustomDrawer extends StatefulWidget {
|
||||
const CustomDrawer({super.key});
|
||||
|
||||
@override
|
||||
State<CustomDrawer> createState() => _CustomDrawerState();
|
||||
}
|
||||
|
||||
class _CustomDrawerState extends State<CustomDrawer> {
|
||||
String _version = '0.1.5';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadVersionInfo();
|
||||
}
|
||||
|
||||
/// 加载版本信息
|
||||
Future<void> _loadVersionInfo() async {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
setState(() {
|
||||
_version = packageInfo.version;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -43,9 +65,9 @@ class CustomDrawer extends StatelessWidget {
|
||||
// 底部版本信息
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: const Text(
|
||||
'MookNote v0.1.5',
|
||||
style: TextStyle(
|
||||
child: Text(
|
||||
'MookNote v$_version',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
|
||||
@@ -42,26 +42,9 @@ class MovieListItem extends StatelessWidget {
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// 评分
|
||||
// 评分 - 5星显示
|
||||
if (movie.rating != null)
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.star,
|
||||
size: 12,
|
||||
color: Color(0xFFFFB800),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
movie.rating!.toStringAsFixed(1),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
_buildStarRating(movie.rating!)
|
||||
else
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
@@ -99,6 +82,50 @@ class MovieListItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建5星评分显示(评分范围1-10,每星2分)
|
||||
Widget _buildStarRating(double rating) {
|
||||
// 将10分制转换为5星制
|
||||
final starValue = rating / 2;
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 5个星星
|
||||
...List.generate(5, (index) {
|
||||
final starIndex = index + 1;
|
||||
IconData iconData;
|
||||
|
||||
if (starValue >= starIndex) {
|
||||
// 满星
|
||||
iconData = Icons.star;
|
||||
} else if (starValue >= starIndex - 0.5) {
|
||||
// 半星
|
||||
iconData = Icons.star_half;
|
||||
} else {
|
||||
// 空星
|
||||
iconData = Icons.star_border;
|
||||
}
|
||||
|
||||
return Icon(
|
||||
iconData,
|
||||
size: 12,
|
||||
color: const Color(0xFFFFB800),
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 4),
|
||||
// 评分数字
|
||||
Text(
|
||||
rating.toStringAsFixed(1),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF666666),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 显示删除确认对话框
|
||||
void _showDeleteDialog(BuildContext context) {
|
||||
showDialog(
|
||||
|
||||
16
pubspec.lock
16
pubspec.lock
@@ -464,6 +464,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.1"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: mooknote
|
||||
description: "A Flutter application for tracking movies, books, and notes"
|
||||
description: "app for tracking movies, books, and notes"
|
||||
publish_to: 'none'
|
||||
version: 0.1.5+1
|
||||
version: 0.1.6+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
@@ -27,6 +27,7 @@ dependencies:
|
||||
http: ^1.2.0
|
||||
url_launcher: ^6.2.5
|
||||
webview_flutter: ^4.8.0
|
||||
package_info_plus: ^8.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user