generated from dellevin/template
优化名称编辑弹窗
This commit is contained in:
@@ -14,6 +14,7 @@ import '../../utils/toast_util.dart';
|
|||||||
import '../../utils/image_path_helper.dart';
|
import '../../utils/image_path_helper.dart';
|
||||||
import '../../widgets/genre_selector_page.dart';
|
import '../../widgets/genre_selector_page.dart';
|
||||||
import '../../widgets/text_input_panel.dart';
|
import '../../widgets/text_input_panel.dart';
|
||||||
|
import '../../widgets/alternate_titles_dialog.dart';
|
||||||
|
|
||||||
/// 从多值字段列表中提取去重排序的唯一值(供 compute 使用)
|
/// 从多值字段列表中提取去重排序的唯一值(供 compute 使用)
|
||||||
List<String> _collectUnique(List<List<String>> lists) {
|
List<String> _collectUnique(List<List<String>> lists) {
|
||||||
@@ -158,18 +159,10 @@ class _BookFormPageState extends State<BookFormPage> {
|
|||||||
children: [
|
children: [
|
||||||
// 第一行:书名 + 别名
|
// 第一行:书名 + 别名
|
||||||
_halfCard('书名', _titleController.text, Icons.book_outlined, required: true,
|
_halfCard('书名', _titleController.text, Icons.book_outlined, required: true,
|
||||||
onTap: () async {
|
onTap: () => _editTitle(),
|
||||||
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,
|
_halfCard('别名', _alternateTitles.isEmpty ? '' : '${_alternateTitles.length}个:${_alternateTitles.join('、')}', Icons.alternate_email_outlined,
|
||||||
onTap: () async {
|
onTap: () => _editAlternateTitles(),
|
||||||
final r = await GenreSelectorPage.show(context: context, title: '添加别名', existingTags: [], initialSelected: _alternateTitles, hint: '输入别名');
|
|
||||||
if (!mounted) return;
|
|
||||||
if (r != null) setState(() => _alternateTitles = r);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
|
||||||
// 第二行:作者 + 译者
|
// 第二行:作者 + 译者
|
||||||
@@ -571,6 +564,65 @@ class _BookFormPageState extends State<BookFormPage> {
|
|||||||
|
|
||||||
// ─── 数据操作 ───
|
// ─── 数据操作 ───
|
||||||
|
|
||||||
|
/// 编辑书名(弹窗)
|
||||||
|
Future<void> _editTitle() async {
|
||||||
|
final controller = TextEditingController(text: _titleController.text);
|
||||||
|
final result = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) {
|
||||||
|
final colors = Theme.of(ctx).colorScheme;
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
title: Text('书名', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||||
|
content: TextField(
|
||||||
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
|
style: TextStyle(fontSize: 15, color: colors.onSurface),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '请输入书名',
|
||||||
|
hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.3)),
|
||||||
|
filled: true,
|
||||||
|
fillColor: colors.surfaceContainerHigh,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
|
||||||
|
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
|
||||||
|
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: colors.primary, width: 1)),
|
||||||
|
),
|
||||||
|
onSubmitted: (v) => Navigator.pop(ctx, v),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, controller.text),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: colors.primary, foregroundColor: colors.onPrimary, elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
),
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
controller.dispose();
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
if (result != null) setState(() => _titleController.text = result.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 编辑别名(弹窗 + 标签式输入)
|
||||||
|
Future<void> _editAlternateTitles() async {
|
||||||
|
final result = await showDialog<List<String>>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlternateTitlesDialog(initial: _alternateTitles),
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (result != null) setState(() => _alternateTitles = result);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _selectPublishDate() async {
|
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)));
|
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 (!mounted) return;
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import '../../data/epub/reader_dao.dart';
|
|||||||
import '../../services/epub/epub_service.dart';
|
import '../../services/epub/epub_service.dart';
|
||||||
import '../../utils/user_prefs.dart';
|
import '../../utils/user_prefs.dart';
|
||||||
import '../../utils/toast_util.dart';
|
import '../../utils/toast_util.dart';
|
||||||
import '../../utils/responsive.dart';
|
|
||||||
import '../../widgets/fade_in_local_image.dart';
|
import '../../widgets/fade_in_local_image.dart';
|
||||||
import '../../widgets/shimmer_skeleton.dart';
|
import '../../widgets/shimmer_skeleton.dart';
|
||||||
import 'epub_detail_page.dart';
|
import 'epub_detail_page.dart';
|
||||||
import 'widgets/book_grid_item.dart';
|
|
||||||
|
|
||||||
/// EPUB 书架页面
|
/// EPUB 书架页面
|
||||||
class EpubLibraryPage extends StatefulWidget {
|
class EpubLibraryPage extends StatefulWidget {
|
||||||
@@ -28,7 +26,6 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
|||||||
bool _isSearching = false;
|
bool _isSearching = false;
|
||||||
final TextEditingController _searchCtrl = TextEditingController();
|
final TextEditingController _searchCtrl = TextEditingController();
|
||||||
int _sortMode = UserPrefs().epubSortMode;
|
int _sortMode = UserPrefs().epubSortMode;
|
||||||
int _viewMode = UserPrefs().epubViewMode; // 0=列表 1=网格
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -236,7 +233,7 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
|||||||
),
|
),
|
||||||
onChanged: (_) => _onSearchChanged(),
|
onChanged: (_) => _onSearchChanged(),
|
||||||
)
|
)
|
||||||
: Text('EPUB 阅读',
|
: Text('阅读',
|
||||||
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(_isSearching ? Icons.close : Icons.arrow_back, size: 20),
|
icon: Icon(_isSearching ? Icons.close : Icons.arrow_back, size: 20),
|
||||||
@@ -262,14 +259,14 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
|||||||
hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)),
|
hintStyle: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.35)),
|
||||||
border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero),
|
border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero),
|
||||||
onChanged: (_) => _onSearchChanged())
|
onChanged: (_) => _onSearchChanged())
|
||||||
: Text('EPUB 阅读',
|
: Text('阅读',
|
||||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6)))),
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6)))),
|
||||||
..._buildActions(colors),
|
..._buildActions(colors),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
// 主体
|
// 主体
|
||||||
Expanded(child: _isLoading && _books.isEmpty
|
Expanded(child: _isLoading && _books.isEmpty
|
||||||
? const BookSkeletonGrid()
|
? _buildSkeletonList(colors)
|
||||||
: _books.isEmpty
|
: _books.isEmpty
|
||||||
? _buildEmpty(colors)
|
? _buildEmpty(colors)
|
||||||
: _filteredBooks.isEmpty
|
: _filteredBooks.isEmpty
|
||||||
@@ -283,14 +280,14 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 主体内容:书架(列表/网格)
|
/// 主体内容:书架列表
|
||||||
Widget _buildContent(ColorScheme colors) {
|
Widget _buildContent(ColorScheme colors) {
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
// 书架分隔标题
|
// 书架分隔标题
|
||||||
SliverToBoxAdapter(child: _buildSectionHeader(colors)),
|
SliverToBoxAdapter(child: _buildSectionHeader(colors)),
|
||||||
// 书架列表/网格
|
// 书架列表
|
||||||
_viewMode == 0 ? _buildSliverListView(colors) : _buildSliverGrid(colors),
|
_buildSliverListView(colors),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -312,15 +309,6 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
|||||||
|
|
||||||
List<Widget> _buildActions(ColorScheme colors) {
|
List<Widget> _buildActions(ColorScheme colors) {
|
||||||
return [
|
return [
|
||||||
if (!_isSearching)
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(_viewMode == 0 ? Icons.grid_view_outlined : Icons.view_list_outlined, size: 20, color: colors.onSurface.withValues(alpha: 0.6)),
|
|
||||||
tooltip: _viewMode == 0 ? '网格视图' : '列表视图',
|
|
||||||
onPressed: () {
|
|
||||||
setState(() => _viewMode = _viewMode == 0 ? 1 : 0);
|
|
||||||
UserPrefs().setEpubViewMode(_viewMode);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (!_isSearching)
|
if (!_isSearching)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.search, size: 20, color: colors.onSurface.withValues(alpha: 0.6)),
|
icon: Icon(Icons.search, size: 20, color: colors.onSurface.withValues(alpha: 0.6)),
|
||||||
@@ -355,7 +343,7 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
|||||||
size: 40, color: colors.onSurface.withValues(alpha: 0.25)),
|
size: 40, color: colors.onSurface.withValues(alpha: 0.25)),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Text('EPUB 阅读',
|
Text('阅读',
|
||||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text('点击右上角导入 .epub 文件',
|
Text('点击右上角导入 .epub 文件',
|
||||||
@@ -393,31 +381,36 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSliverGrid(ColorScheme colors) {
|
/// 加载骨架列表
|
||||||
return SliverPadding(
|
Widget _buildSkeletonList(ColorScheme colors) {
|
||||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 100),
|
return ListView.builder(
|
||||||
sliver: SliverLayoutBuilder(
|
padding: const EdgeInsets.fromLTRB(12, 8, 12, 100),
|
||||||
builder: (context, constraints) {
|
itemCount: 8,
|
||||||
final crossAxisCount =
|
itemBuilder: (_, __) => Container(
|
||||||
responsiveCrossAxisCount(constraints.crossAxisExtent, minItemWidth: 110);
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
return SliverGrid(
|
padding: const EdgeInsets.all(12),
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
decoration: BoxDecoration(
|
||||||
crossAxisCount: crossAxisCount,
|
color: colors.surfaceContainerHigh,
|
||||||
childAspectRatio: 0.55,
|
borderRadius: BorderRadius.circular(12),
|
||||||
crossAxisSpacing: 12,
|
),
|
||||||
mainAxisSpacing: 16,
|
child: Row(
|
||||||
),
|
children: [
|
||||||
delegate: SliverChildBuilderDelegate(
|
const ShimmerSkeleton(width: 48, height: 64, borderRadius: 6),
|
||||||
(context, index) => BookGridItem(
|
const SizedBox(width: 12),
|
||||||
book: _filteredBooks[index],
|
Expanded(
|
||||||
viewMode: ViewMode.relaxed,
|
child: Column(
|
||||||
onTap: () => _openBook(_filteredBooks[index]),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
onLongPress: () => _deleteBook(_filteredBooks[index]),
|
children: const [
|
||||||
|
ShimmerSkeleton(width: double.infinity, height: 15),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
ShimmerSkeleton(width: 120, height: 12),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
ShimmerSkeleton(width: 60, height: 11),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
childCount: _filteredBooks.length,
|
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
import '../../data/gallery/gallery_dao.dart';
|
import '../../data/gallery/gallery_dao.dart';
|
||||||
@@ -180,17 +182,11 @@ class _GalleryPageState extends State<GalleryPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// 图片
|
// 图片(按真实比例显示)
|
||||||
AspectRatio(
|
_GalleryImageCard(
|
||||||
aspectRatio: _aspectRatioFor(item.category),
|
path: item.path,
|
||||||
child: FadeInLocalImage(
|
fallbackAspectRatio: _aspectRatioFor(item.category),
|
||||||
path: item.path,
|
colors: colors,
|
||||||
fit: BoxFit.cover,
|
|
||||||
errorWidget: Container(
|
|
||||||
color: colors.surfaceContainerHighest,
|
|
||||||
child: Icon(Icons.broken_image_outlined, color: colors.onSurface.withValues(alpha: 0.3)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// 类别标签
|
// 类别标签
|
||||||
@@ -228,7 +224,7 @@ class _GalleryPageState extends State<GalleryPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 不同类别用不同宽高比,让瀑布流有错落感
|
/// 不同类别用作加载占位的默认宽高比;图片真实尺寸读出后会被覆盖
|
||||||
double _aspectRatioFor(String category) {
|
double _aspectRatioFor(String category) {
|
||||||
switch (category) {
|
switch (category) {
|
||||||
case 'movie_poster':
|
case 'movie_poster':
|
||||||
@@ -265,3 +261,79 @@ class _GalleryPageState extends State<GalleryPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 读取本地图片真实宽高,按真实比例显示;未读出前用 fallback 占位。
|
||||||
|
class _GalleryImageCard extends StatefulWidget {
|
||||||
|
final String path;
|
||||||
|
final double fallbackAspectRatio;
|
||||||
|
final ColorScheme colors;
|
||||||
|
|
||||||
|
const _GalleryImageCard({
|
||||||
|
required this.path,
|
||||||
|
required this.fallbackAspectRatio,
|
||||||
|
required this.colors,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_GalleryImageCard> createState() => _GalleryImageCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GalleryImageCardState extends State<_GalleryImageCard> {
|
||||||
|
double? _aspectRatio;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_resolveAspectRatio();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(_GalleryImageCard oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.path != widget.path) {
|
||||||
|
_aspectRatio = null;
|
||||||
|
_resolveAspectRatio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _resolveAspectRatio() async {
|
||||||
|
final path = widget.path;
|
||||||
|
try {
|
||||||
|
if (path.startsWith('http')) {
|
||||||
|
// 网络图片不解析尺寸,直接用占位比例
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final file = File(path);
|
||||||
|
if (!await file.exists()) return;
|
||||||
|
final bytes = await file.readAsBytes();
|
||||||
|
if (!mounted) return;
|
||||||
|
final data = Uint8List.fromList(bytes);
|
||||||
|
final decoded = await decodeImageFromList(data);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (decoded.width > 0 && decoded.height > 0) {
|
||||||
|
setState(() => _aspectRatio = decoded.width / decoded.height);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// 解析失败则保留占位比例
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ratio = _aspectRatio ?? widget.fallbackAspectRatio;
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: ratio,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: FadeInLocalImage(
|
||||||
|
path: widget.path,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorWidget: Container(
|
||||||
|
color: widget.colors.surfaceContainerHighest,
|
||||||
|
child: Icon(Icons.broken_image_outlined, color: widget.colors.onSurface.withValues(alpha: 0.3)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -185,16 +185,7 @@ class _GameFormPageState extends State<GameFormPage> {
|
|||||||
value: _titleController.text,
|
value: _titleController.text,
|
||||||
required: true,
|
required: true,
|
||||||
icon: Icons.sports_esports_outlined,
|
icon: Icons.sports_esports_outlined,
|
||||||
onTap: () async {
|
onTap: _editTitle,
|
||||||
final result = await TextInputPanel.show(
|
|
||||||
context: context,
|
|
||||||
title: '游戏名称',
|
|
||||||
initialValue: _titleController.text,
|
|
||||||
hint: '请输入游戏名称',
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
if (result != null) setState(() => _titleController.text = result);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 平台
|
// 平台
|
||||||
@@ -1007,6 +998,55 @@ class _GameFormPageState extends State<GameFormPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 编辑游戏名称(弹窗)
|
||||||
|
Future<void> _editTitle() async {
|
||||||
|
final controller = TextEditingController(text: _titleController.text);
|
||||||
|
final result = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) {
|
||||||
|
final colors = Theme.of(ctx).colorScheme;
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
title: Text('游戏名称', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||||
|
content: TextField(
|
||||||
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
|
style: TextStyle(fontSize: 15, color: colors.onSurface),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '请输入游戏名称',
|
||||||
|
hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.3)),
|
||||||
|
filled: true,
|
||||||
|
fillColor: colors.surfaceContainerHigh,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
|
||||||
|
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
|
||||||
|
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: colors.primary, width: 1)),
|
||||||
|
),
|
||||||
|
onSubmitted: (v) => Navigator.pop(ctx, v),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, controller.text),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: colors.primary, foregroundColor: colors.onPrimary, elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
),
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
controller.dispose();
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
if (result != null) setState(() => _titleController.text = result.trim());
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _editPlayCount() async {
|
Future<void> _editPlayCount() async {
|
||||||
final controller = TextEditingController(text: _playCount > 0 ? '$_playCount' : '');
|
final controller = TextEditingController(text: _playCount > 0 ? '$_playCount' : '');
|
||||||
final result = await showDialog<String>(
|
final result = await showDialog<String>(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import '../../utils/image_path_helper.dart';
|
|||||||
import '../../widgets/genre_selector_page.dart';
|
import '../../widgets/genre_selector_page.dart';
|
||||||
import '../../widgets/text_input_panel.dart';
|
import '../../widgets/text_input_panel.dart';
|
||||||
import '../../widgets/duration_picker.dart';
|
import '../../widgets/duration_picker.dart';
|
||||||
|
import '../../widgets/alternate_titles_dialog.dart';
|
||||||
|
|
||||||
/// 从多值字段列表中提取去重排序的唯一值(供 compute 使用)
|
/// 从多值字段列表中提取去重排序的唯一值(供 compute 使用)
|
||||||
List<String> _collectUnique(List<List<String>> lists) {
|
List<String> _collectUnique(List<List<String>> lists) {
|
||||||
@@ -436,16 +437,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
|||||||
value: _titleController.text,
|
value: _titleController.text,
|
||||||
required: true,
|
required: true,
|
||||||
icon: Icons.movie_outlined,
|
icon: Icons.movie_outlined,
|
||||||
onTap: () async {
|
onTap: () => _editTitle(),
|
||||||
final result = await TextInputPanel.show(
|
|
||||||
context: context,
|
|
||||||
title: '影视名称',
|
|
||||||
initialValue: _titleController.text,
|
|
||||||
hint: '请输入影视名称',
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
if (result != null) setState(() => _titleController.text = result);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -458,17 +450,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
|||||||
: '${_alternateTitles.length}个:${_alternateTitles.join('、')}',
|
: '${_alternateTitles.length}个:${_alternateTitles.join('、')}',
|
||||||
icon: Icons.alternate_email_outlined,
|
icon: Icons.alternate_email_outlined,
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
onTap: () async {
|
onTap: () => _editAlternateTitles(),
|
||||||
final result = await GenreSelectorPage.show(
|
|
||||||
context: context,
|
|
||||||
title: '添加别名',
|
|
||||||
existingTags: [],
|
|
||||||
initialSelected: _alternateTitles,
|
|
||||||
hint: '输入别名',
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
if (result != null) setState(() => _alternateTitles = result);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -763,6 +745,65 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 编辑名称(弹窗)
|
||||||
|
Future<void> _editTitle() async {
|
||||||
|
final controller = TextEditingController(text: _titleController.text);
|
||||||
|
final result = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) {
|
||||||
|
final colors = Theme.of(ctx).colorScheme;
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
title: Text('影视名称', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||||
|
content: TextField(
|
||||||
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
|
style: TextStyle(fontSize: 15, color: colors.onSurface),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '请输入影视名称',
|
||||||
|
hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.3)),
|
||||||
|
filled: true,
|
||||||
|
fillColor: colors.surfaceContainerHigh,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
|
||||||
|
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
|
||||||
|
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: colors.primary, width: 1)),
|
||||||
|
),
|
||||||
|
onSubmitted: (v) => Navigator.pop(ctx, v),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.pop(ctx), child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6)))),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, controller.text),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: colors.primary, foregroundColor: colors.onPrimary, elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
),
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
controller.dispose();
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
if (result != null) setState(() => _titleController.text = result.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 编辑别名(弹窗 + 标签式输入)
|
||||||
|
Future<void> _editAlternateTitles() async {
|
||||||
|
final result = await showDialog<List<String>>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlternateTitlesDialog(initial: _alternateTitles),
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (result != null) setState(() => _alternateTitles = result);
|
||||||
|
}
|
||||||
|
|
||||||
/// 编辑观看次数
|
/// 编辑观看次数
|
||||||
Future<void> _editWatchCount() async {
|
Future<void> _editWatchCount() async {
|
||||||
final controller = TextEditingController(text: _watchCount > 0 ? '$_watchCount' : '');
|
final controller = TextEditingController(text: _watchCount > 0 ? '$_watchCount' : '');
|
||||||
|
|||||||
@@ -1457,6 +1457,11 @@ class AppProvider extends ChangeNotifier {
|
|||||||
final trimmed = name.trim();
|
final trimmed = name.trim();
|
||||||
if (trimmed.isEmpty || !seen.add(trimmed)) continue;
|
if (trimmed.isEmpty || !seen.add(trimmed)) continue;
|
||||||
final person = await findOrCreate(trimmed, occupationLabel);
|
final person = await findOrCreate(trimmed, occupationLabel);
|
||||||
|
// 演员角色:若该影视已存在该人物的配音关联,则跳过添加演员关联
|
||||||
|
if (roleType == 'actor' &&
|
||||||
|
await _moviePersonDao.existsRelation(movie.id, person.id, 'voiceActor')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!await _moviePersonDao.existsRelation(movie.id, person.id, roleType)) {
|
if (!await _moviePersonDao.existsRelation(movie.id, person.id, roleType)) {
|
||||||
await _moviePersonDao.insert(MoviePerson(
|
await _moviePersonDao.insert(MoviePerson(
|
||||||
id: uuid.v4(),
|
id: uuid.v4(),
|
||||||
|
|||||||
@@ -359,10 +359,6 @@ class UserPrefs {
|
|||||||
double get epubFontSize => prefs.getDouble('epubFontSize') ?? 18.0;
|
double get epubFontSize => prefs.getDouble('epubFontSize') ?? 18.0;
|
||||||
Future<bool> setEpubFontSize(double value) => prefs.setDouble('epubFontSize', value);
|
Future<bool> setEpubFontSize(double value) => prefs.setDouble('epubFontSize', value);
|
||||||
|
|
||||||
/// EPUB 书架视图模式: 0=宽松, 1=紧凑
|
|
||||||
int get epubViewMode => prefs.getInt('epubViewMode') ?? 0;
|
|
||||||
Future<bool> setEpubViewMode(int value) => prefs.setInt('epubViewMode', value);
|
|
||||||
|
|
||||||
/// EPUB 书架排序模式: 0=更新时间, 1=创建时间, 2=阅读进度, 3=书名
|
/// EPUB 书架排序模式: 0=更新时间, 1=创建时间, 2=阅读进度, 3=书名
|
||||||
int get epubSortMode => prefs.getInt('epubSortMode') ?? 0;
|
int get epubSortMode => prefs.getInt('epubSortMode') ?? 0;
|
||||||
Future<bool> setEpubSortMode(int value) => prefs.setInt('epubSortMode', value);
|
Future<bool> setEpubSortMode(int value) => prefs.setInt('epubSortMode', value);
|
||||||
|
|||||||
133
lib/widgets/alternate_titles_dialog.dart
Normal file
133
lib/widgets/alternate_titles_dialog.dart
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// 别名标签式输入弹窗(影视/书籍共用)
|
||||||
|
class AlternateTitlesDialog extends StatefulWidget {
|
||||||
|
final List<String> initial;
|
||||||
|
const AlternateTitlesDialog({super.key, required this.initial});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AlternateTitlesDialog> createState() => _AlternateTitlesDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlternateTitlesDialogState extends State<AlternateTitlesDialog> {
|
||||||
|
late final TextEditingController _controller;
|
||||||
|
late List<String> _items;
|
||||||
|
final _focus = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = TextEditingController();
|
||||||
|
_items = List.from(widget.initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
_focus.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _add() {
|
||||||
|
final v = _controller.text.trim();
|
||||||
|
if (v.isEmpty) return;
|
||||||
|
if (!_items.contains(v)) {
|
||||||
|
setState(() => _items.add(v));
|
||||||
|
}
|
||||||
|
_controller.clear();
|
||||||
|
_focus.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _remove(int i) => setState(() => _items.removeAt(i));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colors = Theme.of(context).colorScheme;
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
title: Text('添加别名', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||||
|
content: SizedBox(
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 输入框 + 添加按钮
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _controller,
|
||||||
|
focusNode: _focus,
|
||||||
|
style: TextStyle(fontSize: 14, color: colors.onSurface),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '输入别名',
|
||||||
|
hintStyle: TextStyle(color: colors.onSurface.withValues(alpha: 0.3)),
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||||
|
filled: true,
|
||||||
|
fillColor: colors.surfaceContainerHigh,
|
||||||
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none),
|
||||||
|
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none),
|
||||||
|
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: colors.primary, width: 1)),
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => _add(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
IconButton(
|
||||||
|
onPressed: _add,
|
||||||
|
icon: Icon(Icons.add_circle, color: colors.primary, size: 28),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// 标签列表
|
||||||
|
if (_items.isEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Text('暂无别名', style: TextStyle(fontSize: 13, color: colors.onSurface.withValues(alpha: 0.3))),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
|
children: List.generate(_items.length, (i) {
|
||||||
|
return Chip(
|
||||||
|
label: Text(_items[i], style: TextStyle(fontSize: 13, color: colors.onSurface)),
|
||||||
|
backgroundColor: colors.surfaceContainerHighest,
|
||||||
|
side: BorderSide.none,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||||
|
deleteIcon: Icon(Icons.close, size: 16, color: colors.onSurface.withValues(alpha: 0.4)),
|
||||||
|
onDeleted: () => _remove(i),
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(context, _items),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: colors.primary, foregroundColor: colors.onPrimary, elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
),
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -352,7 +352,7 @@ class _CustomDrawerState extends State<CustomDrawer> {
|
|||||||
if (userPrefs.showSidebarGallery) toolItems.add((SvgPicture.string('<svg viewBox="0 0 1064 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M71.68 348.16A296.96 296.96 0 0 1 368.64 51.2h327.68a296.96 296.96 0 0 1 296.96 296.96v327.68A296.96 296.96 0 0 1 696.32 972.8H368.64a296.96 296.96 0 0 1-296.96-296.96v-327.68zM368.64 153.6A194.56 194.56 0 0 0 174.08 348.16v327.68A194.56 194.56 0 0 0 368.64 870.4h327.68a194.56 194.56 0 0 0 194.56-194.56v-327.68A194.56 194.56 0 0 0 696.32 153.6H368.64z" fill="currentColor"/><path d="M947.69152 606.08512a317.80864 317.80864 0 0 0-264.02816 209.7152l-96.54272-34.16064a420.20864 420.20864 0 0 1 349.34784-277.2992l11.22304 101.74464z" fill="currentColor"/><path d="M798.72 327.68a81.92 81.92 0 1 1-163.84 0 81.92 81.92 0 0 1 163.84 0z" fill="currentColor"/><path d="M163.84 542.72c-12.4928 0-24.86272 0.49152-37.0688 1.39264l-7.7824-102.11328c14.82752-1.10592 29.77792-1.67936 44.8512-1.67936 284.01664 0 520.6016 202.79296 572.90752 471.49056l-100.51584 19.57888C593.1008 709.87776 397.9264 542.72 163.84 542.72z" fill="currentColor"/></svg>', color: colors.onSurface), '图库', const GalleryPage()));
|
if (userPrefs.showSidebarGallery) toolItems.add((SvgPicture.string('<svg viewBox="0 0 1064 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M71.68 348.16A296.96 296.96 0 0 1 368.64 51.2h327.68a296.96 296.96 0 0 1 296.96 296.96v327.68A296.96 296.96 0 0 1 696.32 972.8H368.64a296.96 296.96 0 0 1-296.96-296.96v-327.68zM368.64 153.6A194.56 194.56 0 0 0 174.08 348.16v327.68A194.56 194.56 0 0 0 368.64 870.4h327.68a194.56 194.56 0 0 0 194.56-194.56v-327.68A194.56 194.56 0 0 0 696.32 153.6H368.64z" fill="currentColor"/><path d="M947.69152 606.08512a317.80864 317.80864 0 0 0-264.02816 209.7152l-96.54272-34.16064a420.20864 420.20864 0 0 1 349.34784-277.2992l11.22304 101.74464z" fill="currentColor"/><path d="M798.72 327.68a81.92 81.92 0 1 1-163.84 0 81.92 81.92 0 0 1 163.84 0z" fill="currentColor"/><path d="M163.84 542.72c-12.4928 0-24.86272 0.49152-37.0688 1.39264l-7.7824-102.11328c14.82752-1.10592 29.77792-1.67936 44.8512-1.67936 284.01664 0 520.6016 202.79296 572.90752 471.49056l-100.51584 19.57888C593.1008 709.87776 397.9264 542.72 163.84 542.72z" fill="currentColor"/></svg>', color: colors.onSurface), '图库', const GalleryPage()));
|
||||||
if (userPrefs.showSidebarTags) toolItems.add((SvgPicture.string('<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M687.012733 1024c-29.085211 0-55.161606-10.029383-74.217434-30.088149L104.305583 487.428012c-21.061704-21.061704-33.096964-50.146915-32.094026-79.232126l7.020568-242.711067a96.282076 96.282076 0 0 1 97.285015-94.2762h233.684623c28.082272 0 55.161606 12.03526 76.22331 32.094025l508.489716 508.489716c22.064643 22.064643 33.096964 54.158668 29.085211 87.255632s-18.052889 61.179236-42.123408 84.246817L784.297747 981.876592c-23.067581 23.067581-53.15573 38.111655-84.246817 42.123408zM176.51714 150.440744c-10.029383 0-17.049951 7.020568-17.049951 17.049951l-7.020568 242.711068c0 7.020568 3.008815 15.044074 9.026445 20.058766l507.486777 507.486778c11.032321 11.032321 37.108717 8.023506 58.170421-13.038198l197.578845-197.578845c21.061704-21.061704 23.067581-48.141038 13.038198-58.170421L429.257591 160.470127c-5.014691-5.014691-13.038198-9.026445-19.055828-9.026444H176.51714z m-57.167483 16.047013z" fill="currentColor"/><path d="M316.928501 442.295788a130.381978 130.381978 0 1 1 130.381979-130.381978 130.381978 130.381978 0 0 1-130.381979 130.381978z m0-180.528893a50.146915 50.146915 0 1 0 50.146915 50.146915 50.146915 50.146915 0 0 0-50.146915-50.146915z" fill="currentColor"/><path d="M258.75808 362.060725c-15.044074 0-32.094025-3.008815-49.143976-8.023507-42.123408-13.038198-86.252693-40.117532-124.364349-78.229187S21.061704 194.570029 8.023506 152.446621c-7.020568-23.067581-9.026445-44.129285-7.020568-64.188051s12.03526-44.129285 27.079334-59.173359S71.208619 1.002938 100.29383 1.002938a40.120541 40.120541 0 1 1 1.002938 80.235064c-5.014691 0-13.038198 1.002938-17.049951 5.014691s-7.020568 23.067581-1.002938 43.126347 30.088149 62.182174 58.170421 90.264447 61.179236 49.143976 90.264446 58.170421 37.108717 6.01763 43.126347-1.002938a40.423428 40.423428 0 0 1 57.167483 57.167482c-15.044074 15.044074-36.105779 25.073457-59.17336 28.082273z" fill="currentColor"/></svg>', color: colors.onSurface), '标签', const TagManagementPage()));
|
if (userPrefs.showSidebarTags) toolItems.add((SvgPicture.string('<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M687.012733 1024c-29.085211 0-55.161606-10.029383-74.217434-30.088149L104.305583 487.428012c-21.061704-21.061704-33.096964-50.146915-32.094026-79.232126l7.020568-242.711067a96.282076 96.282076 0 0 1 97.285015-94.2762h233.684623c28.082272 0 55.161606 12.03526 76.22331 32.094025l508.489716 508.489716c22.064643 22.064643 33.096964 54.158668 29.085211 87.255632s-18.052889 61.179236-42.123408 84.246817L784.297747 981.876592c-23.067581 23.067581-53.15573 38.111655-84.246817 42.123408zM176.51714 150.440744c-10.029383 0-17.049951 7.020568-17.049951 17.049951l-7.020568 242.711068c0 7.020568 3.008815 15.044074 9.026445 20.058766l507.486777 507.486778c11.032321 11.032321 37.108717 8.023506 58.170421-13.038198l197.578845-197.578845c21.061704-21.061704 23.067581-48.141038 13.038198-58.170421L429.257591 160.470127c-5.014691-5.014691-13.038198-9.026445-19.055828-9.026444H176.51714z m-57.167483 16.047013z" fill="currentColor"/><path d="M316.928501 442.295788a130.381978 130.381978 0 1 1 130.381979-130.381978 130.381978 130.381978 0 0 1-130.381979 130.381978z m0-180.528893a50.146915 50.146915 0 1 0 50.146915 50.146915 50.146915 50.146915 0 0 0-50.146915-50.146915z" fill="currentColor"/><path d="M258.75808 362.060725c-15.044074 0-32.094025-3.008815-49.143976-8.023507-42.123408-13.038198-86.252693-40.117532-124.364349-78.229187S21.061704 194.570029 8.023506 152.446621c-7.020568-23.067581-9.026445-44.129285-7.020568-64.188051s12.03526-44.129285 27.079334-59.173359S71.208619 1.002938 100.29383 1.002938a40.120541 40.120541 0 1 1 1.002938 80.235064c-5.014691 0-13.038198 1.002938-17.049951 5.014691s-7.020568 23.067581-1.002938 43.126347 30.088149 62.182174 58.170421 90.264447 61.179236 49.143976 90.264446 58.170421 37.108717 6.01763 43.126347-1.002938a40.423428 40.423428 0 0 1 57.167483 57.167482c-15.044074 15.044074-36.105779 25.073457-59.17336 28.082273z" fill="currentColor"/></svg>', color: colors.onSurface), '标签', const TagManagementPage()));
|
||||||
if (userPrefs.showSidebarMdReader) toolItems.add((Icon(Icons.description_outlined, size: 20, color: colors.onSurface), 'MD阅读', const MdReaderTabPage()));
|
if (userPrefs.showSidebarMdReader) toolItems.add((Icon(Icons.description_outlined, size: 20, color: colors.onSurface), 'MD阅读', const MdReaderTabPage()));
|
||||||
if (userPrefs.showSidebarEpub) toolItems.add((SvgPicture.string('<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M900.829867 143.581867a34.133333 34.133333 0 0 1 43.690666 52.394666l-4.437333 3.6864a173.960533 173.960533 0 0 0-24.951467 27.6992C897.706667 251.460267 887.466667 278.254933 887.466667 307.2c0 28.945067 10.24 55.739733 27.648 79.854933 6.263467 8.635733 12.970667 16.247467 19.626666 22.715734l2.9696 2.833066c1.792 1.655467 3.191467 2.8672 4.096 3.584l0.5632 0.4608a34.133333 34.133333 0 0 1-41.540266 54.1696c-10.973867-8.3968-26.0608-23.074133-41.028267-43.776C834.56 392.123733 819.2 351.914667 819.2 307.2c0-44.714667 15.36-84.923733 40.618667-119.842133 14.9504-20.6848 30.037333-35.362133 41.0112-43.776zM75.3152 559.5136a34.133333 34.133333 0 0 1 47.854933-6.331733c10.9568 8.413867 26.0608 23.0912 41.028267 43.776C189.44 631.876267 204.8 672.085333 204.8 716.8c0 44.714667-15.36 84.923733-40.618667 119.842133-14.9504 20.701867-30.037333 35.3792-41.0112 43.776a34.133333 34.133333 0 0 1-43.690666-52.3776l4.437333-3.703466c1.365333-1.194667 3.191467-2.850133 5.358933-4.949334 6.656-6.485333 13.346133-14.097067 19.592534-22.7328C126.293333 772.539733 136.533333 745.745067 136.533333 716.8c0-28.945067-10.24-55.739733-27.648-79.837867a173.960533 173.960533 0 0 0-19.626666-22.715733l-4.232534-3.9936a77.858133 77.858133 0 0 0-2.816-2.440533l-0.580266-0.443734a34.133333 34.133333 0 0 1-6.314667-47.854933z" fill="currentColor"/><path d="M921.6 136.533333a34.133333 34.133333 0 0 1 2.56 68.181334L921.6 204.8H238.933333a102.4 102.4 0 0 0-3.84 204.731733L238.933333 409.6h682.666667a34.133333 34.133333 0 0 1 2.56 68.181333L921.6 477.866667H238.933333C144.674133 477.866667 68.266667 401.4592 68.266667 307.2c0-92.672 73.847467-168.072533 165.888-170.5984L238.933333 136.533333h682.666667zM785.066667 546.133333c94.2592 0 170.666667 76.407467 170.666666 170.666667 0 92.672-73.847467 168.072533-165.888 170.5984L785.066667 887.466667H102.4a34.133333 34.133333 0 0 1-2.56-68.164267L102.4 819.2h682.666667a102.4 102.4 0 0 0 3.84-204.731733L785.066667 614.4H102.4a34.133333 34.133333 0 0 1-2.56-68.164267L102.4 546.133333h682.666667z" fill="currentColor"/><path d="M375.466667 256v221.866667a34.133333 34.133333 0 1 1-68.266667 0V256h68.266667z" fill="#00B386"/></svg>', color: colors.onSurface), 'EPUB阅读', const EpubLibraryPage()));
|
if (userPrefs.showSidebarEpub) toolItems.add((SvgPicture.string('<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M900.829867 143.581867a34.133333 34.133333 0 0 1 43.690666 52.394666l-4.437333 3.6864a173.960533 173.960533 0 0 0-24.951467 27.6992C897.706667 251.460267 887.466667 278.254933 887.466667 307.2c0 28.945067 10.24 55.739733 27.648 79.854933 6.263467 8.635733 12.970667 16.247467 19.626666 22.715734l2.9696 2.833066c1.792 1.655467 3.191467 2.8672 4.096 3.584l0.5632 0.4608a34.133333 34.133333 0 0 1-41.540266 54.1696c-10.973867-8.3968-26.0608-23.074133-41.028267-43.776C834.56 392.123733 819.2 351.914667 819.2 307.2c0-44.714667 15.36-84.923733 40.618667-119.842133 14.9504-20.6848 30.037333-35.362133 41.0112-43.776zM75.3152 559.5136a34.133333 34.133333 0 0 1 47.854933-6.331733c10.9568 8.413867 26.0608 23.0912 41.028267 43.776C189.44 631.876267 204.8 672.085333 204.8 716.8c0 44.714667-15.36 84.923733-40.618667 119.842133-14.9504 20.701867-30.037333 35.3792-41.0112 43.776a34.133333 34.133333 0 0 1-43.690666-52.3776l4.437333-3.703466c1.365333-1.194667 3.191467-2.850133 5.358933-4.949334 6.656-6.485333 13.346133-14.097067 19.592534-22.7328C126.293333 772.539733 136.533333 745.745067 136.533333 716.8c0-28.945067-10.24-55.739733-27.648-79.837867a173.960533 173.960533 0 0 0-19.626666-22.715733l-4.232534-3.9936a77.858133 77.858133 0 0 0-2.816-2.440533l-0.580266-0.443734a34.133333 34.133333 0 0 1-6.314667-47.854933z" fill="currentColor"/><path d="M921.6 136.533333a34.133333 34.133333 0 0 1 2.56 68.181334L921.6 204.8H238.933333a102.4 102.4 0 0 0-3.84 204.731733L238.933333 409.6h682.666667a34.133333 34.133333 0 0 1 2.56 68.181333L921.6 477.866667H238.933333C144.674133 477.866667 68.266667 401.4592 68.266667 307.2c0-92.672 73.847467-168.072533 165.888-170.5984L238.933333 136.533333h682.666667zM785.066667 546.133333c94.2592 0 170.666667 76.407467 170.666666 170.666667 0 92.672-73.847467 168.072533-165.888 170.5984L785.066667 887.466667H102.4a34.133333 34.133333 0 0 1-2.56-68.164267L102.4 819.2h682.666667a102.4 102.4 0 0 0 3.84-204.731733L785.066667 614.4H102.4a34.133333 34.133333 0 0 1-2.56-68.164267L102.4 546.133333h682.666667z" fill="currentColor"/><path d="M375.466667 256v221.866667a34.133333 34.133333 0 1 1-68.266667 0V256h68.266667z" fill="#00B386"/></svg>', color: colors.onSurface), '阅读', const EpubLibraryPage()));
|
||||||
|
|
||||||
if (exploreItems.isEmpty && toolItems.isEmpty) return const SizedBox.shrink();
|
if (exploreItems.isEmpty && toolItems.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user