Merge branch 'feature-m3Design'

This commit is contained in:
guozhigq
2023-07-26 11:44:12 +08:00
84 changed files with 1288 additions and 855 deletions

View File

@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
class StyleString { class StyleString {
static const double cardSpace = 8; static const double cardSpace = 8;
static BorderRadius mdRadius = BorderRadius.circular(6); static const double safeSpace = 12;
static const Radius imgRadius = Radius.circular(6); static BorderRadius mdRadius = BorderRadius.circular(10);
static const Radius imgRadius = Radius.circular(10);
static const double aspectRatio = 16 / 10; static const double aspectRatio = 16 / 10;
} }

View File

@ -27,9 +27,6 @@ class VideoCardHSkeleton extends StatelessWidget {
aspectRatio: StyleString.aspectRatio, aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
double PR = MediaQuery.of(context).devicePixelRatio;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context) color: Theme.of(context)

View File

@ -53,7 +53,7 @@ class VideoCardVSkeleton extends StatelessWidget {
), ),
Container( Container(
width: 80, width: 80,
height: 13, height: 12,
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
), ),
], ],

View File

@ -13,7 +13,6 @@ class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
final bool visible; final bool visible;
@override @override
// TODO: implement preferredSize
Size get preferredSize => child.preferredSize; Size get preferredSize => child.preferredSize;
@override @override

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class HttpError extends StatelessWidget { class HttpError extends StatelessWidget {
HttpError({required this.errMsg, required this.fn, super.key}); const HttpError({required this.errMsg, required this.fn, super.key});
String errMsg = ''; final String? errMsg;
final Function()? fn; final Function()? fn;
@override @override
@ -16,7 +16,7 @@ class HttpError extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
errMsg, errMsg ?? '请求异常',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),

View File

@ -6,9 +6,10 @@ import 'package:pilipala/pages/rcmd/controller.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
class LiveCard extends StatelessWidget { class LiveCard extends StatelessWidget {
var liveItem; // ignore: prefer_typing_uninitialized_variables
final liveItem;
LiveCard({ const LiveCard({
Key? key, Key? key,
required this.liveItem, required this.liveItem,
}) : super(key: key); }) : super(key: key);
@ -37,14 +38,11 @@ class LiveCard extends StatelessWidget {
child: LayoutBuilder(builder: (context, boxConstraints) { child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth; double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
double PR = MediaQuery.of(context).devicePixelRatio;
return Stack( return Stack(
children: [ children: [
Hero( Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
// 指定图片尺寸
// src: videoItem.pic + '@${(maxWidth * 2).toInt()}w',
src: liveItem.cover + '@.webp', src: liveItem.cover + '@.webp',
type: 'emote', type: 'emote',
width: maxWidth, width: maxWidth,
@ -79,6 +77,7 @@ class LiveCard extends StatelessWidget {
} }
class LiveContent extends StatelessWidget { class LiveContent extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables
final liveItem; final liveItem;
const LiveContent({Key? key, required this.liveItem}) : super(key: key); const LiveContent({Key? key, required this.liveItem}) : super(key: key);
@override @override
@ -140,19 +139,19 @@ class LiveStat extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( // Row(
children: [ // children: [
// StatView( // StatView(
// theme: 'white', // theme: 'white',
// view: view, // view: view,
// ), // ),
// const SizedBox(width: 8), // const SizedBox(width: 8),
// StatDanMu( // StatDanMu(
// theme: 'white', // theme: 'white',
// danmu: danmaku, // danmu: danmaku,
// ), // ),
], // ],
), // ),
Text( Text(
online.toString(), online.toString(),
style: const TextStyle(fontSize: 11, color: Colors.white), style: const TextStyle(fontSize: 11, color: Colors.white),

View File

@ -3,8 +3,8 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
class OverlayPop extends StatelessWidget { class OverlayPop extends StatelessWidget {
var videoItem; final dynamic videoItem;
OverlayPop({super.key, this.videoItem}); const OverlayPop({super.key, this.videoItem});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,3 +1,5 @@
// ignore_for_file: depend_on_referenced_packages
import 'dart:math'; import 'dart:math';
import 'dart:ui' as ui show Image; import 'dart:ui' as ui show Image;
@ -15,7 +17,8 @@ class PullToRefreshHeader extends StatelessWidget {
this.info, this.info,
this.lastRefreshTime, { this.lastRefreshTime, {
this.color, this.color,
}); Key? key,
}) : super(key: key);
final PullToRefreshScrollNotificationInfo? info; final PullToRefreshScrollNotificationInfo? info;
final DateTime? lastRefreshTime; final DateTime? lastRefreshTime;
@ -23,21 +26,21 @@ class PullToRefreshHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final PullToRefreshScrollNotificationInfo? _info = info; final PullToRefreshScrollNotificationInfo? infos = info;
if (_info == null) { if (infos == null) {
return Container(); return Container();
} }
String text = ''; String text = '';
if (_info.mode == PullToRefreshIndicatorMode.armed) { if (infos.mode == PullToRefreshIndicatorMode.armed) {
text = 'Release to refresh'; text = 'Release to refresh';
} else if (_info.mode == PullToRefreshIndicatorMode.refresh || } else if (infos.mode == PullToRefreshIndicatorMode.refresh ||
_info.mode == PullToRefreshIndicatorMode.snap) { infos.mode == PullToRefreshIndicatorMode.snap) {
text = 'Loading...'; text = 'Loading...';
} else if (_info.mode == PullToRefreshIndicatorMode.done) { } else if (infos.mode == PullToRefreshIndicatorMode.done) {
text = 'Refresh completed.'; text = 'Refresh completed.';
} else if (_info.mode == PullToRefreshIndicatorMode.drag) { } else if (infos.mode == PullToRefreshIndicatorMode.drag) {
text = 'Pull to refresh'; text = 'Pull to refresh';
} else if (_info.mode == PullToRefreshIndicatorMode.canceled) { } else if (infos.mode == PullToRefreshIndicatorMode.canceled) {
text = 'Cancel refresh'; text = 'Cancel refresh';
} }
@ -67,16 +70,15 @@ class PullToRefreshHeader extends StatelessWidget {
Expanded( Expanded(
child: Container( child: Container(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: RefreshImage(top),
margin: const EdgeInsets.only(right: 12.0), margin: const EdgeInsets.only(right: 12.0),
child: RefreshImage(top, null),
), ),
), ),
Column( Column(
children: <Widget>[ children: <Widget>[
Text(text, style: ts), Text(text, style: ts),
Text( Text(
'Last updated:' + 'Last updated:${DateFormat('yyyy-MM-dd hh:mm').format(time)}',
DateFormat('yyyy-MM-dd hh:mm').format(time),
style: ts.copyWith(fontSize: 14), style: ts.copyWith(fontSize: 14),
) )
], ],
@ -92,7 +94,7 @@ class PullToRefreshHeader extends StatelessWidget {
} }
class RefreshImage extends StatelessWidget { class RefreshImage extends StatelessWidget {
const RefreshImage(this.top); const RefreshImage(this.top, Key? key) : super(key: key);
final double top; final double top;

View File

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
class SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
SliverHeaderDelegate({required this.height, required this.child});
final double height;
final Widget child;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return child;
}
@override
double get maxExtent => height;
@override
double get minExtent => height;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
true;
}

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -12,12 +11,16 @@ class StatDanMu extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color color = Map<String, Color> colorObject = {
theme == 'white' ? Colors.white : Theme.of(context).colorScheme.outline; 'white': Colors.white,
'gray': Theme.of(context).colorScheme.outline,
'black': Theme.of(context).colorScheme.onBackground.withOpacity(0.8),
};
Color color = colorObject[theme]!;
return Row( return Row(
children: [ children: [
Icon( Icon(
CupertinoIcons.ellipses_bubble, Icons.subtitles_outlined,
size: 14, size: 14,
color: color, color: color,
), ),

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -12,12 +11,16 @@ class StatView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color color = Map<String, Color> colorObject = {
theme == 'white' ? Colors.white : Theme.of(context).colorScheme.outline; 'white': Colors.white,
'gray': Theme.of(context).colorScheme.outline,
'black': Theme.of(context).colorScheme.onBackground.withOpacity(0.8),
};
Color color = colorObject[theme]!;
return Row( return Row(
children: [ children: [
Icon( Icon(
CupertinoIcons.play_rectangle, Icons.play_circle_outlined,
size: 13, size: 13,
color: color, color: color,
), ),

View File

@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/http/search.dart'; import 'package:pilipala/http/search.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -11,11 +12,11 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
// 视频卡片 - 水平布局 // 视频卡片 - 水平布局
class VideoCardH extends StatelessWidget { class VideoCardH extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables // ignore: prefer_typing_uninitialized_variables
var videoItem; final videoItem;
Function()? longPress; final Function()? longPress;
Function()? longPressEnd; final Function()? longPressEnd;
VideoCardH({ const VideoCardH({
Key? key, Key? key,
required this.videoItem, required this.videoItem,
this.longPress, this.longPress,
@ -53,11 +54,11 @@ class VideoCardH extends StatelessWidget {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
StyleString.cardSpace, 7, StyleString.cardSpace, 7), StyleString.safeSpace, 6, StyleString.safeSpace, 6),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double width = double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; (boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
return SizedBox( return SizedBox(
height: width / StyleString.aspectRatio, height: width / StyleString.aspectRatio,
child: Row( child: Row(
@ -70,21 +71,16 @@ class VideoCardH extends StatelessWidget {
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth; double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
double PR =
MediaQuery.of(context).devicePixelRatio;
return Stack( return Stack(
children: [ children: [
Hero( Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
// src: videoItem['pic'] +
// '@${(maxWidth * 2).toInt()}w',
src: videoItem.pic + '@.webp', src: videoItem.pic + '@.webp',
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
), ),
// Image.network( videoItem['pic'], width: double.infinity, height: double.infinity,),
pBadge(Utils.timeFormat(videoItem.duration!), pBadge(Utils.timeFormat(videoItem.duration!),
context, null, 6.0, 6.0, null, context, null, 6.0, 6.0, null,
type: 'gray'), type: 'gray'),
@ -104,12 +100,12 @@ class VideoCardH extends StatelessWidget {
}, },
), ),
), ),
Divider( // Divider(
height: 1, // height: 1,
indent: 8, // indent: 8,
endIndent: 12, // endIndent: 12,
color: Theme.of(context).dividerColor.withOpacity(0.08), // color: Theme.of(context).dividerColor.withOpacity(0.08),
) // )
], ],
), ),
), ),
@ -118,6 +114,7 @@ class VideoCardH extends StatelessWidget {
} }
class VideoContent extends StatelessWidget { class VideoContent extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables
final videoItem; final videoItem;
const VideoContent({super.key, required this.videoItem}); const VideoContent({super.key, required this.videoItem});
@ -133,9 +130,11 @@ class VideoContent extends StatelessWidget {
Text( Text(
videoItem.title, videoItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: const TextStyle(
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, fontSize: 13,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -150,6 +149,7 @@ class VideoContent extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
letterSpacing: 0.3,
color: i['type'] == 'em' color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface, : Theme.of(context).colorScheme.onSurface,
@ -183,12 +183,13 @@ class VideoContent extends StatelessWidget {
Text( Text(
videoItem.owner.name, videoItem.owner.name,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
], ],
), ),
const SizedBox(height: 3),
Row( Row(
children: [ children: [
StatView( StatView(
@ -196,12 +197,16 @@ class VideoContent extends StatelessWidget {
view: videoItem.stat.view, view: videoItem.stat.view,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( StatDanMu(
Utils.dateFormat(videoItem.pubdate!), theme: 'gray',
style: TextStyle( danmu: videoItem.stat.danmaku,
fontSize: 11, ),
color: Theme.of(context).colorScheme.outline), // Text(
) // Utils.dateFormat(videoItem.pubdate!),
// style: TextStyle(
// fontSize: 11,
// color: Theme.of(context).colorScheme.outline),
// )
], ],
), ),
], ],

View File

@ -6,16 +6,16 @@ import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/pages/rcmd/index.dart'; import 'package:pilipala/pages/rcmd/index.dart';
import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/pages/home/controller.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局 // 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget { class VideoCardV extends StatelessWidget {
var videoItem; // ignore: prefer_typing_uninitialized_variables
Function()? longPress; final videoItem;
Function()? longPressEnd; final Function()? longPress;
final Function()? longPressEnd;
VideoCardV({ const VideoCardV({
Key? key, Key? key,
required this.videoItem, required this.videoItem,
this.longPress, this.longPress,
@ -26,7 +26,7 @@ class VideoCardV extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(videoItem.id); String heroTag = Utils.makeHeroTag(videoItem.id);
return Card( return Card(
elevation: 0.8, elevation: 0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: StyleString.mdRadius, borderRadius: StyleString.mdRadius,
@ -56,41 +56,40 @@ class VideoCardV extends StatelessWidget {
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: StyleString.imgRadius, topLeft: StyleString.imgRadius,
topRight: StyleString.imgRadius, topRight: StyleString.imgRadius,
bottomLeft: StyleString.imgRadius,
bottomRight: StyleString.imgRadius,
), ),
child: AspectRatio( child: AspectRatio(
aspectRatio: StyleString.aspectRatio, aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) { child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth; double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
double PR = MediaQuery.of(context).devicePixelRatio;
return Stack( return Stack(
children: [ children: [
Hero( Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
// 指定图片尺寸
// src: videoItem.pic + '@${(maxWidth * 2).toInt()}w',
src: videoItem.pic + '@.webp', src: videoItem.pic + '@.webp',
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
), ),
if (videoItem.stat.view is int && // if (videoItem.stat.view is int &&
videoItem.stat.danmaku is int) // videoItem.stat.danmaku is int)
Positioned( // Positioned(
left: 0, // left: 0,
right: 0, // right: 0,
bottom: 0, // bottom: 0,
child: AnimatedOpacity( // child: AnimatedOpacity(
opacity: 1, // opacity: 1,
duration: const Duration(milliseconds: 200), // duration: const Duration(milliseconds: 200),
child: VideoStat( // child: VideoStat(
view: videoItem.stat.view, // view: videoItem.stat.view,
danmaku: videoItem.stat.danmaku, // danmaku: videoItem.stat.danmaku,
duration: videoItem.duration, // duration: videoItem.duration,
), // ),
), // ),
), // ),
], ],
); );
}), }),
@ -106,6 +105,7 @@ class VideoCardV extends StatelessWidget {
} }
class VideoContent extends StatelessWidget { class VideoContent extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables
final videoItem; final videoItem;
const VideoContent({Key? key, required this.videoItem}) : super(key: key); const VideoContent({Key? key, required this.videoItem}) : super(key: key);
@override @override
@ -113,7 +113,7 @@ class VideoContent extends StatelessWidget {
return Expanded( return Expanded(
child: Padding( child: Padding(
// 多列 // 多列
padding: const EdgeInsets.fromLTRB(8, 8, 6, 7), padding: const EdgeInsets.fromLTRB(4, 5, 6, 6),
// 单列 // 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8), // padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column( child: Column(
@ -124,78 +124,76 @@ class VideoContent extends StatelessWidget {
videoItem.title, videoItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
// fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
), ),
maxLines: Get.find<RcmdController>().crossAxisCount, maxLines: Get.find<RcmdController>().crossAxisCount,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
SizedBox(
height: 18, Row(
child: Row( children: [
children: [ if (videoItem.rcmdReason != null &&
if (videoItem.rcmdReason != null && videoItem.rcmdReason.content != '' ||
videoItem.rcmdReason.content != '') ...[ videoItem.isFollowed == 1) ...[
Container( Container(
padding: const EdgeInsets.fromLTRB(3, 1, 3, 1), padding: const EdgeInsets.fromLTRB(3, 0, 3, 0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.primaryContainer .primaryContainer
.withOpacity(0.6), .withOpacity(0.6),
borderRadius: BorderRadius.circular(3)), borderRadius: BorderRadius.circular(3)),
child: Text( child: Center(
videoItem.rcmdReason.content,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.primary,
),
),
),
const SizedBox(width: 4)
] else if (videoItem.isFollowed == 1) ...[
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(
'已关注',
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( child: Text(
videoItem.owner.name, videoItem.rcmdReason != null &&
maxLines: 1, videoItem.rcmdReason.content != ''
? videoItem.rcmdReason.content
: '已关注',
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context) fontSize: Theme.of(context)
.textTheme .textTheme
.labelMedium! .labelSmall!
.fontSize, .fontSize,
color: Theme.of(context).colorScheme.outline, 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,
),
),
);
}),
),
],
), ),
// Row(
// children: [
// StatView(
// theme: 'black',
// view: videoItem.stat.view,
// ),
// const SizedBox(width: 6),
// StatDanMu(
// theme: 'black',
// danmu: videoItem.stat.danmaku,
// ),
// ],
// ),
], ],
), ),
), ),
@ -219,7 +217,7 @@ class VideoStat extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
height: 45, height: 45,
padding: const EdgeInsets.only(top: 22, left: 8, right: 8), padding: const EdgeInsets.only(top: 22, left: 6, right: 6),
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
@ -240,7 +238,7 @@ class VideoStat extends StatelessWidget {
theme: 'white', theme: 'white',
view: view, view: view,
), ),
const SizedBox(width: 8), const SizedBox(width: 6),
StatDanMu( StatDanMu(
theme: 'white', theme: 'white',
danmu: danmaku, danmu: danmaku,

View File

@ -151,7 +151,7 @@ class Request {
cancelToken: cancelToken, cancelToken: cancelToken,
); );
return response; return response;
} on DioError catch (e) { } on DioException catch (e) {
print('get error: $e'); print('get error: $e');
return Future.error(await ApiInterceptor.dioError(e)); return Future.error(await ApiInterceptor.dioError(e));
} }
@ -173,7 +173,7 @@ class Request {
); );
print('post success: ${response.data}'); print('post success: ${response.data}');
return response; return response;
} on DioError catch (e) { } on DioException catch (e) {
print('post error: $e'); print('post error: $e');
return Future.error(await ApiInterceptor.dioError(e)); return Future.error(await ApiInterceptor.dioError(e));
} }
@ -193,7 +193,7 @@ class Request {
print('downloadFile success: ${response.data}'); print('downloadFile success: ${response.data}');
return response.data; return response.data;
} on DioError catch (e) { } on DioException catch (e) {
print('downloadFile error: $e'); print('downloadFile error: $e');
return Future.error(ApiInterceptor.dioError(e)); return Future.error(ApiInterceptor.dioError(e));
} }

View File

@ -1,12 +1,12 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart' hide Response; // import 'package:get/get.dart' hide Response;
class ApiInterceptor extends Interceptor { class ApiInterceptor extends Interceptor {
@override @override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) { void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print("请求之前"); // print("请求之前");
// 在请求之前添加头部或认证信息 // 在请求之前添加头部或认证信息
// options.headers['Authorization'] = 'Bearer token'; // options.headers['Authorization'] = 'Bearer token';
// options.headers['Content-Type'] = 'application/json'; // options.headers['Content-Type'] = 'application/json';
@ -19,30 +19,30 @@ class ApiInterceptor extends Interceptor {
} }
@override @override
void onError(DioError err, ErrorInterceptorHandler handler) async { void onError(DioException err, ErrorInterceptorHandler handler) async {
// 处理网络请求错误 // 处理网络请求错误
// handler.next(err); // handler.next(err);
SmartDialog.showToast(await dioError(err)); SmartDialog.showToast(await dioError(err));
super.onError(err, handler); super.onError(err, handler);
} }
static Future dioError(DioError error) async { static Future dioError(DioException error) async {
switch (error.type) { switch (error.type) {
case DioErrorType.badCertificate: case DioExceptionType.badCertificate:
return '证书有误!'; return '证书有误!';
case DioErrorType.badResponse: case DioExceptionType.badResponse:
return '服务器异常,请稍后重试!'; return '服务器异常,请稍后重试!';
case DioErrorType.cancel: case DioExceptionType.cancel:
return "请求已被取消,请重新请求"; return "请求已被取消,请重新请求";
case DioErrorType.connectionError: case DioExceptionType.connectionError:
return '连接错误,请检查网络设置'; return '连接错误,请检查网络设置';
case DioErrorType.connectionTimeout: case DioExceptionType.connectionTimeout:
return "网络连接超时,请检查网络设置"; return "网络连接超时,请检查网络设置";
case DioErrorType.receiveTimeout: case DioExceptionType.receiveTimeout:
return "响应超时,请稍后重试!"; return "响应超时,请稍后重试!";
case DioErrorType.sendTimeout: case DioExceptionType.sendTimeout:
return "发送请求超时,请检查网络设置"; return "发送请求超时,请检查网络设置";
case DioErrorType.unknown: case DioExceptionType.unknown:
var res = await checkConect(); var res = await checkConect();
return res + " \n 网络异常,请稍后重试!"; return res + " \n 网络异常,请稍后重试!";
default: default:

View File

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:pilipala/http/index.dart'; import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/bangumi/info.dart'; import 'package:pilipala/models/bangumi/info.dart';
import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/common/search_type.dart';
@ -57,7 +55,7 @@ class SearchHttp {
'page': page 'page': page
}); });
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) { if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
var data; Object data;
switch (searchType) { switch (searchType) {
case SearchType.video: case SearchType.video:
data = SearchVideoModel.fromJson(res.data['data']); data = SearchVideoModel.fromJson(res.data['data']);

View File

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:pilipala/http/api.dart'; import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart'; import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/model_hot_video_item.dart'; import 'package:pilipala/models/model_hot_video_item.dart';

View File

@ -125,7 +125,6 @@ class VideoHttp {
return {'status': false, 'data': []}; return {'status': false, 'data': []};
} }
} catch (err) { } catch (err) {
print('🐯:$err');
return {'status': false, 'data': [], 'msg': err}; return {'status': false, 'data': [], 'msg': err};
} }
} }
@ -200,7 +199,7 @@ class VideoHttp {
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
} else { } else {
return {'status': true, 'data': [], 'msg': ''}; return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} }
@ -338,7 +337,7 @@ class VideoHttp {
// 视频播放进度 // 视频播放进度
static Future heartBeat({bvid, cid, progress, realtime}) async { static Future heartBeat({bvid, cid, progress, realtime}) async {
var res = await Request().post(Api.heartBeat, queryParameters: { await Request().post(Api.heartBeat, queryParameters: {
// 'aid': aid, // 'aid': aid,
'bvid': bvid, 'bvid': bvid,
'cid': cid, 'cid': cid,

View File

@ -34,6 +34,7 @@ class MyApp extends StatelessWidget {
return GetMaterialApp( return GetMaterialApp(
title: 'PiLiPaLa', title: 'PiLiPaLa',
theme: ThemeData( theme: ThemeData(
fontFamily: 'HarmonyOS',
colorScheme: lightDynamic ?? colorScheme: lightDynamic ??
ColorScheme.fromSeed( ColorScheme.fromSeed(
seedColor: Colors.green, seedColor: Colors.green,
@ -42,6 +43,7 @@ class MyApp extends StatelessWidget {
useMaterial3: true, useMaterial3: true,
), ),
darkTheme: ThemeData( darkTheme: ThemeData(
fontFamily: 'HarmonyOS',
colorScheme: darkDynamic ?? colorScheme: darkDynamic ??
ColorScheme.fromSeed( ColorScheme.fromSeed(
seedColor: Colors.green, seedColor: Colors.green,

View File

@ -193,7 +193,7 @@ class SearchUserItemModel {
usign = json['usign']; usign = json['usign'];
fans = json['fans']; fans = json['fans'];
videos = json['videos']; videos = json['videos'];
upic = 'https:' + json['upic']; upic = 'https:${json['upic']}';
faceNft = json['face_nft']; faceNft = json['face_nft'];
faceNftType = json['face_nft_type']; faceNftType = json['face_nft_type'];
verifyInfo = json['verify_info']; verifyInfo = json['verify_info'];

View File

@ -31,7 +31,7 @@ class SearchSuggestItem {
SearchSuggestItem.fromJson(Map<String, dynamic> json, String inputTerm) { SearchSuggestItem.fromJson(Map<String, dynamic> json, String inputTerm) {
value = json['value']; value = json['value'];
term = json['term']; term = json['term'];
String reg = '<em class=\"suggest_high_light\">$inputTerm</em>'; String reg = '<em class="suggest_high_light">$inputTerm</em>';
try { try {
if (json['name'].indexOf(inputTerm) != -1) { if (json['name'].indexOf(inputTerm) != -1) {
String str = json['name'].replaceAll(reg, '^'); String str = json['name'].replaceAll(reg, '^');

View File

@ -1,5 +1,3 @@
import 'package:get/get.dart';
class ReplyMember { class ReplyMember {
ReplyMember({ ReplyMember({
this.mid, this.mid,

View File

@ -1,3 +1,5 @@
// ignore_for_file: avoid_print
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -7,7 +9,6 @@ import 'package:pilipala/models/common/dynamics_type.dart';
import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/models/dynamics/up.dart'; import 'package:pilipala/models/dynamics/up.dart';
import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/utils/utils.dart';
class DynamicsController extends GetxController { class DynamicsController extends GetxController {
int page = 1; int page = 1;

View File

@ -1,14 +1,13 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/reply.dart'; import 'package:pilipala/http/reply.dart';
import 'package:pilipala/models/common/reply_sort_type.dart'; import 'package:pilipala/models/common/reply_sort_type.dart';
import 'package:pilipala/models/video/reply/data.dart';
import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/models/video/reply/item.dart';
class DynamicDetailController extends GetxController { class DynamicDetailController extends GetxController {
DynamicDetailController(this.oid, this.type); DynamicDetailController(this.oid, this.type);
int? oid; int? oid;
int? type; int? type;
var item; dynamic item;
int? floor; int? floor;
int currentPage = 0; int currentPage = 0;
bool isLoadingMore = false; bool isLoadingMore = false;

View File

@ -77,8 +77,12 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
Get.to( Get.to(
() => Scaffold( () => Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('评论详情'), titleSpacing: 0,
centerTitle: false, centerTitle: false,
title: Text(
'评论详情',
style: Theme.of(context).textTheme.titleMedium,
),
), ),
body: VideoReplyReplyPanel( body: VideoReplyReplyPanel(
oid: oid, oid: oid,

View File

@ -1,13 +1,11 @@
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/skeleton/dynamic_card.dart'; import 'package:pilipala/common/skeleton/dynamic_card.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/common/dynamics_type.dart';
import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
@ -55,6 +53,7 @@ class _DynamicsPageState extends State<DynamicsPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,

View File

@ -8,11 +8,12 @@ import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
class ActionPanel extends StatefulWidget { class ActionPanel extends StatefulWidget {
ActionPanel({ const ActionPanel({
super.key, super.key,
this.item, this.item,
}); });
var item; // ignore: prefer_typing_uninitialized_variables
final item;
@override @override
State<ActionPanel> createState() => _ActionPanelState(); State<ActionPanel> createState() => _ActionPanelState();

View File

@ -153,12 +153,12 @@ Widget addWidget(item, context, type, {floor = 1}) {
), ),
)); ));
case 'ADDITIONAL_TYPE_MATCH': case 'ADDITIONAL_TYPE_MATCH':
return SizedBox(); return const SizedBox();
case 'ADDITIONAL_TYPE_COMMON': case 'ADDITIONAL_TYPE_COMMON':
return SizedBox(); return const SizedBox();
case 'ADDITIONAL_TYPE_VOTE': case 'ADDITIONAL_TYPE_VOTE':
return SizedBox(); return const SizedBox();
default: default:
return Text('11'); return const Text('11');
} }
} }

View File

@ -7,8 +7,8 @@ import 'content_panel.dart';
import 'forward_panel.dart'; import 'forward_panel.dart';
class DynamicPanel extends StatelessWidget { class DynamicPanel extends StatelessWidget {
var item; final dynamic item;
String? source; final String? source;
DynamicPanel({this.item, this.source, Key? key}) : super(key: key); DynamicPanel({this.item, this.source, Key? key}) : super(key: key);
final DynamicsController _dynamicsController = Get.put(DynamicsController()); final DynamicsController _dynamicsController = Get.put(DynamicsController());

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';

View File

@ -90,7 +90,7 @@ InlineSpan richNode(item, context) {
'/webview', '/webview',
parameters: { parameters: {
'url': 'url':
'https://t.bilibili.com/vote/h5/index/#/result?vote_id=${i.rid}&dynamic_id=${dynamicId}&isWeb=1', 'https://t.bilibili.com/vote/h5/index/#/result?vote_id=${i.rid}&dynamic_id=$dynamicId&isWeb=1',
'type': 'vote', 'type': 'vote',
'pageTitle': '投票' 'pageTitle': '投票'
}, },

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
@ -10,8 +9,8 @@ import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
class UpPanel extends StatefulWidget { class UpPanel extends StatefulWidget {
FollowUpModel? upData; final FollowUpModel? upData;
UpPanel(this.upData, {Key? key}) : super(key: key); const UpPanel(this.upData, {Key? key}) : super(key: key);
@override @override
State<UpPanel> createState() => _UpPanelState(); State<UpPanel> createState() => _UpPanelState();
@ -168,7 +167,9 @@ class _UpPanelState extends State<UpPanel> {
smallSize: 8, smallSize: 8,
label: data.type == 'live' ? const Text('Live') : null, label: data.type == 'live' ? const Text('Live') : null,
textColor: Theme.of(context).colorScheme.onSecondaryContainer, textColor: Theme.of(context).colorScheme.onSecondaryContainer,
alignment: AlignmentDirectional.bottomCenter, alignment: data.type == 'live'
? AlignmentDirectional.topCenter
: AlignmentDirectional.topEnd,
padding: const EdgeInsets.only(left: 6, right: 6), padding: const EdgeInsets.only(left: 6, right: 6),
isLabelVisible: data.type == 'live' || isLabelVisible: data.type == 'live' ||
(data.type == 'up' && (data.hasUpdate ?? false)), (data.type == 'up' && (data.hasUpdate ?? false)),

View File

@ -44,7 +44,11 @@ class _FansPageState extends State<FansPage> {
elevation: 0, elevation: 0,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
centerTitle: false, centerTitle: false,
title: const Text('我的粉丝'), titleSpacing: 0,
title: Text(
'我的粉丝',
style: Theme.of(context).textTheme.titleMedium,
),
), ),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: () async => await _fansController.queryFans('init'), onRefresh: () async => await _fansController.queryFans('init'),
@ -57,7 +61,7 @@ class _FansPageState extends State<FansPage> {
List<FansItemModel> list = _fansController.fansList; List<FansItemModel> list = _fansController.fansList;
return Obx( return Obx(
() => list.length == 1 () => list.length == 1
? SizedBox() ? const SizedBox()
: ListView.builder( : ListView.builder(
controller: scrollController, controller: scrollController,
itemCount: list.length, itemCount: list.length,
@ -74,7 +78,7 @@ class _FansPageState extends State<FansPage> {
} }
} else { } else {
// 骨架屏 // 骨架屏
return SizedBox(); return const SizedBox();
} }
}, },
), ),

View File

@ -5,8 +5,9 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
class FavItem extends StatelessWidget { class FavItem extends StatelessWidget {
var favFolderItem; // ignore: prefer_typing_uninitialized_variables
FavItem({super.key, required this.favFolderItem}); final favFolderItem;
const FavItem({super.key, required this.favFolderItem});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -38,7 +39,6 @@ class FavItem extends StatelessWidget {
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth; double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
double PR = MediaQuery.of(context).devicePixelRatio;
return Hero( return Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
@ -62,7 +62,7 @@ class FavItem extends StatelessWidget {
} }
class VideoContent extends StatelessWidget { class VideoContent extends StatelessWidget {
final favFolderItem; final dynamic favFolderItem;
const VideoContent({super.key, required this.favFolderItem}); const VideoContent({super.key, required this.favFolderItem});
@override @override
@ -76,9 +76,11 @@ class VideoContent extends StatelessWidget {
Text( Text(
favFolderItem.title, favFolderItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: const TextStyle(
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, fontSize: 13,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
), ),
Text( Text(
'${favFolderItem.mediaCount}个内容', '${favFolderItem.mediaCount}个内容',

View File

@ -11,7 +11,7 @@ import '../controller.dart';
// 收藏视频卡片 - 水平布局 // 收藏视频卡片 - 水平布局
class FavVideoCardH extends StatelessWidget { class FavVideoCardH extends StatelessWidget {
var videoItem; final dynamic videoItem;
final FavDetailController _favDetailController = final FavDetailController _favDetailController =
Get.put(FavDetailController()); Get.put(FavDetailController());
@ -28,9 +28,9 @@ class FavVideoCardH extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.errorContainer, color: Theme.of(context).colorScheme.errorContainer,
), ),
child: Row( child: const Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: const [ children: [
Icon(Icons.clear_all_rounded), Icon(Icons.clear_all_rounded),
SizedBox(width: 6), SizedBox(width: 6),
Text('取消收藏') Text('取消收藏')
@ -51,7 +51,8 @@ class FavVideoCardH extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB(12, 5, 12, 5), padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double width = double width =
@ -68,21 +69,16 @@ class FavVideoCardH extends StatelessWidget {
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth; double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
double PR =
MediaQuery.of(context).devicePixelRatio;
return Stack( return Stack(
children: [ children: [
Hero( Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
// src: videoItem['pic'] +
// '@${(maxWidth * 2).toInt()}w',
src: videoItem.pic + '@.webp', src: videoItem.pic + '@.webp',
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
), ),
// Image.network( videoItem['pic'], width: double.infinity, height: double.infinity,),
Positioned( Positioned(
right: 4, right: 4,
bottom: 4, bottom: 4,
@ -121,7 +117,7 @@ class FavVideoCardH extends StatelessWidget {
} }
class VideoContent extends StatelessWidget { class VideoContent extends StatelessWidget {
final videoItem; final dynamic videoItem;
const VideoContent({super.key, required this.videoItem}); const VideoContent({super.key, required this.videoItem});
@override @override
@ -135,9 +131,11 @@ class VideoContent extends StatelessWidget {
Text( Text(
videoItem.title, videoItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: const TextStyle(
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, fontSize: 13,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -145,10 +143,11 @@ class VideoContent extends StatelessWidget {
Text( Text(
videoItem.owner.name, videoItem.owner.name,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
const SizedBox(height: 2),
Row( Row(
children: [ children: [
StatView( StatView(

View File

@ -43,8 +43,12 @@ class _FollowPageState extends State<FollowPage> {
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
titleSpacing: 0,
centerTitle: false, centerTitle: false,
title: const Text('我的关注'), title: Text(
'我的关注',
style: Theme.of(context).textTheme.titleMedium,
),
), ),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: () async => onRefresh: () async =>
@ -58,7 +62,7 @@ class _FollowPageState extends State<FollowPage> {
List<FollowItemModel> list = _followController.followList; List<FollowItemModel> list = _followController.followList;
return Obx( return Obx(
() => list.length == 1 () => list.length == 1
? SizedBox() ? const SizedBox()
: ListView.builder( : ListView.builder(
controller: scrollController, controller: scrollController,
itemCount: list.length, itemCount: list.length,
@ -75,7 +79,7 @@ class _FollowPageState extends State<FollowPage> {
} }
} else { } else {
// 骨架屏 // 骨架屏
return SizedBox(); return const SizedBox();
} }
}, },
)), )),

View File

@ -11,13 +11,16 @@ Widget followItem({item}) {
leading: Hero( leading: Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
width: 38, width: 45,
height: 38, height: 45,
type: 'avatar', type: 'avatar',
src: item.face, src: item.face,
), ),
), ),
title: Text(item.uname), title: Text(
item.uname,
style: const TextStyle(fontSize: 14),
),
subtitle: Text( subtitle: Text(
item.sign, item.sign,
maxLines: 1, maxLines: 1,

View File

@ -10,8 +10,8 @@ import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
class HistoryItem extends StatelessWidget { class HistoryItem extends StatelessWidget {
var videoItem; final dynamic videoItem;
HistoryItem({super.key, required this.videoItem}); const HistoryItem({super.key, required this.videoItem});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -59,7 +59,7 @@ class HistoryItem extends StatelessWidget {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
StyleString.cardSpace, 7, StyleString.cardSpace, 7), StyleString.cardSpace, 5, StyleString.cardSpace, 5),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double width = double width =
@ -76,14 +76,11 @@ class HistoryItem extends StatelessWidget {
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth; double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
double PR = MediaQuery.of(context).devicePixelRatio;
return Stack( return Stack(
children: [ children: [
Hero( Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
// src: videoItem['pic'] +
// '@${(maxWidth * 2).toInt()}w',
src: (videoItem.cover != '' src: (videoItem.cover != ''
? videoItem.cover ? videoItem.cover
: videoItem.covers.first) + : videoItem.covers.first) +
@ -124,12 +121,6 @@ class HistoryItem extends StatelessWidget {
}, },
), ),
), ),
Divider(
height: 1,
indent: 8,
endIndent: 12,
color: Theme.of(context).dividerColor.withOpacity(0.08),
)
], ],
), ),
); );
@ -137,7 +128,7 @@ class HistoryItem extends StatelessWidget {
} }
class VideoContent extends StatelessWidget { class VideoContent extends StatelessWidget {
final videoItem; final dynamic videoItem;
const VideoContent({super.key, required this.videoItem}); const VideoContent({super.key, required this.videoItem});
@override @override
@ -151,9 +142,11 @@ class VideoContent extends StatelessWidget {
Text( Text(
videoItem.title, videoItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: const TextStyle(
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, fontSize: 13,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
maxLines: videoItem.videos > 1 ? 1 : 2, maxLines: videoItem.videos > 1 ? 1 : 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -162,7 +155,7 @@ class VideoContent extends StatelessWidget {
videoItem.showTitle, videoItem.showTitle,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.outline), color: Theme.of(context).colorScheme.outline),
maxLines: 2, maxLines: 2,
@ -174,7 +167,7 @@ class VideoContent extends StatelessWidget {
Text( Text(
videoItem.authorName, videoItem.authorName,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
@ -185,7 +178,8 @@ class VideoContent extends StatelessWidget {
Text( Text(
Utils.dateFormat(videoItem.viewAt!), Utils.dateFormat(videoItem.viewAt!),
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline), color: Theme.of(context).colorScheme.outline),
) )
], ],

View File

@ -7,9 +7,30 @@ import 'package:pilipala/pages/rcmd/index.dart';
class HomeController extends GetxController with GetTickerProviderStateMixin { class HomeController extends GetxController with GetTickerProviderStateMixin {
bool flag = false; bool flag = false;
List tabs = [ List tabs = [
{'label': '直播', 'type': 'live'}, {
{'label': '推荐', 'type': 'rcm'}, 'icon': const Icon(
{'label': '热门', 'type': 'hot'}, Icons.live_tv_outlined,
size: 15,
),
'label': '直播',
'type': 'live'
},
{
'icon': const Icon(
Icons.thumb_up_off_alt_outlined,
size: 15,
),
'label': '推荐',
'type': 'rcm'
},
{
'icon': const Icon(
Icons.whatshot_outlined,
size: 15,
),
'label': '热门',
'type': 'hot'
},
]; ];
int initialIndex = 1; int initialIndex = 1;
late TabController tabController; late TabController tabController;

View File

@ -29,7 +29,7 @@ class _HomePageState extends State<HomePage>
appBar: AppBar( appBar: AppBar(
titleSpacing: 0, titleSpacing: 0,
title: Padding( title: Padding(
padding: const EdgeInsets.only(left: 4, right: 4), padding: const EdgeInsets.only(left: 12, right: 12, bottom: 0),
child: Stack( child: Stack(
children: [ children: [
const Align( const Align(
@ -55,26 +55,45 @@ class _HomePageState extends State<HomePage>
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
), ),
child: TabBar( child: Padding(
controller: _homeController.tabController, padding: const EdgeInsets.only(top: 4),
tabs: [ child: TabBar(
for (var i in _homeController.tabs) Tab(text: i['label']), controller: _homeController.tabController,
], tabs: [
isScrollable: true, for (var i in _homeController.tabs)
indicatorWeight: 0, // Tab(text: i['label'])
indicatorPadding: Padding(
const EdgeInsets.symmetric(horizontal: 3, vertical: 8), padding: const EdgeInsets.symmetric(
indicator: BoxDecoration( horizontal: 0, vertical: 11),
color: Theme.of(context).colorScheme.secondaryContainer, child: Row(
borderRadius: const BorderRadius.all(Radius.circular(20)), children: [
i['icon'],
const SizedBox(width: 4),
Text(i['label'])
],
),
),
],
isScrollable: true,
indicatorWeight: 0,
indicatorPadding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 5),
indicator: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.8),
borderRadius:
const BorderRadius.all(Radius.circular(20)),
),
indicatorSize: TabBarIndicatorSize.tab,
labelColor: Theme.of(context).colorScheme.primary,
labelStyle: const TextStyle(fontSize: 13),
dividerColor: Colors.transparent,
unselectedLabelColor:
Theme.of(context).colorScheme.outline,
onTap: (value) => {_homeController.initialIndex = value},
), ),
indicatorSize: TabBarIndicatorSize.tab,
labelColor:
Theme.of(context).colorScheme.onSecondaryContainer,
labelStyle: const TextStyle(fontSize: 13),
dividerColor: Colors.transparent,
unselectedLabelColor: Theme.of(context).colorScheme.outline,
onTap: (value) => {_homeController.initialIndex = value},
), ),
), ),
), ),

View File

@ -1,7 +1,5 @@
import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';

View File

@ -86,7 +86,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
return SliverList( return SliverList(
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
}, childCount: 5), }, childCount: 10),
); );
} }
}, },

View File

@ -26,8 +26,12 @@ class _LaterPageState extends State<LaterPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('稍后再看'), titleSpacing: 0,
centerTitle: false, centerTitle: false,
title: Text(
'稍后再看',
style: Theme.of(context).textTheme.titleMedium,
),
), ),
body: CustomScrollView( body: CustomScrollView(
controller: _laterController.scrollController, controller: _laterController.scrollController,

View File

@ -19,9 +19,6 @@ class LivePage extends StatefulWidget {
class _LivePageState extends State<LivePage> { class _LivePageState extends State<LivePage> {
final LiveController _liveController = Get.put(LiveController()); final LiveController _liveController = Get.put(LiveController());
@override
bool get wantKeepAlive => true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -50,7 +47,7 @@ class _LivePageState extends State<LivePage> {
SliverPadding( SliverPadding(
// 单列布局 EdgeInsets.zero // 单列布局 EdgeInsets.zero
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
StyleString.cardSpace, 0, StyleString.cardSpace, 8), StyleString.safeSpace, 0, StyleString.safeSpace, 0),
sliver: FutureBuilder( sliver: FutureBuilder(
future: _liveController.queryLiveList('init'), future: _liveController.queryLiveList('init'),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -97,13 +94,13 @@ class _LivePageState extends State<LivePage> {
return SliverGrid( return SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// 行间距 // 行间距
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace + 2,
// 列间距 // 列间距
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace + 3,
// 列数 // 列数
crossAxisCount: ctr.crossAxisCount, crossAxisCount: ctr.crossAxisCount,
mainAxisExtent: mainAxisExtent:
Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 70, Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 60,
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@ -8,11 +8,11 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局 // 视频卡片 - 垂直布局
class LiveCardV extends StatelessWidget { class LiveCardV extends StatelessWidget {
LiveItemModel liveItem; final LiveItemModel liveItem;
Function()? longPress; final Function()? longPress;
Function()? longPressEnd; final Function()? longPressEnd;
LiveCardV({ const LiveCardV({
Key? key, Key? key,
required this.liveItem, required this.liveItem,
this.longPress, this.longPress,
@ -23,7 +23,7 @@ class LiveCardV extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(liveItem.roomId); String heroTag = Utils.makeHeroTag(liveItem.roomId);
return Card( return Card(
elevation: 0.8, elevation: 0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: StyleString.mdRadius, borderRadius: StyleString.mdRadius,
@ -52,6 +52,8 @@ class LiveCardV extends StatelessWidget {
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: StyleString.imgRadius, topLeft: StyleString.imgRadius,
topRight: StyleString.imgRadius, topRight: StyleString.imgRadius,
bottomLeft: StyleString.imgRadius,
bottomRight: StyleString.imgRadius,
), ),
child: AspectRatio( child: AspectRatio(
aspectRatio: StyleString.aspectRatio, aspectRatio: StyleString.aspectRatio,
@ -68,6 +70,18 @@ class LiveCardV extends StatelessWidget {
height: maxHeight, height: maxHeight,
), ),
), ),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: AnimatedOpacity(
opacity: 1,
duration: const Duration(milliseconds: 200),
child: VideoStat(
liveItem: liveItem,
),
),
),
], ],
); );
}), }),
@ -83,36 +97,39 @@ class LiveCardV extends StatelessWidget {
} }
class LiveContent extends StatelessWidget { class LiveContent extends StatelessWidget {
final liveItem; final dynamic liveItem;
const LiveContent({Key? key, required this.liveItem}) : super(key: key); const LiveContent({Key? key, required this.liveItem}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return Expanded(
child: Padding( child: Padding(
// 多列 // 多列
padding: const EdgeInsets.fromLTRB(8, 7, 6, 4), padding: const EdgeInsets.fromLTRB(4, 5, 6, 6),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
liveItem.title, liveItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
), ),
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 4),
Row( Row(
children: [ children: [
UpTag(), const UpTag(),
Expanded( Expanded(
child: Text( child: Text(
liveItem.uname, liveItem.uname,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -120,23 +137,50 @@ class LiveContent extends StatelessWidget {
) )
], ],
), ),
const SizedBox(height: 2),
Row(
children: [
Text(
'${'[' + liveItem.areaName}]',
style: const TextStyle(fontSize: 11),
),
const Text(''),
Text(
liveItem.watchedShow['text_large'],
style: const TextStyle(fontSize: 11),
),
],
),
], ],
), ),
), ),
); );
} }
} }
class VideoStat extends StatelessWidget {
final LiveItemModel? liveItem;
const VideoStat({
Key? key,
required this.liveItem,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 45,
padding: const EdgeInsets.only(top: 22, left: 10, right: 10),
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: [
Text(
liveItem!.areaName!,
style: const TextStyle(fontSize: 11, color: Colors.white),
),
Text(
liveItem!.watchedShow!['text_small'],
style: const TextStyle(fontSize: 11, color: Colors.white),
),
],
),
);
}
}

View File

@ -8,7 +8,7 @@ import 'package:pilipala/models/live/room_info.dart';
class LiveRoomController extends GetxController { class LiveRoomController extends GetxController {
String cover = ''; String cover = '';
late int roomId; late int roomId;
var liveItem; dynamic liveItem;
late String heroTag; late String heroTag;
double volume = 0.0; double volume = 0.0;
// 静音状态 // 静音状态
@ -75,6 +75,5 @@ class LiveRoomController extends GetxController {
volumeOff.value = true; volumeOff.value = true;
meeduPlayerController.setVolume(0); meeduPlayerController.setVolume(0);
} }
print('🌹:${volumeOff.value}');
} }
} }

View File

@ -1,9 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_meedu_media_kit/meedu_player.dart'; import 'package:flutter_meedu_media_kit/meedu_player.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'dart:ui';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'controller.dart'; import 'controller.dart';
@ -18,7 +15,6 @@ class LiveRoomPage extends StatefulWidget {
class _LiveRoomPageState extends State<LiveRoomPage> { class _LiveRoomPageState extends State<LiveRoomPage> {
final LiveRoomController _liveRoomController = Get.put(LiveRoomController()); final LiveRoomController _liveRoomController = Get.put(LiveRoomController());
MeeduPlayerController? _meeduPlayerController; MeeduPlayerController? _meeduPlayerController;
StreamSubscription? _playerEventSubs;
bool isShowCover = true; bool isShowCover = true;
bool isPlay = true; bool isPlay = true;
@ -27,7 +23,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
void initState() { void initState() {
super.initState(); super.initState();
_meeduPlayerController = _liveRoomController.meeduPlayerController; _meeduPlayerController = _liveRoomController.meeduPlayerController;
_playerEventSubs = _meeduPlayerController!.onPlayerStatusChanged.listen( _meeduPlayerController!.onPlayerStatusChanged.listen(
(PlayerStatus status) { (PlayerStatus status) {
if (status == PlayerStatus.playing) { if (status == PlayerStatus.playing) {
isShowCover = false; isShowCover = false;
@ -45,7 +41,6 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double statusBarHeight = MediaQuery.of(context).padding.top;
final videoHeight = MediaQuery.of(context).size.width * 9 / 16; final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
return Scaffold( return Scaffold(
@ -97,8 +92,8 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
child: MeeduVideoPlayer( child: MeeduVideoPlayer(
header: (BuildContext context, header: (BuildContext context,
MeeduPlayerController _meeduPlayerController, MeeduPlayerController meeduPlayerController,
Responsive) { Responsive responsive) {
return AppBar( return AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
primary: false, primary: false,
@ -115,7 +110,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
height: 38, height: 38,
child: IconButton( child: IconButton(
onPressed: () => onPressed: () =>
_meeduPlayerController.enterPip(context), meeduPlayerController.enterPip(context),
icon: const Icon( icon: const Icon(
Icons.branding_watermark_outlined, Icons.branding_watermark_outlined,
size: 19, size: 19,

View File

@ -1,19 +1,12 @@
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/view.dart'; import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/hot/view.dart';
import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/utils/storage.dart';
class MainController extends GetxController { class MainController extends GetxController {
List<Widget> pages = <Widget>[ List<Widget> pages = <Widget>[
const HomePage(), const HomePage(),
// const HotPage(),
const DynamicsPage(), const DynamicsPage(),
const MediaPage(), const MediaPage(),
]; ];
@ -25,13 +18,6 @@ class MainController extends GetxController {
), ),
'label': "推荐", 'label': "推荐",
}, },
// {
// 'icon': const Icon(
// Icons.eco,
// size: 20,
// ),
// 'label': "热门",
// },
{ {
'icon': const Icon( 'icon': const Icon(
Icons.bolt, Icons.bolt,

View File

@ -155,7 +155,7 @@ class MediaPage extends StatelessWidget {
} }
} else { } else {
// 骨架屏 // 骨架屏
return SizedBox(); return const SizedBox();
} }
}), }),
), ),
@ -165,9 +165,9 @@ class MediaPage extends StatelessWidget {
} }
class FavFolderItem extends StatelessWidget { class FavFolderItem extends StatelessWidget {
FavFolderItem({super.key, this.item, this.index}); const FavFolderItem({super.key, this.item, this.index});
FavFolderItemData? item; final FavFolderItemData? item;
int? index; final int? index;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(item!.fid); String heroTag = Utils.makeHeroTag(item!.fid);

View File

@ -24,6 +24,7 @@ class _ArchivePanelState extends State<ArchivePanel>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return PullToRefreshNotification( return PullToRefreshNotification(
onRefresh: () async { onRefresh: () async {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));

View File

@ -7,7 +7,6 @@ import 'package:pilipala/http/video.dart';
import 'package:pilipala/models/member/archive.dart'; import 'package:pilipala/models/member/archive.dart';
import 'package:pilipala/models/member/info.dart'; import 'package:pilipala/models/member/info.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/wbi_sign.dart';
class MemberController extends GetxController { class MemberController extends GetxController {
late int mid; late int mid;
@ -48,13 +47,13 @@ class MemberController extends GetxController {
return res; return res;
} }
Future getMemberCardInfo() async { // Future getMemberCardInfo() async {
var res = await MemberHttp.memberCardInfo(mid: mid); // var res = await MemberHttp.memberCardInfo(mid: mid);
if (res['status']) { // if (res['status']) {
print(userStat); // print(userStat);
} // }
return res; // return res;
} // }
// 关注/取关up // 关注/取关up
Future actionRelationMod() async { Future actionRelationMod() async {

View File

@ -2,12 +2,8 @@ import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:loading_more_list/loading_more_list.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/pages/member/archive/view.dart'; import 'package:pilipala/pages/member/archive/view.dart';
import 'package:pilipala/pages/member/index.dart'; import 'package:pilipala/pages/member/index.dart';
import 'package:pilipala/utils/utils.dart';
import 'widgets/profile.dart'; import 'widgets/profile.dart';
@ -231,7 +227,7 @@ class _MemberPageState extends State<MemberPage>
), ),
); );
} else { } else {
return SizedBox(); return const SizedBox();
} }
} else { } else {
// 骨架屏 // 骨架屏

View File

@ -1,3 +1,5 @@
// ignore_for_file: no_leading_underscores_for_local_identifiers
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -321,11 +323,11 @@ class MinePage extends StatelessWidget {
} }
class ActionItem extends StatelessWidget { class ActionItem extends StatelessWidget {
Icon? icon; final Icon? icon;
Function? onTap; final Function? onTap;
String? text; final String? text;
ActionItem({ const ActionItem({
Key? key, Key? key,
this.icon, this.icon,
this.onTap, this.onTap,

View File

@ -7,7 +7,6 @@ import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
class PreviewController extends GetxController { class PreviewController extends GetxController {
@ -36,10 +35,8 @@ class PreviewController extends GetxController {
// Permission.photos // Permission.photos
].request(); ].request();
final info = statuses[Permission.storage].toString(); statuses[Permission.storage].toString();
// final photosInfo = statuses[Permission.photos].toString(); // final photosInfo = statuses[Permission.photos].toString();
print('授权状态:$info');
} }
// 图片保存 // 图片保存
@ -52,6 +49,7 @@ class PreviewController extends GetxController {
name: "pic_vvex${DateTime.now().toString().split('-').join()}"); name: "pic_vvex${DateTime.now().toString().split('-').join()}");
if (result != null) { if (result != null) {
if (result['isSuccess']) { if (result['isSuccess']) {
// ignore: avoid_print
print('已保存到相册'); print('已保存到相册');
} }
} }

View File

@ -1,8 +1,9 @@
// ignore_for_file: library_private_types_in_public_api
import 'package:dismissible_page/dismissible_page.dart'; import 'package:dismissible_page/dismissible_page.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:extended_image/extended_image.dart'; import 'package:extended_image/extended_image.dart';
import 'package:pilipala/common/widgets/appbar.dart';
import 'controller.dart'; import 'controller.dart';
typedef DoubleClickAnimationListener = void Function(); typedef DoubleClickAnimationListener = void Function();
@ -145,6 +146,8 @@ class _ImagePreviewState extends State<ImagePreview>
], ],
), ),
); );
} else {
return const SizedBox();
} }
}, },
initGestureConfigHandler: (ExtendedImageState state) { initGestureConfigHandler: (ExtendedImageState state) {
@ -168,7 +171,8 @@ class _ImagePreviewState extends State<ImagePreview>
bottom: 0, bottom: 0,
child: Container( child: Container(
// height: 45, // height: 45,
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom, top: 20), padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom, top: 20),
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
@ -186,23 +190,29 @@ class _ImagePreviewState extends State<ImagePreview>
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Obx( Obx(
() => Text.rich( () => Text.rich(
TextSpan( TextSpan(
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white, fontSize: 18),
fontSize: 18
),
children: [ children: [
TextSpan(text: _previewController.currentPage.toString()), TextSpan(
const TextSpan(text: ' / '), text:
TextSpan(text: _previewController.imgList.length.toString()), _previewController.currentPage.toString()),
]), const TextSpan(text: ' / '),
TextSpan(
text: _previewController.imgList.length
.toString()),
]),
), ),
), ),
const Spacer(), const Spacer(),
ElevatedButton(onPressed: () => _previewController.onShareImg(), child: Text('分享')), ElevatedButton(
onPressed: () => _previewController.onShareImg(),
child: const Text('分享')),
const SizedBox(width: 10), const SizedBox(width: 10),
ElevatedButton(onPressed: () => _previewController.onSaveImg(), child: Text('保存')) ElevatedButton(
onPressed: () => _previewController.onSaveImg(),
child: const Text('保存'))
], ],
), ),
), ),

View File

@ -41,6 +41,7 @@ class _RcmdPageState extends State<RcmdPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
return await _rcmdController.onRefresh(); return await _rcmdController.onRefresh();
@ -53,7 +54,7 @@ class _RcmdPageState extends State<RcmdPage>
padding: _rcmdController.crossAxisCount == 1 padding: _rcmdController.crossAxisCount == 1
? EdgeInsets.zero ? EdgeInsets.zero
: const EdgeInsets.fromLTRB( : const EdgeInsets.fromLTRB(
StyleString.cardSpace, 0, StyleString.cardSpace, 8), StyleString.safeSpace, 0, StyleString.safeSpace, 0),
sliver: FutureBuilder( sliver: FutureBuilder(
future: _rcmdController.queryRcmdFeed('init'), future: _rcmdController.queryRcmdFeed('init'),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -99,13 +100,13 @@ class _RcmdPageState extends State<RcmdPage>
return SliverGrid( return SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// 行间距 // 行间距
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace + 2,
// 列间距 // 列间距
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace + 3,
// 列数 // 列数
crossAxisCount: ctr.crossAxisCount, crossAxisCount: ctr.crossAxisCount,
mainAxisExtent: mainAxisExtent:
Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 70, Get.size.width / ctr.crossAxisCount / StyleString.aspectRatio + 60,
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@ -1,10 +1,9 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/hotKeyword.dart'; import 'widgets/hot_keyword.dart';
import 'widgets/search_text.dart'; import 'widgets/search_text.dart';
class SearchPage extends StatefulWidget { class SearchPage extends StatefulWidget {

View File

@ -1,7 +1,6 @@
// ignore: file_names // ignore: file_names
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
class HotKeyword extends StatelessWidget { class HotKeyword extends StatelessWidget {
final double? width; final double? width;

View File

@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class SearchText extends StatelessWidget { class SearchText extends StatelessWidget {
String? searchText; final String? searchText;
Function? onSelect; final Function? onSelect;
int? searchTextIdx; final int? searchTextIdx;
SearchText({super.key, this.searchText, this.onSelect, this.searchTextIdx}); const SearchText(
{super.key, this.searchText, this.onSelect, this.searchTextIdx});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -11,10 +11,10 @@ import 'widgets/user_panel.dart';
import 'widgets/video_panel.dart'; import 'widgets/video_panel.dart';
class SearchPanel extends StatefulWidget { class SearchPanel extends StatefulWidget {
String? keyword; final String? keyword;
SearchType? searchType; final SearchType? searchType;
String? tag; final String? tag;
SearchPanel( const SearchPanel(
{required this.keyword, required this.searchType, this.tag, Key? key}) {required this.keyword, required this.searchType, this.tag, Key? key})
: super(key: key); : super(key: key);
@ -57,6 +57,7 @@ class _SearchPanelState extends State<SearchPanel>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
await _searchPanelController!.onRefresh(); await _searchPanelController!.onRefresh();

View File

@ -12,8 +12,8 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
controller: ctr!.scrollController, controller: ctr!.scrollController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace + 2,
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace + 3,
mainAxisExtent: mainAxisExtent:
MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio + MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio +
65, 65,
@ -22,7 +22,7 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
itemBuilder: (context, index) { itemBuilder: (context, index) {
var i = list![index]; var i = list![index];
return Card( return Card(
elevation: 0.8, elevation: 0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: StyleString.mdRadius, borderRadius: StyleString.mdRadius,
@ -32,41 +32,46 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
onTap: () {}, onTap: () {},
child: Column( child: Column(
children: [ children: [
AspectRatio( ClipRRect(
aspectRatio: StyleString.aspectRatio, borderRadius: const BorderRadius.only(
child: LayoutBuilder(builder: (context, boxConstraints) { topLeft: StyleString.imgRadius,
double maxWidth = boxConstraints.maxWidth; topRight: StyleString.imgRadius,
double maxHeight = boxConstraints.maxHeight; bottomLeft: StyleString.imgRadius,
double PR = MediaQuery.of(context).devicePixelRatio; bottomRight: StyleString.imgRadius,
return Stack( ),
children: [ child: AspectRatio(
Hero( aspectRatio: StyleString.aspectRatio,
tag: Utils.makeHeroTag(i.roomid), child: LayoutBuilder(builder: (context, boxConstraints) {
child: NetworkImgLayer( double maxWidth = boxConstraints.maxWidth;
// 指定图片尺寸 double maxHeight = boxConstraints.maxHeight;
// src: videoItem.pic + '@${(maxWidth * 2).toInt()}w', return Stack(
src: i.cover + '@.webp', children: [
type: 'emote', Hero(
width: maxWidth, tag: Utils.makeHeroTag(i.roomid),
height: maxHeight, child: NetworkImgLayer(
), src: i.cover + '@.webp',
), type: 'emote',
Positioned( width: maxWidth,
left: 0, height: maxHeight,
right: 0,
bottom: 0,
child: AnimatedOpacity(
opacity: 1,
duration: const Duration(milliseconds: 200),
child: LiveStat(
online: i.online,
cateName: i.cateName,
), ),
), ),
), Positioned(
], left: 0,
); right: 0,
}), bottom: 0,
child: AnimatedOpacity(
opacity: 1,
duration: const Duration(milliseconds: 200),
child: LiveStat(
online: i.online,
cateName: i.cateName,
),
),
),
],
);
}),
),
), ),
LiveContent(liveItem: i) LiveContent(liveItem: i)
], ],
@ -79,13 +84,13 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
} }
class LiveContent extends StatelessWidget { class LiveContent extends StatelessWidget {
final liveItem; final dynamic liveItem;
const LiveContent({Key? key, required this.liveItem}) : super(key: key); const LiveContent({Key? key, required this.liveItem}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 6, 7), padding: const EdgeInsets.fromLTRB(4, 5, 6, 6),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -99,6 +104,7 @@ class LiveContent extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
letterSpacing: 0.3,
color: i['type'] == 'em' color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface, : Theme.of(context).colorScheme.onSurface,

View File

@ -42,9 +42,8 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
children: [ children: [
Text( Text(
i!.uname, i!.uname,
style: TextStyle( style: const TextStyle(
fontSize: fontSize: 14,
Theme.of(context).textTheme.titleMedium!.fontSize,
), ),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
@ -71,7 +70,6 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
), ),
), ),
); );
;
}, },
); );
} }

View File

@ -5,6 +5,7 @@ import 'package:pilipala/pages/setting/index.dart';
class SettingPage extends StatelessWidget { class SettingPage extends StatelessWidget {
const SettingPage({super.key}); const SettingPage({super.key});
@override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final SettingController settingController = Get.put(SettingController()); final SettingController settingController = Get.put(SettingController());
return Scaffold( return Scaffold(

View File

@ -1,6 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_meedu_media_kit/meedu_player.dart'; import 'package:flutter_meedu_media_kit/meedu_player.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';

View File

@ -105,7 +105,6 @@ class VideoIntroController extends GetxController {
// 获取up主粉丝数 // 获取up主粉丝数
Future queryUserStat() async { Future queryUserStat() async {
var result = await UserHttp.userStat(mid: videoDetail.value.owner!.mid!); var result = await UserHttp.userStat(mid: videoDetail.value.owner!.mid!);
print('🌹:$result');
if (result['status']) { if (result['status']) {
userStat = result['data']; userStat = result['data'];
} }
@ -185,9 +184,11 @@ class VideoIntroController extends GetxController {
if (!hasLike.value) { if (!hasLike.value) {
SmartDialog.showToast('点赞成功 👍'); SmartDialog.showToast('点赞成功 👍');
hasLike.value = true; hasLike.value = true;
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
} else if (hasLike.value) { } else if (hasLike.value) {
SmartDialog.showToast('取消赞'); SmartDialog.showToast('取消赞');
hasLike.value = false; hasLike.value = false;
videoDetail.value.stat!.like = videoDetail.value.stat!.like! - 1;
} }
hasLike.refresh(); hasLike.refresh();
} else { } else {
@ -238,14 +239,15 @@ class VideoIntroController extends GetxController {
onPressed: () async { onPressed: () async {
var res = await VideoHttp.coinVideo( var res = await VideoHttp.coinVideo(
bvid: bvid, multiply: _tempThemeValue); bvid: bvid, multiply: _tempThemeValue);
print(res);
if (res['status']) { if (res['status']) {
SmartDialog.showToast('投币成功'); SmartDialog.showToast('投币成功 👏');
hasCoin.value = true;
videoDetail.value.stat!.coin =
videoDetail.value.stat!.coin! + _tempThemeValue;
} else { } else {
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
} }
Get.back(); Get.back();
queryHasCoinVideo();
}, },
child: const Text('确定')) child: const Text('确定'))
], ],
@ -263,7 +265,10 @@ class VideoIntroController extends GetxController {
delMediaIdsNew.add(i.id); delMediaIdsNew.add(i.id);
} }
} }
} catch (e) {} } catch (e) {
// ignore: avoid_print
print(e);
}
var result = await VideoHttp.favVideo( var result = await VideoHttp.favVideo(
aid: IdUtils.bv2av(bvid), aid: IdUtils.bv2av(bvid),
addIds: addMediaIdsNew.join(','), addIds: addMediaIdsNew.join(','),
@ -282,10 +287,8 @@ class VideoIntroController extends GetxController {
// 分享视频 // 分享视频
Future actionShareVideo() async { Future actionShareVideo() async {
var result = var result = await Share.share('${HttpString.baseUrl}/video/$bvid')
await Share.share('${HttpString.baseUrl}/video/$bvid').whenComplete(() { .whenComplete(() {});
print("share completion block ");
});
return result; return result;
} }

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@ -7,10 +6,7 @@ import 'package:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/fav/index.dart';
import 'package:pilipala/pages/favDetail/index.dart';
import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/widgets/expandable_section.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/common/widgets/stat/view.dart';
@ -19,7 +15,9 @@ import 'package:pilipala/pages/video/detail/introduction/controller.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'widgets/action_row_item.dart';
import 'widgets/fav_panel.dart'; import 'widgets/fav_panel.dart';
import 'widgets/intro_detail.dart';
import 'widgets/season.dart'; import 'widgets/season.dart';
class VideoIntroPanel extends StatefulWidget { class VideoIntroPanel extends StatefulWidget {
@ -55,6 +53,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return FutureBuilder( return FutureBuilder(
future: videoIntroController.queryVideoIntro(), future: videoIntroController.queryVideoIntro(),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -79,10 +78,10 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
} }
class VideoInfo extends StatefulWidget { class VideoInfo extends StatefulWidget {
bool loadingStatus = false; final bool loadingStatus;
VideoDetailData? videoDetail; final VideoDetailData? videoDetail;
VideoInfo({Key? key, required this.loadingStatus, this.videoDetail}) const VideoInfo({Key? key, this.loadingStatus = false, this.videoDetail})
: super(key: key); : super(key: key);
@override @override
@ -95,14 +94,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
Get.put(VideoIntroController(), tag: Get.arguments['heroTag']); Get.put(VideoIntroController(), tag: Get.arguments['heroTag']);
bool isExpand = false; bool isExpand = false;
/// 手动控制动画的控制器
late AnimationController? _manualController;
/// 手动控制
late Animation<double>? _manualAnimation;
final FavController _favController = Get.put(FavController());
late VideoDetailController? videoDetailCtr; late VideoDetailController? videoDetailCtr;
Box localCache = GStrorage.localCache; Box localCache = GStrorage.localCache;
late double sheetHeight; late double sheetHeight;
@ -111,13 +102,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
void initState() { void initState() {
super.initState(); super.initState();
/// 不设置重复使用代码控制进度动画时间1秒
_manualController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_manualAnimation =
Tween<double>(begin: 0.5, end: 1.5).animate(_manualController!);
videoDetailCtr = videoDetailCtr =
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']); Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
sheetHeight = localCache.get('sheetHeight'); sheetHeight = localCache.get('sheetHeight');
@ -141,143 +125,123 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverPadding( return SliverPadding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 10), padding: const EdgeInsets.only(
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 13),
sliver: SliverToBoxAdapter( sliver: SliverToBoxAdapter(
child: !widget.loadingStatus || videoItem.isNotEmpty child: !widget.loadingStatus || videoItem.isNotEmpty
? Column( ? Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SelectableRegion( GestureDetector(
magnifierConfiguration: const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Text(
!widget.loadingStatus
? widget.videoDetail!.title
: videoItem['title'],
style: Theme.of(context).textTheme.titleMedium!.copyWith(
letterSpacing: 0.5,
),
),
),
InkWell(
splashColor: Colors.transparent,
hoverColor: Colors.transparent,
highlightColor: Colors.transparent,
onTap: () { onTap: () {
_manualController!.animateTo(isExpand ? 0 : 0.5); showBottomSheet(
setState(() { context: context,
isExpand = !isExpand; enableDrag: true,
}); builder: (BuildContext context) {
return IntroDetail(videoDetail: widget.videoDetail!);
},
);
}, },
child: Row( child: Row(
children: [ children: [
const SizedBox(width: 2), Expanded(
StatView( child: Text(
theme: 'gray', !widget.loadingStatus
view: !widget.loadingStatus ? widget.videoDetail!.title
? widget.videoDetail!.stat!.view : videoItem['title'],
: videoItem['stat'].view, style: const TextStyle(
size: 'medium', fontSize: 18,
), letterSpacing: 0.3,
const SizedBox(width: 10), fontWeight: FontWeight.w500,
StatDanMu(
theme: 'gray',
danmu: !widget.loadingStatus
? widget.videoDetail!.stat!.danmaku
: videoItem['stat'].danmaku,
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(
!widget.loadingStatus
? widget.videoDetail!.pubdate
: videoItem['pubdate'],
formatType: 'detail'),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline),
),
const Spacer(),
RotationTransition(
turns: _manualAnimation!,
child: SizedBox(
width: 35,
height: 35,
child: IconButton(
padding: const EdgeInsets.all(2.0),
onPressed: () {
/// 0.5代表 180弧度
_manualController!
.animateTo(isExpand ? 0 : 0.5);
setState(() {
isExpand = !isExpand;
});
},
icon: Icon(
FontAwesomeIcons.angleUp,
size: 15,
color: Theme.of(context).colorScheme.outline,
),
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 20),
SizedBox(
width: 34,
height: 34,
child: IconButton(
style: ButtonStyle(
padding:
MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith((states) {
return Theme.of(context)
.highlightColor
.withOpacity(0.2);
}),
),
onPressed: () {
showBottomSheet(
context: context,
enableDrag: true,
builder: (BuildContext context) {
return IntroDetail(
videoDetail: widget.videoDetail!);
},
);
},
icon: const Icon(Icons.more_horiz),
), ),
), ),
const SizedBox(width: 10),
], ],
), ),
), ),
// 简介 默认收起 const SizedBox(height: 6),
if (!widget.loadingStatus) Row(
ExpandedSection( children: [
expand: isExpand, const SizedBox(width: 2),
begin: 0.0, StatView(
end: 1.0, theme: 'black',
child: DefaultTextStyle( view: !widget.loadingStatus
style: TextStyle( ? widget.videoDetail!.stat!.view
color: Theme.of(context).colorScheme.outline, : videoItem['stat'].view,
height: 1.5, size: 'medium',
fontSize: ),
Theme.of(context).textTheme.labelMedium?.fontSize, const SizedBox(width: 10),
), StatDanMu(
child: Padding( theme: 'black',
padding: const EdgeInsets.only(bottom: 10), danmu: !widget.loadingStatus
child: SelectableRegion( ? widget.videoDetail!.stat!.danmaku
magnifierConfiguration: : videoItem['stat'].danmaku,
const TextMagnifierConfiguration(), size: 'medium',
focusNode: FocusNode(), ),
selectionControls: MaterialTextSelectionControls(), const SizedBox(width: 10),
child: Column( Text(
crossAxisAlignment: CrossAxisAlignment.start, Utils.dateFormat(
children: [ !widget.loadingStatus
Text(widget.videoDetail!.bvid!), ? widget.videoDetail!.pubdate
Text.rich( : videoItem['pubdate'],
TextSpan( formatType: 'detail'),
children: [ style: const TextStyle(fontSize: 12),
buildContent( ),
context, widget.videoDetail!), ],
], ),
), // 点赞收藏转发
), Padding(
], padding: const EdgeInsets.only(top: 15),
), child: SingleChildScrollView(
), scrollDirection: Axis.horizontal,
), child: actionRow(
context,
videoIntroController,
videoDetailCtr,
), ),
), ),
const SizedBox(height: 8), ),
// 点赞收藏转发
_actionGrid(context, videoIntroController, videoDetailCtr),
// 合集 // 合集
if (!widget.loadingStatus && if (!widget.loadingStatus &&
widget.videoDetail!.ugcSeason != null) ...[ widget.videoDetail!.ugcSeason != null) ...[
seasonPanel(widget.videoDetail!.ugcSeason!, seasonPanel(widget.videoDetail!.ugcSeason!,
widget.videoDetail!.pages!.first.cid, sheetHeight) widget.videoDetail!.pages!.first.cid, sheetHeight)
], ],
Divider( // Divider(
height: 26, // height: 26,
color: Theme.of(context).dividerColor.withOpacity(0.1), // color: Theme.of(context).dividerColor.withOpacity(0.1),
), // ),
const SizedBox(height: 20),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
int mid = !widget.loadingStatus int mid = !widget.loadingStatus
@ -293,53 +257,84 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}, },
child: Row( child: Row(
children: [ children: [
const SizedBox(width: 5),
NetworkImgLayer( NetworkImgLayer(
type: 'avatar', type: 'avatar',
src: !widget.loadingStatus src: !widget.loadingStatus
? widget.videoDetail!.owner!.face ? widget.videoDetail!.owner!.face
: videoItem['owner'].face, : videoItem['owner'].face,
width: 38, width: 34,
height: 38, height: 34,
fadeInDuration: Duration.zero, fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero, fadeOutDuration: Duration.zero,
), ),
const SizedBox(width: 14), const SizedBox(width: 10),
Column( Text(
crossAxisAlignment: CrossAxisAlignment.start, !widget.loadingStatus
children: [ ? widget.videoDetail!.owner!.name
Text(!widget.loadingStatus : videoItem['owner'].name,
? widget.videoDetail!.owner!.name style: const TextStyle(fontSize: 13),
: videoItem['owner'].name), ),
// const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
widget.loadingStatus widget.loadingStatus
? '- 粉丝' ? '- 粉丝'
: '${Utils.numFormat(videoIntroController.userStat['follower'])}粉丝', : Utils.numFormat(
style: TextStyle( videoIntroController.userStat['follower']),
fontSize: Theme.of(context) style: TextStyle(
.textTheme fontSize: Theme.of(context)
.labelSmall! .textTheme
.fontSize, .labelSmall!
color: Theme.of(context).colorScheme.outline), .fontSize,
), color: Theme.of(context).colorScheme.outline),
],
), ),
const Spacer(), const Spacer(),
AnimatedOpacity( AnimatedOpacity(
opacity: widget.loadingStatus ? 0 : 1, opacity: widget.loadingStatus ? 0 : 1,
duration: const Duration(milliseconds: 150), duration: const Duration(milliseconds: 150),
child: SizedBox( child: SizedBox(
height: 36, height: 32,
child: Obx( child: Obx(
() => videoIntroController.followStatus.isNotEmpty () => videoIntroController.followStatus.isNotEmpty
? ElevatedButton( ? TextButton(
onPressed: () => videoIntroController onPressed: () => videoIntroController
.actionRelationMod(), .actionRelationMod(),
child: Text(videoIntroController style: TextButton.styleFrom(
.followStatus['attribute'] == padding: const EdgeInsets.only(
0 left: 8, right: 8),
? '关注' foregroundColor:
: '已关注'), videoIntroController.followStatus[
'attribute'] !=
0
? Theme.of(context)
.colorScheme
.outline
: Theme.of(context)
.colorScheme
.onPrimary,
backgroundColor:
videoIntroController.followStatus[
'attribute'] !=
0
? Theme.of(context)
.colorScheme
.onInverseSurface
: Theme.of(context)
.colorScheme
.primary, // 设置按钮背景色
),
child: Text(
videoIntroController.followStatus[
'attribute'] !=
0
? '已关注'
: '关注',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
),
) )
: ElevatedButton( : ElevatedButton(
onPressed: () => videoIntroController onPressed: () => videoIntroController
@ -349,110 +344,87 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
), ),
), ),
), ),
const SizedBox(width: 4)
], ],
), ),
), ),
const SizedBox(height: 12),
Divider( // Divider(
height: 26, // height: 12,
color: Theme.of(context).dividerColor.withOpacity(0.1), // color: Theme.of(context).dividerColor.withOpacity(0.1),
), // ),
// const SizedBox(height: 10),
], ],
) )
: const Center(child: CircularProgressIndicator()), : const SizedBox(
height: 100,
child: Center(
child: CircularProgressIndicator(),
),
),
), ),
); );
} }
// 喜欢 投币 分享 Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
Widget _actionGrid( return Row(children: [
BuildContext context, videoIntroController, videoDetailCtr) { Obx(
return LayoutBuilder(builder: (context, constraints) { () => ActionRowItem(
return SizedBox( icon: const Icon(FontAwesomeIcons.thumbsUp),
height: constraints.maxWidth / 5 * 0.8, onTap: () => videoIntroController.actionLikeVideo(),
child: Material( selectStatus: videoIntroController.hasLike.value,
child: GridView.count( loadingStatus: widget.loadingStatus,
primary: false, text: !widget.loadingStatus
padding: const EdgeInsets.all(0), ? widget.videoDetail!.stat!.like!.toString()
crossAxisCount: 5, : '-',
childAspectRatio: 1.25,
children: <Widget>[
// InkWell(
// onTap: () => videoIntroController.actionOneThree(),
// borderRadius: StyleString.mdRadius,
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Image.asset(
// 'assets/images/logo/logo_big.png',
// width: 10,
// height: 10,
// ),
// ),
// ),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: () => videoIntroController.actionLikeVideo(),
selectStatus: videoIntroController.hasLike.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.like!.toString()
: '-'),
),
// ActionItem(
// icon: const Icon(FontAwesomeIcons.thumbsDown),
// selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
// onTap: () => {},
// selectStatus: false,
// loadingStatus: widget.loadingStatus,
// text: '不喜欢'),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: () => videoIntroController.actionCoinVideo(),
selectStatus: videoIntroController.hasCoin.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.coin!.toString()
: '-'),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.heart),
selectIcon: const Icon(FontAwesomeIcons.heartCircleCheck),
onTap: () => showFavBottomSheet(),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.favorite!.toString()
: '-'),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.share!.toString()
: '-'),
ActionItem(
icon: const Icon(FontAwesomeIcons.comments),
onTap: () {
videoDetailCtr.tabCtr.animateTo(1);
},
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.reply!.toString()
: '-'),
],
),
), ),
); ),
}); const SizedBox(width: 8),
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.b),
onTap: () => videoIntroController.actionCoinVideo(),
selectStatus: videoIntroController.hasCoin.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.coin!.toString()
: '-',
),
),
const SizedBox(width: 8),
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.heart),
onTap: () => showFavBottomSheet(),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.favorite!.toString()
: '-',
),
),
const SizedBox(width: 8),
ActionRowItem(
icon: const Icon(FontAwesomeIcons.comment),
onTap: () {
videoDetailCtr.tabCtr.animateTo(1);
},
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.videoDetail!.stat!.reply!.toString()
: '-',
),
const SizedBox(width: 8),
ActionRowItem(
icon: const Icon(FontAwesomeIcons.share),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: widget.loadingStatus,
// text: !widget.loadingStatus
// ? widget.videoDetail!.stat!.share!.toString()
// : '-',
text: '转发'),
]);
} }
InlineSpan buildContent(BuildContext context, content) { InlineSpan buildContent(BuildContext context, content) {
@ -491,54 +463,3 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
return TextSpan(children: spanChilds); return TextSpan(children: spanChilds);
} }
} }
class ActionItem extends StatelessWidget {
Icon? icon;
Icon? selectIcon;
Function? onTap;
bool? loadingStatus;
String? text;
bool selectStatus = false;
ActionItem({
Key? key,
this.icon,
this.selectIcon,
this.onTap,
this.loadingStatus,
this.text,
required this.selectStatus,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onTap!(),
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 4),
selectStatus
? Icon(selectIcon!.icon!,
size: 21, color: Theme.of(context).primaryColor)
: Icon(icon!.icon!,
size: 21, color: Theme.of(context).colorScheme.outline),
const SizedBox(height: 4),
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: Text(
text ?? '',
style: TextStyle(
color: selectStatus
? Theme.of(context).primaryColor
: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelSmall?.fontSize),
),
),
],
),
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
class ActionItem extends StatelessWidget {
final Icon? icon;
final Icon? selectIcon;
final Function? onTap;
final bool? loadingStatus;
final String? text;
final bool selectStatus;
const ActionItem({
Key? key,
this.icon,
this.selectIcon,
this.onTap,
this.loadingStatus,
this.text,
this.selectStatus = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onTap!(),
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 4),
selectStatus
? Icon(selectIcon!.icon!,
size: 21, color: Theme.of(context).primaryColor)
: Icon(icon!.icon!,
size: 21, color: Theme.of(context).colorScheme.outline),
const SizedBox(height: 4),
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: Text(
text ?? '',
style: TextStyle(
color: selectStatus
? Theme.of(context).primaryColor
: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelSmall?.fontSize),
),
),
],
),
);
}
}

View File

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
class ActionRowItem extends StatelessWidget {
final Icon? icon;
final Icon? selectIcon;
final Function? onTap;
final bool? loadingStatus;
final String? text;
final bool selectStatus;
const ActionRowItem({
Key? key,
this.icon,
this.selectIcon,
this.onTap,
this.loadingStatus,
this.text,
this.selectStatus = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
color: selectStatus
? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.6)
: Theme.of(context).highlightColor.withOpacity(0.2),
borderRadius: const BorderRadius.all(Radius.circular(30)),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onTap!(),
child: Padding(
padding: const EdgeInsets.fromLTRB(13, 6.5, 15, 6.3),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon!.icon!,
size: 13,
color: selectStatus
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSecondaryContainer),
const SizedBox(width: 6),
],
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: Text(
text ?? '',
key: ValueKey<String>(text ?? ''),
style: TextStyle(
color: selectStatus
? Theme.of(context).colorScheme.primary
: null,
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize),
),
),
),
],
),
),
),
);
}
}

View File

@ -5,8 +5,8 @@ import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
class FavPanel extends StatefulWidget { class FavPanel extends StatefulWidget {
var ctr; final dynamic ctr;
FavPanel({this.ctr}); const FavPanel({super.key, this.ctr});
@override @override
State<FavPanel> createState() => _FavPanelState(); State<FavPanel> createState() => _FavPanelState();
@ -111,7 +111,7 @@ class _FavPanelState extends State<FavPanel> {
} }
} else { } else {
// 骨架屏 // 骨架屏
return Text('请求中'); return const Text('请求中');
} }
}, },
), ),

View File

@ -0,0 +1,149 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
Box localCache = GStrorage.localCache;
late double sheetHeight;
class IntroDetail extends StatelessWidget {
final dynamic videoDetail;
const IntroDetail({
Key? key,
this.videoDetail,
}) : super(key: key);
@override
Widget build(BuildContext context) {
sheetHeight = localCache.get('sheetHeight');
return Container(
color: Theme.of(context).colorScheme.background,
padding: const EdgeInsets.only(left: 14, right: 14),
height: sheetHeight,
child: Column(
children: [
Container(
height: 35,
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onSecondaryContainer
.withOpacity(0.5),
borderRadius: const BorderRadius.all(Radius.circular(3))),
),
),
),
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
videoDetail!.title,
style: const TextStyle(
fontSize: 18,
letterSpacing: 0.5,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 10),
Row(
children: [
const SizedBox(width: 2),
StatView(
theme: 'black',
view: videoDetail!.stat!.view,
size: 'medium',
),
const SizedBox(width: 10),
StatDanMu(
theme: 'black',
danmu: videoDetail!.stat!.danmaku,
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(videoDetail!.pubdate,
formatType: 'detail'),
style: const TextStyle(fontSize: 12),
),
],
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: SelectableRegion(
magnifierConfiguration:
const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(videoDetail!.bvid!),
const SizedBox(height: 4),
Text.rich(
TextSpan(
children: [
buildContent(context, videoDetail!),
],
),
),
],
),
),
),
],
),
),
)
],
));
}
InlineSpan buildContent(BuildContext context, content) {
String desc = content.desc;
List descV2 = content.descV2;
// type
// 1 普通文本
// 2 @用户
List<InlineSpan> spanChilds = [];
if (descV2.isNotEmpty) {
for (var i = 0; i < descV2.length; i++) {
if (descV2[i].type == 1) {
spanChilds.add(TextSpan(text: descV2[i].rawText));
} else if (descV2[i].type == 2) {
spanChilds.add(
TextSpan(
text: '@${descV2[i].rawText}',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
recognizer: TapGestureRecognizer()
..onTap = () {
String heroTag = Utils.makeHeroTag(descV2[i].bizId);
Get.toNamed(
'/member?mid=${descV2[i].bizId}',
arguments: {'face': '', 'heroTag': heroTag},
);
},
),
);
}
}
} else {
spanChilds.add(TextSpan(text: desc));
}
return TextSpan(children: spanChilds);
}
}

View File

@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
class MenuRow extends StatelessWidget {
final bool? loadingStatus;
const MenuRow({
Key? key,
this.loadingStatus,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: Theme.of(context).colorScheme.background,
padding: const EdgeInsets.only(top: 9, bottom: 9, left: 12),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(children: [
actionRowLineItem(
context,
() => {},
loadingStatus,
'推荐',
selectStatus: true,
),
const SizedBox(width: 8),
actionRowLineItem(
context,
() => {},
loadingStatus,
'弹幕',
selectStatus: false,
),
const SizedBox(width: 8),
actionRowLineItem(
context,
() => {},
loadingStatus,
'评论列表',
selectStatus: false,
),
const SizedBox(width: 8),
actionRowLineItem(
context,
() => {},
loadingStatus,
'播放列表',
selectStatus: false,
),
]),
),
);
}
Widget actionRowLineItem(
context, Function? onTap, bool? loadingStatus, String? text,
{bool selectStatus = false}) {
return Material(
color: selectStatus
? Theme.of(context).highlightColor.withOpacity(0.2)
: Colors.transparent,
borderRadius: const BorderRadius.all(Radius.circular(30)),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onTap!(),
child: Container(
padding: const EdgeInsets.fromLTRB(13, 5.5, 13, 5.5),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(30)),
border: Border.all(
color: selectStatus
? Colors.transparent
: Theme.of(context).highlightColor.withOpacity(0.2),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedOpacity(
opacity: loadingStatus! ? 0 : 1,
duration: const Duration(milliseconds: 200),
child: Text(
text!,
style: const TextStyle(fontSize: 13),
),
),
],
),
),
),
);
}
}

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/models/video_detail_res.dart';
Widget seasonPanel(UgcSeason ugcSeason, cid, sheetHeight) { Widget seasonPanel(UgcSeason ugcSeason, cid, sheetHeight) {
@ -75,21 +74,28 @@ Widget seasonPanel(UgcSeason ugcSeason, cid, sheetHeight) {
), ),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
'合集:${ugcSeason.title!}', '合集:${ugcSeason.title!}',
style: Theme.of(context).textTheme.labelMedium,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
const SizedBox(width: 15), const SizedBox(width: 15),
Image.asset(
'assets/images/live.gif',
color: Theme.of(context).colorScheme.primary,
height: 11,
),
const SizedBox(width: 4),
Text( Text(
'${currentIndex + 1} / ${ugcSeason.epCount}', '${currentIndex + 1} / ${ugcSeason.epCount}',
style: Theme.of(context).textTheme.labelSmall, style: Theme.of(context).textTheme.labelMedium,
), ),
const SizedBox(width: 2), const SizedBox(width: 6),
const Icon( const Icon(
Icons.arrow_forward_ios_outlined, Icons.arrow_forward_ios_outlined,
size: 13, size: 13,

View File

@ -114,6 +114,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
_videoReplyController.currentPage = 0; _videoReplyController.currentPage = 0;
@ -130,8 +131,18 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
floating: true, floating: true,
delegate: _MySliverPersistentHeaderDelegate( delegate: _MySliverPersistentHeaderDelegate(
child: Container( child: Container(
color: Theme.of(context).colorScheme.background, height: 45,
padding: const EdgeInsets.fromLTRB(12, 6, 10, 6), padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
border: Border(
bottom: BorderSide(
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.1)),
),
),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -155,9 +166,11 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
child: TextButton.icon( child: TextButton.icon(
onPressed: () => onPressed: () =>
_videoReplyController.queryBySort(), _videoReplyController.queryBySort(),
icon: const Icon(Icons.sort, size: 17), icon: const Icon(Icons.sort, size: 15),
label: Obx(() => Text( label: Obx(() => Text(
_videoReplyController.sortTypeLabel.value)), _videoReplyController.sortTypeLabel.value,
style: const TextStyle(fontSize: 12),
)),
), ),
) )
], ],

View File

@ -12,21 +12,21 @@ import 'package:pilipala/utils/utils.dart';
import 'zan.dart'; import 'zan.dart';
class ReplyItem extends StatelessWidget { class ReplyItem extends StatelessWidget {
ReplyItem({ const ReplyItem({
super.key,
this.replyItem, this.replyItem,
this.addReply, this.addReply,
this.replyLevel, this.replyLevel,
this.showReplyRow, this.showReplyRow = true,
this.replyReply, this.replyReply,
this.replyType, this.replyType,
}); Key? key,
ReplyItemModel? replyItem; }) : super(key: key);
Function? addReply; final ReplyItemModel? replyItem;
String? replyLevel; final Function? addReply;
bool? showReplyRow = true; final String? replyLevel;
Function? replyReply; final bool? showReplyRow;
ReplyType? replyType; final Function? replyReply;
final ReplyType? replyType;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -108,8 +108,7 @@ class ReplyItem extends StatelessWidget {
replyItem!.member!.vip!['vipStatus'] > 0 replyItem!.member!.vip!['vipStatus'] > 0
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline, : Theme.of(context).colorScheme.outline,
fontSize: fontSize: 13,
Theme.of(context).textTheme.titleSmall!.fontSize,
), ),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
@ -118,7 +117,7 @@ class ReplyItem extends StatelessWidget {
height: 11, height: 11,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
if (replyItem!.isUp!) UpTag(), if (replyItem!.isUp!) const UpTag(),
], ],
), ),
Positioned( Positioned(
@ -186,7 +185,7 @@ class ReplyItem extends StatelessWidget {
TextSpan( TextSpan(
children: [ children: [
if (replyItem!.isTop!) if (replyItem!.isTop!)
WidgetSpan(child: UpTag(tagText: 'TOP')), const WidgetSpan(child: UpTag(tagText: 'TOP')),
buildContent(context, replyItem!, replyReply, null), buildContent(context, replyItem!, replyReply, null),
], ],
), ),
@ -204,7 +203,7 @@ class ReplyItem extends StatelessWidget {
child: ReplyItemRow( child: ReplyItemRow(
replies: replyItem!.replies, replies: replyItem!.replies,
replyControl: replyItem!.replyControl, replyControl: replyItem!.replyControl,
f_rpid: replyItem!.rpid, // f_rpid: replyItem!.rpid,
replyItem: replyItem, replyItem: replyItem,
replyReply: replyReply, replyReply: replyReply,
), ),
@ -216,7 +215,6 @@ class ReplyItem extends StatelessWidget {
// 感谢、回复、复制 // 感谢、回复、复制
Widget bottonAction(context, replyControl) { Widget bottonAction(context, replyControl) {
var color = Theme.of(context).colorScheme.outline;
return Row( return Row(
children: [ children: [
const SizedBox(width: 48), const SizedBox(width: 48),
@ -297,13 +295,13 @@ class ReplyItemRow extends StatelessWidget {
super.key, super.key,
this.replies, this.replies,
this.replyControl, this.replyControl,
this.f_rpid, // this.f_rpid,
this.replyItem, this.replyItem,
this.replyReply, this.replyReply,
}); });
List? replies; List? replies;
ReplyControl? replyControl; ReplyControl? replyControl;
int? f_rpid; // int? f_rpid;
ReplyItemModel? replyItem; ReplyItemModel? replyItem;
Function? replyReply; Function? replyReply;
@ -361,7 +359,7 @@ class ReplyItemRow extends StatelessWidget {
}, },
), ),
if (replies![i].isUp) if (replies![i].isUp)
WidgetSpan( const WidgetSpan(
child: UpTag(), child: UpTag(),
), ),
buildContent( buildContent(
@ -439,16 +437,14 @@ InlineSpan buildContent(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => { ..onTap = () => Get.toNamed(
Get.toNamed( '/webview',
'/webview', parameters: {
parameters: { 'url': content.vote['url'],
'url': content.vote['url'], 'type': 'vote',
'type': 'vote', 'pageTitle': content.vote['title'],
'pageTitle': content.vote['title'], },
}, ),
)
},
), ),
); );
return ''; return '';
@ -554,11 +550,9 @@ InlineSpan buildContent(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => { ..onTap = () => Get.toNamed('/searchResult', parameters: {
Get.toNamed('/searchResult', parameters: { 'keyword': content.jumpUrl[matchStr]['title']
'keyword': content.jumpUrl[matchStr]['title'] }),
})
},
), ),
); );
spanChilds.add( spanChilds.add(
@ -718,16 +712,14 @@ InlineSpan buildContent(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => { ..onTap = () => Get.toNamed(
Get.toNamed( '/webview',
'/webview', parameters: {
parameters: { 'url': content.richText['note']['click_url'],
'url': content.richText['note']['click_url'], 'type': 'note',
'type': 'note', 'pageTitle': '笔记预览'
'pageTitle': '笔记预览' },
}, ),
)
},
), ),
); );
} }
@ -736,8 +728,8 @@ InlineSpan buildContent(
} }
class UpTag extends StatelessWidget { class UpTag extends StatelessWidget {
String? tagText; final String? tagText;
UpTag({super.key, this.tagText = 'UP'}); const UpTag({super.key, this.tagText = 'UP'});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color primary = Theme.of(context).colorScheme.primary; Color primary = Theme.of(context).colorScheme.primary;

View File

@ -6,13 +6,13 @@ import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/models/video/reply/item.dart';
class ZanButton extends StatefulWidget { class ZanButton extends StatefulWidget {
ZanButton({ const ZanButton({
super.key, super.key,
this.replyItem, this.replyItem,
this.replyType, this.replyType,
}); });
ReplyItemModel? replyItem; final ReplyItemModel? replyItem;
final ReplyType? replyType; final ReplyType? replyType;
@override @override

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -10,13 +9,14 @@ import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
class VideoReplyNewDialog extends StatefulWidget { class VideoReplyNewDialog extends StatefulWidget {
int? oid; final int? oid;
int? root; final int? root;
int? parent; final int? parent;
ReplyType? replyType; final ReplyType? replyType;
ReplyItemModel? replyItem; final ReplyItemModel? replyItem;
VideoReplyNewDialog({ const VideoReplyNewDialog({
super.key,
this.oid, this.oid,
this.root, this.root,
this.parent, this.parent,
@ -56,7 +56,9 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
_autoFocus() async { _autoFocus() async {
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
FocusScope.of(context).requestFocus(replyContentFocusNode); if (context.mounted) {
FocusScope.of(context).requestFocus(replyContentFocusNode);
}
} }
_printLatestValue() { _printLatestValue() {
@ -91,9 +93,8 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
super.didChangeMetrics(); super.didChangeMetrics();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
// 键盘高度 // 键盘高度
final viewInsets = EdgeInsets.fromWindowPadding( final viewInsets = EdgeInsets.fromViewPadding(
WidgetsBinding.instance.window.viewInsets, View.of(context).viewInsets, View.of(context).devicePixelRatio);
WidgetsBinding.instance.window.devicePixelRatio);
_debouncer.run(() { _debouncer.run(() {
if (mounted) { if (mounted) {
setState(() { setState(() {
@ -197,7 +198,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
} }
} }
typedef void DebounceCallback(); typedef DebounceCallback = void Function();
class Debouncer { class Debouncer {
DebounceCallback? callback; DebounceCallback? callback;

View File

@ -81,7 +81,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
if (widget.source == 'videoDetail') if (widget.source == 'videoDetail')
Container( Container(
height: 45, height: 45,
padding: const EdgeInsets.only(left: 14, right: 14), padding: const EdgeInsets.only(left: 12, right: 2),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View File

@ -5,6 +5,8 @@ import 'package:flutter_meedu_media_kit/meedu_player.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/sliver_header.dart';
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
import 'package:pilipala/pages/video/detail/reply/index.dart'; import 'package:pilipala/pages/video/detail/reply/index.dart';
import 'package:pilipala/pages/video/detail/controller.dart'; import 'package:pilipala/pages/video/detail/controller.dart';
import 'package:pilipala/pages/video/detail/introduction/index.dart'; import 'package:pilipala/pages/video/detail/introduction/index.dart';
@ -171,8 +173,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
controller: _meeduPlayerController!, controller: _meeduPlayerController!,
header: (BuildContext context, header: (BuildContext context,
MeeduPlayerController MeeduPlayerController
_meeduPlayerController, meeduPlayerController,
Responsive) { Responsive responsive) {
return AppBar( return AppBar(
toolbarHeight: 40, toolbarHeight: 40,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@ -230,7 +232,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
children: [ children: [
Container( Container(
width: double.infinity, width: double.infinity,
height: 45, height: 0,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
@ -250,8 +252,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
() => TabBar( () => TabBar(
controller: videoDetailController.tabCtr, controller: videoDetailController.tabCtr,
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
// indicatorColor: indicatorColor:
// Theme.of(context).colorScheme.background, Theme.of(context).colorScheme.background,
tabs: videoDetailController.tabs tabs: videoDetailController.tabs
.map((String name) => Tab(text: name)) .map((String name) => Tab(text: name))
.toList(), .toList(),
@ -267,11 +269,20 @@ class _VideoDetailPageState extends State<VideoDetailPage>
children: [ children: [
Builder( Builder(
builder: (context) { builder: (context) {
return const CustomScrollView( return CustomScrollView(
key: PageStorageKey<String>('简介'), key: const PageStorageKey<String>('简介'),
slivers: <Widget>[ slivers: <Widget>[
VideoIntroPanel(), const VideoIntroPanel(),
RelatedVideoPanel(), SliverPersistentHeader(
floating: true,
pinned: true,
delegate: SliverHeaderDelegate(
height: 50,
child:
const MenuRow(loadingStatus: false),
),
),
const RelatedVideoPanel(),
], ],
); );
}, },
@ -296,6 +307,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
snapshot.data!.toDouble(), snapshot.data!.toDouble(),
continuePlay, continuePlay,
playerStatus, playerStatus,
null,
); );
}), }),
) )

View File

@ -3,14 +3,15 @@ import 'package:flutter_meedu_media_kit/meedu_player.dart';
class ScrollAppBar extends StatelessWidget { class ScrollAppBar extends StatelessWidget {
final double scrollVal; final double scrollVal;
Function callback; final Function callback;
final PlayerStatus playerStatus; final PlayerStatus playerStatus;
ScrollAppBar( const ScrollAppBar(
this.scrollVal, this.scrollVal,
this.callback, this.callback,
this.playerStatus, this.playerStatus,
); Key? key,
) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,16 +1,20 @@
// ignore_for_file: library_private_types_in_public_api
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ExpandedSection extends StatefulWidget { class ExpandedSection extends StatefulWidget {
final Widget child; final Widget? child;
final bool expand; final bool expand;
double begin = 0.0; final double begin;
double end = 1.0; final double end;
ExpandedSection( const ExpandedSection({
{this.expand = false, super.key,
required this.child, this.expand = false,
required this.begin, this.child,
required this.end}); this.begin = 0.0,
this.end = 1.0,
});
@override @override
_ExpandedSectionState createState() => _ExpandedSectionState(); _ExpandedSectionState createState() => _ExpandedSectionState();

View File

@ -1,3 +1,5 @@
// ignore_for_file: avoid_print
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -5,7 +7,6 @@ import 'package:pilipala/http/constants.dart';
import 'package:pilipala/http/init.dart'; import 'package:pilipala/http/init.dart';
import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/user.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/pages/rcmd/controller.dart'; import 'package:pilipala/pages/rcmd/controller.dart';
import 'package:pilipala/utils/cookie.dart'; import 'package:pilipala/utils/cookie.dart';

View File

@ -67,6 +67,6 @@ class Routes {
// 用户中心 // 用户中心
GetPage(name: '/member', page: () => const MemberPage()), GetPage(name: '/member', page: () => const MemberPage()),
// 二级回复 // 二级回复
GetPage(name: '/replyReply', page: () => VideoReplyReplyPanel()), GetPage(name: '/replyReply', page: () => const VideoReplyReplyPanel()),
]; ];
} }

View File

@ -1,3 +1,5 @@
// ignore_for_file: constant_identifier_names
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -1,4 +1,6 @@
// 工具函数 // 工具函数
// ignore_for_file: non_constant_identifier_names
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
@ -52,7 +54,7 @@ class Utils {
// 当前时间 // 当前时间
int time = (DateTime.now().millisecondsSinceEpoch / 1000).round(); int time = (DateTime.now().millisecondsSinceEpoch / 1000).round();
// 对比 // 对比
int _distance = (time - timeStamp).toInt(); int distance = (time - timeStamp).toInt();
// 当前年日期 // 当前年日期
String currentYearStr = 'MM月DD日 hh:mm'; String currentYearStr = 'MM月DD日 hh:mm';
String lastYearStr = 'YY年MM月DD日 hh:mm'; String lastYearStr = 'YY年MM月DD日 hh:mm';
@ -65,12 +67,12 @@ class Utils {
toInt: false, toInt: false,
formatType: formatType); formatType: formatType);
} }
if (_distance <= 60) { if (distance <= 60) {
return '刚刚'; return '刚刚';
} else if (_distance <= 3600) { } else if (distance <= 3600) {
return '${(_distance / 60).floor()}分钟前'; return '${(distance / 60).floor()}分钟前';
} else if (_distance <= 43200) { } else if (distance <= 43200) {
return '${(_distance / 60 / 60).floor()}小时前'; return '${(distance / 60 / 60).floor()}小时前';
} else if (DateTime.fromMillisecondsSinceEpoch(time * 1000).year == } else if (DateTime.fromMillisecondsSinceEpoch(time * 1000).year ==
DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000).year) { DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000).year) {
return CustomStamp_str( return CustomStamp_str(