This commit is contained in:
bggRGjQaUbCoE
2024-10-16 20:27:32 +08:00
parent 0b45788cc4
commit 2e0f1477f5
17 changed files with 583 additions and 58 deletions

View File

@@ -300,6 +300,8 @@ class Api {
static const String spaceBangumi =
'${HttpString.appBaseUrl}/x/v2/space/bangumi';
static const String spaceFav = '/x/v3/fav/folder/space';
// 用户名片信息
static const String memberCardInfo = '/x/web-interface/card';

View File

@@ -3,6 +3,7 @@ import 'package:PiliPalaX/grpc/grpc_repo.dart';
import 'package:PiliPalaX/http/constants.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/space/data.dart';
import 'package:PiliPalaX/models/space_fav/space_fav.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType;
import 'package:PiliPalaX/utils/storage.dart';
@@ -57,6 +58,51 @@ class MemberHttp {
}
}
static Future<LoadingState> spaceFav({
required int mid,
}) async {
String? accessKey = GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
Map<String, String> data = {
if (accessKey != null) 'access_key': accessKey,
'appkey': Constants.appKey,
'build': '1462100',
'c_locale': 'zh_CN',
'channel': 'yingyongbao',
'mobi_app': 'android_hd',
'platform': 'android',
's_locale': 'zh_CN',
'statistics': Constants.statistics,
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
'up_mid': mid.toString(),
};
String sign = Utils.appSign(
data,
Constants.appKey,
Constants.appSec,
);
data['sign'] = sign;
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
dynamic res = await Request().get(
Api.spaceFav,
data: data,
options: Options(
headers: {
'env': 'prod',
'app-key': 'android_hd',
'x-bili-mid': _mid,
'bili-http-engine': 'cronet',
'user-agent': Constants.userAgent,
},
),
);
if (res.data['code'] == 0) {
return LoadingState.success(SpaceFav.fromJson(res.data).data);
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState> spaceArchive({
required ContributeType type,
required int? mid,

View File

@@ -0,0 +1,19 @@
import 'package:json_annotation/json_annotation.dart';
import 'media_list_response.dart';
part 'datum.g.dart';
@JsonSerializable()
class Datum {
int? id;
String? name;
MediaListResponse? mediaListResponse;
String? uri;
Datum({this.id, this.name, this.mediaListResponse, this.uri});
factory Datum.fromJson(Map<String, dynamic> json) => _$DatumFromJson(json);
Map<String, dynamic> toJson() => _$DatumToJson(this);
}

View File

@@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'datum.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Datum _$DatumFromJson(Map<String, dynamic> json) => Datum(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String?,
mediaListResponse: json['mediaListResponse'] == null
? null
: MediaListResponse.fromJson(
json['mediaListResponse'] as Map<String, dynamic>),
uri: json['uri'] as String?,
);
Map<String, dynamic> _$DatumToJson(Datum instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'mediaListResponse': instance.mediaListResponse,
'uri': instance.uri,
};

View File

@@ -0,0 +1,70 @@
import 'package:json_annotation/json_annotation.dart';
import 'upper.dart';
part 'list.g.dart';
@JsonSerializable()
class FavList {
int? id;
int? fid;
int? mid;
int? attr;
@JsonKey(name: 'attr_desc')
String? attrDesc;
String? title;
String? cover;
Upper? upper;
@JsonKey(name: 'cover_type')
int? coverType;
String? intro;
int? ctime;
int? mtime;
int? state;
@JsonKey(name: 'fav_state')
int? favState;
@JsonKey(name: 'media_count')
int? mediaCount;
@JsonKey(name: 'view_count')
int? viewCount;
int? vt;
@JsonKey(name: 'is_top')
bool? isTop;
@JsonKey(name: 'recent_fav')
dynamic recentFav;
@JsonKey(name: 'play_switch')
int? playSwitch;
int? type;
String? link;
String? bvid;
FavList({
this.id,
this.fid,
this.mid,
this.attr,
this.attrDesc,
this.title,
this.cover,
this.upper,
this.coverType,
this.intro,
this.ctime,
this.mtime,
this.state,
this.favState,
this.mediaCount,
this.viewCount,
this.vt,
this.isTop,
this.recentFav,
this.playSwitch,
this.type,
this.link,
this.bvid,
});
factory FavList.fromJson(Map<String, dynamic> json) => _$ListFromJson(json);
Map<String, dynamic> toJson() => _$ListToJson(this);
}

View File

@@ -0,0 +1,61 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'list.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
FavList _$ListFromJson(Map<String, dynamic> json) => FavList(
id: (json['id'] as num?)?.toInt(),
fid: (json['fid'] as num?)?.toInt(),
mid: (json['mid'] as num?)?.toInt(),
attr: (json['attr'] as num?)?.toInt(),
attrDesc: json['attr_desc'] as String?,
title: json['title'] as String?,
cover: json['cover'] as String?,
upper: json['upper'] == null
? null
: Upper.fromJson(json['upper'] as Map<String, dynamic>),
coverType: (json['cover_type'] as num?)?.toInt(),
intro: json['intro'] as String?,
ctime: (json['ctime'] as num?)?.toInt(),
mtime: (json['mtime'] as num?)?.toInt(),
state: (json['state'] as num?)?.toInt(),
favState: (json['fav_state'] as num?)?.toInt(),
mediaCount: (json['media_count'] as num?)?.toInt(),
viewCount: (json['view_count'] as num?)?.toInt(),
vt: (json['vt'] as num?)?.toInt(),
isTop: json['is_top'] as bool?,
recentFav: json['recent_fav'],
playSwitch: (json['play_switch'] as num?)?.toInt(),
type: (json['type'] as num?)?.toInt(),
link: json['link'] as String?,
bvid: json['bvid'] as String?,
);
Map<String, dynamic> _$ListToJson(FavList instance) => <String, dynamic>{
'id': instance.id,
'fid': instance.fid,
'mid': instance.mid,
'attr': instance.attr,
'attr_desc': instance.attrDesc,
'title': instance.title,
'cover': instance.cover,
'upper': instance.upper,
'cover_type': instance.coverType,
'intro': instance.intro,
'ctime': instance.ctime,
'mtime': instance.mtime,
'state': instance.state,
'fav_state': instance.favState,
'media_count': instance.mediaCount,
'view_count': instance.viewCount,
'vt': instance.vt,
'is_top': instance.isTop,
'recent_fav': instance.recentFav,
'play_switch': instance.playSwitch,
'type': instance.type,
'link': instance.link,
'bvid': instance.bvid,
};

View File

@@ -0,0 +1,21 @@
import 'package:json_annotation/json_annotation.dart';
import 'list.dart';
part 'media_list_response.g.dart';
@JsonSerializable()
class MediaListResponse {
int? count;
List<FavList>? list;
@JsonKey(name: 'has_more')
bool? hasMore;
MediaListResponse({this.count, this.list, this.hasMore});
factory MediaListResponse.fromJson(Map<String, dynamic> json) {
return _$MediaListResponseFromJson(json);
}
Map<String, dynamic> toJson() => _$MediaListResponseToJson(this);
}

View File

@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'media_list_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
MediaListResponse _$MediaListResponseFromJson(Map<String, dynamic> json) =>
MediaListResponse(
count: (json['count'] as num?)?.toInt(),
list: (json['list'] as List<dynamic>?)
?.map((item) => FavList.fromJson(item))
.toList(),
hasMore: json['has_more'] as bool?,
);
Map<String, dynamic> _$MediaListResponseToJson(MediaListResponse instance) =>
<String, dynamic>{
'count': instance.count,
'list': instance.list,
'has_more': instance.hasMore,
};

View File

@@ -0,0 +1,21 @@
import 'package:json_annotation/json_annotation.dart';
import 'datum.dart';
part 'space_fav.g.dart';
@JsonSerializable()
class SpaceFav {
int? code;
String? message;
int? ttl;
List<Datum>? data;
SpaceFav({this.code, this.message, this.ttl, this.data});
factory SpaceFav.fromJson(Map<String, dynamic> json) {
return _$SpaceFavFromJson(json);
}
Map<String, dynamic> toJson() => _$SpaceFavToJson(this);
}

View File

@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'space_fav.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SpaceFav _$SpaceFavFromJson(Map<String, dynamic> json) => SpaceFav(
code: (json['code'] as num?)?.toInt(),
message: json['message'] as String?,
ttl: (json['ttl'] as num?)?.toInt(),
data: (json['data'] as List<dynamic>?)
?.map((e) => Datum.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$SpaceFavToJson(SpaceFav instance) => <String, dynamic>{
'code': instance.code,
'message': instance.message,
'ttl': instance.ttl,
'data': instance.data,
};

View File

@@ -0,0 +1,16 @@
import 'package:json_annotation/json_annotation.dart';
part 'upper.g.dart';
@JsonSerializable()
class Upper {
int? mid;
String? name;
String? face;
Upper({this.mid, this.name, this.face});
factory Upper.fromJson(Map<String, dynamic> json) => _$UpperFromJson(json);
Map<String, dynamic> toJson() => _$UpperToJson(this);
}

View File

@@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'upper.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Upper _$UpperFromJson(Map<String, dynamic> json) => Upper(
mid: (json['mid'] as num?)?.toInt(),
name: json['name'] as String?,
face: json['face'] as String?,
);
Map<String, dynamic> _$UpperToJson(Upper instance) => <String, dynamic>{
'mid': instance.mid,
'name': instance.name,
'face': instance.face,
};

View File

@@ -7,15 +7,18 @@ import 'package:PiliPalaX/http/video.dart';
import 'package:PiliPalaX/models/user/fav_folder.dart';
class FavDetailController extends CommonController {
FavFolderItemData? item;
// FavFolderItemData? item;
int? mediaId;
late String heroTag;
RxString loadingText = '加载中...'.obs;
int mediaCount = 0;
RxString title = ''.obs;
RxString cover = ''.obs;
RxString name = ''.obs;
@override
void onInit() {
item = Get.arguments;
// item = Get.arguments;
if (Get.parameters.keys.isNotEmpty) {
mediaId = int.parse(Get.parameters['mediaId']!);
heroTag = Get.parameters['heroTag']!;
@@ -42,6 +45,9 @@ class FavDetailController extends CommonController {
@override
bool customHandleResponse(Success response) {
if (currentPage == 1) {
title.value = response.response.info['title'];
cover.value = response.response.info['cover'];
name.value = response.response.info['upper']['name'];
mediaCount = response.response.info['media_count'];
}
List currentList = loadingState.value is Success

View File

@@ -79,19 +79,21 @@ class _FavDetailPageState extends State<FavDetailPage> {
duration: const Duration(milliseconds: 500),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_favDetailController.item!.title!,
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'${_favDetailController.item!.mediaCount!}条视频',
style: Theme.of(context).textTheme.labelMedium,
)
],
)
Obx(
() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_favDetailController.title.value,
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'${_favDetailController.mediaCount}条视频',
style: Theme.of(context).textTheme.labelMedium,
)
],
),
),
],
),
);
@@ -134,52 +136,58 @@ class _FavDetailPageState extends State<FavDetailPage> {
// mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Hero(
tag: _favDetailController.heroTag,
child: NetworkImgLayer(
width: 180,
height: 110,
src: _favDetailController.item!.cover,
Obx(
() => Hero(
tag: _favDetailController.heroTag,
child: NetworkImgLayer(
width: 180,
height: 110,
src: _favDetailController.cover.value,
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
_favDetailController.item!.title!,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.titleMedium!
.fontSize,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
_favDetailController.item!.upper!.name!,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
color: Theme.of(context).colorScheme.outline),
),
const Spacer(),
Text(
'${_favDetailController.item!.mediaCount!}条视频',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
color: Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 20),
],
Obx(
() => Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
_favDetailController.title.value,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.titleMedium!
.fontSize,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
_favDetailController.name.value,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
color:
Theme.of(context).colorScheme.outline),
),
const Spacer(),
Text(
'${_favDetailController.mediaCount}条视频',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
color:
Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 20),
],
),
),
),
],

