diff --git a/lib/src/presentation/pages/anime_details/videos_page.dart b/lib/src/presentation/pages/anime_details/videos_page.dart index 64e39b2..ddef179 100644 --- a/lib/src/presentation/pages/anime_details/videos_page.dart +++ b/lib/src/presentation/pages/anime_details/videos_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:collection/collection.dart'; import '../../../services/http/http_service_provider.dart'; import '../../../utils/extensions/riverpod_extensions.dart'; @@ -64,56 +65,75 @@ class AnimeVideosPage extends ConsumerWidget { ...videos.when( data: (data) { return [ - SliverList.builder( - itemCount: data.length, - itemBuilder: (context, index) { - final item = data[index]; - - return ListTile( - visualDensity: VisualDensity.compact, - //onTap: () {}, - onTap: () => launchUrlString( - item.url, - mode: LaunchMode.externalApplication, - ), - leading: SizedBox( - height: 48, - child: AspectRatio( - aspectRatio: 16 / 9, - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(8), - ), - child: CachedImage( - item.imageUrl, - fit: BoxFit.cover, - ), - ), - ), - ), - title: Text( - item.name ?? '', - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: context.colorScheme.onBackground, - // fontSize: 14, - height: 1.2, - ), - ), - subtitle: Text( - item.kind.rusName, - style: TextStyle( - //fontSize: 12, - color: context.colorScheme.onBackground - .withOpacity(0.8), - ), - ), - ); - }, + SliverPadding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), + sliver: SliverGrid.builder( + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 380.0, // 440 ?? 340 + childAspectRatio: 16 / 9, + crossAxisSpacing: 8.0, + mainAxisSpacing: 8.0, + ), + itemBuilder: (context, index) => VideoCard(data[index]), + itemCount: data.length, + ), ), + // SliverList.builder( + // itemCount: data.length, + // itemBuilder: (context, index) { + // final item = data[index]; + + // return Padding( + // padding: const EdgeInsets.symmetric(horizontal: 16.0), + // child: VideoCard(item), + // ); + + // return ListTile( + // visualDensity: VisualDensity.compact, + // //onTap: () {}, + // onTap: () => launchUrlString( + // item.url, + // mode: LaunchMode.externalApplication, + // ), + // leading: SizedBox( + // height: 48, + // child: AspectRatio( + // aspectRatio: 16 / 9, + // child: Container( + // clipBehavior: Clip.antiAlias, + // decoration: BoxDecoration( + // color: Colors.black, + // borderRadius: BorderRadius.circular(8), + // ), + // child: CachedImage( + // item.imageUrl, + // fit: BoxFit.cover, + // ), + // ), + // ), + // ), + // title: Text( + // item.name ?? '', + // maxLines: 2, + // overflow: TextOverflow.ellipsis, + // style: TextStyle( + // color: context.colorScheme.onBackground, + // // fontSize: 14, + // height: 1.2, + // ), + // ), + // subtitle: Text( + // item.kind.rusName, + // style: TextStyle( + // //fontSize: 12, + // color: context.colorScheme.onBackground + // .withOpacity(0.8), + // ), + // ), + // ); + // }, + // ), ]; }, loading: () => [ @@ -130,6 +150,11 @@ class AnimeVideosPage extends ConsumerWidget { ), ], ), + SliverPadding( + padding: EdgeInsets.only( + bottom: context.padding.bottom + 16.0, + ), + ), ], ), ), @@ -137,6 +162,120 @@ class AnimeVideosPage extends ConsumerWidget { } } +class VideoCard extends StatelessWidget { + const VideoCard(this.item, {super.key}); + + final AnimeVideo item; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, boxConstraints) { + return DecoratedBox( + position: DecorationPosition.foreground, + decoration: BoxDecoration( + border: Border.all( + color: context.colorScheme.outline, + ), + borderRadius: BorderRadius.circular(12.0), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12.0), + child: Stack( + children: [ + Positioned.fill( + // top: -1, + // left: -1, + // right: -1, + // bottom: -1, + child: CachedImage(item.imageUrl), + ), + Positioned.fill( + top: -1, + bottom: -1, + left: -1, + right: -1, + child: IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + context.theme.scaffoldBackgroundColor + .withOpacity(0), + context.theme.scaffoldBackgroundColor, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + ), + ), + Align( + alignment: Alignment.bottomLeft, + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // if (item.hosting != null) + // Text( + // item.hosting!, + // style: context.textTheme.bodySmall, + // ), + Text( + item.kind.rusName, + style: context.textTheme.bodySmall?.copyWith( + letterSpacing: 1, + ), + ), + const SizedBox( + height: 2.0, + ), + Text( + (item.name == null || item.name!.isEmpty) + ? '[Без нзвания]' + : item.name!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: context.textTheme.bodyLarge?.copyWith( + height: 1.2, + ), + ), + ], + ), + ), + ), + Align( + child: SizedBox( + height: boxConstraints.maxHeight, + child: AspectRatio( + aspectRatio: 16 / 9, + child: Material( + type: MaterialType.transparency, + child: InkWell( + // onTap: () { + // print('url: ${item.url}'); + // }, + onTap: () => launchUrlString( + item.url, + mode: LaunchMode.externalApplication, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } +} + final animeVideosProvider = FutureProvider.autoDispose.family, int>((ref, id) async { final dio = ref.read(httpServiceProvider); @@ -151,7 +290,11 @@ final animeVideosProvider = for (final e in response) AnimeVideo.fromJson(e), ]; - return list; + //list.sortBy((e) => e.kind.index); + + final sortedList = list.sortedBy((e) => e.kind.index); + + return sortedList; }, name: 'animeVideosProvider'); class AnimeVideo { @@ -173,27 +316,35 @@ class AnimeVideo { this.hosting, }); - factory AnimeVideo.fromJson(Map json) => AnimeVideo( - id: json["id"], - url: json["url"], - imageUrl: json["image_url"], - playerUrl: json["player_url"], - name: json["name"], - kind: AnimeVideoKind.fromValue(json["kind"]), - hosting: json["hosting"], - ); + factory AnimeVideo.fromJson(Map json) { + final url = (json["url"] as String).replaceFirst('http://', 'https://'); + final imageUrl = + (json["image_url"] as String).replaceFirst('http://', 'https://'); + final playerUrl = + (json["player_url"] as String).replaceFirst('http://', 'https://'); + + return AnimeVideo( + id: json["id"], + url: url, + imageUrl: imageUrl, + playerUrl: playerUrl, + name: json["name"], + kind: AnimeVideoKind.fromValue(json["kind"]), + hosting: json["hosting"], + ); + } } enum AnimeVideoKind { - pv('pv'), - characterTrailer('character_trailer'), - cm('cm'), op('op'), ed('ed'), + episodePreview('episode_preview'), + pv('pv'), opEdClip('op_ed_clip'), clip('clip'), - other('other'), - episodePreview('episode_preview'); + characterTrailer('character_trailer'), + cm('cm'), + other('other'); final String value;