feat: 用户投稿
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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -203,4 +203,19 @@ class Api {
|
|||||||
|
|
||||||
// 用户名片信息
|
// 用户名片信息
|
||||||
static const String memberCardInfo = '/x/web-interface/card';
|
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/http/index.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/wbi_sign.dart';
|
||||||
|
|
||||||
class MemberHttp {
|
class MemberHttp {
|
||||||
static Future memberInfo({String? params}) async {
|
static Future memberInfo({
|
||||||
var res = await Request().get(Api.memberInfo + params!);
|
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) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'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'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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 = '';
|
||||||
|
}
|
||||||
|
}
|
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,7 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/member.dart';
|
import 'package:pilipala/http/member.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';
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
@ -13,6 +14,8 @@ class MemberController extends GetxController {
|
|||||||
String? heroTag;
|
String? heroTag;
|
||||||
Box user = GStrorage.user;
|
Box user = GStrorage.user;
|
||||||
late int ownerMid;
|
late int ownerMid;
|
||||||
|
// 投稿列表
|
||||||
|
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -26,14 +29,7 @@ class MemberController extends GetxController {
|
|||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
Future<Map<String, dynamic>> getInfo() async {
|
Future<Map<String, dynamic>> getInfo() async {
|
||||||
await getMemberStat();
|
await getMemberStat();
|
||||||
String params = await WbiSign().makSign({
|
var res = await MemberHttp.memberInfo(mid: mid);
|
||||||
'mid': mid,
|
|
||||||
'token': '',
|
|
||||||
'platform': 'web',
|
|
||||||
'web_location': 1550101,
|
|
||||||
});
|
|
||||||
params = '?$params';
|
|
||||||
var res = await MemberHttp.memberInfo(params: params);
|
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
memberInfo.value = res['data'];
|
memberInfo.value = res['data'];
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.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: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/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/models/live/item.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/pages/member/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
@ -19,13 +19,15 @@ class MemberPage extends StatefulWidget {
|
|||||||
class _MemberPageState extends State<MemberPage>
|
class _MemberPageState extends State<MemberPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
final MemberController _memberController = Get.put(MemberController());
|
final MemberController _memberController = Get.put(MemberController());
|
||||||
|
Future? _futureBuilderFuture;
|
||||||
final ScrollController _extendNestCtr = ScrollController();
|
final ScrollController _extendNestCtr = ScrollController();
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: 3, vsync: this);
|
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
|
||||||
|
_futureBuilderFuture = _memberController.getInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -90,7 +92,7 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 18, right: 18),
|
padding: const EdgeInsets.only(left: 18, right: 18),
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _memberController.getInfo(),
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
ConnectionState.done) {
|
ConnectionState.done) {
|
||||||
@ -264,7 +266,7 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
children: [
|
children: [
|
||||||
Text('主页'),
|
Text('主页'),
|
||||||
Text('动态'),
|
Text('动态'),
|
||||||
Text('投稿'),
|
ArchivePanel(),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
|
@ -12,6 +12,7 @@ class GStrorage {
|
|||||||
static late final Box userInfo;
|
static late final Box userInfo;
|
||||||
static late final Box hotKeyword;
|
static late final Box hotKeyword;
|
||||||
static late final Box historyword;
|
static late final Box historyword;
|
||||||
|
static late final Box localCache;
|
||||||
|
|
||||||
static Future<void> init() async {
|
static Future<void> init() async {
|
||||||
final dir = await getApplicationDocumentsDirectory();
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
@ -28,6 +29,8 @@ class GStrorage {
|
|||||||
hotKeyword = await Hive.openBox('hotKeyword');
|
hotKeyword = await Hive.openBox('hotKeyword');
|
||||||
// 搜索历史
|
// 搜索历史
|
||||||
historyword = await Hive.openBox('historyWord');
|
historyword = await Hive.openBox('historyWord');
|
||||||
|
// 本地缓存
|
||||||
|
localCache = await Hive.openBox('localCache');
|
||||||
}
|
}
|
||||||
|
|
||||||
static regAdapter() {
|
static regAdapter() {
|
||||||
|
@ -2,11 +2,15 @@
|
|||||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md
|
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md
|
||||||
// import md5 from 'md5'
|
// import md5 from 'md5'
|
||||||
// import axios from 'axios'
|
// import axios from 'axios'
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class WbiSign {
|
class WbiSign {
|
||||||
|
static Box localCache = GStrorage.user;
|
||||||
List mixinKeyEncTab = [
|
List mixinKeyEncTab = [
|
||||||
46,
|
46,
|
||||||
47,
|
47,
|
||||||
@ -83,7 +87,7 @@ class WbiSign {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 为请求参数进行 wbi 签名
|
// 为请求参数进行 wbi 签名
|
||||||
String encWbi(params, imgKey, subKey) {
|
Map<String, dynamic> encWbi(params, imgKey, subKey) {
|
||||||
String mixinKey = getMixinKey(imgKey + subKey);
|
String mixinKey = getMixinKey(imgKey + subKey);
|
||||||
DateTime now = DateTime.now();
|
DateTime now = DateTime.now();
|
||||||
int currTime = (now.millisecondsSinceEpoch / 1000).round();
|
int currTime = (now.millisecondsSinceEpoch / 1000).round();
|
||||||
@ -99,19 +103,25 @@ class WbiSign {
|
|||||||
String queryStr = query.join('&');
|
String queryStr = query.join('&');
|
||||||
String wbiSign =
|
String wbiSign =
|
||||||
md5.convert(utf8.encode(queryStr + mixinKey)).toString(); // 计算 w_rid
|
md5.convert(utf8.encode(queryStr + mixinKey)).toString(); // 计算 w_rid
|
||||||
print('w_rid: $wbiSign');
|
return {'wts': currTime.toString(), 'w_rid': wbiSign};
|
||||||
return '$queryStr&w_rid=$wbiSign';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取最新的 img_key 和 sub_key
|
// 获取最新的 img_key 和 sub_key 可以从缓存中获取
|
||||||
static Future<Map<String, dynamic>> getWbiKeys() async {
|
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 =
|
var resp =
|
||||||
await Request().get('https://api.bilibili.com/x/web-interface/nav');
|
await Request().get('https://api.bilibili.com/x/web-interface/nav');
|
||||||
var jsonContent = resp.data['data'];
|
var jsonContent = resp.data['data'];
|
||||||
|
|
||||||
String imgUrl = jsonContent['wbi_img']['img_url'];
|
String imgUrl = jsonContent['wbi_img']['img_url'];
|
||||||
String subUrl = jsonContent['wbi_img']['sub_url'];
|
String subUrl = jsonContent['wbi_img']['sub_url'];
|
||||||
return {
|
Map<String, dynamic> wbiKeys = {
|
||||||
'imgKey': imgUrl
|
'imgKey': imgUrl
|
||||||
.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.length)
|
.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.length)
|
||||||
.split('.')[0],
|
.split('.')[0],
|
||||||
@ -119,12 +129,16 @@ class WbiSign {
|
|||||||
.substring(subUrl.lastIndexOf('/') + 1, subUrl.length)
|
.substring(subUrl.lastIndexOf('/') + 1, subUrl.length)
|
||||||
.split('.')[0]
|
.split('.')[0]
|
||||||
};
|
};
|
||||||
|
localCache.put('wbiKeys', wbiKeys);
|
||||||
|
localCache.put('timeStamp', nowDate.millisecondsSinceEpoch);
|
||||||
|
return wbiKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
makSign(Map<String, dynamic> params) async {
|
makSign(Map<String, dynamic> params) async {
|
||||||
// params 为需要加密的请求参数
|
// params 为需要加密的请求参数
|
||||||
Map<String, dynamic> wbiKeys = await getWbiKeys();
|
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;
|
return query;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -926,6 +926,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.3"
|
version: "1.2.3"
|
||||||
|
pull_to_refresh_notification:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: pull_to_refresh_notification
|
||||||
|
sha256: "3f27b9695c98770db3f9f50550e5ab44a6d946d022311a55bbe6d5cd4c69a1ad"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -87,6 +87,7 @@ dependencies:
|
|||||||
custom_sliding_segmented_control: ^1.7.5
|
custom_sliding_segmented_control: ^1.7.5
|
||||||
loading_more_list: ^5.0.3
|
loading_more_list: ^5.0.3
|
||||||
crypto: any
|
crypto: any
|
||||||
|
pull_to_refresh_notification: ^3.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user