View File

@@ -0,0 +1,135 @@
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/favorite/member_favorite_ctr.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MemberFavorite extends StatefulWidget {
const MemberFavorite({
super.key,
required this.heroTag,
required this.mid,
});
final String? heroTag;
final int mid;
@override
State<MemberFavorite> createState() => _MemberFavoriteState();
}
class _MemberFavoriteState extends State<MemberFavorite>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
late final _controller = Get.put(
MemberFavoriteCtr(mid: widget.mid),
tag: widget.heroTag,
);
@override
Widget build(BuildContext context) {
super.build(context);
return Obx(() => _buildBody(_controller.loadingState.value));
}
_buildBody(LoadingState loadingState) {
return loadingState is Success
? RefreshIndicator(
onRefresh: () async {
await _controller.onRefresh();
},
child: ListView.builder(
itemCount: loadingState.response.length,
itemBuilder: (_, index) {
dynamic item = loadingState.response[index];
return item.mediaListResponse.list is List
? ExpansionTile(
dense: true,
title: Text.rich(
TextSpan(
children: [
TextSpan(
text: item.name,
style: TextStyle(fontSize: 14),
),
TextSpan(
text: ' ${item.mediaListResponse.count}',
style: TextStyle(fontSize: 13),
),
],
),
),
controlAffinity: ListTileControlAffinity.leading,
children: (item.mediaListResponse.list as List)
.map(
(item1) => ListTile(
onTap: () {
if (item1.state == 1) {
return;
}
if (item.id == 1) {
Get.toNamed(
'/favDetail',
parameters: {
'mediaId': item1.id.toString(),
'heroTag': widget.heroTag ?? '',
},
);
} else {
// TODO
}
},
leading: Container(
margin:
const EdgeInsets.symmetric(vertical: 6),
child: LayoutBuilder(
builder: (_, constraints) =>
NetworkImgLayer(
radius: 6,
src: item1.cover,
width: constraints.maxHeight *
StyleString.aspectRatio,
height: constraints.maxHeight,
),
),
),
title: Text(item1.title),
subtitle: Text(
'${item1.mediaCount}个内容 · ${item1.mid == widget.mid ? [
0,
22
].contains(item1.attr) ? '公开' : '私密' : item1.upper.name}',
style: TextStyle(
color:
Theme.of(context).colorScheme.outline,
),
),
),
)
.toList(),
)
: const SizedBox.shrink();
},
),
)
: loadingState is Error
? Center(
child: CustomScrollView(
shrinkWrap: true,
slivers: [
HttpError(
errMsg: loadingState.errMsg,
fn: _controller.onReload,
),
],
),
)
: Center(
child: CircularProgressIndicator(),
);
}
}

View File

@@ -0,0 +1,26 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/http/member.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
class MemberFavoriteCtr extends CommonController {
MemberFavoriteCtr({
required this.mid,
});
final int mid;
@override
void onInit() {
super.onInit();
queryData();
}
@override
bool customHandleResponse(Success response) {
loadingState.value = response;
return true;
}
@override
Future<LoadingState> customGetData() => MemberHttp.spaceFav(mid: mid);
}

View File

@@ -4,6 +4,7 @@ import 'package:PiliPalaX/common/widgets/dynamic_sliver_appbar.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/bangumi/member_bangumi.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart';
import 'package:PiliPalaX/pages/member/new/content/member_dynamic/member_dynamic.dart';
import 'package:PiliPalaX/pages/member/new/content/member_home/member_home.dart';
@@ -225,6 +226,10 @@ class _MemberPageNewState extends State<MemberPageNew>
heroTag: _heroTag,
mid: _mid ?? -1,
),
'favorite' => MemberFavorite(
heroTag: _heroTag,
mid: _mid ?? -1,
),
_ => Center(child: Text(item.title ?? '')),
};
}).toList(),