mod: 首页推荐视频

This commit is contained in:
guozhigq
2023-04-19 08:12:08 +08:00
parent fbfdc2138b
commit 3a344843f9
22 changed files with 499 additions and 15 deletions

View File

@ -4,4 +4,5 @@ class StyleString {
static const double cardSpace = 8;
static BorderRadius mdRadius = BorderRadius.circular(6);
static const Radius imgRadius = Radius.circular(6);
static const double aspectRatio = 16 / 9;
}

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
class NetworkImgLayer extends StatelessWidget {
final String? src;
final double? width;
final double? height;
final double? cacheW;
final double? cacheH;
final String? type;
final Duration? fadeOutDuration;
final Duration? fadeInDuration;
var onTap;
NetworkImgLayer(
{Key? key,
this.src,
required this.width,
required this.height,
this.cacheW,
this.cacheH,
this.type,
this.fadeOutDuration,
this.fadeInDuration,
this.onTap})
: super(key: key);
@override
Widget build(BuildContext context) {
return src != ''
? ClipRRect(
borderRadius: BorderRadius.circular(type == 'avatar' ? 50 : 4),
child: CachedNetworkImage(
imageUrl: src!,
width: width ?? double.infinity,
height: height ?? double.infinity,
maxWidthDiskCache: (cacheW ?? width!).toInt(),
maxHeightDiskCache: (cacheH ?? height!).toInt(),
memCacheWidth: (cacheW ?? width!).toInt(),
memCacheHeight: (cacheH ?? height!).toInt(),
fit: BoxFit.cover,
fadeOutDuration:
fadeOutDuration ?? const Duration(milliseconds: 200),
fadeInDuration:
fadeInDuration ?? const Duration(milliseconds: 200),
filterQuality: FilterQuality.high,
errorWidget: (context, url, error) => placeholder(context),
placeholder: (context, url) => placeholder(context),
),
)
: placeholder(context);
}
Widget placeholder(context) {
return SizedBox(
width: width ?? double.infinity,
height: height ?? double.infinity,
child: Center(
child: Image.asset(
'assets/images/loading.png',
width: 300,
height: 300,
)),
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:pilipala/utils/utils.dart';
class StatDanMu extends StatelessWidget {
final String? theme;
final int? danmu;
final String? size;
const StatDanMu({Key? key, this.theme, this.danmu, this.size})
: super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
Image.asset(
'assets/images/dm_$theme.png',
width: size == 'medium' ? 16 : 14,
height: size == 'medium' ? 16 : 14,
),
const SizedBox(width: 2),
Text(
Utils.numFormat(danmu!),
style: TextStyle(
fontSize: size == 'medium' ? 12 : 11,
color: theme == 'white'
? Colors.white
: Theme.of(context).colorScheme.outline,
),
)
],
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:pilipala/utils/utils.dart';
class StatView extends StatelessWidget {
final String? theme;
final int? view;
final String? size;
const StatView({Key? key, this.theme, this.view, this.size}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
Image.asset(
'assets/images/view_$theme.png',
width: size == 'medium' ? 16 : 14,
height: size == 'medium' ? 16 : 14,
),
const SizedBox(width: 2),
Text(
Utils.numFormat(view!),
// videoItem['stat']['view'].toString(),
style: TextStyle(
fontSize: size == 'medium' ? 12 : 11,
color: theme == 'white'
? Colors.white
: Theme.of(context).colorScheme.outline,
),
),
],
);
}
}

View File

@ -0,0 +1,214 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/pages/home/controller.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget {
var videoItem;
VideoCardV({Key? key, required this.videoItem}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
elevation: 0.8,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: StyleString.mdRadius,
),
margin: EdgeInsets.zero,
child: InkWell(
onTap: () async {
await Future.delayed(const Duration(milliseconds: 200));
Get.toNamed('/video?aid=${videoItem.id}',
arguments: {'videoItem': videoItem});
},
onLongPress: () {
print('长按');
},
child: Column(
children: [
ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: StyleString.imgRadius,
topRight: StyleString.imgRadius,
),
child: AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
double PR = MediaQuery.of(context).devicePixelRatio;
return Stack(
children: [
NetworkImgLayer(
// 指定图片尺寸
// src: videoItem['pic'] + '@${(maxWidth * 2).toInt() }w',
src: videoItem.pic + '@.webp',
width: maxWidth,
height: maxHeight,
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: AnimatedOpacity(
opacity: 1,
duration: const Duration(milliseconds: 200),
child: VideoStat(
view: videoItem.stat.view,
danmaku: videoItem.stat.danmaku,
duration: videoItem.duration,
),
),
)
],
);
}),
),
),
VideoContent(videoItem: videoItem)
],
),
),
);
}
}
class VideoContent extends StatelessWidget {
final videoItem;
const VideoContent({Key? key, required this.videoItem}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
// 多列
padding: const EdgeInsets.fromLTRB(8, 8, 6, 7),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
videoItem.title,
textAlign: TextAlign.start,
style: const TextStyle(
// fontSize:
// Theme.of(context).textTheme.titleSmall!.fontSize,
fontSize: 13,
fontWeight: FontWeight.w500),
maxLines: Get.find<HomeController>().crossAxisCount,
overflow: TextOverflow.ellipsis,
),
SizedBox(
height: 18,
child: Row(
children: [
if (videoItem.rcmdReason.content != '') ...[
Container(
padding: const EdgeInsets.fromLTRB(3, 1, 3, 1),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.6),
borderRadius: BorderRadius.circular(3)),
child: Text(
videoItem.rcmdReason.content,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.primary,
),
),
),
const SizedBox(width: 4)
],
Expanded(
child: LayoutBuilder(builder:
(BuildContext context, BoxConstraints constraints) {
return SizedBox(
width: constraints.maxWidth,
child: Text(
videoItem.owner.name,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
);
}),
),
],
),
),
],
),
),
);
}
}
class VideoStat extends StatelessWidget {
final int? view;
final int? danmaku;
final int? duration;
const VideoStat(
{Key? key,
required this.view,
required this.danmaku,
required this.duration})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 45,
padding: const EdgeInsets.only(top: 22, left: 8, right: 8),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Colors.transparent,
Colors.black54,
],
tileMode: TileMode.mirror,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
StatView(
theme: 'white',
view: view,
),
const SizedBox(width: 8),
StatDanMu(
theme: 'white',
danmu: danmaku,
),
],
),
Text(
Utils.timeFormat(duration!),
style: const TextStyle(fontSize: 11, color: Colors.white),
)
],
),
);
}
}