优化代码结构,修复部分bug

This commit is contained in:
DelLevin-Home
2026-07-06 02:49:46 +08:00
parent 4a7296f00f
commit 2fdcd4551d
23 changed files with 372 additions and 356 deletions

View File

@@ -1374,14 +1374,16 @@ class _BookDetailPageState extends State<BookDetailPage> {
ElevatedButton(
onPressed: () async {
await context.read<AppProvider>().removeBook(widget.book.id);
if (!mounted) return;
if (!mounted || !context.mounted) return;
Navigator.pop(context);
if (widget.embedded) {
context.read<AppProvider>().selectBook(null);
} else {
Navigator.pop(context);
}
ToastUtil.show(context, '已删除');
if (mounted && context.mounted) {
ToastUtil.show(context, '已删除');
}
},
style: ElevatedButton.styleFrom(
backgroundColor: colors.error,

View File

@@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
import 'package:http/http.dart' as http;
import '../../providers/app_provider.dart';
import '../../widgets/fade_in_local_image.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
import '../../utils/image_path_helper.dart';
@@ -157,12 +158,14 @@ class _BookFormPageState extends State<BookFormPage> {
_halfCard('书名', _titleController.text, Icons.book_outlined, required: true,
onTap: () async {
final r = await TextInputPanel.show(context: context, title: '书名', initialValue: _titleController.text, hint: '请输入书名');
if (!mounted) return;
if (r != null) setState(() => _titleController.text = r);
},
),
_halfCard('别名', _alternateTitles.isEmpty ? '' : '${_alternateTitles.length}个:${_alternateTitles.join('')}', Icons.alternate_email_outlined,
onTap: () async {
final r = await GenreSelectorPage.show(context: context, title: '添加别名', existingTags: [], initialSelected: _alternateTitles, hint: '输入别名');
if (!mounted) return;
if (r != null) setState(() => _alternateTitles = r);
},
),
@@ -173,6 +176,7 @@ class _BookFormPageState extends State<BookFormPage> {
final provider = context.read<AppProvider>();
final data = provider.books.map((b) => b.authors).toList();
final r = await GenreSelectorPage.show(context: context, title: '选择作者', existingTagsFuture: compute(_collectUnique, data), initialSelected: _authors, hint: '如:余华、莫言');
if (!mounted) return;
if (r != null) setState(() => _authors = r);
},
),
@@ -181,12 +185,14 @@ class _BookFormPageState extends State<BookFormPage> {
final provider = context.read<AppProvider>();
final data = provider.books.map((b) => b.translators).toList();
final r = await GenreSelectorPage.show(context: context, title: '选择译者', existingTagsFuture: compute(_collectUnique, data), initialSelected: _translators, hint: '如:李继宏、许钧');
if (!mounted) return;
if (r != null) setState(() => _translators = r);
},
),
_halfCard('出版社', _publisherController.text, Icons.business_outlined,
onTap: () async {
final r = await TextInputPanel.show(context: context, title: '出版社', initialValue: _publisherController.text, hint: '请输入出版社');
if (!mounted) return;
if (r != null) setState(() => _publisherController.text = r);
},
),
@@ -198,12 +204,14 @@ class _BookFormPageState extends State<BookFormPage> {
final tags = await provider.getTags('book_genre', excludeHidden: true);
if (!mounted) return;
final r = await GenreSelectorPage.show(context: context, title: '选择类型', existingTags: tags.map((t) => t['name'] as String).toList(), initialSelected: _genres, hint: '如:小说、历史、传记');
if (!mounted) return;
if (r != null) setState(() => _genres = r);
},
),
_halfCard('ISBN', _isbnController.text, Icons.qr_code_outlined,
onTap: () async {
final r = await TextInputPanel.show(context: context, title: 'ISBN', initialValue: _isbnController.text, hint: '请输入ISBN编号');
if (!mounted) return;
if (r != null) setState(() => _isbnController.text = r);
},
),
@@ -425,10 +433,11 @@ class _BookFormPageState extends State<BookFormPage> {
final picked = await _picker.pickImage(source: ImageSource.gallery, maxWidth: 800, maxHeight: 1200, imageQuality: 85);
if (picked != null) {
final fileName = 'cover_${DateTime.now().millisecondsSinceEpoch}.jpg';
final bookId = widget.book?.id ?? DateTime.now().millisecondsSinceEpoch.toString();
final bookId = widget.book?.id ?? const Uuid().v4();
final targetPath = await ImagePathHelper.instance.getBookCoverPath(bookId, fileName);
await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath));
await File(picked.path).copy(targetPath);
if (!mounted) return;
setState(() => _coverPath = targetPath);
}
} catch (e) {
@@ -492,10 +501,11 @@ class _BookFormPageState extends State<BookFormPage> {
if (response.bodyBytes.length > 10 * 1024 * 1024) throw Exception('图片太大');
final fileName = 'cover_${DateTime.now().millisecondsSinceEpoch}.jpg';
final bookId = widget.book?.id ?? DateTime.now().millisecondsSinceEpoch.toString();
final bookId = widget.book?.id ?? const Uuid().v4();
final targetPath = await ImagePathHelper.instance.getBookCoverPath(bookId, fileName);
await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath));
await File(targetPath).writeAsBytes(response.bodyBytes);
if (!mounted) return;
setState(() => _coverPath = targetPath);
} catch (e) {
debugPrint('封面下载失败: $e');
@@ -556,21 +566,25 @@ class _BookFormPageState extends State<BookFormPage> {
Future<void> _selectPublishDate() async {
final picked = await showDatePicker(context: context, initialDate: _publishDate ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime.now().add(const Duration(days: 365 * 5)));
if (!mounted) return;
if (picked != null) setState(() => _publishDate = picked);
}
Future<void> _selectStartDate() async {
final picked = await showDatePicker(context: context, initialDate: _startDate ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime.now().add(const Duration(days: 365 * 5)));
if (!mounted) return;
if (picked != null) setState(() => _startDate = picked);
}
Future<void> _selectFinishDate() async {
final picked = await showDatePicker(context: context, initialDate: _finishDate ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime.now().add(const Duration(days: 365 * 5)));
if (!mounted) return;
if (picked != null) setState(() => _finishDate = picked);
}
Future<void> _editSummary() async {
final result = await Navigator.push<String>(context, MaterialPageRoute(builder: (_) => _SummaryEditorPage(initialText: _summaryController.text)));
if (!mounted) return;
if (result != null) setState(() => _summaryController.text = result);
}
@@ -619,7 +633,7 @@ class _BookFormPageState extends State<BookFormPage> {
final now = DateTime.now();
if (widget.book == null) {
final newBookId = now.millisecondsSinceEpoch.toString();
final newBookId = const Uuid().v4();
String? finalCoverPath;
if (_coverPath != null && _coverPath!.isNotEmpty) {
finalCoverPath = await _moveCoverToNewId(_coverPath!, newBookId);

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/app_provider.dart';
import '../../widgets/fade_in_local_image.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
@@ -313,7 +314,7 @@ class _BookReviewFormPageState extends State<BookReviewFormPage> {
if (widget.review == null) {
final newReview = BookReview(
id: now.millisecondsSinceEpoch.toString(),
id: const Uuid().v4(),
bookId: widget.bookId,
content: _contentController.text.trim(),
reviewer: _reviewerController.text.trim(),

View File

@@ -295,7 +295,7 @@ class _BookTabPageState extends State<BookTabPage> {
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))),
ElevatedButton(onPressed: () async { await context.read<AppProvider>().removeBook(book.id); Navigator.pop(ctx); _loadFirst(); },
ElevatedButton(onPressed: () async { await context.read<AppProvider>().removeBook(book.id); if (!ctx.mounted) return; Navigator.pop(ctx); if (mounted) _loadFirst(); },
style: ElevatedButton.styleFrom(backgroundColor: colors.error, foregroundColor: colors.onError, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
child: const Text('删除'),

View File

@@ -960,7 +960,7 @@ class _GameDetailPageState extends State<GameDetailPage> {
onPressed: () async {
final provider = context.read<AppProvider>();
await provider.removeGame(widget.game.id);
if (!mounted) return;
if (!mounted || !context.mounted) return;
if (widget.embedded) {
Navigator.of(context).pop();
provider.selectGame(null);
@@ -969,7 +969,7 @@ class _GameDetailPageState extends State<GameDetailPage> {
navigator.pop();
navigator.pop();
}
if (mounted) {
if (mounted && context.mounted) {
ToastUtil.show(context, '已删除');
}
},

View File

@@ -8,6 +8,7 @@ import 'package:path/path.dart' as p;
import 'package:provider/provider.dart';
import '../../providers/app_provider.dart';
import '../../widgets/fade_in_local_image.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
import '../../utils/image_path_helper.dart';
@@ -185,6 +186,7 @@ class _GameFormPageState extends State<GameFormPage> {
initialValue: _titleController.text,
hint: '请输入游戏名称',
);
if (!mounted) return;
if (result != null) setState(() => _titleController.text = result);
},
),
@@ -210,6 +212,7 @@ class _GameFormPageState extends State<GameFormPage> {
initialSelected: _platforms,
hint: 'PS5、Switch、Steam',
);
if (!mounted) return;
if (result != null) setState(() => _platforms = result);
},
),
@@ -235,6 +238,7 @@ class _GameFormPageState extends State<GameFormPage> {
initialSelected: _versions,
hint: '如:标准版、豪华版',
);
if (!mounted) return;
if (result != null) setState(() => _versions = result);
},
),
@@ -269,6 +273,7 @@ class _GameFormPageState extends State<GameFormPage> {
initialSelected: _genres,
hint: 'RPG、动作、冒险',
);
if (!mounted) return;
if (result != null) setState(() => _genres = result);
},
),
@@ -305,6 +310,7 @@ class _GameFormPageState extends State<GameFormPage> {
initialSelected: _purchasePlatforms,
hint: 'Steam、eShop、PlayStation Store',
);
if (!mounted) return;
if (result != null) setState(() => _purchasePlatforms = result);
},
),
@@ -344,6 +350,7 @@ class _GameFormPageState extends State<GameFormPage> {
hint: '298元、49.99美元',
keyboardType: TextInputType.text,
);
if (!mounted) return;
if (result != null) setState(() => _purchasePriceController.text = result);
},
),
@@ -496,6 +503,7 @@ class _GameFormPageState extends State<GameFormPage> {
builder: (_) => _SummaryEditorPage(initialText: _summaryController.text),
),
);
if (!mounted) return;
if (result != null) {
setState(() => _summaryController.text = result);
}
@@ -753,10 +761,11 @@ class _GameFormPageState extends State<GameFormPage> {
if (pickedFile != null) {
final fileName = 'cover_${DateTime.now().millisecondsSinceEpoch}.jpg';
final gameId = widget.game?.id ?? DateTime.now().millisecondsSinceEpoch.toString();
final gameId = widget.game?.id ?? const Uuid().v4();
final targetPath = await ImagePathHelper.instance.getGameCoverPath(gameId, fileName);
await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath));
await File(pickedFile.path).copy(targetPath);
if (!mounted) return;
setState(() => _coverPath = targetPath);
}
} catch (e) {
@@ -919,11 +928,12 @@ class _GameFormPageState extends State<GameFormPage> {
if (response.bodyBytes.length > 10 * 1024 * 1024) throw Exception('图片太大');
final fileName = 'cover_${DateTime.now().millisecondsSinceEpoch}.jpg';
final gameId = widget.game?.id ?? DateTime.now().millisecondsSinceEpoch.toString();
final gameId = widget.game?.id ?? const Uuid().v4();
final targetPath = await ImagePathHelper.instance.getGameCoverPath(gameId, fileName);
await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath));
await File(targetPath).writeAsBytes(response.bodyBytes);
if (!mounted) return;
setState(() => _coverPath = targetPath);
} catch (e) {
debugPrint('封面下载失败: $e');
@@ -1009,6 +1019,7 @@ class _GameFormPageState extends State<GameFormPage> {
lastDate: DateTime.now().add(const Duration(days: 365 * 5)),
builder: (context, child) => child!,
);
if (!mounted) return;
if (picked != null) {
setState(() => _purchaseDate = picked);
}
@@ -1073,7 +1084,7 @@ class _GameFormPageState extends State<GameFormPage> {
final now = DateTime.now();
if (widget.game == null) {
final newGameId = now.millisecondsSinceEpoch.toString();
final newGameId = const Uuid().v4();
String? finalCoverPath;
if (_coverPath != null && _coverPath!.isNotEmpty) {
finalCoverPath = await _moveCoverToNewId(_coverPath!, newGameId);

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/app_provider.dart';
import '../../widgets/fade_in_local_image.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
@@ -235,7 +236,7 @@ class _GameReviewFormPageState extends State<GameReviewFormPage> {
final now = DateTime.now();
if (widget.review == null) {
final newReview = GameReview(
id: now.millisecondsSinceEpoch.toString(),
id: const Uuid().v4(),
gameId: widget.gameId,
content: _contentController.text.trim(),
reviewer: _reviewerController.text.trim(),

View File

@@ -7,6 +7,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:http/http.dart' as http;
import '../../providers/app_provider.dart';
import '../../widgets/fade_in_local_image.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
import '../../utils/image_path_helper.dart';
@@ -230,7 +231,7 @@ class _GameScreenshotsPageState extends State<GameScreenshotsPage> {
await File(pickedFile.path).copy(targetPath);
final newScreenshot = GameScreenshot(
id: DateTime.now().millisecondsSinceEpoch.toString(),
id: const Uuid().v4(),
gameId: widget.game.id,
screenshotPath: targetPath,
createdAt: DateTime.now(),
@@ -317,7 +318,7 @@ class _GameScreenshotsPageState extends State<GameScreenshotsPage> {
await File(targetPath).writeAsBytes(response.bodyBytes);
final newScreenshot = GameScreenshot(
id: DateTime.now().millisecondsSinceEpoch.toString(),
id: const Uuid().v4(),
gameId: widget.game.id,
screenshotPath: targetPath,
createdAt: DateTime.now(),

View File

@@ -446,8 +446,9 @@ class _GameTabPageState extends State<GameTabPage> {
ElevatedButton(
onPressed: () async {
await context.read<AppProvider>().removeGame(game.id);
if (!ctx.mounted) return;
Navigator.pop(ctx);
_loadFirst();
if (mounted) _loadFirst();
},
style: ElevatedButton.styleFrom(backgroundColor: colors.error, foregroundColor: colors.onError, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),

View File

@@ -45,12 +45,19 @@ class _MainContentPageState extends State<MainContentPage> {
}
void _loadTabSettings() {
setState(() {
_showMovieTab = _userPrefs.showMovieTab;
_showBookTab = _userPrefs.showBookTab;
_showNoteTab = _userPrefs.showNoteTab;
_showGameTab = _userPrefs.showGameTab;
});
final newMovie = _userPrefs.showMovieTab;
final newBook = _userPrefs.showBookTab;
final newNote = _userPrefs.showNoteTab;
final newGame = _userPrefs.showGameTab;
if (newMovie != _showMovieTab || newBook != _showBookTab ||
newNote != _showNoteTab || newGame != _showGameTab) {
setState(() {
_showMovieTab = newMovie;
_showBookTab = newBook;
_showNoteTab = newNote;
_showGameTab = newGame;
});
}
}
@override

View File

@@ -1189,7 +1189,7 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
onPressed: () async {
final provider = context.read<AppProvider>();
await provider.removeMovie(widget.movie.id);
if (!mounted) return;
if (!mounted || !context.mounted) return;
if (widget.embedded) {
Navigator.of(context).pop(); // close dialog
provider.selectMovie(null);
@@ -1198,7 +1198,7 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
navigator.pop();
navigator.pop();
}
if (mounted) {
if (mounted && context.mounted) {
ToastUtil.show(context, '已删除');
}
},

View File

@@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
import 'package:http/http.dart' as http;
import '../../providers/app_provider.dart';
import '../../widgets/fade_in_local_image.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
import '../../utils/image_path_helper.dart';
@@ -256,6 +257,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
arguments: url,
);
if (!mounted) return;
// 处理返回的影视信息
if (result != null && result is Map<String, dynamic>) {
_fillMovieInfo(result);
@@ -353,7 +355,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
if (response.bodyBytes.length > 10 * 1024 * 1024) throw Exception('图片太大');
final fileName = 'poster_${DateTime.now().millisecondsSinceEpoch}.jpg';
final movieId = widget.movie?.id ?? DateTime.now().millisecondsSinceEpoch.toString();
final movieId = widget.movie?.id ?? const Uuid().v4();
final targetPath = await ImagePathHelper.instance.getMoviePosterPath(movieId, fileName);
await ImagePathHelper.instance.ensureDirExists(p.dirname(targetPath));
await File(targetPath).writeAsBytes(response.bodyBytes);
@@ -436,6 +438,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
initialValue: _titleController.text,
hint: '请输入影视名称',
);
if (!mounted) return;
if (result != null) setState(() => _titleController.text = result);
},
),
@@ -458,6 +461,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
initialSelected: _alternateTitles,
hint: '输入别名',
);
if (!mounted) return;
if (result != null) setState(() => _alternateTitles = result);
},
),
@@ -483,6 +487,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
initialSelected: _directors,
hint: '如:张艺谋、李安',
);
if (!mounted) return;
if (result != null) setState(() => _directors = result);
},
),
@@ -506,6 +511,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
initialSelected: _writers,
hint: '如:刘慈欣、王家卫',
);
if (!mounted) return;
if (result != null) setState(() => _writers = result);
},
),
@@ -531,6 +537,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
initialSelected: _actors,
hint: '如:梁朝伟、周星驰',
);
if (!mounted) return;
if (result != null) setState(() => _actors = result);
},
),
@@ -556,6 +563,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
initialSelected: _genres,
hint: '如:剧情、科幻、悬疑',
);
if (!mounted) return;
if (result != null) setState(() => _genres = result);
},
),
@@ -734,6 +742,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
builder: (_) => _SummaryEditorPage(initialText: _summaryController.text),
),
);
if (!mounted) return;
if (result != null) {
setState(() => _summaryController.text = result);
}
@@ -1144,7 +1153,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
final fileName = 'poster_${DateTime.now().millisecondsSinceEpoch}.jpg';
// 如果是编辑模式使用现有影视ID如果是新建模式使用临时ID保存时会替换
final movieId = widget.movie?.id ?? DateTime.now().millisecondsSinceEpoch.toString();
final movieId = widget.movie?.id ?? const Uuid().v4();
// 保存到新的路径结构: images/movies/{movieId}/{fileName}
final targetPath = await ImagePathHelper.instance.getMoviePosterPath(
@@ -1155,6 +1164,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
await File(pickedFile.path).copy(targetPath);
if (!mounted) return;
setState(() => _posterPath = targetPath);
}
} catch (e) {
@@ -1237,6 +1247,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
builder: (context, child) => child!,
);
if (!mounted) return;
if (picked != null) {
setState(() => _releaseDate = picked);
}
@@ -1252,6 +1263,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
builder: (context, child) => child!,
);
if (!mounted) return;
if (picked != null) {
setState(() => _watchDate = picked);
}
@@ -1321,7 +1333,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
if (widget.movie == null) {
// 生成新的影视ID
final newMovieId = now.millisecondsSinceEpoch.toString();
final newMovieId = const Uuid().v4();
// 如果有海报需要移动到正确的ID目录
String? finalPosterPath;

View File

@@ -7,6 +7,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:http/http.dart' as http;
import '../../providers/app_provider.dart';
import '../../widgets/fade_in_local_image.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
import '../../utils/image_path_helper.dart';
@@ -286,7 +287,7 @@ class _MoviePostersPageState extends State<MoviePostersPage> {
await File(pickedFile.path).copy(targetPath);
final newPoster = MoviePoster(
id: DateTime.now().millisecondsSinceEpoch.toString(),
id: const Uuid().v4(),
movieId: widget.movie.id,
posterPath: targetPath,
createdAt: DateTime.now(),
@@ -423,7 +424,7 @@ class _MoviePostersPageState extends State<MoviePostersPage> {
await File(targetPath).writeAsBytes(response.bodyBytes);
final newPoster = MoviePoster(
id: DateTime.now().millisecondsSinceEpoch.toString(),
id: const Uuid().v4(),
movieId: widget.movie.id,
posterPath: targetPath,
createdAt: DateTime.now(),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/app_provider.dart';
import '../../widgets/fade_in_local_image.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
@@ -307,7 +308,7 @@ class _MovieReviewFormPageState extends State<MovieReviewFormPage> {
if (widget.review == null) {
final newReview = MovieReview(
id: now.millisecondsSinceEpoch.toString(),
id: const Uuid().v4(),
movieId: widget.movieId,
content: _contentController.text.trim(),
reviewer: _reviewerController.text.trim(),

View File

@@ -447,8 +447,9 @@ class _MovieTabPageState extends State<MovieTabPage> {
ElevatedButton(
onPressed: () async {
await context.read<AppProvider>().removeMovie(movie.id);
if (!ctx.mounted) return;
Navigator.pop(ctx);
_loadFirst();
if (mounted) _loadFirst();
},
style: ElevatedButton.styleFrom(backgroundColor: colors.error, foregroundColor: colors.onError, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),

View File

@@ -6,6 +6,7 @@ import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart' as p;
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import '../../providers/app_provider.dart';
import 'package:uuid/uuid.dart';
import '../../models/data_models.dart';
import '../../utils/toast_util.dart';
import '../../utils/image_path_helper.dart';
@@ -97,7 +98,7 @@ class _NoteFormPageState extends State<NoteFormPage> {
await context.read<AppProvider>().updateNote(updatedNote);
_savedNote = updatedNote;
} else {
final noteId = now.millisecondsSinceEpoch.toString();
final noteId = const Uuid().v4();
List<String> finalImages = [];
if (_images.isNotEmpty) {
final oldNoteId = _tempNoteId ?? noteId;
@@ -701,7 +702,7 @@ class _NoteFormPageState extends State<NoteFormPage> {
await context.read<AppProvider>().updateNote(updatedNote);
} else {
// 添加新笔记
final noteId = now.millisecondsSinceEpoch.toString();
final noteId = const Uuid().v4();
// 如果有图片需要移动到正确的ID目录
List<String> finalImages = [];
@@ -799,7 +800,7 @@ class _NoteFormPageState extends State<NoteFormPage> {
noteId = widget.note!.id;
} else {
// 新建模式使用已存在的临时ID或生成新的
noteId = _tempNoteId ?? DateTime.now().millisecondsSinceEpoch.toString();
noteId = _tempNoteId ?? const Uuid().v4();
_tempNoteId = noteId;
}
@@ -810,10 +811,11 @@ class _NoteFormPageState extends State<NoteFormPage> {
await File(image.path).copy(targetPath);
if (!mounted) return;
setState(() => _images.add(targetPath));
}
} catch (e) {
ToastUtil.show(context, '选择图片失败: $e');
if (mounted) ToastUtil.show(context, '选择图片失败: $e');
}
}

View File

@@ -392,7 +392,7 @@ class _NoteTabPageState extends State<NoteTabPage> {
TextButton(onPressed: () => Navigator.pop(ctx),
child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))),
ElevatedButton(
onPressed: () async { await context.read<AppProvider>().removeNote(note.id); Navigator.pop(ctx); _loadFirst(); },
onPressed: () async { await context.read<AppProvider>().removeNote(note.id); if (!ctx.mounted) return; Navigator.pop(ctx); if (mounted) _loadFirst(); },
style: ElevatedButton.styleFrom(backgroundColor: colors.error, foregroundColor: colors.onError, elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),

View File

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/app_provider.dart';
import '../../services/sync/backup_service.dart';
import '../../services/sync/auto_backup_service.dart';
import '../../utils/toast_util.dart';
/// 本地备份页面
@@ -16,27 +15,6 @@ class BackupPage extends StatefulWidget {
class _BackupPageState extends State<BackupPage> {
bool _isExporting = false;
bool _isImporting = false;
bool _autoBackupEnabled = false;
bool _isLoading = true;
String? _backupDirPath;
@override
void initState() {
super.initState();
_loadAutoBackupStatus();
}
Future<void> _loadAutoBackupStatus() async {
final enabled = await AutoBackupService.instance.getEnabled();
final dirPath = await AutoBackupService.instance.getBackupDirectoryPath();
if (mounted) {
setState(() {
_autoBackupEnabled = enabled;
_backupDirPath = dirPath;
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
@@ -46,16 +24,9 @@ class _BackupPageState extends State<BackupPage> {
appBar: AppBar(
title: const Text('本地备份'),
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: ListView(
body: ListView(
padding: const EdgeInsets.all(20),
children: [
// 自动备份开关 - 紧凑一行
_buildAutoBackupSection(colors),
const SizedBox(height: 20),
// 手动备份
_buildSectionTitle(colors, '手动备份'),
const SizedBox(height: 10),
@@ -492,74 +463,4 @@ class _BackupPageState extends State<BackupPage> {
}
}
}
/// 构建自动备份区域 - 紧凑一行
Widget _buildAutoBackupSection(ColorScheme colors) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: colors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: colors.outline, width: 0.5),
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
),
child: Icon(Icons.schedule,
size: 18, color: colors.onSurface.withValues(alpha: 0.6)),
),
const SizedBox(width: 10),
Expanded(
child: _backupDirPath != null && _autoBackupEnabled
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'自动本地备份',
style: TextStyle(
fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface),
),
const SizedBox(height: 2),
Text(
_backupDirPath!,
style:
TextStyle(fontSize: 11, color: colors.onSurface.withValues(alpha: 0.4)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
)
: Text(
'自动本地备份',
style: TextStyle(
fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface),
),
),
Switch(
value: _autoBackupEnabled,
onChanged: (value) async {
setState(() => _autoBackupEnabled = value);
await AutoBackupService.instance.setEnabled(value);
if (value) {
ToastUtil.show(context, '自动备份已开启');
} else {
ToastUtil.show(context, '自动备份已关闭');
}
await _loadAutoBackupStatus();
},
activeThumbColor: colors.primary,
activeTrackColor: colors.primary.withValues(alpha: 0.3),
inactiveThumbColor: colors.surface,
inactiveTrackColor: colors.outline,
),
],
),
);
}
}