diff --git a/lib/pages/book/book_detail_page.dart b/lib/pages/book/book_detail_page.dart index 90288b8..8999a2c 100644 --- a/lib/pages/book/book_detail_page.dart +++ b/lib/pages/book/book_detail_page.dart @@ -947,7 +947,7 @@ class _BookDetailPageState extends State { isOverlay: true, ), // 关联人物 - WorkPeopleSection(workId: book.id, workType: 'book'), + WorkPeopleSection(workId: book.id, workType: 'book', isOverlay: true), // 简介:内部已有毛玻璃卡片 if (book.summary != null && book.summary!.isNotEmpty) ...[ const SizedBox(height: 12), diff --git a/lib/pages/game/game_detail_page.dart b/lib/pages/game/game_detail_page.dart index f2746be..50f1d0c 100644 --- a/lib/pages/game/game_detail_page.dart +++ b/lib/pages/game/game_detail_page.dart @@ -1185,7 +1185,7 @@ class _GameDetailPageState extends State { isOverlay: true, ), // 关联人物 - WorkPeopleSection(workId: game.id, workType: 'game'), + WorkPeopleSection(workId: game.id, workType: 'game', isOverlay: true), if (game.summary != null && game.summary!.isNotEmpty) ...[ const SizedBox(height: 12), _buildOverlaySummary(game), diff --git a/lib/pages/movies/movie_detail_page.dart b/lib/pages/movies/movie_detail_page.dart index b86bb29..235e3f3 100644 --- a/lib/pages/movies/movie_detail_page.dart +++ b/lib/pages/movies/movie_detail_page.dart @@ -1107,7 +1107,7 @@ class _MovieDetailPageState extends State { isOverlay: true, ), // 关联人物 - WorkPeopleSection(workId: movie.id, workType: 'movie'), + WorkPeopleSection(workId: movie.id, workType: 'movie', isOverlay: true), const SizedBox(height: 12), // 简介:内部已有毛玻璃卡片 if (movie.summary != null && movie.summary!.isNotEmpty) diff --git a/lib/pages/playlist/playlist_create_page.dart b/lib/pages/playlist/playlist_create_page.dart index e46afc6..87c43f9 100644 --- a/lib/pages/playlist/playlist_create_page.dart +++ b/lib/pages/playlist/playlist_create_page.dart @@ -15,15 +15,16 @@ class PlaylistCreatePage extends StatefulWidget { class _PlaylistCreatePageState extends State { late final TextEditingController _nameController; late final TextEditingController _descController; + final FocusNode _nameFocusNode = FocusNode(); late String _selectedType; bool _isSaving = false; bool get _isEdit => widget.playlist != null; static const _types = [ - ('movie', '影视', Icons.movie_outlined, Colors.blue), - ('book', '书籍', Icons.menu_book_outlined, Colors.teal), - ('game', '游戏', Icons.sports_esports_outlined, Colors.orange), + ('movie', '影视', Icons.movie_outlined), + ('book', '书籍', Icons.menu_book_outlined), + ('game', '游戏', Icons.sports_esports_outlined), ]; @override @@ -38,12 +39,21 @@ class _PlaylistCreatePageState extends State { void dispose() { _nameController.dispose(); _descController.dispose(); + _nameFocusNode.dispose(); super.dispose(); } Future _save() async { final name = _nameController.text.trim(); - if (name.isEmpty) return; + if (name.isEmpty) { + _nameFocusNode.requestFocus(); + if (mounted) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar(const SnackBar(content: Text('请输入片单名称'))); + } + return; + } if (_isSaving) return; setState(() => _isSaving = true); @@ -80,15 +90,21 @@ class _PlaylistCreatePageState extends State { return Scaffold( appBar: AppBar( title: Text(_isEdit ? '编辑片单' : '创建片单'), - actions: [ - TextButton( + ), + bottomNavigationBar: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 12), + child: FilledButton( onPressed: _isSaving ? null : _save, - child: Text(_isEdit ? '保存' : '创建', style: TextStyle( - color: _nameController.text.trim().isEmpty ? colors.onSurface.withValues(alpha: 0.3) : colors.primary, - fontWeight: FontWeight.w600, - )), + style: FilledButton.styleFrom( + minimumSize: const Size.fromHeight(48), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + child: _isSaving + ? const SizedBox(width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2.5)) + : Text(_isEdit ? '保存' : '创建', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), ), - ], + ), ), body: ListView( padding: const EdgeInsets.all(16), @@ -97,36 +113,45 @@ class _PlaylistCreatePageState extends State { if (!_isEdit) ...[ Text('片单类型', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.onSurface.withValues(alpha: 0.6))), const SizedBox(height: 10), - Row( - children: _types.map((t) { - final (type, label, icon, color) = t; - final selected = _selectedType == type; - return Expanded( - child: GestureDetector( - onTap: () => setState(() => _selectedType = type), - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 4), - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: selected ? color.withValues(alpha: 0.1) : colors.surfaceContainerHighest, - borderRadius: BorderRadius.circular(10), - border: selected ? Border.all(color: color.withValues(alpha: 0.3)) : null, - ), - child: Column( - children: [ - Icon(icon, size: 22, color: selected ? color : colors.onSurface.withValues(alpha: 0.4)), - const SizedBox(height: 6), - Text(label, style: TextStyle( - fontSize: 12, - fontWeight: selected ? FontWeight.w600 : FontWeight.normal, - color: selected ? color : colors.onSurface.withValues(alpha: 0.5), - )), - ], + Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: colors.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: _types.map((t) { + final (type, label, icon) = t; + final selected = _selectedType == type; + return Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => setState(() => _selectedType = type), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + curve: Curves.easeOut, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: selected ? colors.surface : Colors.transparent, + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 16, color: selected ? colors.primary : colors.onSurface.withValues(alpha: 0.4)), + const SizedBox(width: 6), + Text(label, style: TextStyle( + fontSize: 13, + fontWeight: selected ? FontWeight.w500 : FontWeight.normal, + color: selected ? colors.onSurface : colors.onSurface.withValues(alpha: 0.4), + )), + ], + ), ), ), - ), - ); - }).toList(), + ); + }).toList(), + ), ), const SizedBox(height: 24), ], @@ -135,6 +160,8 @@ class _PlaylistCreatePageState extends State { const SizedBox(height: 8), TextField( controller: _nameController, + focusNode: _nameFocusNode, + autofocus: !_isEdit, maxLength: 30, decoration: InputDecoration( hintText: '输入片单名称', diff --git a/lib/widgets/work_people_section.dart b/lib/widgets/work_people_section.dart index 0930693..78151c5 100644 --- a/lib/widgets/work_people_section.dart +++ b/lib/widgets/work_people_section.dart @@ -11,11 +11,13 @@ import 'person_info_sheet.dart'; class WorkPeopleSection extends StatefulWidget { final String workId; final String workType; // 'movie' / 'book' / 'game' + final bool isOverlay; // 叠层模式(深色毛玻璃背景)下用白色文字 const WorkPeopleSection({ super.key, required this.workId, required this.workType, + this.isOverlay = false, }); @override @@ -170,6 +172,8 @@ class _WorkPeopleSectionState extends State { if (_items.isEmpty) return const SizedBox.shrink(); final colors = Theme.of(context).colorScheme; + final primaryColor = widget.isOverlay ? Colors.white : colors.onSurface; + final secondaryColor = widget.isOverlay ? Colors.white.withValues(alpha: 0.5) : colors.onSurface.withValues(alpha: 0.4); return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20), @@ -182,7 +186,7 @@ class _WorkPeopleSectionState extends State { width: 4, height: 16, decoration: BoxDecoration( - color: colors.onSurface, + color: primaryColor, borderRadius: BorderRadius.circular(2), ), ), @@ -192,7 +196,7 @@ class _WorkPeopleSectionState extends State { style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: colors.onSurface, + color: primaryColor, ), ), ], @@ -220,7 +224,7 @@ class _WorkPeopleSectionState extends State { final idx = _items.indexOf(item); return Padding( padding: EdgeInsets.only(left: idx == 0 ? 0 : 16), - child: _buildPersonChip(item, colors), + child: _buildPersonChip(item, primaryColor, secondaryColor), ); }).toList(), ), @@ -231,7 +235,7 @@ class _WorkPeopleSectionState extends State { ); } - Widget _buildPersonChip(_PersonRole item, ColorScheme colors) { + Widget _buildPersonChip(_PersonRole item, Color primaryColor, Color secondaryColor) { final person = item.person; return GestureDetector( onTap: () => PersonInfoSheet.show(context, person), @@ -248,7 +252,7 @@ class _WorkPeopleSectionState extends State { const SizedBox(height: 8), Text( person.name, - style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: colors.onSurface), + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: primaryColor), maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, @@ -258,7 +262,7 @@ class _WorkPeopleSectionState extends State { _buildRoleText(item), style: TextStyle( fontSize: 10, - color: colors.onSurface.withValues(alpha: 0.4), + color: secondaryColor, height: 1.3, ), maxLines: 2,