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 '../../widgets/genre_selector_page.dart';
|
||||
import '../../widgets/text_input_panel.dart';
|
||||
import '../../widgets/alternate_titles_dialog.dart';
|
||||
|
||||
/// 从多值字段列表中提取去重排序的唯一值(供 compute 使用)
|
||||
List<String> _collectUnique(List<List<String>> lists) {
|
||||
@@ -158,18 +159,10 @@ class _BookFormPageState extends State<BookFormPage> {
|
||||
children: [
|
||||
// 第一行:书名 + 别名
|
||||
_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);
|
||||
},
|
||||
onTap: () => _editTitle(),
|
||||
),
|
||||
_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);
|
||||
},
|
||||
onTap: () => _editAlternateTitles(),
|
||||
),
|
||||
|
||||
// 第二行:作者 + 译者
|
||||
@@ -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 {
|
||||
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;
|
||||
|
||||
@@ -5,11 +5,9 @@ import '../../data/epub/reader_dao.dart';
|
||||
import '../../services/epub/epub_service.dart';
|
||||
import '../../utils/user_prefs.dart';
|
||||
import '../../utils/toast_util.dart';
|
||||
import '../../utils/responsive.dart';
|
||||
import '../../widgets/fade_in_local_image.dart';
|
||||
import '../../widgets/shimmer_skeleton.dart';
|
||||
import 'epub_detail_page.dart';
|
||||
import 'widgets/book_grid_item.dart';
|
||||
|
||||
/// EPUB 书架页面
|
||||
class EpubLibraryPage extends StatefulWidget {
|
||||
@@ -28,7 +26,6 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
||||
bool _isSearching = false;
|
||||
final TextEditingController _searchCtrl = TextEditingController();
|
||||
int _sortMode = UserPrefs().epubSortMode;
|
||||
int _viewMode = UserPrefs().epubViewMode; // 0=列表 1=网格
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -236,7 +233,7 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
||||
),
|
||||
onChanged: (_) => _onSearchChanged(),
|
||||
)
|
||||
: Text('EPUB 阅读',
|
||||
: Text('阅读',
|
||||
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||
leading: IconButton(
|
||||
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)),
|
||||
border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero),
|
||||
onChanged: (_) => _onSearchChanged())
|
||||
: Text('EPUB 阅读',
|
||||
: Text('阅读',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6)))),
|
||||
..._buildActions(colors),
|
||||
]),
|
||||
),
|
||||
// 主体
|
||||
Expanded(child: _isLoading && _books.isEmpty
|
||||
? const BookSkeletonGrid()
|
||||
? _buildSkeletonList(colors)
|
||||
: _books.isEmpty
|
||||
? _buildEmpty(colors)
|
||||
: _filteredBooks.isEmpty
|
||||
@@ -283,14 +280,14 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 主体内容:书架(列表/网格)
|
||||
/// 主体内容:书架列表
|
||||
Widget _buildContent(ColorScheme colors) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
// 书架分隔标题
|
||||
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) {
|
||||
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)
|
||||
IconButton(
|
||||
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)),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text('EPUB 阅读',
|
||||
Text('阅读',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: colors.onSurface)),
|
||||
const SizedBox(height: 8),
|
||||
Text('点击右上角导入 .epub 文件',
|
||||
@@ -393,31 +381,36 @@ class _EpubLibraryPageState extends State<EpubLibraryPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSliverGrid(ColorScheme colors) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 100),
|
||||
sliver: SliverLayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final crossAxisCount =
|
||||
responsiveCrossAxisCount(constraints.crossAxisExtent, minItemWidth: 110);
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
childAspectRatio: 0.55,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 16,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => BookGridItem(
|
||||
book: _filteredBooks[index],
|
||||
viewMode: ViewMode.relaxed,
|
||||
onTap: () => _openBook(_filteredBooks[index]),
|
||||
onLongPress: () => _deleteBook(_filteredBooks[index]),
|
||||
/// 加载骨架列表
|
||||
Widget _buildSkeletonList(ColorScheme colors) {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 100),
|
||||
itemCount: 8,
|
||||
itemBuilder: (_, __) => Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const ShimmerSkeleton(width: 48, height: 64, borderRadius: 6),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import '../../data/gallery/gallery_dao.dart';
|
||||
@@ -180,17 +182,11 @@ class _GalleryPageState extends State<GalleryPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 图片
|
||||
AspectRatio(
|
||||
aspectRatio: _aspectRatioFor(item.category),
|
||||
child: FadeInLocalImage(
|
||||
path: item.path,
|
||||
fit: BoxFit.cover,
|
||||
errorWidget: Container(
|
||||
color: colors.surfaceContainerHighest,
|
||||
child: Icon(Icons.broken_image_outlined, color: colors.onSurface.withValues(alpha: 0.3)),
|
||||
),
|
||||
),
|
||||
// 图片(按真实比例显示)
|
||||
_GalleryImageCard(
|
||||
path: item.path,
|
||||
fallbackAspectRatio: _aspectRatioFor(item.category),
|
||||
colors: colors,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 类别标签
|
||||
@@ -228,7 +224,7 @@ class _GalleryPageState extends State<GalleryPage> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 不同类别用不同宽高比,让瀑布流有错落感
|
||||
/// 不同类别用作加载占位的默认宽高比;图片真实尺寸读出后会被覆盖
|
||||
double _aspectRatioFor(String category) {
|
||||
switch (category) {
|
||||
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,
|
||||
required: true,
|
||||
icon: Icons.sports_esports_outlined,
|
||||
onTap: () async {
|
||||
final result = await TextInputPanel.show(
|
||||
context: context,
|
||||
title: '游戏名称',
|
||||
initialValue: _titleController.text,
|
||||
hint: '请输入游戏名称',
|
||||
);
|
||||
if (!mounted) return;
|
||||
if (result != null) setState(() => _titleController.text = result);
|
||||
},
|
||||
onTap: _editTitle,
|
||||
),
|
||||
),
|
||||
// 平台
|
||||
@@ -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 {
|
||||
final controller = TextEditingController(text: _playCount > 0 ? '$_playCount' : '');
|
||||
final result = await showDialog<String>(
|
||||
|
||||
@@ -15,6 +15,7 @@ import '../../utils/image_path_helper.dart';
|
||||
import '../../widgets/genre_selector_page.dart';
|
||||
import '../../widgets/text_input_panel.dart';
|
||||
import '../../widgets/duration_picker.dart';
|
||||
import '../../widgets/alternate_titles_dialog.dart';
|
||||
|
||||
/// 从多值字段列表中提取去重排序的唯一值(供 compute 使用)
|
||||
List<String> _collectUnique(List<List<String>> lists) {
|
||||
@@ -436,16 +437,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
||||
value: _titleController.text,
|
||||
required: true,
|
||||
icon: Icons.movie_outlined,
|
||||
onTap: () async {
|
||||
final result = await TextInputPanel.show(
|
||||
context: context,
|
||||
title: '影视名称',
|
||||
initialValue: _titleController.text,
|
||||
hint: '请输入影视名称',
|
||||
);
|
||||
if (!mounted) return;
|
||||
if (result != null) setState(() => _titleController.text = result);
|
||||
},
|
||||
onTap: () => _editTitle(),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
@@ -458,17 +450,7 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
||||
: '${_alternateTitles.length}个:${_alternateTitles.join('、')}',
|
||||
icon: Icons.alternate_email_outlined,
|
||||
scrollable: true,
|
||||
onTap: () async {
|
||||
final result = await GenreSelectorPage.show(
|
||||
context: context,
|
||||
title: '添加别名',
|
||||
existingTags: [],
|
||||
initialSelected: _alternateTitles,
|
||||
hint: '输入别名',
|
||||
);
|
||||
if (!mounted) return;
|
||||
if (result != null) setState(() => _alternateTitles = result);
|
||||
},
|
||||
onTap: () => _editAlternateTitles(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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 {
|
||||
final controller = TextEditingController(text: _watchCount > 0 ? '$_watchCount' : '');
|
||||
|
||||
Reference in New Issue
Block a user