mod: 图标修改
This commit is contained in:
129
lib/common/widgets/pull_to_refresh_header.dart
Normal file
129
lib/common/widgets/pull_to_refresh_header.dart
Normal file
@ -0,0 +1,129 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui show Image;
|
||||
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
|
||||
|
||||
double get maxDragOffset => 100;
|
||||
double hideHeight = maxDragOffset / 2.3;
|
||||
double refreshHeight = maxDragOffset / 1.5;
|
||||
|
||||
class PullToRefreshHeader extends StatelessWidget {
|
||||
const PullToRefreshHeader(
|
||||
this.info,
|
||||
this.lastRefreshTime, {
|
||||
this.color,
|
||||
});
|
||||
|
||||
final PullToRefreshScrollNotificationInfo? info;
|
||||
final DateTime? lastRefreshTime;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final PullToRefreshScrollNotificationInfo? _info = info;
|
||||
if (_info == null) {
|
||||
return Container();
|
||||
}
|
||||
String text = '';
|
||||
if (_info.mode == PullToRefreshIndicatorMode.armed) {
|
||||
text = 'Release to refresh';
|
||||
} else if (_info.mode == PullToRefreshIndicatorMode.refresh ||
|
||||
_info.mode == PullToRefreshIndicatorMode.snap) {
|
||||
text = 'Loading...';
|
||||
} else if (_info.mode == PullToRefreshIndicatorMode.done) {
|
||||
text = 'Refresh completed.';
|
||||
} else if (_info.mode == PullToRefreshIndicatorMode.drag) {
|
||||
text = 'Pull to refresh';
|
||||
} else if (_info.mode == PullToRefreshIndicatorMode.canceled) {
|
||||
text = 'Cancel refresh';
|
||||
}
|
||||
|
||||
final TextStyle ts = const TextStyle(
|
||||
color: Colors.grey,
|
||||
).copyWith(fontSize: 14);
|
||||
|
||||
final double dragOffset = info?.dragOffset ?? 0.0;
|
||||
|
||||
final DateTime time = lastRefreshTime ?? DateTime.now();
|
||||
final double top = -hideHeight + dragOffset;
|
||||
return Container(
|
||||
height: dragOffset,
|
||||
color: color ?? Colors.transparent,
|
||||
// padding: EdgeInsets.only(top: dragOffset / 3),
|
||||
// padding: EdgeInsets.only(bottom: 5.0),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Positioned(
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
top: top,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.centerRight,
|
||||
child: RefreshImage(top),
|
||||
margin: const EdgeInsets.only(right: 12.0),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Text(text, style: ts),
|
||||
Text(
|
||||
'Last updated:' +
|
||||
DateFormat('yyyy-MM-dd hh:mm').format(time),
|
||||
style: ts.copyWith(fontSize: 14),
|
||||
)
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RefreshImage extends StatelessWidget {
|
||||
const RefreshImage(this.top);
|
||||
|
||||
final double top;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double imageSize = 30;
|
||||
return ExtendedImage.asset(
|
||||
'assets/flutterCandies_grey.png',
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
afterPaintImage: (Canvas canvas, Rect rect, ui.Image image, Paint paint) {
|
||||
final double imageHeight = image.height.toDouble();
|
||||
final double imageWidth = image.width.toDouble();
|
||||
final Size size = rect.size;
|
||||
final double y =
|
||||
(1 - min(top / (refreshHeight - hideHeight), 1)) * imageHeight;
|
||||
|
||||
canvas.drawImageRect(
|
||||
image,
|
||||
Rect.fromLTWH(0.0, y, imageWidth, imageHeight - y),
|
||||
Rect.fromLTWH(rect.left, rect.top + y / imageHeight * size.height,
|
||||
size.width, (imageHeight - y) / imageHeight * size.height),
|
||||
Paint()
|
||||
..colorFilter =
|
||||
const ColorFilter.mode(Color(0xFFea5504), BlendMode.srcIn)
|
||||
..isAntiAlias = false
|
||||
..filterQuality = FilterQuality.low,
|
||||
);
|
||||
|
||||
//canvas.restore();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -12,13 +12,17 @@ class StatDanMu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color =
|
||||
theme == 'white' ? Colors.white : Theme.of(context).colorScheme.outline;
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline,
|
||||
'black': Theme.of(context).colorScheme.onBackground.withOpacity(0.8),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
// CupertinoIcons.ellipses_bubble,
|
||||
Icons.subtitles_outlined,
|
||||
Icons.subtitles_sharp,
|
||||
size: 14,
|
||||
color: color,
|
||||
),
|
||||
|
@ -12,13 +12,17 @@ class StatView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color =
|
||||
theme == 'white' ? Colors.white : Theme.of(context).colorScheme.outline;
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline,
|
||||
'black': Theme.of(context).colorScheme.onBackground.withOpacity(0.8),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
// CupertinoIcons.play_rectangle,
|
||||
Icons.play_circle_outlined,
|
||||
Icons.play_circle_fill_outlined,
|
||||
size: 13,
|
||||
color: color,
|
||||
),
|
||||
|
@ -80,22 +80,22 @@ class VideoCardV extends StatelessWidget {
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
if (videoItem.stat.view is int &&
|
||||
videoItem.stat.danmaku is int)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: AnimatedOpacity(
|
||||
opacity: 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: VideoStat(
|
||||
view: videoItem.stat.view,
|
||||
danmaku: videoItem.stat.danmaku,
|
||||
duration: videoItem.duration,
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (videoItem.stat.view is int &&
|
||||
// videoItem.stat.danmaku is int)
|
||||
// Positioned(
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// bottom: 0,
|
||||
// child: AnimatedOpacity(
|
||||
// opacity: 1,
|
||||
// duration: const Duration(milliseconds: 200),
|
||||
// child: VideoStat(
|
||||
// view: videoItem.stat.view,
|
||||
// danmaku: videoItem.stat.danmaku,
|
||||
// duration: videoItem.duration,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}),
|
||||
@ -118,7 +118,7 @@ class VideoContent extends StatelessWidget {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
// 多列
|
||||
padding: const EdgeInsets.fromLTRB(4, 5, 6, 8),
|
||||
padding: const EdgeInsets.fromLTRB(4, 5, 6, 12),
|
||||
// 单列
|
||||
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
|
||||
child: Column(
|
||||
@ -136,71 +136,84 @@ class VideoContent extends StatelessWidget {
|
||||
maxLines: Get.find<RcmdController>().crossAxisCount,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(
|
||||
height: 18,
|
||||
child: Row(
|
||||
children: [
|
||||
if (videoItem.rcmdReason != null &&
|
||||
videoItem.rcmdReason.content != '') ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(3, 1, 3, 1),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer
|
||||
.withOpacity(0.6),
|
||||
borderRadius: BorderRadius.circular(3)),
|
||||
child: Text(
|
||||
videoItem.rcmdReason.content,
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4)
|
||||
] 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(
|
||||
videoItem.owner.name,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
// SizedBox(
|
||||
// height: 18,
|
||||
// child: Row(
|
||||
// children: [
|
||||
// if (videoItem.rcmdReason != null &&
|
||||
// videoItem.rcmdReason.content != '') ...[
|
||||
// Container(
|
||||
// padding: const EdgeInsets.fromLTRB(3, 1, 3, 1),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Theme.of(context)
|
||||
// .colorScheme
|
||||
// .primaryContainer
|
||||
// .withOpacity(0.6),
|
||||
// borderRadius: BorderRadius.circular(3)),
|
||||
// child: Text(
|
||||
// videoItem.rcmdReason.content,
|
||||
// style: TextStyle(
|
||||
// fontSize:
|
||||
// Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 4)
|
||||
// ] 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(
|
||||
// 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -203,4 +203,19 @@ class Api {
|
||||
|
||||
// 用户名片信息
|
||||
static const String memberCardInfo = '/x/web-interface/card';
|
||||
|
||||
// 用户投稿
|
||||
// https://api.bilibili.com/x/space/wbi/arc/search?
|
||||
// mid=85754245&
|
||||
// ps=30&
|
||||
// tid=0&
|
||||
// pn=1&
|
||||
// keyword=&
|
||||
// order=pubdate&
|
||||
// platform=web&
|
||||
// web_location=1550101&
|
||||
// order_avoided=true&
|
||||
// w_rid=d893cf98a4e010cf326373194a648360&
|
||||
// wts=1689767832
|
||||
static const String memberArchive = '/x/space/wbi/arc/search';
|
||||
}
|
||||
|
@ -1,9 +1,23 @@
|
||||
import 'package:pilipala/http/index.dart';
|
||||
import 'package:pilipala/models/member/archive.dart';
|
||||
import 'package:pilipala/models/member/info.dart';
|
||||
import 'package:pilipala/utils/wbi_sign.dart';
|
||||
|
||||
class MemberHttp {
|
||||
static Future memberInfo({String? params}) async {
|
||||
var res = await Request().get(Api.memberInfo + params!);
|
||||
static Future memberInfo({
|
||||
int? mid,
|
||||
String token = '',
|
||||
}) async {
|
||||
Map params = await WbiSign().makSign({
|
||||
'mid': mid,
|
||||
'token': token,
|
||||
'platform': 'web',
|
||||
'web_location': 1550101,
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.memberInfo,
|
||||
data: params,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
@ -44,4 +58,42 @@ class MemberHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future memberArchive({
|
||||
int? mid,
|
||||
int ps = 30,
|
||||
int tid = 0,
|
||||
int? pn,
|
||||
String keyword = '',
|
||||
String order = 'pubdate',
|
||||
bool orderAvoided = true,
|
||||
}) async {
|
||||
Map params = await WbiSign().makSign({
|
||||
'mid': mid,
|
||||
'ps': ps,
|
||||
'tid': tid,
|
||||
'pn': pn,
|
||||
'keyword': keyword,
|
||||
'order': order,
|
||||
'platform': 'web',
|
||||
'web_location': 1550101,
|
||||
'order_avoided': orderAvoided
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.memberArchive,
|
||||
data: params,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MemberArchiveDataModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class ReplyHttp {
|
||||
Map errMap = {
|
||||
-400: '请求错误',
|
||||
-404: '无此项',
|
||||
12002: '评论区已关闭',
|
||||
12002: '当前页面评论功能已关闭"',
|
||||
12009: '评论主体的type不合法',
|
||||
12061: 'UP主已关闭评论区',
|
||||
};
|
||||
@ -48,6 +48,7 @@ class ReplyHttp {
|
||||
'pn': pageNum,
|
||||
'type': type,
|
||||
'sort': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
|
@ -4,6 +4,8 @@ enum ReplyType {
|
||||
video,
|
||||
// 话题
|
||||
topic,
|
||||
//
|
||||
unset2,
|
||||
// 活动
|
||||
activity,
|
||||
// 小视频
|
||||
|
164
lib/models/member/archive.dart
Normal file
164
lib/models/member/archive.dart
Normal file
@ -0,0 +1,164 @@
|
||||
class MemberArchiveDataModel {
|
||||
MemberArchiveDataModel({
|
||||
this.list,
|
||||
this.page,
|
||||
});
|
||||
|
||||
ArchiveListModel? list;
|
||||
Map? page;
|
||||
|
||||
MemberArchiveDataModel.fromJson(Map<String, dynamic> json) {
|
||||
list = ArchiveListModel.fromJson(json['list']);
|
||||
page = json['page'];
|
||||
}
|
||||
}
|
||||
|
||||
class ArchiveListModel {
|
||||
ArchiveListModel({
|
||||
this.tlist,
|
||||
this.vlist,
|
||||
});
|
||||
|
||||
Map<String, TListItemModel>? tlist;
|
||||
List<VListItemModel>? vlist;
|
||||
|
||||
ArchiveListModel.fromJson(Map<String, dynamic> json) {
|
||||
tlist = json['tlist'] != null
|
||||
? Map.from(json['tlist']).map((k, v) =>
|
||||
MapEntry<String, TListItemModel>(k, TListItemModel.fromJson(v)))
|
||||
: {};
|
||||
vlist = json['vlist']
|
||||
.map<VListItemModel>((e) => VListItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class TListItemModel {
|
||||
TListItemModel({
|
||||
this.tid,
|
||||
this.count,
|
||||
this.name,
|
||||
});
|
||||
|
||||
int? tid;
|
||||
int? count;
|
||||
String? name;
|
||||
|
||||
TListItemModel.fromJson(Map<String, dynamic> json) {
|
||||
tid = json['tid'];
|
||||
count = json['count'];
|
||||
name = json['name'];
|
||||
}
|
||||
}
|
||||
|
||||
class VListItemModel {
|
||||
VListItemModel({
|
||||
this.comment,
|
||||
this.typeid,
|
||||
this.play,
|
||||
this.pic,
|
||||
this.subtitle,
|
||||
this.description,
|
||||
this.copyright,
|
||||
this.title,
|
||||
this.review,
|
||||
this.author,
|
||||
this.mid,
|
||||
this.created,
|
||||
this.pubdate,
|
||||
this.length,
|
||||
this.duration,
|
||||
this.videoReview,
|
||||
this.aid,
|
||||
this.bvid,
|
||||
this.cid,
|
||||
this.hideClick,
|
||||
this.isChargingSrc,
|
||||
this.rcmdReason,
|
||||
this.owner,
|
||||
});
|
||||
|
||||
int? comment;
|
||||
int? typeid;
|
||||
int? play;
|
||||
String? pic;
|
||||
String? subtitle;
|
||||
String? description;
|
||||
String? copyright;
|
||||
String? title;
|
||||
int? review;
|
||||
String? author;
|
||||
int? mid;
|
||||
int? created;
|
||||
int? pubdate;
|
||||
String? length;
|
||||
String? duration;
|
||||
int? videoReview;
|
||||
int? aid;
|
||||
String? bvid;
|
||||
int? cid;
|
||||
bool? hideClick;
|
||||
bool? isChargingSrc;
|
||||
Stat? stat;
|
||||
String? rcmdReason;
|
||||
Owner? owner;
|
||||
|
||||
VListItemModel.fromJson(Map<String, dynamic> json) {
|
||||
comment = json['comment'];
|
||||
typeid = json['typeid'];
|
||||
play = json['play'];
|
||||
pic = json['pic'];
|
||||
subtitle = json['subtitle'];
|
||||
description = json['description'];
|
||||
copyright = json['copyright'];
|
||||
title = json['title'];
|
||||
review = json['review'];
|
||||
author = json['author'];
|
||||
mid = json['mid'];
|
||||
created = json['created'];
|
||||
pubdate = json['created'];
|
||||
length = json['length'];
|
||||
duration = json['length'];
|
||||
videoReview = json['video_review'];
|
||||
aid = json['aid'];
|
||||
bvid = json['bvid'];
|
||||
cid = null;
|
||||
hideClick = json['hide_click'];
|
||||
isChargingSrc = json['is_charging_arc'];
|
||||
stat = Stat.fromJson(json);
|
||||
rcmdReason = null;
|
||||
owner = Owner.fromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
class Stat {
|
||||
Stat({
|
||||
this.view,
|
||||
this.danmaku,
|
||||
});
|
||||
|
||||
int? view;
|
||||
int? danmaku;
|
||||
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
view = json["play"];
|
||||
danmaku = json['comment'];
|
||||
}
|
||||
}
|
||||
|
||||
class Owner {
|
||||
Owner({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
});
|
||||
int? mid;
|
||||
String? name;
|
||||
String? face;
|
||||
|
||||
Owner.fromJson(Map<String, dynamic> json) {
|
||||
mid = json["mid"];
|
||||
name = json["author"];
|
||||
face = '';
|
||||
}
|
||||
}
|
@ -71,6 +71,7 @@ class LiveRoom {
|
||||
this.cover,
|
||||
this.roomId,
|
||||
this.roundStatus,
|
||||
this.watchedShow,
|
||||
});
|
||||
|
||||
int? roomStatus;
|
||||
@ -80,6 +81,7 @@ class LiveRoom {
|
||||
String? cover;
|
||||
int? roomId;
|
||||
int? roundStatus;
|
||||
Map? watchedShow;
|
||||
|
||||
LiveRoom.fromJson(Map<String, dynamic> json) {
|
||||
roomStatus = json['roomStatus'];
|
||||
@ -89,5 +91,6 @@ class LiveRoom {
|
||||
cover = json['cover'];
|
||||
roomId = json['roomid'];
|
||||
roundStatus = json['roundStatus'];
|
||||
watchedShow = json['watched_show'];
|
||||
}
|
||||
}
|
||||
|
@ -57,9 +57,9 @@ class DynamicsController extends GetxController {
|
||||
);
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
dynamicsList!.value = res['data'].items;
|
||||
dynamicsList.value = res['data'].items;
|
||||
} else {
|
||||
dynamicsList!.addAll(res['data'].items);
|
||||
dynamicsList.addAll(res['data'].items);
|
||||
}
|
||||
offset = res['data'].offset;
|
||||
page++;
|
||||
@ -69,7 +69,7 @@ class DynamicsController extends GetxController {
|
||||
|
||||
onSelectType(value) async {
|
||||
dynamicsType.value = filterTypeList[value - 1]['value'];
|
||||
dynamicsList!.value = [DynamicItemModel()];
|
||||
dynamicsList.value = [DynamicItemModel()];
|
||||
page = 1;
|
||||
initialValue.value = value;
|
||||
await queryFollowDynamic();
|
||||
@ -128,6 +128,7 @@ class DynamicsController extends GetxController {
|
||||
'mid': author.mid,
|
||||
'face': author.face,
|
||||
'roomid': liveRcmd.roomId,
|
||||
'watched_show': liveRcmd.watchedShow,
|
||||
});
|
||||
Get.toNamed('/liveRoom?roomid=${liveItem.roomId}', arguments: {
|
||||
'liveItem': liveItem,
|
||||
@ -151,7 +152,7 @@ class DynamicsController extends GetxController {
|
||||
|
||||
onSelectUp(mid) async {
|
||||
dynamicsType.value = DynamicsType.values[0];
|
||||
dynamicsList!.value = [DynamicItemModel()];
|
||||
dynamicsList.value = [DynamicItemModel()];
|
||||
page = 1;
|
||||
queryFollowDynamic();
|
||||
}
|
||||
|
@ -4,9 +4,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/pages/dynamics/deatil/index.dart';
|
||||
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
|
||||
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||
|
||||
import '../widgets/dynamic_panel.dart';
|
||||
|
||||
@ -25,19 +27,20 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
bool _visibleTitle = false;
|
||||
String? action;
|
||||
// 回复类型
|
||||
late int type;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
int oid = 0;
|
||||
int type = 0;
|
||||
// floor 1原创 2转发
|
||||
if (Get.arguments['floor'] == 1) {
|
||||
oid = int.parse(Get.arguments['item'].basic!['comment_id_str']);
|
||||
type = Get.arguments['item'].basic!['comment_type'];
|
||||
} else {
|
||||
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
||||
type = 11;
|
||||
}
|
||||
type = Get.arguments['item'].basic!['comment_type'];
|
||||
action =
|
||||
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
||||
_dynamicDetailController = Get.put(DynamicDetailController(oid, type));
|
||||
@ -68,6 +71,26 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||
}
|
||||
}
|
||||
|
||||
void replyReply(replyItem, paddingTop) {
|
||||
int oid = replyItem.replies!.first.oid;
|
||||
int rpid = replyItem.rpid!;
|
||||
Get.to(
|
||||
() => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('评论详情'),
|
||||
centerTitle: false,
|
||||
),
|
||||
body: VideoReplyReplyPanel(
|
||||
oid: oid,
|
||||
rpid: rpid,
|
||||
paddingTop: paddingTop,
|
||||
source: 'dynamic',
|
||||
replyType: ReplyType.values[type],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -189,10 +212,14 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||
);
|
||||
} else {
|
||||
return ReplyItem(
|
||||
replyItem: _dynamicDetailController!
|
||||
.replyList[index],
|
||||
showReplyRow: true,
|
||||
replyLevel: '1');
|
||||
replyItem:
|
||||
_dynamicDetailController!.replyList[index],
|
||||
showReplyRow: true,
|
||||
replyLevel: '1',
|
||||
replyReply: (replyItem, paddingTop) =>
|
||||
replyReply(replyItem, paddingTop),
|
||||
replyType: ReplyType.album,
|
||||
);
|
||||
}
|
||||
},
|
||||
childCount:
|
||||
@ -212,7 +239,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoReplySkeleton();
|
||||
}, childCount: 5),
|
||||
}, childCount: 8),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -234,7 +234,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
List<DynamicItemModel> list =
|
||||
_dynamicsController.dynamicsList!;
|
||||
_dynamicsController.dynamicsList;
|
||||
return Obx(
|
||||
() => list.length == 1
|
||||
? skeleton()
|
||||
|
@ -23,7 +23,7 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 15, top: 10, right: 15, bottom: 8),
|
||||
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
||||
color: bgColor,
|
||||
child: Row(
|
||||
children: [
|
||||
@ -60,90 +60,104 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
||||
),
|
||||
);
|
||||
case 'ADDITIONAL_TYPE_RESERVE':
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 8),
|
||||
padding:
|
||||
const EdgeInsets.only(left: 15, top: 12, right: 15, bottom: 10),
|
||||
color: bgColor,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(dynamicProperty[type].title),
|
||||
Text.rich(TextSpan(
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize),
|
||||
children: [
|
||||
TextSpan(text: dynamicProperty[type].desc1['text']),
|
||||
TextSpan(text: dynamicProperty[type].desc2['text']),
|
||||
]))
|
||||
],
|
||||
),
|
||||
// TextButton(onPressed: () {}, child: Text('123'))
|
||||
],
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.only(left: 12, top: 10, right: 12, bottom: 10),
|
||||
color: bgColor,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
dynamicProperty[type].title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize),
|
||||
children: [
|
||||
TextSpan(text: dynamicProperty[type].desc1['text']),
|
||||
const TextSpan(text: ' '),
|
||||
TextSpan(text: dynamicProperty[type].desc2['text']),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
// TextButton(onPressed: () {}, child: Text('123'))
|
||||
),
|
||||
),
|
||||
);
|
||||
case 'ADDITIONAL_TYPE_GOODS':
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 6),
|
||||
padding: const EdgeInsets.only(left: 15, top: 10, right: 15, bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 75,
|
||||
height: 75,
|
||||
src: dynamicProperty[type].items.first.cover,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
dynamicProperty[type].items.first.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
NetworkImgLayer(
|
||||
width: 75,
|
||||
height: 75,
|
||||
src: dynamicProperty[type].items.first.cover,
|
||||
),
|
||||
Text(
|
||||
dynamicProperty[type].items.first.brief,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
dynamicProperty[type].items.first.price,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
dynamicProperty[type].items.first.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
dynamicProperty[type].items.first.brief,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
dynamicProperty[type].items.first.price,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
));
|
||||
case 'ADDITIONAL_TYPE_MATCH':
|
||||
return SizedBox();
|
||||
case 'ADDITIONAL_TYPE_COMMON':
|
||||
return SizedBox();
|
||||
case 'ADDITIONAL_TYPE_VOTE':
|
||||
return SizedBox();
|
||||
default:
|
||||
return Text('11');
|
||||
}
|
||||
|
@ -7,24 +7,34 @@ Widget content(item, context, source) {
|
||||
TextStyle authorStyle =
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary);
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (item.modules.moduleDynamic.topic != null) ...[
|
||||
GestureDetector(
|
||||
child: Text(
|
||||
'#${item.modules.moduleDynamic.topic.name}',
|
||||
style: authorStyle,
|
||||
),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (item.modules.moduleDynamic.topic != null) ...[
|
||||
GestureDetector(
|
||||
child: Text(
|
||||
'#${item.modules.moduleDynamic.topic.name}',
|
||||
style: authorStyle,
|
||||
),
|
||||
],
|
||||
Text.rich(
|
||||
richNode(item, context),
|
||||
maxLines: source == 'detail' ? 999 : 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
));
|
||||
IgnorePointer(
|
||||
// 禁用SelectableRegion的触摸交互功能
|
||||
ignoring: source == 'detail' ? false : true,
|
||||
child: SelectableRegion(
|
||||
magnifierConfiguration: const TextMagnifierConfiguration(),
|
||||
focusNode: FocusNode(),
|
||||
selectionControls: MaterialTextSelectionControls(),
|
||||
child: Text.rich(
|
||||
richNode(item, context),
|
||||
maxLines: source == 'detail' ? 999 : 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -64,7 +64,12 @@ InlineSpan richNode(item, context) {
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {'url': i.origText, 'type': 'url', 'pageTitle': ''},
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
i.text,
|
||||
style: authorStyle,
|
||||
@ -79,7 +84,18 @@ InlineSpan richNode(item, context) {
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
String dynamicId = item.basic['comment_id_str'];
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url':
|
||||
'https://t.bilibili.com/vote/h5/index/#/result?vote_id=${i.rid}&dynamic_id=${dynamicId}&isWeb=1',
|
||||
'type': 'vote',
|
||||
'pageTitle': '投票'
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'投票:${i.text}',
|
||||
style: authorStyle,
|
||||
|
@ -7,6 +7,7 @@ import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/models/live/item.dart';
|
||||
import 'package:pilipala/pages/dynamics/controller.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class UpPanel extends StatefulWidget {
|
||||
FollowUpModel? upData;
|
||||
@ -92,8 +93,10 @@ class _UpPanelState extends State<UpPanel> {
|
||||
child: Center(
|
||||
child: Text(
|
||||
'全部',
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).primaryColor),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -146,6 +149,11 @@ class _UpPanelState extends State<UpPanel> {
|
||||
);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
String heroTag = Utils.makeHeroTag(data.mid);
|
||||
Get.toNamed('/member?mid=${data.mid}',
|
||||
arguments: {'face': data.face, 'heroTag': heroTag});
|
||||
},
|
||||
child: Padding(
|
||||
padding: itemPadding,
|
||||
child: AnimatedOpacity(
|
||||
|
@ -29,7 +29,7 @@ class _HomePageState extends State<HomePage>
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8),
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 8),
|
||||
child: Stack(
|
||||
children: [
|
||||
const Align(
|
||||
@ -79,13 +79,12 @@ class _HomePageState extends State<HomePage>
|
||||
indicatorPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 5),
|
||||
indicator: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
labelColor:
|
||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
labelColor: Theme.of(context).colorScheme.onPrimary,
|
||||
labelStyle: const TextStyle(fontSize: 13),
|
||||
dividerColor: Colors.transparent,
|
||||
unselectedLabelColor:
|
||||
|
@ -10,11 +10,14 @@ class LiveRoomController extends GetxController {
|
||||
late int roomId;
|
||||
var liveItem;
|
||||
late String heroTag;
|
||||
double volume = 0.0;
|
||||
// 静音状态
|
||||
RxBool volumeOff = false.obs;
|
||||
|
||||
MeeduPlayerController meeduPlayerController = MeeduPlayerController(
|
||||
colorTheme: Theme.of(Get.context!).colorScheme.primary,
|
||||
pipEnabled: true,
|
||||
controlsStyle: ControlsStyle.youtube,
|
||||
controlsStyle: ControlsStyle.live,
|
||||
enabledButtons: const EnabledButtons(pip: true),
|
||||
);
|
||||
|
||||
@ -45,6 +48,7 @@ class LiveRoomController extends GetxController {
|
||||
),
|
||||
autoplay: true,
|
||||
);
|
||||
volume = meeduPlayerController.volume.value;
|
||||
}
|
||||
|
||||
Future queryLiveInfo() async {
|
||||
@ -59,4 +63,18 @@ class LiveRoomController extends GetxController {
|
||||
playerInit(videoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
void setVolumn(value) {
|
||||
if (value == 0) {
|
||||
// 设置音量
|
||||
volumeOff.value = false;
|
||||
meeduPlayerController.setVolume(volume);
|
||||
} else {
|
||||
// 取消音量
|
||||
volume = value;
|
||||
volumeOff.value = true;
|
||||
meeduPlayerController.setVolume(0);
|
||||
}
|
||||
print('🌹:${volumeOff.value}');
|
||||
}
|
||||
}
|
||||
|
@ -69,13 +69,23 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
_liveRoomController.liveItem.uname,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(_liveRoomController.liveItem.title,
|
||||
style: const TextStyle(fontSize: 12)),
|
||||
const SizedBox(height: 1),
|
||||
if (_liveRoomController.liveItem.watchedShow != null)
|
||||
Text(
|
||||
_liveRoomController.liveItem.watchedShow['text_large'] ??
|
||||
'',
|
||||
style: const TextStyle(fontSize: 12)),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
SizedBox(
|
||||
height: 34,
|
||||
child: ElevatedButton(onPressed: () {}, child: const Text('关注')),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
@ -86,6 +96,36 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: MeeduVideoPlayer(
|
||||
header: (BuildContext context,
|
||||
MeeduPlayerController _meeduPlayerController,
|
||||
Responsive) {
|
||||
return AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
primary: false,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
foregroundColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: false,
|
||||
title: Text(_liveRoomController.liveItem.title,
|
||||
style: const TextStyle(fontSize: 12)),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () =>
|
||||
_meeduPlayerController.enterPip(context),
|
||||
icon: const Icon(
|
||||
Icons.branding_watermark_outlined,
|
||||
size: 19,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12)
|
||||
],
|
||||
);
|
||||
},
|
||||
controller: _meeduPlayerController!,
|
||||
),
|
||||
),
|
||||
@ -110,9 +150,6 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Row(children: [
|
||||
Text(_liveRoomController.liveItem.watchedShow['text_large']),
|
||||
]),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
border: Border(
|
||||
@ -120,6 +157,56 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1)),
|
||||
),
|
||||
),
|
||||
child: Row(children: <Widget>[
|
||||
// SizedBox(
|
||||
// width: 38,
|
||||
// height: 38,
|
||||
// child: IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: const Icon(
|
||||
// Icons.subtitles_outlined,
|
||||
// size: 21,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
const Spacer(),
|
||||
// SizedBox(
|
||||
// width: 38,
|
||||
// height: 38,
|
||||
// child: IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: const Icon(
|
||||
// Icons.hd_outlined,
|
||||
// size: 20,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () => _liveRoomController
|
||||
.setVolumn(_meeduPlayerController!.volume.value),
|
||||
icon: Obx(() => Icon(
|
||||
_liveRoomController.volumeOff.value
|
||||
? Icons.volume_off_outlined
|
||||
: Icons.volume_up_outlined,
|
||||
size: 21,
|
||||
)),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
height: 38,
|
||||
child: IconButton(
|
||||
onPressed: () =>
|
||||
_meeduPlayerController!.goToFullscreen(context),
|
||||
icon: const Icon(
|
||||
Icons.fullscreen,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
22
lib/pages/member/archive/controller.dart
Normal file
22
lib/pages/member/archive/controller.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
|
||||
class ArchiveController extends GetxController {
|
||||
int? mid;
|
||||
int pn = 1;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
mid = int.parse(Get.parameters['mid']!);
|
||||
}
|
||||
|
||||
// 获取用户投稿
|
||||
Future getMemberArchive() async {
|
||||
var res = await MemberHttp.memberArchive(mid: mid, pn: pn);
|
||||
if (res['status']) {
|
||||
pn += 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
4
lib/pages/member/archive/index.dart
Normal file
4
lib/pages/member/archive/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library archive_panel;
|
||||
|
||||
export './controller.dart';
|
||||
export 'index.dart';
|
77
lib/pages/member/archive/view.dart
Normal file
77
lib/pages/member/archive/view.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loading_more_list/loading_more_list.dart';
|
||||
import 'package:pilipala/common/widgets/pull_to_refresh_header.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||
import 'package:pilipala/models/member/archive.dart';
|
||||
import 'package:pilipala/pages/member/archive/index.dart';
|
||||
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
|
||||
|
||||
class ArchivePanel extends StatefulWidget {
|
||||
const ArchivePanel({super.key});
|
||||
|
||||
@override
|
||||
State<ArchivePanel> createState() => _ArchivePanelState();
|
||||
}
|
||||
|
||||
class _ArchivePanelState extends State<ArchivePanel>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
DateTime lastRefreshTime = DateTime.now();
|
||||
late final LoadMoreListSource source = LoadMoreListSource();
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PullToRefreshNotification(
|
||||
onRefresh: () async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return true;
|
||||
},
|
||||
maxDragOffset: 50,
|
||||
child: GlowNotificationWidget(
|
||||
Column(
|
||||
children: <Widget>[
|
||||
// 下拉刷新指示器
|
||||
PullToRefreshContainer(
|
||||
(PullToRefreshScrollNotificationInfo? info) {
|
||||
return PullToRefreshHeader(info, lastRefreshTime);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Expanded(
|
||||
child: LoadingMoreList<VListItemModel>(
|
||||
ListConfig<VListItemModel>(
|
||||
sourceList: source,
|
||||
itemBuilder:
|
||||
(BuildContext c, VListItemModel item, int index) {
|
||||
return VideoCardH(videoItem: item);
|
||||
},
|
||||
indicatorBuilder: (context, status) {
|
||||
return const Center(child: Text('加载中'));
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
showGlowLeading: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
||||
final ArchiveController _archiveController = Get.put(ArchiveController());
|
||||
@override
|
||||
Future<bool> loadData([bool isloadMoreAction = false]) {
|
||||
return Future<bool>(() async {
|
||||
var res = await _archiveController.getMemberArchive();
|
||||
if (res['status']) {
|
||||
addAll(res['data'].list.vlist);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/member/archive.dart';
|
||||
import 'package:pilipala/models/member/info.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/wbi_sign.dart';
|
||||
@ -13,6 +17,8 @@ class MemberController extends GetxController {
|
||||
String? heroTag;
|
||||
Box user = GStrorage.user;
|
||||
late int ownerMid;
|
||||
// 投稿列表
|
||||
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -26,14 +32,7 @@ class MemberController extends GetxController {
|
||||
// 获取用户信息
|
||||
Future<Map<String, dynamic>> getInfo() async {
|
||||
await getMemberStat();
|
||||
String params = await WbiSign().makSign({
|
||||
'mid': mid,
|
||||
'token': '',
|
||||
'platform': 'web',
|
||||
'web_location': 1550101,
|
||||
});
|
||||
params = '?$params';
|
||||
var res = await MemberHttp.memberInfo(params: params);
|
||||
var res = await MemberHttp.memberInfo(mid: mid);
|
||||
if (res['status']) {
|
||||
memberInfo.value = res['data'];
|
||||
}
|
||||
@ -56,4 +55,43 @@ class MemberController extends GetxController {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 关注/取关up
|
||||
Future actionRelationMod() async {
|
||||
if (user.get(UserBoxKey.userMid) == null) {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
child: const Text('点错了')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await VideoHttp.relationMod(
|
||||
mid: mid,
|
||||
act: memberInfo.value.isFollowed! ? 2 : 1,
|
||||
reSrc: 11,
|
||||
);
|
||||
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showLoading();
|
||||
SmartDialog.dismiss();
|
||||
memberInfo.update((val) {});
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.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/models/user/stat.dart';
|
||||
import 'package:pilipala/pages/member/archive/view.dart';
|
||||
import 'package:pilipala/pages/member/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import 'widgets/profile.dart';
|
||||
|
||||
class MemberPage extends StatefulWidget {
|
||||
const MemberPage({super.key});
|
||||
|
||||
@ -19,13 +21,15 @@ class MemberPage extends StatefulWidget {
|
||||
class _MemberPageState extends State<MemberPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final MemberController _memberController = Get.put(MemberController());
|
||||
Future? _futureBuilderFuture;
|
||||
final ScrollController _extendNestCtr = ScrollController();
|
||||
late TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 3, vsync: this);
|
||||
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
|
||||
_futureBuilderFuture = _memberController.getInfo();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -90,7 +94,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 18, right: 18),
|
||||
child: FutureBuilder(
|
||||
future: _memberController.getInfo(),
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
@ -104,8 +108,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
profile(
|
||||
_memberController.memberInfo.value),
|
||||
profile(_memberController),
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
children: [
|
||||
@ -232,7 +235,8 @@ class _MemberPageState extends State<MemberPage>
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return profile(null, loadingStatus: true);
|
||||
return profile(_memberController,
|
||||
loadingStatus: true);
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -249,10 +253,10 @@ class _MemberPageState extends State<MemberPage>
|
||||
onlyOneScrollInBody: true,
|
||||
body: Column(
|
||||
children: [
|
||||
Container(
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: TabBar(controller: _tabController, tabs: [
|
||||
child: TabBar(controller: _tabController, tabs: const [
|
||||
Tab(text: '主页'),
|
||||
Tab(text: '动态'),
|
||||
Tab(text: '投稿'),
|
||||
@ -261,10 +265,10 @@ class _MemberPageState extends State<MemberPage>
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
children: const [
|
||||
Text('主页'),
|
||||
Text('动态'),
|
||||
Text('投稿'),
|
||||
ArchivePanel(),
|
||||
],
|
||||
))
|
||||
],
|
||||
@ -272,186 +276,4 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget profile(memberInfo, {loadingStatus = false}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 3 * MediaQuery.of(context).padding.top),
|
||||
child: Row(
|
||||
children: [
|
||||
Hero(
|
||||
tag: _memberController.heroTag!,
|
||||
child: Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 90,
|
||||
height: 90,
|
||||
type: 'avatar',
|
||||
src: !loadingStatus
|
||||
? memberInfo.face
|
||||
: _memberController.face,
|
||||
),
|
||||
if (!loadingStatus &&
|
||||
memberInfo.liveRoom != null &&
|
||||
memberInfo.liveRoom.liveStatus == 1)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 14,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
LiveItemModel liveItem = LiveItemModel.fromJson({
|
||||
'title': memberInfo.liveRoom.title,
|
||||
'uname': memberInfo.name,
|
||||
'face': memberInfo.face,
|
||||
'roomid': memberInfo.liveRoom.roomId,
|
||||
});
|
||||
Get.toNamed(
|
||||
'/liveRoom?roomid=${memberInfo.liveRoom.roomId}',
|
||||
arguments: {'liveItem': liveItem},
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
child: Row(children: [
|
||||
Image.asset(
|
||||
'assets/images/live.gif',
|
||||
height: 10,
|
||||
),
|
||||
Text(
|
||||
' 直播中',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize),
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
!loadingStatus
|
||||
? _memberController.userStat!['following']
|
||||
.toString()
|
||||
: '-',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
'关注',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize),
|
||||
)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
!loadingStatus
|
||||
? Utils.numFormat(
|
||||
_memberController.userStat!['follower'],
|
||||
)
|
||||
: '-',
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text('粉丝',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize))
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
const Text('-',
|
||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
'获赞',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (_memberController.ownerMid != _memberController.mid) ...[
|
||||
Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 42, right: 42),
|
||||
foregroundColor:
|
||||
!loadingStatus && memberInfo.isFollowed
|
||||
? Theme.of(context).colorScheme.outline
|
||||
: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: !loadingStatus &&
|
||||
memberInfo.isFollowed
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(!loadingStatus && memberInfo.isFollowed
|
||||
? '取关'
|
||||
: '关注'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 42, right: 42),
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
child: const Text('发消息'),
|
||||
)
|
||||
],
|
||||
)
|
||||
] else ...[
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 80, right: 80),
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
child: const Text('编辑资料'),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
193
lib/pages/member/widgets/profile.dart
Normal file
193
lib/pages/member/widgets/profile.dart
Normal file
@ -0,0 +1,193 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/live/item.dart';
|
||||
import 'package:pilipala/models/member/info.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
Widget profile(ctr, {loadingStatus = false}) {
|
||||
MemberInfoModel memberInfo = ctr.memberInfo.value;
|
||||
return Builder(
|
||||
builder: ((context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 3 * MediaQuery.of(context).padding.top),
|
||||
child: Row(
|
||||
children: [
|
||||
Hero(
|
||||
tag: ctr.heroTag!,
|
||||
child: Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 90,
|
||||
height: 90,
|
||||
type: 'avatar',
|
||||
src: !loadingStatus ? memberInfo.face : ctr.face,
|
||||
),
|
||||
if (!loadingStatus &&
|
||||
memberInfo.liveRoom != null &&
|
||||
memberInfo.liveRoom!.liveStatus == 1)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 14,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
LiveItemModel liveItem = LiveItemModel.fromJson({
|
||||
'title': memberInfo.liveRoom!.title,
|
||||
'uname': memberInfo.name,
|
||||
'face': memberInfo.face,
|
||||
'roomid': memberInfo.liveRoom!.roomId,
|
||||
'watched_show': memberInfo.liveRoom!.watchedShow,
|
||||
});
|
||||
Get.toNamed(
|
||||
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
|
||||
arguments: {'liveItem': liveItem},
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
child: Row(children: [
|
||||
Image.asset(
|
||||
'assets/images/live.gif',
|
||||
height: 10,
|
||||
),
|
||||
Text(
|
||||
' 直播中',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize),
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
!loadingStatus
|
||||
? ctr.userStat!['following'].toString()
|
||||
: '-',
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
'关注',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize),
|
||||
)
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
!loadingStatus
|
||||
? Utils.numFormat(
|
||||
ctr.userStat!['follower'],
|
||||
)
|
||||
: '-',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold)),
|
||||
Text('粉丝',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize))
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
const Text('-',
|
||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
'获赞',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (ctr.ownerMid != ctr.mid) ...[
|
||||
Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => ctr.actionRelationMod(),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 42, right: 42),
|
||||
foregroundColor:
|
||||
!loadingStatus && memberInfo.isFollowed!
|
||||
? Theme.of(context).colorScheme.outline
|
||||
: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: !loadingStatus &&
|
||||
memberInfo.isFollowed!
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(!loadingStatus && memberInfo.isFollowed!
|
||||
? '取关'
|
||||
: '关注'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 42, right: 42),
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
child: const Text('发消息'),
|
||||
)
|
||||
],
|
||||
)
|
||||
] else ...[
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 80, right: 80),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
child: const Text('编辑资料'),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
@ -7,6 +7,7 @@ import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/video/play/url.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||
@ -87,6 +88,8 @@ class VideoDetailController extends GetxController
|
||||
},
|
||||
firstFloor: firstFloor,
|
||||
paddingTop: paddingTop,
|
||||
replyType: ReplyType.video,
|
||||
source: 'videoDetail',
|
||||
);
|
||||
});
|
||||
ctr?.closed.then((value) {
|
||||
@ -95,6 +98,7 @@ class VideoDetailController extends GetxController
|
||||
}
|
||||
|
||||
playerInit(source, audioSource, {Duration defaultST = Duration.zero}) {
|
||||
meeduPlayerController.onVideoFitChange(BoxFit.cover);
|
||||
meeduPlayerController.setDataSource(
|
||||
DataSource(
|
||||
type: DataSourceType.network,
|
||||
@ -157,7 +161,7 @@ class VideoDetailController extends GetxController
|
||||
@override
|
||||
void onClose() {
|
||||
markHeartBeat();
|
||||
if (timer!.isActive) {
|
||||
if (timer != null && timer!.isActive) {
|
||||
timer!.cancel();
|
||||
}
|
||||
super.onClose();
|
||||
|
@ -84,11 +84,11 @@ class VideoIntroController extends GetxController {
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||
.tabs
|
||||
.value = ['简介', '评论 ${result['data']!.stat!.reply}'];
|
||||
// 获取到粉丝数再返回
|
||||
await queryUserStat();
|
||||
} else {
|
||||
responseMsg = result['msg'];
|
||||
}
|
||||
// 获取到粉丝数再返回
|
||||
await queryUserStat();
|
||||
if (userLogin) {
|
||||
// 获取点赞状态
|
||||
queryHasLikeVideo();
|
||||
@ -99,13 +99,13 @@ class VideoIntroController extends GetxController {
|
||||
//
|
||||
queryFollowStatus();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取up主粉丝数
|
||||
Future queryUserStat() async {
|
||||
var result = await UserHttp.userStat(mid: videoDetail.value.owner!.mid!);
|
||||
print('🌹:$result');
|
||||
if (result['status']) {
|
||||
userStat = result['data'];
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
// 请求错误
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
fn: () => Get.back(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@ -231,7 +231,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 20),
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 10),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !widget.loadingStatus || videoItem.isNotEmpty
|
||||
? Column(
|
||||
@ -248,6 +248,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
|
@ -1,4 +0,0 @@
|
||||
library video_player;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
@ -6,6 +6,7 @@ import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import 'controller.dart';
|
||||
@ -52,9 +53,10 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
tag: widget.rpid.toString());
|
||||
_videoReplyController.rPid = widget.rpid;
|
||||
} else {
|
||||
int oid = Get.parameters['bvid'] != null
|
||||
? IdUtils.bv2av(Get.parameters['bvid']!)
|
||||
: 0;
|
||||
// fix 评论加载不对称
|
||||
// int oid = Get.parameters['bvid'] != null
|
||||
// ? IdUtils.bv2av(Get.parameters['bvid']!)
|
||||
// : 0;
|
||||
_videoReplyController = Get.put(VideoReplyController(oid, '', '1'),
|
||||
tag: Get.arguments['heroTag']);
|
||||
}
|
||||
@ -113,6 +115,16 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
_videoReplyController.wakeUpReply();
|
||||
}
|
||||
|
||||
// 展示二级回复
|
||||
void replyReply(replyItem, paddingTop) {
|
||||
VideoDetailController videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||
videoDetailCtr.oid = replyItem.replies!.first.oid;
|
||||
videoDetailCtr.fRpid = replyItem.rpid!;
|
||||
videoDetailCtr.firstFloor = replyItem;
|
||||
videoDetailCtr.showReplyReplyPanel(paddingTop);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
@ -164,10 +176,13 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
);
|
||||
} else {
|
||||
return ReplyItem(
|
||||
replyItem:
|
||||
_videoReplyController.replyList[index],
|
||||
showReplyRow: true,
|
||||
replyLevel: replyLevel);
|
||||
replyItem:
|
||||
_videoReplyController.replyList[index],
|
||||
showReplyRow: true,
|
||||
replyLevel: replyLevel,
|
||||
replyReply: (replyItem, paddingTop) =>
|
||||
replyReply(replyItem, paddingTop),
|
||||
);
|
||||
}
|
||||
},
|
||||
childCount:
|
||||
|
@ -6,6 +6,7 @@ import 'package:flutter_meedu_media_kit/meedu_player.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/controller.dart';
|
||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||
@ -14,16 +15,21 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class ReplyItem extends StatelessWidget {
|
||||
ReplyItem(
|
||||
{super.key,
|
||||
this.replyItem,
|
||||
this.addReply,
|
||||
this.replyLevel,
|
||||
this.showReplyRow});
|
||||
ReplyItem({
|
||||
super.key,
|
||||
this.replyItem,
|
||||
this.addReply,
|
||||
this.replyLevel,
|
||||
this.showReplyRow,
|
||||
this.replyReply,
|
||||
this.replyType,
|
||||
});
|
||||
ReplyItemModel? replyItem;
|
||||
Function? addReply;
|
||||
String? replyLevel;
|
||||
bool? showReplyRow = true;
|
||||
Function? replyReply;
|
||||
ReplyType? replyType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -212,6 +218,7 @@ class ReplyItem extends StatelessWidget {
|
||||
replyControl: replyItem!.replyControl,
|
||||
f_rpid: replyItem!.rpid,
|
||||
replyItem: replyItem,
|
||||
replyReply: replyReply,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -272,11 +279,13 @@ class ReplyItem extends StatelessWidget {
|
||||
isScrollControlled: true,
|
||||
builder: (builder) {
|
||||
return VideoReplyNewDialog(
|
||||
replyLevel: replyLevel,
|
||||
oid: replyItem!.oid,
|
||||
root: replyItem!.rpid,
|
||||
parent: replyItem!.rpid,
|
||||
paddingTop: paddingTop);
|
||||
replyLevel: replyLevel,
|
||||
oid: replyItem!.oid,
|
||||
root: replyItem!.rpid,
|
||||
parent: replyItem!.rpid,
|
||||
paddingTop: paddingTop,
|
||||
replyType: replyType,
|
||||
);
|
||||
},
|
||||
).then((value) => {
|
||||
// 完成评论,数据添加
|
||||
@ -320,21 +329,25 @@ class ReplyItem extends StatelessWidget {
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ReplyItemRow extends StatelessWidget {
|
||||
ReplyItemRow(
|
||||
{super.key,
|
||||
this.replies,
|
||||
this.replyControl,
|
||||
this.f_rpid,
|
||||
this.replyItem});
|
||||
ReplyItemRow({
|
||||
super.key,
|
||||
this.replies,
|
||||
this.replyControl,
|
||||
this.f_rpid,
|
||||
this.replyItem,
|
||||
this.replyReply,
|
||||
});
|
||||
List? replies;
|
||||
ReplyControl? replyControl;
|
||||
int? f_rpid;
|
||||
ReplyItemModel? replyItem;
|
||||
Function? replyReply;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isShow = replyControl!.isShow!;
|
||||
int extraRow = replyControl != null && isShow ? 1 : 0;
|
||||
double paddingTop = MediaQuery.of(context).padding.top;
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 42, right: 4, top: 0),
|
||||
child: Material(
|
||||
@ -347,8 +360,7 @@ class ReplyItemRow extends StatelessWidget {
|
||||
children: [
|
||||
for (var i = 0; i < replies!.length; i++) ...[
|
||||
InkWell(
|
||||
onTap: () =>
|
||||
replyReply(replyItem, MediaQuery.of(context).padding.top),
|
||||
onTap: () => replyReply!(replyItem, paddingTop),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
@ -398,8 +410,7 @@ class ReplyItemRow extends StatelessWidget {
|
||||
],
|
||||
if (extraRow == 1)
|
||||
InkWell(
|
||||
onTap: () =>
|
||||
replyReply(replyItem, MediaQuery.of(context).padding.top),
|
||||
onTap: () => replyReply!(replyItem, paddingTop),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(8, 5, 8, 8),
|
||||
@ -428,16 +439,6 @@ class ReplyItemRow extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void replyReply(replyItem, paddingTop) {
|
||||
// replyItem 楼主评论
|
||||
VideoDetailController videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||
videoDetailCtr.oid = replies!.first.oid;
|
||||
videoDetailCtr.fRpid = f_rpid!;
|
||||
videoDetailCtr.firstFloor = replyItem;
|
||||
videoDetailCtr.showReplyReplyPanel(paddingTop);
|
||||
}
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, content) {
|
||||
|
@ -13,6 +13,7 @@ class VideoReplyNewDialog extends StatefulWidget {
|
||||
String? replyLevel;
|
||||
int? parent;
|
||||
double? paddingTop;
|
||||
ReplyType? replyType;
|
||||
|
||||
VideoReplyNewDialog({
|
||||
this.oid,
|
||||
@ -20,6 +21,7 @@ class VideoReplyNewDialog extends StatefulWidget {
|
||||
this.replyLevel,
|
||||
this.parent,
|
||||
this.paddingTop,
|
||||
this.replyType,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -64,7 +66,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
Future submitReplyAdd() async {
|
||||
String message = _replyContentController.text;
|
||||
var result = await VideoHttp.replyAdd(
|
||||
type: ReplyType.video,
|
||||
type: widget.replyType!,
|
||||
oid: widget.oid!,
|
||||
root: widget.root!,
|
||||
parent: widget.parent!,
|
||||
|
@ -1,16 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/reply.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/video/reply/data.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
|
||||
class VideoReplyReplyController extends GetxController {
|
||||
VideoReplyReplyController(this.aid, this.rpid);
|
||||
VideoReplyReplyController(this.aid, this.rpid, this.replyType);
|
||||
final ScrollController scrollController = ScrollController();
|
||||
// 视频aid 请求时使用的oid
|
||||
int? aid;
|
||||
// rpid 请求楼中楼回复
|
||||
String? rpid;
|
||||
ReplyType replyType = ReplyType.video;
|
||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||
// 当前页
|
||||
int currentPage = 0;
|
||||
@ -41,7 +43,10 @@ class VideoReplyReplyController extends GetxController {
|
||||
}
|
||||
isLoadingMore = true;
|
||||
var res = await ReplyHttp.replyReplyList(
|
||||
oid: aid!, root: rpid!, pageNum: currentPage + 1, type: 1);
|
||||
oid: aid!,
|
||||
root: rpid!,
|
||||
pageNum: currentPage + 1,
|
||||
type: replyType.index);
|
||||
if (res['status']) {
|
||||
res['data'] = ReplyData.fromJson(res['data']);
|
||||
if (res['data'].replies.isNotEmpty) {
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/models/common/reply_type.dart';
|
||||
import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
||||
|
||||
@ -13,6 +14,8 @@ class VideoReplyReplyPanel extends StatefulWidget {
|
||||
Function? closePanel;
|
||||
ReplyItemModel? firstFloor;
|
||||
double? paddingTop;
|
||||
String? source;
|
||||
ReplyType? replyType;
|
||||
|
||||
VideoReplyReplyPanel({
|
||||
this.oid,
|
||||
@ -20,6 +23,8 @@ class VideoReplyReplyPanel extends StatefulWidget {
|
||||
this.closePanel,
|
||||
this.firstFloor,
|
||||
this.paddingTop,
|
||||
this.source,
|
||||
this.replyType,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -34,7 +39,8 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
||||
@override
|
||||
void initState() {
|
||||
_videoReplyReplyController = Get.put(
|
||||
VideoReplyReplyController(widget.oid, widget.rpid.toString()),
|
||||
VideoReplyReplyController(
|
||||
widget.oid, widget.rpid.toString(), widget.replyType!),
|
||||
tag: widget.rpid.toString());
|
||||
super.initState();
|
||||
|
||||
@ -62,34 +68,37 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height -
|
||||
MediaQuery.of(context).size.width * 9 / 16 -
|
||||
widget.paddingTop!,
|
||||
height: widget.source == 'videoDetail'
|
||||
? MediaQuery.of(context).size.height -
|
||||
MediaQuery.of(context).size.width * 9 / 16 -
|
||||
widget.paddingTop!
|
||||
: null,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'评论详情',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_videoReplyReplyController.currentPage = 0;
|
||||
_videoReplyReplyController.rPid = 0;
|
||||
widget.closePanel!();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
if (widget.source == 'videoDetail')
|
||||
Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'评论详情',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_videoReplyReplyController.currentPage = 0;
|
||||
_videoReplyReplyController.rPid = 0;
|
||||
widget.closePanel!();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
@ -188,7 +197,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoReplySkeleton();
|
||||
}, childCount: 5),
|
||||
}, childCount: 8),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -90,14 +90,16 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
@override
|
||||
void dispose() {
|
||||
videoDetailController.meeduPlayerController.dispose();
|
||||
videoDetailController.timer!.cancel();
|
||||
if (videoDetailController.timer != null) {
|
||||
videoDetailController.timer!.cancel();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
// 离开当前页面时
|
||||
void didPushNext() async {
|
||||
if (!_meeduPlayerController!.pipEnabled) {
|
||||
if (!_meeduPlayerController!.pipAvailable.value) {
|
||||
_meeduPlayerController!.pause();
|
||||
}
|
||||
if (videoDetailController.timer!.isActive) {
|
||||
@ -232,7 +234,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 0,
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
@ -252,8 +254,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
() => TabBar(
|
||||
controller: videoDetailController.tabCtr,
|
||||
dividerColor: Colors.transparent,
|
||||
indicatorColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
// indicatorColor:
|
||||
// Theme.of(context).colorScheme.background,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
@ -278,7 +280,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
);
|
||||
},
|
||||
),
|
||||
VideoReplyPanel()
|
||||
VideoReplyPanel(
|
||||
bvid: videoDetailController.bvid,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -15,6 +15,7 @@ import 'package:pilipala/pages/preview/index.dart';
|
||||
import 'package:pilipala/pages/search/index.dart';
|
||||
import 'package:pilipala/pages/searchResult/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||
import 'package:pilipala/pages/webview/index.dart';
|
||||
import 'package:pilipala/pages/setting/index.dart';
|
||||
import 'package:pilipala/pages/media/index.dart';
|
||||
@ -65,5 +66,7 @@ class Routes {
|
||||
GetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
|
||||
// 用户中心
|
||||
GetPage(name: '/member', page: () => const MemberPage()),
|
||||
// 二级回复
|
||||
GetPage(name: '/replyReply', page: () => VideoReplyReplyPanel()),
|
||||
];
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ class GStrorage {
|
||||
static late final Box userInfo;
|
||||
static late final Box hotKeyword;
|
||||
static late final Box historyword;
|
||||
static late final Box localCache;
|
||||
|
||||
static Future<void> init() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
@ -28,6 +29,8 @@ class GStrorage {
|
||||
hotKeyword = await Hive.openBox('hotKeyword');
|
||||
// 搜索历史
|
||||
historyword = await Hive.openBox('historyWord');
|
||||
// 本地缓存
|
||||
localCache = await Hive.openBox('localCache');
|
||||
}
|
||||
|
||||
static regAdapter() {
|
||||
|
@ -2,11 +2,15 @@
|
||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md
|
||||
// import md5 from 'md5'
|
||||
// import axios from 'axios'
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/index.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class WbiSign {
|
||||
static Box localCache = GStrorage.user;
|
||||
List mixinKeyEncTab = [
|
||||
46,
|
||||
47,
|
||||
@ -83,7 +87,7 @@ class WbiSign {
|
||||
}
|
||||
|
||||
// 为请求参数进行 wbi 签名
|
||||
String encWbi(params, imgKey, subKey) {
|
||||
Map<String, dynamic> encWbi(params, imgKey, subKey) {
|
||||
String mixinKey = getMixinKey(imgKey + subKey);
|
||||
DateTime now = DateTime.now();
|
||||
int currTime = (now.millisecondsSinceEpoch / 1000).round();
|
||||
@ -99,19 +103,25 @@ class WbiSign {
|
||||
String queryStr = query.join('&');
|
||||
String wbiSign =
|
||||
md5.convert(utf8.encode(queryStr + mixinKey)).toString(); // 计算 w_rid
|
||||
print('w_rid: $wbiSign');
|
||||
return '$queryStr&w_rid=$wbiSign';
|
||||
return {'wts': currTime.toString(), 'w_rid': wbiSign};
|
||||
}
|
||||
|
||||
// 获取最新的 img_key 和 sub_key
|
||||
// 获取最新的 img_key 和 sub_key 可以从缓存中获取
|
||||
static Future<Map<String, dynamic>> getWbiKeys() async {
|
||||
DateTime nowDate = DateTime.now();
|
||||
if (localCache.get('wbiKeys') != null &&
|
||||
DateTime.fromMillisecondsSinceEpoch(localCache.get('timeStamp')).day ==
|
||||
nowDate.day) {
|
||||
Map cacheWbiKeys = localCache.get('wbiKeys');
|
||||
return Map<String, dynamic>.from(cacheWbiKeys);
|
||||
}
|
||||
var resp =
|
||||
await Request().get('https://api.bilibili.com/x/web-interface/nav');
|
||||
var jsonContent = resp.data['data'];
|
||||
|
||||
String imgUrl = jsonContent['wbi_img']['img_url'];
|
||||
String subUrl = jsonContent['wbi_img']['sub_url'];
|
||||
return {
|
||||
Map<String, dynamic> wbiKeys = {
|
||||
'imgKey': imgUrl
|
||||
.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.length)
|
||||
.split('.')[0],
|
||||
@ -119,12 +129,16 @@ class WbiSign {
|
||||
.substring(subUrl.lastIndexOf('/') + 1, subUrl.length)
|
||||
.split('.')[0]
|
||||
};
|
||||
localCache.put('wbiKeys', wbiKeys);
|
||||
localCache.put('timeStamp', nowDate.millisecondsSinceEpoch);
|
||||
return wbiKeys;
|
||||
}
|
||||
|
||||
makSign(Map<String, dynamic> params) async {
|
||||
// params 为需要加密的请求参数
|
||||
Map<String, dynamic> wbiKeys = await getWbiKeys();
|
||||
String query = encWbi(params, wbiKeys['imgKey'], wbiKeys['subKey']);
|
||||
Map<String, dynamic> query = params
|
||||
..addAll(encWbi(params, wbiKeys['imgKey'], wbiKeys['subKey']));
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user