mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
Refactor member page (#3)
* refactor: member page * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MemberArticle extends StatefulWidget {
|
||||
const MemberArticle({
|
||||
super.key,
|
||||
required this.heroTag,
|
||||
});
|
||||
|
||||
final String? heroTag;
|
||||
|
||||
@override
|
||||
State<MemberArticle> createState() => _MemberArticleState();
|
||||
}
|
||||
|
||||
class _MemberArticleState extends State<MemberArticle>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Center(
|
||||
child: Text('Article'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MemberAudio extends StatefulWidget {
|
||||
const MemberAudio({
|
||||
super.key,
|
||||
required this.heroTag,
|
||||
});
|
||||
|
||||
final String? heroTag;
|
||||
|
||||
@override
|
||||
State<MemberAudio> createState() => _MemberAudioState();
|
||||
}
|
||||
|
||||
class _MemberAudioState extends State<MemberAudio>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Center(
|
||||
child: Text('Audio'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/pages/bangumi/widgets/bangumi_card_v_member_home.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart';
|
||||
import 'package:PiliPalaX/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberBangumi extends StatefulWidget {
|
||||
const MemberBangumi({
|
||||
super.key,
|
||||
required this.heroTag,
|
||||
required this.mid,
|
||||
});
|
||||
|
||||
final String? heroTag;
|
||||
final int mid;
|
||||
|
||||
@override
|
||||
State<MemberBangumi> createState() => _MemberBangumiState();
|
||||
}
|
||||
|
||||
class _MemberBangumiState extends State<MemberBangumi>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
late final _controller = Get.put(
|
||||
MemberBangumiCtr(
|
||||
heroTag: widget.heroTag,
|
||||
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: CustomScrollView(
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
top: StyleString.safeSpace,
|
||||
bottom: StyleString.safeSpace +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace - 2,
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2,
|
||||
childAspectRatio: 0.65,
|
||||
mainAxisExtent:
|
||||
MediaQuery.textScalerOf(context).scale(60),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index == loadingState.response.length - 1) {
|
||||
_controller.onLoadMore();
|
||||
}
|
||||
return BangumiCardVMemberHome(
|
||||
bangumiItem: loadingState.response[index],
|
||||
);
|
||||
},
|
||||
childCount: loadingState.response.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: loadingState is Error
|
||||
? Center(
|
||||
child: CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
fn: _controller.onReload,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/http/member.dart';
|
||||
import 'package:PiliPalaX/models/space_archive/data.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart'
|
||||
show ContributeType;
|
||||
import 'package:PiliPalaX/pages/member/new/controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberBangumiCtr extends CommonController {
|
||||
MemberBangumiCtr({
|
||||
required this.mid,
|
||||
required this.heroTag,
|
||||
});
|
||||
|
||||
final int mid;
|
||||
final String? heroTag;
|
||||
bool isEnd = false;
|
||||
late final int count;
|
||||
late final _ctr = Get.find<MemberControllerNew>(tag: heroTag);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
currentPage = 2;
|
||||
dynamic res = (_ctr.loadingState.value as Success).response.season;
|
||||
loadingState.value = LoadingState.success(res.item);
|
||||
count = res.count;
|
||||
isEnd = res.item!.length >= count;
|
||||
}
|
||||
|
||||
@override
|
||||
Future onRefresh() async {
|
||||
isEnd = false;
|
||||
currentPage = 1;
|
||||
await queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
Future queryData([bool isRefresh = true]) {
|
||||
if (isEnd) return Future.value();
|
||||
return super.queryData(isRefresh);
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
Data data = response.response;
|
||||
if (currentPage != 1 && loadingState.value is Success) {
|
||||
data.item?.insertAll(0, (loadingState.value as Success).response);
|
||||
}
|
||||
isEnd = data.item!.length >= count;
|
||||
loadingState.value = LoadingState.success(data.item);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() => MemberHttp.spaceArchive(
|
||||
type: ContributeType.bangumi,
|
||||
mid: mid,
|
||||
pn: currentPage,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/badge.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/models/space_fav/datum.dart';
|
||||
import 'package:PiliPalaX/models/space_fav/list.dart';
|
||||
import 'package:PiliPalaX/models/user/sub_folder.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/favorite/member_favorite_ctr.dart';
|
||||
import 'package:PiliPalaX/utils/app_scheme.dart';
|
||||
import 'package:PiliPalaX/utils/utils.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: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Obx(
|
||||
() => _controller.first.value.mediaListResponse?.list
|
||||
?.isNotEmpty ==
|
||||
true
|
||||
? _buildItem(_controller.first.value, true)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Obx(
|
||||
() => _controller.second.value.mediaListResponse?.list
|
||||
?.isNotEmpty ==
|
||||
true
|
||||
? _buildItem(_controller.second.value, false)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 12 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: loadingState is Error
|
||||
? Center(
|
||||
child: CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
fn: _controller.onReload,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
_buildItem(Datum data, bool isFirst) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
dividerColor: Colors.transparent,
|
||||
),
|
||||
child: ExpansionTile(
|
||||
dense: true,
|
||||
initiallyExpanded: true,
|
||||
title: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: data.name,
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${data.mediaListResponse?.count}',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
children: [
|
||||
...(data.mediaListResponse?.list as List<FavList>).map(
|
||||
(item1) => ListTile(
|
||||
onTap: () {
|
||||
if (item1.state == 1) {
|
||||
// invalid
|
||||
return;
|
||||
}
|
||||
if (item1.type == 0) {
|
||||
Get.toNamed(
|
||||
'/favDetail',
|
||||
parameters: {
|
||||
'mediaId': item1.id.toString(),
|
||||
'heroTag': widget.heroTag ?? '',
|
||||
},
|
||||
);
|
||||
} else if (item1.type == 21) {
|
||||
PiliScheme.routePush(Uri.parse(item1.link ?? ''));
|
||||
} else if (item1.type == 11) {
|
||||
Get.toNamed(
|
||||
'/subDetail',
|
||||
arguments: SubFolderItemData(
|
||||
type: 11,
|
||||
title: item1.title,
|
||||
cover: item1.cover,
|
||||
upper: Upper(
|
||||
mid: item1.upper?.mid,
|
||||
name: item1.upper?.name,
|
||||
face: item1.upper?.face,
|
||||
),
|
||||
mediaCount: item1.mediaCount,
|
||||
viewCount: item1.viewCount,
|
||||
),
|
||||
parameters: {
|
||||
'heroTag': widget.heroTag ?? '',
|
||||
'id': item1.id.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
leading: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 6,
|
||||
src: item1.cover,
|
||||
width:
|
||||
constraints.maxHeight * StyleString.aspectRatio,
|
||||
height: constraints.maxHeight,
|
||||
),
|
||||
if (item1.type == 21)
|
||||
PBadge(
|
||||
right: 3,
|
||||
bottom: 3,
|
||||
text: '合集',
|
||||
bold: false,
|
||||
size: 'small',
|
||||
)
|
||||
else if (item1.type == 0 || item1.type == 11)
|
||||
Positioned(
|
||||
right: 3,
|
||||
bottom: 3,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(5),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.video_library_outlined,
|
||||
size: 12,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
item1.title ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
item1.type == 0
|
||||
? '${item1.mediaCount}个内容 · ${[
|
||||
0,
|
||||
2,
|
||||
22
|
||||
].contains(item1.attr) ? '公开' : '私密'}'
|
||||
: item1.type == 11
|
||||
? '${item1.mediaCount}个内容 · ${item1.upper?.name}'
|
||||
: item1.type == 21
|
||||
? '创建者: ${item1.upper?.name}\n${item1.mediaCount}个视频 · ${Utils.numFormat(item1.viewCount)}播放'
|
||||
: '${item1.mediaCount}个内容',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => (isFirst
|
||||
? _controller.firstEnd.value
|
||||
: _controller.secondEnd.value)
|
||||
? const SizedBox.shrink()
|
||||
: _buildLoadMoreItem(isFirst),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildLoadMoreItem(bool isFirst) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
if (isFirst) {
|
||||
_controller.userfavFolder();
|
||||
} else {
|
||||
_controller.userSubFolder();
|
||||
}
|
||||
},
|
||||
title: Text(
|
||||
'查看更多内容',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'package:PiliPalaX/http/api.dart';
|
||||
import 'package:PiliPalaX/http/init.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/http/member.dart';
|
||||
import 'package:PiliPalaX/models/space_fav/datum.dart';
|
||||
import 'package:PiliPalaX/models/space_fav/list.dart';
|
||||
import 'package:PiliPalaX/models/space_fav/space_fav.dart';
|
||||
import 'package:PiliPalaX/models/user/fav_folder.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberFavoriteCtr extends CommonController {
|
||||
MemberFavoriteCtr({
|
||||
required this.mid,
|
||||
});
|
||||
|
||||
final int mid;
|
||||
|
||||
Rx<Datum> first = Datum().obs;
|
||||
Rx<Datum> second = Datum().obs;
|
||||
|
||||
RxBool firstEnd = true.obs;
|
||||
RxBool secondEnd = true.obs;
|
||||
|
||||
late int page = 2;
|
||||
late int page1 = 2;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
Future onRefresh() {
|
||||
page = 2;
|
||||
page1 = 2;
|
||||
return super.onRefresh();
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
try {
|
||||
List<Datum> res = response.response;
|
||||
first.value = res.first;
|
||||
second.value = res[1];
|
||||
|
||||
firstEnd.value = (res.first.mediaListResponse?.count ?? -1) <=
|
||||
(res.first.mediaListResponse?.list?.length ?? -1);
|
||||
secondEnd.value = (res[1].mediaListResponse?.count ?? -1) <=
|
||||
(res[1].mediaListResponse?.list?.length ?? -1);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
loadingState.value = response;
|
||||
return true;
|
||||
}
|
||||
|
||||
Future userfavFolder() async {
|
||||
var res = await Request().get(Api.userFavFolder, data: {
|
||||
'pn': page,
|
||||
'ps': 20,
|
||||
'up_mid': mid,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
page++;
|
||||
firstEnd.value = res.data['data']['has_more'] == false;
|
||||
if (res.data['data'] != null) {
|
||||
List<FavList> list = (res.data['data']['list'] as List<dynamic>?)
|
||||
?.map((item) => FavList.fromJson(item))
|
||||
.toList() ??
|
||||
<FavList>[];
|
||||
first.value.mediaListResponse?.list?.addAll(list);
|
||||
first.refresh();
|
||||
} else {
|
||||
firstEnd.value = true;
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(res.data['message'] ?? '账号未登录');
|
||||
}
|
||||
}
|
||||
|
||||
Future userSubFolder() async {
|
||||
var res = await Request().get(Api.userSubFolder, data: {
|
||||
'up_mid': mid,
|
||||
'ps': 20,
|
||||
'pn': page1,
|
||||
'platform': 'web',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
page++;
|
||||
secondEnd.value = res.data['data']['has_more'] == false;
|
||||
if (res.data['data'] != null) {
|
||||
List<FavList> list = (res.data['data']['list'] as List<dynamic>?)
|
||||
?.map((item) => FavList.fromJson(item))
|
||||
.toList() ??
|
||||
<FavList>[];
|
||||
second.value.mediaListResponse?.list?.addAll(list);
|
||||
second.refresh();
|
||||
} else {
|
||||
secondEnd.value = true;
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(res.data['message'] ?? '账号未登录');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() => MemberHttp.spaceFav(mid: mid);
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/common/widgets/video_card_h_member_video.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart'
|
||||
show ContributeType;
|
||||
import 'package:PiliPalaX/pages/video/detail/reply/view.dart';
|
||||
import 'package:PiliPalaX/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberVideo extends StatefulWidget {
|
||||
const MemberVideo({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.heroTag,
|
||||
required this.mid,
|
||||
this.seasonId,
|
||||
this.seriesId,
|
||||
});
|
||||
|
||||
final ContributeType type;
|
||||
final String? heroTag;
|
||||
final int mid;
|
||||
final int? seasonId;
|
||||
final int? seriesId;
|
||||
|
||||
@override
|
||||
State<MemberVideo> createState() => _MemberVideoState();
|
||||
}
|
||||
|
||||
class _MemberVideoState extends State<MemberVideo>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
late final _controller = Get.put(
|
||||
MemberVideoCtr(
|
||||
type: widget.type,
|
||||
mid: widget.mid,
|
||||
seasonId: widget.seasonId,
|
||||
seriesId: widget.seriesId,
|
||||
),
|
||||
tag:
|
||||
'${widget.heroTag}${widget.type.name}${widget.seasonId}${widget.seriesId}',
|
||||
);
|
||||
|
||||
@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: CustomScrollView(
|
||||
slivers: [
|
||||
SliverPersistentHeader(
|
||||
pinned: false,
|
||||
floating: true,
|
||||
delegate: MySliverPersistentHeaderDelegate(
|
||||
child: Container(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
_controller.count.value != -1
|
||||
? '共${_controller.count.value}视频'
|
||||
: '',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 35,
|
||||
child: TextButton.icon(
|
||||
onPressed: _controller.queryBySort,
|
||||
icon: const Icon(Icons.sort, size: 16),
|
||||
label: Obx(
|
||||
() => Text(
|
||||
widget.type == ContributeType.video
|
||||
? _controller.order.value == 'pubdate'
|
||||
? '最新发布'
|
||||
: '最多播放'
|
||||
: _controller.sort.value == 'desc'
|
||||
? '默认'
|
||||
: '倒序',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace,
|
||||
StyleString.safeSpace - 5,
|
||||
StyleString.safeSpace,
|
||||
MediaQuery.of(context).padding.bottom + 10,
|
||||
),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.4,
|
||||
mainAxisExtent: 0,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (widget.type != ContributeType.season &&
|
||||
index == loadingState.response.length - 1) {
|
||||
_controller.onLoadMore();
|
||||
}
|
||||
return VideoCardHMemberVideo(
|
||||
videoItem: loadingState.response[index],
|
||||
showPubdate: true,
|
||||
);
|
||||
},
|
||||
childCount: loadingState.response.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: loadingState is Error
|
||||
? Center(
|
||||
child: CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
fn: _controller.onReload,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/http/member.dart';
|
||||
import 'package:PiliPalaX/models/space_archive/data.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart'
|
||||
show ContributeType;
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberVideoCtr extends CommonController {
|
||||
MemberVideoCtr({
|
||||
required this.type,
|
||||
required this.mid,
|
||||
required this.seasonId,
|
||||
required this.seriesId,
|
||||
});
|
||||
|
||||
ContributeType type;
|
||||
int? seasonId;
|
||||
int? seriesId;
|
||||
final int mid;
|
||||
String? aid;
|
||||
RxString order = 'pubdate'.obs;
|
||||
RxString sort = 'desc'.obs;
|
||||
bool isEnd = false;
|
||||
RxInt count = (-1).obs;
|
||||
int? next;
|
||||
|
||||
@override
|
||||
Future onRefresh() async {
|
||||
isEnd = false;
|
||||
aid = null;
|
||||
next = null;
|
||||
currentPage = 0;
|
||||
await queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
currentPage = 0;
|
||||
queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
Future queryData([bool isRefresh = true]) {
|
||||
if (isEnd) return Future.value();
|
||||
return super.queryData(isRefresh);
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
Data data = response.response;
|
||||
next = data.next;
|
||||
aid = data.item?.lastOrNull?.param;
|
||||
isEnd =
|
||||
type == ContributeType.video ? data.hasNext == false : data.next == 0;
|
||||
if (currentPage == 0) {
|
||||
count.value = type == ContributeType.season
|
||||
? (data.item?.length ?? -1)
|
||||
: (data.count ?? -1);
|
||||
} else if (loadingState.value is Success) {
|
||||
data.item?.insertAll(0, (loadingState.value as Success).response);
|
||||
}
|
||||
loadingState.value = LoadingState.success(data.item);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() => MemberHttp.spaceArchive(
|
||||
type: type,
|
||||
mid: mid,
|
||||
aid: type == ContributeType.video ? aid : null,
|
||||
order: order.value,
|
||||
sort: sort.value,
|
||||
pn: type == ContributeType.charging ? currentPage : null,
|
||||
next: next,
|
||||
seasonId: seasonId,
|
||||
seriesId: seriesId,
|
||||
);
|
||||
|
||||
queryBySort() {
|
||||
if (type == ContributeType.video) {
|
||||
order.value = order.value == 'pubdate' ? 'click' : 'pubdate';
|
||||
} else {
|
||||
sort.value = sort.value == 'desc' ? 'asc' : 'desc';
|
||||
}
|
||||
loadingState.value = LoadingState.loading();
|
||||
onRefresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/article/member_article.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/audio/member_audio.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/video/member_video.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute_ctr.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
enum ContributeType { video, charging, season, series, bangumi }
|
||||
|
||||
class MemberContribute extends StatefulWidget {
|
||||
const MemberContribute({
|
||||
super.key,
|
||||
this.heroTag,
|
||||
this.initialIndex,
|
||||
required this.mid,
|
||||
});
|
||||
|
||||
final String? heroTag;
|
||||
final int? initialIndex;
|
||||
final int mid;
|
||||
|
||||
@override
|
||||
State<MemberContribute> createState() => _MemberContributeState();
|
||||
}
|
||||
|
||||
class _MemberContributeState extends State<MemberContribute>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
late final _controller = Get.put(
|
||||
MemberContributeCtr(
|
||||
heroTag: widget.heroTag,
|
||||
initialIndex: widget.initialIndex,
|
||||
),
|
||||
tag: widget.heroTag,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return _controller.tabs != null
|
||||
? Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
),
|
||||
child: TabBar(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
isScrollable: true,
|
||||
tabs: _controller.tabs!,
|
||||
tabAlignment: TabAlignment.start,
|
||||
controller: _controller.tabController,
|
||||
dividerHeight: 0,
|
||||
indicatorWeight: 0,
|
||||
indicatorPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
|
||||
indicator: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
labelStyle: const TextStyle(fontSize: 14),
|
||||
labelColor:
|
||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _controller.tabController,
|
||||
children: _controller.items!
|
||||
.map(
|
||||
(item) => switch (item.param) {
|
||||
'video' => MemberVideo(
|
||||
type: ContributeType.video,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
),
|
||||
'charging_video' => MemberVideo(
|
||||
type: ContributeType.charging,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
),
|
||||
'article' => MemberArticle(heroTag: widget.heroTag),
|
||||
'audio' => MemberAudio(heroTag: widget.heroTag),
|
||||
'season_video' => MemberVideo(
|
||||
type: ContributeType.season,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
seasonId: item.seasonId,
|
||||
),
|
||||
'series' => MemberVideo(
|
||||
type: ContributeType.series,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
seriesId: item.seriesId,
|
||||
),
|
||||
_ => Center(child: Text(item.title!))
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: MemberVideo(
|
||||
type: ContributeType.video,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/models/space/tab2.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/controller.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../../models/space/item.dart';
|
||||
|
||||
class MemberContributeCtr extends CommonController
|
||||
with GetTickerProviderStateMixin {
|
||||
MemberContributeCtr({
|
||||
required this.heroTag,
|
||||
required this.initialIndex,
|
||||
});
|
||||
final String? heroTag;
|
||||
final int? initialIndex;
|
||||
|
||||
TabController? tabController;
|
||||
List<Tab>? tabs;
|
||||
late final _ctr = Get.find<MemberControllerNew>(tag: heroTag);
|
||||
List<Item>? items;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
Tab2 contribute =
|
||||
_ctr.tab2!.firstWhere((item) => item.param == 'contribute');
|
||||
if (contribute.items?.isNullOrEmpty == false &&
|
||||
contribute.items!.length > 1) {
|
||||
items = contribute.items;
|
||||
tabs = items!.map((item) => Tab(text: item.title)).toList();
|
||||
tabController = TabController(
|
||||
vsync: this,
|
||||
length: items!.length,
|
||||
initialIndex: max(0, initialIndex ?? 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/pages/dynamics/widgets/dynamic_panel_grpc.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_dynamic/member_dynamic_ctr.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberDynamic extends StatefulWidget {
|
||||
const MemberDynamic({
|
||||
super.key,
|
||||
required this.mid,
|
||||
});
|
||||
|
||||
final int mid;
|
||||
|
||||
@override
|
||||
State<MemberDynamic> createState() => _MemberDynamicState();
|
||||
}
|
||||
|
||||
class _MemberDynamicState extends State<MemberDynamic>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
late final _controller = Get.put(MemberDynamicCtr(mid: widget.mid));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return ListView.separated(
|
||||
itemCount: 100,
|
||||
itemBuilder: (_, index) {
|
||||
return ListTile(title: Text(index.toString()));
|
||||
},
|
||||
separatorBuilder: (_, index) => const SizedBox(height: 10),
|
||||
);
|
||||
return Obx(() => _buildBody(_controller.loadingState.value));
|
||||
}
|
||||
|
||||
_buildBody(LoadingState loadingState) {
|
||||
return loadingState is Success
|
||||
? RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _controller.onRefresh();
|
||||
},
|
||||
child: ListView.separated(
|
||||
itemCount: loadingState.response.length,
|
||||
itemBuilder: (_, index) {
|
||||
if (index == loadingState.response.length - 1) {
|
||||
_controller.onLoadMore();
|
||||
}
|
||||
return DynamicPanelGrpc(
|
||||
item: loadingState.response[index],
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, index) => const SizedBox(height: 10),
|
||||
),
|
||||
)
|
||||
: loadingState is Error
|
||||
? Center(
|
||||
child: CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: loadingState.errMsg,
|
||||
fn: _controller.onReload,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'package:PiliPalaX/grpc/app/dynamic/v2/dynamic.pb.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/http/member.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
|
||||
class MemberDynamicCtr extends CommonController {
|
||||
MemberDynamicCtr({
|
||||
required this.mid,
|
||||
});
|
||||
int mid;
|
||||
bool isEnd = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
Future onRefresh() {
|
||||
isEnd = false;
|
||||
return super.onRefresh();
|
||||
}
|
||||
|
||||
@override
|
||||
Future queryData([bool isRefresh = true]) {
|
||||
if (isEnd) return Future.value();
|
||||
return super.queryData(isRefresh);
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
DynSpaceRsp res = response.response;
|
||||
isEnd = !res.hasMore;
|
||||
if (currentPage != 1) {
|
||||
res.list.insertAll(
|
||||
0, (loadingState.value as Success?)?.response ?? <DynamicItem>[]);
|
||||
}
|
||||
loadingState.value = LoadingState.success(res.list);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() => MemberHttp.spaceDynamic(
|
||||
mid: mid,
|
||||
page: currentPage,
|
||||
);
|
||||
}
|
||||
214
lib/pages/member/new/content/member_home/member_home.dart
Normal file
214
lib/pages/member/new/content/member_home/member_home.dart
Normal file
@@ -0,0 +1,214 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/video_card_v_member_home.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/models/space/data.dart';
|
||||
import 'package:PiliPalaX/models/space/item.dart';
|
||||
import 'package:PiliPalaX/pages/bangumi/widgets/bangumi_card_v_member_home.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute_ctr.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/controller.dart';
|
||||
import 'package:PiliPalaX/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberHome extends StatefulWidget {
|
||||
const MemberHome({super.key, this.heroTag});
|
||||
|
||||
final String? heroTag;
|
||||
|
||||
@override
|
||||
State<MemberHome> createState() => _MemberHomeState();
|
||||
}
|
||||
|
||||
class _MemberHomeState extends State<MemberHome>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
late final _ctr = Get.find<MemberControllerNew>(tag: widget.heroTag);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return _buildBody(_ctr.loadingState.value);
|
||||
}
|
||||
|
||||
Widget _buildBody(LoadingState loadingState) {
|
||||
return loadingState is Success && loadingState.response is Data
|
||||
? CustomScrollView(
|
||||
slivers: [
|
||||
if (loadingState.response?.archive?.item?.isNotEmpty == true) ...[
|
||||
_videoHeader(
|
||||
title: '视频',
|
||||
param: 'contribute',
|
||||
param1: 'video',
|
||||
count: loadingState.response.archive.count,
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth,
|
||||
childAspectRatio: StyleString.aspectRatio,
|
||||
mainAxisExtent:
|
||||
MediaQuery.textScalerOf(context).scale(90),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return VideoCardVMemberHome(
|
||||
videoItem: loadingState.response.archive.item[index],
|
||||
);
|
||||
},
|
||||
childCount:
|
||||
min(4, loadingState.response.archive.item.length),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (loadingState.response?.article?.item?.isNotEmpty == true) ...[
|
||||
_videoHeader(
|
||||
title: '专栏',
|
||||
param: 'contribute',
|
||||
param1: 'article',
|
||||
count: loadingState.response.article.count,
|
||||
),
|
||||
// TODO
|
||||
],
|
||||
if (loadingState.response?.audios?.item?.isNotEmpty == true) ...[
|
||||
_videoHeader(
|
||||
title: '音频',
|
||||
param: 'contribute',
|
||||
param1: 'audio',
|
||||
count: loadingState.response.audios.count,
|
||||
),
|
||||
// TODO
|
||||
],
|
||||
if (loadingState.response?.season?.item?.isNotEmpty == true) ...[
|
||||
_videoHeader(
|
||||
title: '追番',
|
||||
param: 'bangumi',
|
||||
count: loadingState.response.season.count,
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace - 2,
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2,
|
||||
childAspectRatio: 0.65,
|
||||
mainAxisExtent:
|
||||
MediaQuery.textScalerOf(context).scale(60),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return BangumiCardVMemberHome(
|
||||
bangumiItem: loadingState.response.season.item[index],
|
||||
);
|
||||
},
|
||||
childCount:
|
||||
min(3, loadingState.response.season.item.length),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 12 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget _videoHeader({
|
||||
required String title,
|
||||
required String param,
|
||||
String? param1,
|
||||
required int count,
|
||||
}) =>
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: '$title '),
|
||||
TextSpan(
|
||||
text: count.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
int index =
|
||||
_ctr.tab2!.indexWhere((item) => item.param == param);
|
||||
if (['video', 'article', 'audio'].contains(param1)) {
|
||||
List<Item> items = _ctr.tab2!
|
||||
.firstWhere((item) => item.param == param)
|
||||
.items!;
|
||||
int index1 =
|
||||
items.indexWhere((item) => item.param == param1);
|
||||
try {
|
||||
final contributeCtr =
|
||||
Get.find<MemberContributeCtr>(tag: widget.heroTag);
|
||||
// contributeCtr.tabController?.animateTo(index1);
|
||||
if (contributeCtr.tabController?.index != index1) {
|
||||
contributeCtr.tabController?.index = index1;
|
||||
}
|
||||
print('initialized');
|
||||
} catch (e) {
|
||||
_ctr.contributeInitialIndex.value = index1;
|
||||
print('not initialized');
|
||||
}
|
||||
}
|
||||
if (index != -1) {
|
||||
_ctr.tabController.animateTo(index);
|
||||
}
|
||||
},
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '查看更多',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 14,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
112
lib/pages/member/new/controller.dart
Normal file
112
lib/pages/member/new/controller.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/http/member.dart';
|
||||
import 'package:PiliPalaX/http/video.dart';
|
||||
import 'package:PiliPalaX/models/space/tab2.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class MemberControllerNew extends CommonController
|
||||
with GetTickerProviderStateMixin {
|
||||
MemberControllerNew({required this.mid});
|
||||
int? mid;
|
||||
RxDouble scrollRatio = 0.0.obs;
|
||||
String? username;
|
||||
int? ownerMid;
|
||||
RxBool isFollow = false.obs;
|
||||
RxInt relation = 1.obs;
|
||||
late final TabController tabController;
|
||||
late final List<Tab> tabs;
|
||||
List<Tab2>? tab2;
|
||||
RxInt contributeInitialIndex = 0.obs;
|
||||
double? top;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
ownerMid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
queryData();
|
||||
}
|
||||
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
username = response.response?.card?.name ?? '';
|
||||
isFollow.value = response.response?.card?.relation?.isFollow == 1;
|
||||
relation.value = response.response?.relation ?? 1;
|
||||
tab2 = response.response.tab2;
|
||||
if (tab2 != null && tab2!.isNotEmpty) {
|
||||
int initialIndex = tab2!
|
||||
.indexWhere((item) => item.param == response.response.defaultTab);
|
||||
if (initialIndex == 0 &&
|
||||
!response.response.tab.toJson().values.contains('true')) {
|
||||
if (tab2!.length > 1) {
|
||||
initialIndex = 1;
|
||||
}
|
||||
// TODO
|
||||
// tab2!.removeAt(0);
|
||||
}
|
||||
tabs = tab2!.map((item) => Tab(text: item.title ?? '')).toList();
|
||||
tabController = TabController(
|
||||
vsync: this,
|
||||
length: response.response.tab2.length,
|
||||
initialIndex: initialIndex == -1
|
||||
? tab2!.length > 1
|
||||
? 1
|
||||
: 0
|
||||
: initialIndex,
|
||||
);
|
||||
}
|
||||
loadingState.value = response;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() => MemberHttp.space(mid: mid);
|
||||
|
||||
Future blockUser(BuildContext context) async {
|
||||
if (ownerMid == null) {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: Text(relation.value != -1 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'点错了',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
var res = await VideoHttp.relationMod(
|
||||
mid: mid ?? -1,
|
||||
act: relation.value != -1 ? 5 : 6,
|
||||
reSrc: 11,
|
||||
);
|
||||
if (res['status']) {
|
||||
relation.value = relation.value != -1 ? -1 : 1;
|
||||
isFollow.value = false;
|
||||
}
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void shareUser() {
|
||||
Share.share('https://space.bilibili.com/$mid');
|
||||
}
|
||||
}
|
||||
307
lib/pages/member/new/member_page.dart
Normal file
307
lib/pages/member/new/member_page.dart
Normal file
@@ -0,0 +1,307 @@
|
||||
import 'dart:math';
|
||||
|
||||
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';
|
||||
import 'package:PiliPalaX/pages/member/new/controller.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/widget/user_info_card.dart';
|
||||
import 'package:PiliPalaX/pages/member/view.dart';
|
||||
import 'package:PiliPalaX/pages/member_dynamics/view.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberPageNew extends StatefulWidget {
|
||||
const MemberPageNew({super.key});
|
||||
|
||||
@override
|
||||
State<MemberPageNew> createState() => _MemberPageNewState();
|
||||
}
|
||||
|
||||
class _MemberPageNewState extends State<MemberPageNew>
|
||||
with TickerProviderStateMixin {
|
||||
int? _mid;
|
||||
String? _heroTag;
|
||||
late final MemberControllerNew _userController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_mid = int.parse(Get.parameters['mid']!);
|
||||
_heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(_mid);
|
||||
_userController = Get.put(
|
||||
MemberControllerNew(mid: _mid),
|
||||
tag: _heroTag,
|
||||
);
|
||||
_userController.scrollController.addListener(() {
|
||||
_userController.scrollRatio.value =
|
||||
min(1.0, _userController.scrollController.offset.round() / 150);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_userController.top ??= MediaQuery.of(context).padding.top;
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Obx(
|
||||
() => _userController.loadingState.value is Success
|
||||
? LayoutBuilder(
|
||||
builder: (_, constraints) {
|
||||
if (constraints.maxHeight > constraints.maxWidth) {
|
||||
return ExtendedNestedScrollView(
|
||||
controller: _userController.scrollController,
|
||||
onlyOneScrollInBody: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: ExtendedNestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: _buildAppBar(),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: _userController.tab2 != null &&
|
||||
_userController.tab2!.isNotEmpty
|
||||
? LayoutBuilder(
|
||||
builder: (context, _) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: ExtendedNestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(
|
||||
context)
|
||||
.layoutExtent ??
|
||||
0,
|
||||
),
|
||||
child: _buildBody,
|
||||
);
|
||||
},
|
||||
)
|
||||
: Center(child: const Text('EMPTY')),
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
_buildAppBar(false),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
left: false,
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: _userController.top),
|
||||
_buildTab,
|
||||
Expanded(child: _buildBody),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
: Center(
|
||||
child: _buildUserInfo(_userController.loadingState.value),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _buildTab => TabBar(
|
||||
controller: _userController.tabController,
|
||||
tabs: _userController.tabs,
|
||||
);
|
||||
|
||||
Widget get _buildBody => TabBarView(
|
||||
controller: _userController.tabController,
|
||||
children: _userController.tab2!.map((item) {
|
||||
return switch (item.param!) {
|
||||
'home' => MemberHome(heroTag: _heroTag),
|
||||
// 'dynamic' => MemberDynamic(mid: _mid ?? -1),
|
||||
'dynamic' => MemberDynamicsPage(mid: _mid),
|
||||
'contribute' => Obx(
|
||||
() => MemberContribute(
|
||||
heroTag: _heroTag,
|
||||
initialIndex: _userController.contributeInitialIndex.value,
|
||||
mid: _mid ?? -1,
|
||||
),
|
||||
),
|
||||
'bangumi' => MemberBangumi(
|
||||
heroTag: _heroTag,
|
||||
mid: _mid ?? -1,
|
||||
),
|
||||
'favorite' => MemberFavorite(
|
||||
heroTag: _heroTag,
|
||||
mid: _mid ?? -1,
|
||||
),
|
||||
_ => Center(child: Text(item.title ?? '')),
|
||||
};
|
||||
}).toList(),
|
||||
);
|
||||
|
||||
Widget _buildAppBar([bool needTab = true]) => MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
removeRight: true,
|
||||
child: DynamicSliverAppBar(
|
||||
leading: Padding(
|
||||
padding: EdgeInsets.only(top: _userController.top ?? 0),
|
||||
child: const BackButton(),
|
||||
),
|
||||
title: Obx(() => _userController.scrollRatio.value == 1 &&
|
||||
_userController.username != null
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(top: _userController.top ?? 0),
|
||||
child: Text(_userController.username!),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
pinned: true,
|
||||
scrolledUnderElevation: 0,
|
||||
flexibleSpace: _buildUserInfo(_userController.loadingState.value),
|
||||
bottom: needTab &&
|
||||
_userController.tab2 != null &&
|
||||
_userController.tab2!.isNotEmpty
|
||||
? PreferredSize(
|
||||
preferredSize: Size.fromHeight(48),
|
||||
child: Material(
|
||||
child: _buildTab,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
actions: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: _userController.top ?? 0),
|
||||
child: IconButton(
|
||||
tooltip: '搜索',
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSearch?mid=$_mid&uname=${_userController.username}'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: _userController.top ?? 0),
|
||||
child: PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
if (_userController.ownerMid != _mid) ...[
|
||||
PopupMenuItem(
|
||||
onTap: () => _userController.blockUser(context),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.block, size: 19),
|
||||
const SizedBox(width: 10),
|
||||
Text(_userController.relation.value != -1
|
||||
? '加入黑名单'
|
||||
: '移除黑名单'),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
PopupMenuItem(
|
||||
onTap: () => _userController.shareUser(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.share_outlined, size: 19),
|
||||
const SizedBox(width: 10),
|
||||
Text(_userController.ownerMid != _mid
|
||||
? '分享UP主'
|
||||
: '分享我的主页'),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_userController.ownerMid != null) ...[
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 16,
|
||||
),
|
||||
content: ReportPanel(
|
||||
name: _userController.username,
|
||||
mid: _mid,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 19,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
'举报',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget _errorWidget(msg) {
|
||||
return CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: msg,
|
||||
fn: () {
|
||||
_userController.loadingState.value = LoadingState.loading();
|
||||
_userController.onRefresh();
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUserInfo(LoadingState userState) {
|
||||
switch (userState) {
|
||||
case Empty():
|
||||
return _errorWidget('EMPTY');
|
||||
case Error():
|
||||
return _errorWidget(userState.errMsg);
|
||||
case Success():
|
||||
return Obx(
|
||||
() => UserInfoCard(
|
||||
relation: _userController.relation.value,
|
||||
isFollow: _userController.isFollow.value,
|
||||
card: userState.response.card,
|
||||
images: userState.response.images,
|
||||
// onFollow: _userController.onFollow,
|
||||
),
|
||||
);
|
||||
}
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
}
|
||||
436
lib/pages/member/new/widget/user_info_card.dart
Normal file
436
lib/pages/member/new/widget/user_info_card.dart
Normal file
@@ -0,0 +1,436 @@
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/models/space/card.dart' as space;
|
||||
import 'package:PiliPalaX/models/space/images.dart' as space;
|
||||
import 'package:PiliPalaX/pages/preview/view.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class UserInfoCard extends StatelessWidget {
|
||||
const UserInfoCard({
|
||||
super.key,
|
||||
required this.card,
|
||||
required this.images,
|
||||
required this.relation,
|
||||
required this.isFollow,
|
||||
// required this.onFollow,
|
||||
});
|
||||
|
||||
final int relation;
|
||||
final bool isFollow;
|
||||
final space.Card card;
|
||||
final space.Images images;
|
||||
// final Function(dynamic uid, dynamic isFollow) onFollow;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool darken = GStorage.brightness == Brightness.dark;
|
||||
String? imgUrl = darken
|
||||
? (images.nightImgurl?.isEmpty == true
|
||||
? images.imgUrl?.http2https
|
||||
: images.nightImgurl?.http2https)
|
||||
: images.imgUrl?.http2https;
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
useSafeArea: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImagePreview(
|
||||
initialPage: 0,
|
||||
imgList: [imgUrl ?? ''],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: imgUrl ?? '',
|
||||
width: double.infinity,
|
||||
height: 135,
|
||||
imageBuilder: (context, imageProvider) => Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.cover,
|
||||
colorFilter: ColorFilter.mode(
|
||||
darken
|
||||
? const Color(0x8D000000)
|
||||
: const Color(0x5DFFFFFF),
|
||||
darken ? BlendMode.darken : BlendMode.lighten,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: double.infinity, height: 85)
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
top: 110,
|
||||
left: 20,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
useSafeArea: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImagePreview(
|
||||
initialPage: 0,
|
||||
imgList: [card.face ?? ''],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 2.5,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: NetworkImgLayer(
|
||||
src: card.face,
|
||||
type: 'avatar',
|
||||
width: 80,
|
||||
height: 80,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (card.officialVerify?.icon?.isNotEmpty == true)
|
||||
Positioned(
|
||||
top: 170,
|
||||
left: 80,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(0.01),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
child: NetworkImgLayer(
|
||||
src: card.officialVerify?.icon,
|
||||
radius: null,
|
||||
width: 24,
|
||||
height: 24,
|
||||
quality: 100,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 140,
|
||||
right: 20,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(
|
||||
5,
|
||||
(index) => index % 2 == 0
|
||||
? Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: _countWidget(
|
||||
title: ['粉丝', '关注', '获赞'][index ~/ 2],
|
||||
count: index == 0
|
||||
? card.fans
|
||||
: index == 2
|
||||
? card.attention
|
||||
: card.likes?.likeNum ?? 0,
|
||||
onTap: () {
|
||||
if (index == 0) {
|
||||
Get.toNamed(
|
||||
'/follow?mid=${card.mid}&name=${card.name}');
|
||||
} else if (index == 2) {
|
||||
Get.toNamed(
|
||||
'/fan?mid=${card.mid}&name=${card.name}');
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 15,
|
||||
width: 1,
|
||||
child: VerticalDivider(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton.outlined(
|
||||
onPressed: () {
|
||||
if (GStorage.userInfo.get('userInfoCache') != null) {
|
||||
Get.toNamed(
|
||||
'/whisperDetail',
|
||||
parameters: {
|
||||
'talkerId': card.mid ?? '',
|
||||
'name': card.name ?? '',
|
||||
'face': card.face ?? '',
|
||||
'mid': card.mid ?? '',
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.mail_outline, size: 21),
|
||||
style: IconButton.styleFrom(
|
||||
side: BorderSide(
|
||||
width: 1.0,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
FilledButton.tonal(
|
||||
onPressed: () {},
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: relation == -1 || isFollow
|
||||
? Theme.of(context).colorScheme.onInverseSurface
|
||||
: null,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
relation == -1
|
||||
? '已拉黑'
|
||||
: isFollow
|
||||
? '取消关注'
|
||||
: '关注',
|
||||
style: TextStyle(
|
||||
color: relation == -1 || isFollow
|
||||
? Theme.of(context).colorScheme.outline
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Wrap(
|
||||
spacing: 5,
|
||||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Utils.copyText(card.name ?? ''),
|
||||
child: Text(
|
||||
card.name ?? '',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: card.vip?.vipType == 2 ? Utils.vipColor : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/lv/lv${card.levelInfo?.currentLevel}.png',
|
||||
height: 11,
|
||||
semanticLabel: '等级${card.levelInfo?.currentLevel}',
|
||||
),
|
||||
if (card.vip?.vipStatus == 1)
|
||||
Image.network(
|
||||
card.vip!.label!.image!,
|
||||
height: 20,
|
||||
),
|
||||
if (card.nameplate?.image?.isNotEmpty == true)
|
||||
Image.network(
|
||||
card.nameplate!.image!,
|
||||
height: 20,
|
||||
),
|
||||
// GestureDetector(
|
||||
// onTap: () {
|
||||
// Utils.copyText(card.mid.toString());
|
||||
// },
|
||||
// child: Container(
|
||||
// padding:
|
||||
// const EdgeInsets.symmetric(horizontal: 8, vertical: 2.5),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
// borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
// ),
|
||||
// child: Text(
|
||||
// 'uid: ${card.mid}',
|
||||
// style: TextStyle(
|
||||
// height: 1,
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
// ),
|
||||
// strutStyle: const StrutStyle(
|
||||
// height: 1,
|
||||
// leading: 0,
|
||||
// fontSize: 12,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (card.officialVerify?.desc?.isNotEmpty == true)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 20, top: 8, right: 20),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
if (card.officialVerify?.icon?.isNotEmpty == true) ...[
|
||||
WidgetSpan(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(0.1),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
child: CachedNetworkImage(
|
||||
width: 18,
|
||||
height: 18,
|
||||
imageUrl: card.officialVerify!.icon!,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ',
|
||||
)
|
||||
],
|
||||
TextSpan(
|
||||
text: card.officialVerify!.spliceTitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.7),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (card.sign?.isNotEmpty == true)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 20, top: 6, right: 20),
|
||||
child: SelectableText(
|
||||
card.sign!.replaceFirst(RegExp('^\n'), ''),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 20, top: 6, right: 20),
|
||||
child: Wrap(
|
||||
spacing: 5,
|
||||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.end,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Utils.copyText(card.mid.toString());
|
||||
},
|
||||
child: Text(
|
||||
'UID: ${card.mid}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!card.spaceTag.isNullOrEmpty)
|
||||
...card.spaceTag!.map(
|
||||
(item) => Text(
|
||||
item.title ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// if (card.spaceTagBottom != null && card.spaceTagBottom!.isNotEmpty)
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(left: 20, top: 8, right: 20),
|
||||
// child: Wrap(
|
||||
// spacing: 5,
|
||||
// runSpacing: 8,
|
||||
// children: card.spaceTagBottom!
|
||||
// .map((item) => Text(item.title ?? ''))
|
||||
// .toList(),
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(height: 50),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _countWidget({
|
||||
required String title,
|
||||
required int count,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
Utils.numFormat(count),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 11,
|
||||
color: Theme.of(Get.context!).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPalaX/grpc/app/dynamic/v2/dynamic.pb.dart' as dyn;
|
||||
import 'package:PiliPalaX/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPalaX/http/member.dart';
|
||||
import 'package:PiliPalaX/http/user.dart';
|
||||
import 'package:PiliPalaX/models/member/info.dart';
|
||||
|
||||
Reference in New Issue
Block a user