generated from dellevin/template
数据备份
This commit is contained in:
251
QUICKSTART.md
251
QUICKSTART.md
@@ -1,251 +0,0 @@
|
||||
# MookNote 快速入门指南
|
||||
|
||||
## 第一步:安装 Flutter 开发环境
|
||||
|
||||
### Windows 系统详细步骤
|
||||
|
||||
#### 1. 下载 Flutter SDK
|
||||
访问:https://docs.flutter.dev/get-started/install/windows
|
||||
- 点击 "Download Flutter SDK"
|
||||
- 下载完成后解压到 `C:\src\flutter`
|
||||
|
||||
#### 2. 添加环境变量
|
||||
```
|
||||
1. 右键"此电脑" → "属性" → "高级系统设置"
|
||||
2. 点击"环境变量"
|
||||
3. 在"系统变量"中找到 "Path",点击"编辑"
|
||||
4. 点击"新建",添加:C:\src\flutter\bin
|
||||
5. 确定保存
|
||||
```
|
||||
|
||||
#### 3. 验证安装
|
||||
|
||||
打开命令提示符(cmd)或 PowerShell:
|
||||
```bash
|
||||
flutter --version
|
||||
flutter doctor
|
||||
```
|
||||
|
||||
#### 4. 安装 Android Studio
|
||||
|
||||
- 下载地址:https://developer.android.com/studio
|
||||
- 安装完成后打开 Android Studio
|
||||
- 配置 Android SDK:
|
||||
- File → Settings → Appearance & Behavior → System Settings → Android SDK
|
||||
- 选择 "SDK Platforms" 标签
|
||||
- 勾选 "Android 13.0 (API 33)" 或更高版本
|
||||
- 选择 "SDK Tools" 标签
|
||||
- 勾选 "Android SDK Build-Tools" 和 "Android Emulator"
|
||||
- 点击 Apply 开始安装
|
||||
|
||||
#### 5. 接受 Android 许可证
|
||||
```bash
|
||||
flutter doctor --android-licenses
|
||||
```
|
||||
按提示输入 `y` 接受所有许可证
|
||||
|
||||
#### 6. 再次运行 flutter doctor
|
||||
```bash
|
||||
flutter doctor
|
||||
```
|
||||
确保看到类似以下输出:
|
||||
```
|
||||
[✓] Flutter (Channel stable, 3.x.x, on Microsoft Windows)
|
||||
[✓] Android toolchain - develop for Android devices
|
||||
[✓] Android Studio
|
||||
[✓] Connected device
|
||||
```
|
||||
|
||||
## 第二步:运行 MookNote 项目
|
||||
|
||||
### 1. 进入项目目录
|
||||
```bash
|
||||
cd d:\UserData\Desktop\my_proj\mooknote
|
||||
```
|
||||
|
||||
### 2. 安装依赖包
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
### 3. 准备设备
|
||||
**选项 A:使用真机**
|
||||
```
|
||||
1. 手机开启"开发者选项"
|
||||
- 设置 → 关于手机 → 连续点击"版本号"7 次
|
||||
2. 开启"USB 调试"
|
||||
- 设置 → 开发者选项 → USB 调试
|
||||
3. 用 USB 连接电脑
|
||||
4. 手机上允许 USB 调试授权
|
||||
```
|
||||
|
||||
**选项 B:使用模拟器**
|
||||
```
|
||||
1. Android Studio → Tools → Device Manager
|
||||
2. Click "Create device"
|
||||
3. 选择设备型号(如 Pixel 6)
|
||||
4. 下载并选择系统镜像(推荐 API 33)
|
||||
5. 完成创建后点击启动按钮
|
||||
```
|
||||
|
||||
### 4. 运行应用
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
|
||||
或者在 Android Studio 中:
|
||||
- File → Open → 选择 mooknote 文件夹
|
||||
- 点击顶部工具栏的运行按钮(绿色三角形)
|
||||
|
||||
## 第三步:理解代码结构
|
||||
|
||||
### 核心文件说明
|
||||
|
||||
#### 1. 入口文件 - `lib/main.dart`
|
||||
```dart
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
```
|
||||
应用的起点,配置主题和路由
|
||||
|
||||
#### 2. 主页面 - `lib/pages/home_page.dart`
|
||||
包含:
|
||||
- 顶部 AppBar(菜单按钮 + 标题)
|
||||
- 三个标签页切换(观影/阅读/笔记)
|
||||
- 主体内容区域
|
||||
|
||||
#### 3. 状态管理 - `lib/providers/app_provider.dart`
|
||||
管理全局状态:
|
||||
- 当前选中的标签页
|
||||
- 当前选中的状态筛选
|
||||
- 侧边菜单开关
|
||||
|
||||
#### 4. 数据模型 - `lib/models/data_models.dart`
|
||||
定义数据结构:
|
||||
- `Movie` - 电影数据
|
||||
- `Book` - 书籍数据
|
||||
- `Note` - 笔记数据
|
||||
|
||||
### Widget 组件层次
|
||||
```
|
||||
MyApp
|
||||
└── HomePage (主页面)
|
||||
├── CustomDrawer (侧边菜单)
|
||||
│ └── 热力图组件
|
||||
├── AppBar (顶部栏)
|
||||
├── TabBar (标签栏)
|
||||
│ ├── 观影
|
||||
│ ├── 阅读
|
||||
│ └── 笔记
|
||||
├── TabContent (标签内容)
|
||||
│ ├── MovieTabPage
|
||||
│ │ ├── MovieStatusBar (状态栏)
|
||||
│ │ └── MovieListItem (列表项)
|
||||
│ ├── BookTabPage
|
||||
│ │ ├── BookStatusBar (状态栏)
|
||||
│ │ └── BookListItem (列表项)
|
||||
│ └── NoteTabPage
|
||||
│ └── NoteListItem (列表项)
|
||||
└── CustomBottomNavBar (底部导航)
|
||||
```
|
||||
|
||||
## 第四步:修改和定制
|
||||
|
||||
### 修改应用名称
|
||||
编辑 `pubspec.yaml`:
|
||||
```yaml
|
||||
name: mooknote # 包名
|
||||
```
|
||||
|
||||
编辑 `android/app/src/main/AndroidManifest.xml`:
|
||||
```xml
|
||||
android:label="MookNote" <!-- 显示名称 -->
|
||||
```
|
||||
|
||||
### 修改主题颜色
|
||||
编辑 `lib/utils/app_theme.dart`:
|
||||
```dart
|
||||
static const Color primaryColor = Color(0xFF6200EE); // 主色调
|
||||
```
|
||||
|
||||
### 添加示例数据
|
||||
编辑对应的 page 文件,例如 `lib/pages/movie_tab_page.dart`:
|
||||
```dart
|
||||
List<Movie> _getSampleMovies(int statusIndex) {
|
||||
// 在这里添加你的电影数据
|
||||
}
|
||||
```
|
||||
|
||||
## 第五步:构建 APK
|
||||
|
||||
### 开发版本
|
||||
```bash
|
||||
flutter build apk --debug
|
||||
```
|
||||
|
||||
### 发布版本
|
||||
```bash
|
||||
flutter build apk --release
|
||||
```
|
||||
|
||||
生成的 APK 位置:
|
||||
```
|
||||
build/app/outputs/flutter-apk/app-release.apk
|
||||
```
|
||||
|
||||
## 常见问题解决
|
||||
|
||||
### Q1: flutter pub get 失败
|
||||
```bash
|
||||
# 清除缓存
|
||||
flutter clean
|
||||
flutter pub cache repair
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
### Q2: 无法识别设备
|
||||
```bash
|
||||
# 查看设备列表
|
||||
flutter devices
|
||||
|
||||
# 如果没有设备,检查:
|
||||
# 1. USB 线是否连接好
|
||||
# 2. 手机 USB 调试是否开启
|
||||
# 3. 是否安装了手机驱动
|
||||
```
|
||||
|
||||
### Q3: 热重载不工作
|
||||
按 `R` 进行热重载,按 `r` 完全重启
|
||||
如果都不行,停止应用重新运行
|
||||
|
||||
### Q4: 中文乱码
|
||||
确保所有文件使用 UTF-8 编码
|
||||
|
||||
## 下一步学习建议
|
||||
|
||||
### 1. 学习 Dart 基础语法(1-2 天)
|
||||
- 变量和类型
|
||||
- 函数
|
||||
- 类和对象
|
||||
- 异步编程(async/await)
|
||||
|
||||
### 2. 学习 Flutter 基础(3-5 天)
|
||||
- Widget 概念
|
||||
- StatelessWidget vs StatefulWidget
|
||||
- 布局组件(Row, Column, Container 等)
|
||||
- 列表(ListView)
|
||||
|
||||
### 3. 实践项目功能
|
||||
- 实现添加功能表单
|
||||
- 集成 SQLite 数据库
|
||||
- 实现搜索功能
|
||||
- 完善详情页
|
||||
|
||||
### 4. 参考资源
|
||||
- [Dart 语言中文文档](https://dart.cn/)
|
||||
- [Flutter 实战电子书](https://book.flutterchina.club/)
|
||||
- [B 站 Flutter 教程](https://search.bilibili.com/all?keyword=flutter 教程)
|
||||
|
||||
---
|
||||
祝你开发顺利!有问题随时询问。
|
||||
106
README.md
106
README.md
@@ -74,18 +74,6 @@ MookNote 是一款用于记录观影、阅读和笔记的 Android 应用,使
|
||||
flutter doctor --android-licenses
|
||||
```
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
$env:HTTP_PROXY="http://127.0.0.1:10808"
|
||||
$env:HTTPS_PROXY="http://127.0.0.1:10808"
|
||||
flutter create --platforms android .
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 运行项目
|
||||
|
||||
### 1. 克隆/复制项目后,进入项目目录
|
||||
@@ -97,9 +85,13 @@ cd mooknote
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
# 如果失败可以先添加代理再进行get
|
||||
$env:HTTP_PROXY="http://127.0.0.1:10808"
|
||||
$env:HTTPS_PROXY="http://127.0.0.1:10808"
|
||||
```
|
||||
|
||||
### 3. 连接设备或启动模拟器
|
||||
|
||||
- 连接 Android 真机(需开启 USB 调试)
|
||||
- 或启动 Android 模拟器
|
||||
|
||||
@@ -112,43 +104,6 @@ flutter run
|
||||
- 打开项目
|
||||
- 点击运行按钮 (Run)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
mooknote/
|
||||
├── android/ # Android 平台配置
|
||||
│ └── app/
|
||||
│ └── src/main/
|
||||
│ ├── AndroidManifest.xml
|
||||
│ └── ...
|
||||
├── lib/ # Dart 源代码
|
||||
│ ├── main.dart # 应用入口
|
||||
│ ├── pages/ # 页面组件
|
||||
│ │ ├── home_page.dart # 主页面
|
||||
│ │ ├── movie_tab_page.dart # 观影标签页
|
||||
│ │ ├── book_tab_page.dart # 阅读标签页
|
||||
│ │ └── note_tab_page.dart # 笔记标签页
|
||||
│ ├── widgets/ # 可复用组件
|
||||
│ │ ├── custom_drawer.dart # 侧边菜单
|
||||
│ │ ├── bottom_nav_bar.dart # 底部导航
|
||||
│ │ ├── movie_status_bar.dart # 观影状态栏
|
||||
│ │ ├── movie_list_item.dart # 观影列表项
|
||||
│ │ ├── book_status_bar.dart # 书籍状态栏
|
||||
│ │ ├── book_list_item.dart # 书籍列表项
|
||||
│ │ └── note_list_item.dart # 笔记列表项
|
||||
│ ├── models/ # 数据模型
|
||||
│ │ └── data_models.dart # Movie, Book, Note 模型
|
||||
│ ├── providers/ # 状态管理
|
||||
│ │ └── app_provider.dart # 全局状态管理
|
||||
│ └── utils/ # 工具类
|
||||
│ └── app_theme.dart # 主题配置
|
||||
├── assets/ # 资源文件
|
||||
│ └── images/ # 图片资源
|
||||
├── fonts/ # 字体文件
|
||||
├── pubspec.yaml # 项目配置文件
|
||||
└── README.md # 本文件
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **Flutter**: 跨平台移动应用框架
|
||||
@@ -162,58 +117,17 @@ mooknote/
|
||||
|
||||
- [x] 集成 SQLite 数据库(sqflite 包)
|
||||
- [x] 实现本地数据持久化
|
||||
- [ ] 数据备份与恢复
|
||||
- [x] 数据备份与恢复
|
||||
|
||||
### 功能增强
|
||||
|
||||
- [x] 添加/编辑表单页面
|
||||
- [ ] 详情页完整实现
|
||||
- [ ] 搜索功能
|
||||
- [ ] 统计图表页面
|
||||
- [ ] 回收站功能
|
||||
- [ ] 设置页面
|
||||
|
||||
### 用户体验
|
||||
- [ ] 加载动画
|
||||
- [ ] 下拉刷新
|
||||
- [ ] 无限滚动加载
|
||||
- [ ] 深色模式优化
|
||||
- [x] 详情页完整实现
|
||||
- [x] 搜索功能
|
||||
- [x] 统计图表页面
|
||||
- [x] 回收站功能
|
||||
|
||||
### 高级功能
|
||||
|
||||
- [ ] 导出功能(JSON/CSV)
|
||||
- [x] 导出功能(JSON)
|
||||
- [ ] 云同步
|
||||
- [ ] 桌面小组件
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: flutter doctor 显示 Android license status unknown
|
||||
A: 运行 `flutter doctor --android-licenses` 接受许可证
|
||||
|
||||
### Q: 模拟器无法启动
|
||||
A: 确保在 BIOS 中启用了虚拟化技术(VT-x/AMD-V)
|
||||
|
||||
### Q: 热重载不生效
|
||||
A: 某些代码更改需要完全重启应用(如 main() 函数修改)
|
||||
|
||||
## 开发建议
|
||||
|
||||
### 对于有 Vue 经验的你
|
||||
Flutter 的很多概念与 Vue 相似:
|
||||
- **Widget** ≈ Vue 组件
|
||||
- **StatefulWidget** ≈ 带数据的 Vue 组件
|
||||
- **setState()** ≈ this.$forceUpdate() + 数据更新
|
||||
- **Provider** ≈ Vuex/Pinia
|
||||
- **pubspec.yaml** ≈ package.json
|
||||
|
||||
### 学习资源
|
||||
- [Flutter 官方文档](https://docs.flutter.dev/)
|
||||
- [Dart 语言指南](https://dart.dev/guides)
|
||||
- [Flutter 中文社区](https://flutterchina.club/)
|
||||
|
||||
## 许可证
|
||||
MIT License
|
||||
|
||||
---
|
||||
**作者**: 你的昵称
|
||||
**创建时间**: 2024-03-04
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/backup_service.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
|
||||
/// 数据备份页面
|
||||
class BackupPage extends StatefulWidget {
|
||||
@@ -185,9 +186,7 @@ class _BackupPageState extends State<BackupPage> {
|
||||
if (!mounted) return;
|
||||
|
||||
if (result.cancelled) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已取消导出')),
|
||||
);
|
||||
ToastUtil.show(context, '已取消导出');
|
||||
} else if (result.success) {
|
||||
// 显示导出成功信息
|
||||
showDialog(
|
||||
@@ -214,15 +213,11 @@ class _BackupPageState extends State<BackupPage> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(result.errorMessage ?? '导出失败')),
|
||||
);
|
||||
ToastUtil.show(context, result.errorMessage ?? '导出失败');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('导出失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '导出失败: $e');
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
@@ -267,9 +262,7 @@ class _BackupPageState extends State<BackupPage> {
|
||||
if (!mounted) return;
|
||||
|
||||
if (result.cancelled) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已取消导入')),
|
||||
);
|
||||
ToastUtil.show(context, '已取消导入');
|
||||
} else if (result.success) {
|
||||
// 刷新数据
|
||||
await context.read<AppProvider>().loadMovies();
|
||||
@@ -293,15 +286,11 @@ class _BackupPageState extends State<BackupPage> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(result.errorMessage ?? '导入失败')),
|
||||
);
|
||||
ToastUtil.show(context, result.errorMessage ?? '导入失败');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('导入失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '导入失败: $e');
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:share_plus/share_plus.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
import 'book_reviews_page.dart';
|
||||
import 'book_excerpts_page.dart';
|
||||
|
||||
@@ -657,9 +658,7 @@ class _BookDetailPageState extends State<BookDetailPage> {
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已删除');
|
||||
},
|
||||
child: const Text('删除', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
@@ -692,9 +691,7 @@ class _BookDetailPageState extends State<BookDetailPage> {
|
||||
);
|
||||
await context.read<AppProvider>().updateBook(updatedBook);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('封面已清空')),
|
||||
);
|
||||
ToastUtil.show(context, '封面已清空');
|
||||
}
|
||||
},
|
||||
child: const Text('清空', style: TextStyle(color: Colors.red)),
|
||||
@@ -707,18 +704,14 @@ class _BookDetailPageState extends State<BookDetailPage> {
|
||||
/// 下载封面到本地
|
||||
Future<void> _downloadCover(Book book) async {
|
||||
if (book.coverPath == null || book.coverPath!.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('没有可下载的封面')),
|
||||
);
|
||||
ToastUtil.show(context, '没有可下载的封面');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final sourceFile = File(book.coverPath!);
|
||||
if (!await sourceFile.exists()) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('封面文件不存在')),
|
||||
);
|
||||
ToastUtil.show(context, '封面文件不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -738,9 +731,7 @@ class _BookDetailPageState extends State<BookDetailPage> {
|
||||
text: '下载自 MookNote',
|
||||
);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('下载失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '下载失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 'package:uuid/uuid.dart';
|
||||
|
||||
/// 摘抄表单页面 - 新增/编辑摘抄
|
||||
@@ -171,15 +172,11 @@ class _BookExcerptFormPageState extends State<BookExcerptFormPage> {
|
||||
|
||||
if (mounted) {
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(_isEditing ? '摘抄已更新' : '摘抄已添加')),
|
||||
);
|
||||
ToastUtil.show(context, _isEditing ? '摘抄已更新' : '摘抄已添加');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('保存失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '保存失败: $e');
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 'book_excerpt_form_page.dart';
|
||||
|
||||
/// 书籍摘抄列表页面
|
||||
@@ -34,9 +35,7 @@ class _BookExcerptsPageState extends State<BookExcerptsPage> {
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() => _isLoading = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('加载失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '加载失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,9 +286,7 @@ class _BookExcerptsPageState extends State<BookExcerptsPage> {
|
||||
await context.read<AppProvider>().removeBookExcerpt(excerpt.id);
|
||||
Navigator.pop(context);
|
||||
_loadExcerpts();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已删除');
|
||||
},
|
||||
child: const Text('删除', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
|
||||
/// 添加/编辑书籍页面 - 紧凑双行布局设计
|
||||
class BookFormPage extends StatefulWidget {
|
||||
@@ -663,9 +664,7 @@ class _BookFormPageState extends State<BookFormPage> {
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('选择封面失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '选择封面失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -718,12 +717,7 @@ class _BookFormPageState extends State<BookFormPage> {
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(widget.book == null ? '添加成功' : '更新成功'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, widget.book == null ? '添加成功' : '更新成功');
|
||||
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 'package:uuid/uuid.dart';
|
||||
|
||||
/// 添加/编辑书评页面 - 极简设计
|
||||
@@ -253,12 +254,7 @@ class _BookReviewFormPageState extends State<BookReviewFormPage> {
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(widget.review == null ? '添加成功' : '更新成功'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, widget.review == null ? '添加成功' : '更新成功');
|
||||
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 'book_review_form_page.dart';
|
||||
|
||||
/// 书籍书评列表页面
|
||||
@@ -34,9 +35,7 @@ class _BookReviewsPageState extends State<BookReviewsPage> {
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() => _isLoading = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('加载失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '加载失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,9 +256,7 @@ class _BookReviewsPageState extends State<BookReviewsPage> {
|
||||
await context.read<AppProvider>().removeBookReview(review.id);
|
||||
Navigator.pop(context);
|
||||
_loadReviews();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已删除');
|
||||
},
|
||||
child: const Text('删除', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:cross_file/cross_file.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
import 'movie_reviews_page.dart';
|
||||
import 'movie_posters_page.dart';
|
||||
|
||||
@@ -212,9 +213,7 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
);
|
||||
await context.read<AppProvider>().updateMovie(updatedMovie);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('海报已清空')),
|
||||
);
|
||||
ToastUtil.show(context, '海报已清空');
|
||||
}
|
||||
},
|
||||
child: const Text('清空', style: TextStyle(color: Colors.red)),
|
||||
@@ -768,9 +767,7 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已删除');
|
||||
},
|
||||
child: const Text('删除', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
@@ -782,18 +779,14 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
/// 下载海报到本地
|
||||
Future<void> _downloadPoster(Movie movie) async {
|
||||
if (movie.posterPath == null || movie.posterPath!.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('没有可下载的海报')),
|
||||
);
|
||||
ToastUtil.show(context, '没有可下载的海报');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final sourceFile = File(movie.posterPath!);
|
||||
if (!await sourceFile.exists()) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('海报文件不存在')),
|
||||
);
|
||||
ToastUtil.show(context, '海报文件不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -813,9 +806,7 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
text: '下载自 MookNote',
|
||||
);
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('下载失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '下载失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
|
||||
/// 添加/编辑影视页面 - 紧凑双行布局设计
|
||||
class MovieFormPage extends StatefulWidget {
|
||||
@@ -705,9 +706,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('选择海报失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '选择海报失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -788,12 +787,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(widget.movie == null ? '添加成功' : '更新成功'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, widget.movie == null ? '添加成功' : '更新成功');
|
||||
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
|
||||
/// 添加/编辑影视记录页面(Typecho 风格)
|
||||
class MovieFormPage extends StatefulWidget {
|
||||
@@ -527,23 +528,12 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(widget.movie == null ? '添加成功' : '更新成功'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, widget.movie == null ? '添加成功' : '更新成功');
|
||||
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('保存失败:$e'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, '保存失败:$e');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
||||
@@ -248,9 +248,7 @@ class _MoviePostersPageState extends State<MoviePostersPage> {
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('添加海报失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '添加海报失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
|
||||
/// 添加/编辑影评页面 - 极简设计
|
||||
class MovieReviewFormPage extends StatefulWidget {
|
||||
@@ -251,12 +252,7 @@ class _MovieReviewFormPageState extends State<MovieReviewFormPage> {
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(widget.review == null ? '添加成功' : '更新成功'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, widget.review == null ? '添加成功' : '更新成功');
|
||||
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 'movie_review_form_page.dart';
|
||||
|
||||
/// 影视影评列表页面
|
||||
@@ -250,9 +251,7 @@ class _MovieReviewsPageState extends State<MovieReviewsPage> {
|
||||
await context.read<AppProvider>().removeMovieReview(review.id);
|
||||
Navigator.pop(context);
|
||||
_loadReviews();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已删除');
|
||||
},
|
||||
child: const Text('删除', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/data_models.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
|
||||
/// 笔记详情页 - 极简主义设计
|
||||
class NoteDetailPage extends StatefulWidget {
|
||||
@@ -255,9 +256,7 @@ class _NoteDetailPageState extends State<NoteDetailPage> {
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已删除');
|
||||
},
|
||||
child: const Text('删除', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
|
||||
/// 添加/编辑笔记页面 - 极简书写界面
|
||||
class NoteFormPage extends StatefulWidget {
|
||||
@@ -331,9 +332,7 @@ class _NoteFormPageState extends State<NoteFormPage> {
|
||||
final content = _contentController.text.trim();
|
||||
|
||||
if (content.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('笔记内容不能为空')),
|
||||
);
|
||||
ToastUtil.show(context, '笔记内容不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -363,12 +362,7 @@ class _NoteFormPageState extends State<NoteFormPage> {
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(_isEditing ? '保存成功' : '添加成功'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, _isEditing ? '保存成功' : '添加成功');
|
||||
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
import 'recycle_bin_page.dart';
|
||||
import 'backup_page.dart';
|
||||
import 'statistics_page.dart';
|
||||
@@ -446,13 +447,7 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
|
||||
/// 显示提示
|
||||
void _showToast(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, message);
|
||||
}
|
||||
|
||||
/// 选择头像
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
|
||||
/// 回收站页面
|
||||
class RecycleBinPage extends StatefulWidget {
|
||||
@@ -246,9 +247,7 @@ class _RecycleBinPageState extends State<RecycleBinPage> with SingleTickerProvid
|
||||
await context.read<AppProvider>().restoreMovie(movie.id);
|
||||
_loadDeletedItems();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('影视已恢复')),
|
||||
);
|
||||
ToastUtil.show(context, '影视已恢复');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,9 +258,7 @@ class _RecycleBinPageState extends State<RecycleBinPage> with SingleTickerProvid
|
||||
await context.read<AppProvider>().permanentDeleteMovie(movie.id);
|
||||
_loadDeletedItems();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已彻底删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已彻底删除');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,9 +268,7 @@ class _RecycleBinPageState extends State<RecycleBinPage> with SingleTickerProvid
|
||||
await context.read<AppProvider>().restoreBook(book.id);
|
||||
_loadDeletedItems();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('书籍已恢复')),
|
||||
);
|
||||
ToastUtil.show(context, '书籍已恢复');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,9 +279,7 @@ class _RecycleBinPageState extends State<RecycleBinPage> with SingleTickerProvid
|
||||
await context.read<AppProvider>().permanentDeleteBook(book.id);
|
||||
_loadDeletedItems();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已彻底删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已彻底删除');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,9 +289,7 @@ class _RecycleBinPageState extends State<RecycleBinPage> with SingleTickerProvid
|
||||
await context.read<AppProvider>().restoreNote(note.id);
|
||||
_loadDeletedItems();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('笔记已恢复')),
|
||||
);
|
||||
ToastUtil.show(context, '笔记已恢复');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,9 +300,7 @@ class _RecycleBinPageState extends State<RecycleBinPage> with SingleTickerProvid
|
||||
await context.read<AppProvider>().permanentDeleteNote(note.id);
|
||||
_loadDeletedItems();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('已彻底删除')),
|
||||
);
|
||||
ToastUtil.show(context, '已彻底删除');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,9 +351,7 @@ class _RecycleBinPageState extends State<RecycleBinPage> with SingleTickerProvid
|
||||
await context.read<AppProvider>().clearRecycleBin();
|
||||
_loadDeletedItems();
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('回收站已清空')),
|
||||
);
|
||||
ToastUtil.show(context, '回收站已清空');
|
||||
}
|
||||
},
|
||||
child: const Text('清空', style: TextStyle(color: Colors.red)),
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 'movie_detail_page.dart';
|
||||
import 'book_detail_page.dart';
|
||||
import 'note_detail_page.dart';
|
||||
@@ -65,9 +66,7 @@ class _SearchPageState extends State<SearchPage> {
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() => _isSearching = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('搜索失败: $e')),
|
||||
);
|
||||
ToastUtil.show(context, '搜索失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../utils/user_prefs.dart';
|
||||
import '../utils/toast_util.dart';
|
||||
|
||||
/// 自定义左侧弹出菜单 - 极简主义设计
|
||||
class CustomDrawer extends StatelessWidget {
|
||||
@@ -188,12 +189,6 @@ class CustomDrawer extends StatelessWidget {
|
||||
|
||||
/// 显示提示
|
||||
void _showToast(BuildContext context, String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
ToastUtil.show(context, message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user