适配平板

This commit is contained in:
DelLevin-Home
2026-06-30 21:51:38 +08:00
parent 18cda23f2a
commit 1192f8f7e0
19 changed files with 699 additions and 377 deletions

View File

@@ -15,8 +15,9 @@ import 'movie_share_page.dart';
/// 影视详情页 - 极简主义设计
class MovieDetailPage extends StatefulWidget {
final Movie movie;
final bool embedded;
const MovieDetailPage({super.key, required this.movie});
const MovieDetailPage({super.key, required this.movie, this.embedded = false});
@override
State<MovieDetailPage> createState() => _MovieDetailPageState();
@@ -119,8 +120,12 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
child: Row(children: [
const SizedBox(width: 4),
IconButton(
icon: Icon(Icons.arrow_back_ios_new, color: colors.onSurface, size: 18),
onPressed: () => Navigator.pop(context),
icon: widget.embedded
? Icon(Icons.close, color: colors.onSurface, size: 18)
: Icon(Icons.arrow_back_ios_new, color: colors.onSurface, size: 18),
onPressed: widget.embedded
? () => context.read<AppProvider>().selectMovie(null)
: () => Navigator.pop(context),
),
const SizedBox(width: 4),
Expanded(
@@ -186,8 +191,12 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
child: Row(children: [
const SizedBox(width: 4),
IconButton(
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white, size: 18),
onPressed: () => Navigator.pop(context),
icon: widget.embedded
? const Icon(Icons.close, color: Colors.white, size: 18)
: const Icon(Icons.arrow_back_ios_new, color: Colors.white, size: 18),
onPressed: widget.embedded
? () => context.read<AppProvider>().selectMovie(null)
: () => Navigator.pop(context),
),
ValueListenableBuilder<bool>(
valueListenable: _showTitle,
@@ -1180,9 +1189,14 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
final provider = context.read<AppProvider>();
await provider.removeMovie(widget.movie.id);
if (!mounted) return;
final navigator = Navigator.of(context);
navigator.pop();
navigator.pop();
if (widget.embedded) {
Navigator.of(context).pop(); // close dialog
provider.selectMovie(null);
} else {
final navigator = Navigator.of(context);
navigator.pop();
navigator.pop();
}
if (mounted) {
ToastUtil.show(context, '已删除');
}

View File

@@ -8,6 +8,10 @@ import '../../widgets/movie_list_item.dart';
import '../../widgets/animated_star_rating.dart';
import '../../widgets/shimmer_skeleton.dart';
import '../../widgets/fade_in_local_image.dart';
import '../../utils/responsive.dart';
import '../../widgets/master_detail_scaffold.dart';
import '../../widgets/detail_placeholder.dart';
import 'movie_detail_page.dart';
/// 观影标签页(分页 + 触底加载)
class MovieTabPage extends StatefulWidget {
@@ -119,16 +123,37 @@ class _MovieTabPageState extends State<MovieTabPage> {
await _loadFirst();
}
void _onMovieTap(Movie movie) {
if (Breakpoint.isWideContent(context)) {
context.read<AppProvider>().selectMovie(movie);
} else {
Navigator.pushNamed(context, '/movie-detail', arguments: movie);
}
}
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Column(
final isWideContent = Breakpoint.isWideContent(context);
final provider = context.watch<AppProvider>();
final masterContent = Column(
children: [
const MovieStatusBar(),
Divider(height: 0.5, thickness: 0.5, color: colors.outline),
Divider(height: 0.5, thickness: 0.5, color: colors.outlineVariant),
Expanded(child: _buildBody(context)),
],
);
if (!isWideContent) return masterContent;
final selectedMovie = provider.selectedMovie;
return MasterDetailScaffold(
master: masterContent,
detail: selectedMovie != null
? MovieDetailPage(movie: selectedMovie, embedded: true)
: const DetailPlaceholder(icon: Icons.movie_outlined, message: '选择一部影片查看详情'),
);
}
Widget _buildBody(BuildContext context) {
@@ -166,16 +191,28 @@ class _MovieTabPageState extends State<MovieTabPage> {
}
Widget _buildGridView() {
return GridView.builder(
controller: _scrollController,
padding: const EdgeInsets.fromLTRB(16, 16, 16, 100),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, childAspectRatio: 0.55, crossAxisSpacing: 12, mainAxisSpacing: 16,
),
itemCount: _items.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index >= _items.length) return _buildLoadMoreIndicator();
return MovieListItem(movie: _items[index]);
return LayoutBuilder(
builder: (context, constraints) {
final crossAxisCount = responsiveCrossAxisCount(constraints.maxWidth, minItemWidth: 110);
final isWideContent = Breakpoint.isWideContent(context);
final provider = context.read<AppProvider>();
return GridView.builder(
controller: _scrollController,
padding: const EdgeInsets.fromLTRB(16, 16, 16, 100),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount, childAspectRatio: 0.55, crossAxisSpacing: 12, mainAxisSpacing: 16,
),
itemCount: _items.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index >= _items.length) return _buildLoadMoreIndicator();
final item = _items[index];
return MovieListItem(
movie: item,
selected: isWideContent && provider.selectedMovie?.id == item.id,
onTap: () => _onMovieTap(item),
);
},
);
},
);
}
@@ -206,7 +243,7 @@ class _MovieTabPageState extends State<MovieTabPage> {
Widget _buildListCard(Movie movie) {
final colors = Theme.of(context).colorScheme;
return GestureDetector(
onTap: () => Navigator.pushNamed(context, '/movie-detail', arguments: movie),
onTap: () => _onMovieTap(movie),
onLongPress: () => _showDeleteDialog(context, movie),
child: Container(
margin: const EdgeInsets.only(bottom: 8),
@@ -267,7 +304,7 @@ class _MovieTabPageState extends State<MovieTabPage> {
Widget _buildCoverCard(Movie movie) {
final colors = Theme.of(context).colorScheme;
return GestureDetector(
onTap: () => Navigator.pushNamed(context, '/movie-detail', arguments: movie),
onTap: () => _onMovieTap(movie),
onLongPress: () => _showDeleteDialog(context, movie),
child: Container(
height: 200,