opt: item

chore: clean up widgets

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-16 21:45:58 +08:00
parent 5ea8a7d313
commit 4972e64cad
104 changed files with 1059 additions and 5871 deletions

View File

@@ -1,10 +1,9 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_article/item.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/widget/item.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/article/member_article_ctr.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/article/widget/item.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -50,11 +49,7 @@ class _MemberArticleState extends State<MemberArticle>
child: CustomScrollView(
slivers: [
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response!.length - 1) {

View File

@@ -59,27 +59,36 @@ class MemberArticleItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.title?.isNotEmpty == true) ...[
Text(
item.title!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
Expanded(
child: Text(
item.title!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.bodyMedium!
.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
),
),
const Spacer(),
],
Text(
'${item.publishTimeText}',
style: TextStyle(
fontSize: 12,
height: 1,
color: Theme.of(context).colorScheme.outline,
),
),
const SizedBox(height: 4),
const SizedBox(height: 3),
Row(
children: [
StatView(
context: context,
value: item.stats?.view ?? 0,
size: 'medium',
goto: 'picture',
textColor: Theme.of(context).colorScheme.outline,
),
@@ -87,7 +96,6 @@ class MemberArticleItem extends StatelessWidget {
StatView(
context: context,
goto: 'reply',
size: 'medium',
value: item.stats?.reply ?? 0,
textColor: Theme.of(context).colorScheme.outline,
),

View File

@@ -4,7 +4,7 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_member_home.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/bangumi/member_bangumi_ctr.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

View File

@@ -3,9 +3,9 @@ import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart'
show ContributeType;
import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/pages/member/controller.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/models/space/data.dart' as space;

View File

@@ -3,8 +3,8 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_fav/datum.dart';
import 'package:PiliPlus/models/space_fav/list.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/favorite/member_favorite_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/favorite/widget/item.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/favorite/member_favorite_ctr.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/favorite/widget/item.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

View File

@@ -1,10 +1,10 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/widget/season_series_card.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/season_series/controller.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/season_series/widget/season_series_card.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -51,11 +51,7 @@ class _SeasonSeriesPageState extends State<SeasonSeriesPage>
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response!.length - 1) {

View File

@@ -87,7 +87,7 @@ class SeasonSeriesCard extends StatelessWidget {
Utils.dateFormat(item['meta']['ptime']),
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
fontSize: 12,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,

View File

@@ -6,10 +6,10 @@ import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/common/widgets/video_card_h_member_video.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
import 'package:PiliPlus/pages/member/content/member_contribute/content/video/member_video_ctr.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart'
show ContributeType;
import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -178,11 +178,7 @@ class _MemberVideoState extends State<MemberVideo>
bottom: MediaQuery.of(context).padding.bottom + 80,
),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (widget.type != ContributeType.season &&

View File

@@ -5,7 +5,7 @@ import 'package:PiliPlus/models/space_archive/data.dart';
import 'package:PiliPlus/models/space_archive/episodic_button.dart';
import 'package:PiliPlus/models/space_archive/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart'
show ContributeType;
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/id_utils.dart';

View File

@@ -1,8 +1,8 @@
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/member_article.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/audio/member_audio.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute_ctr.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/article/member_article.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/audio/member_audio.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/season_series/season_series_page.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute_ctr.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

View File

@@ -3,12 +3,12 @@ import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../../models/space/item.dart';
import '../../../../models/space/item.dart';
class MemberContributeCtr extends CommonDataController
with GetTickerProviderStateMixin {

View File

@@ -7,10 +7,10 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_member_home.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/widget/item.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute_ctr.dart';
import 'package:PiliPlus/pages/member/new/content/member_home/widget/fav_item.dart';
import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/article/widget/item.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute_ctr.dart';
import 'package:PiliPlus/pages/member/content/member_home/widget/fav_item.dart';
import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/pages/member_coin/index.dart';
import 'package:PiliPlus/pages/member_like/index.dart';
import 'package:PiliPlus/utils/grid.dart';
@@ -172,11 +172,7 @@ class _MemberHomeState extends State<MemberHome>
count: loadingState.response.article.count,
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
return MemberArticleItem(

View File

@@ -1,205 +1,150 @@
import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/models/member/info.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:intl/intl.dart';
import '../video/detail/introduction/widgets/group_panel.dart';
enum MemberTabType { none, home, dynamic, contribute, favorite, bangumi }
class MemberController extends GetxController {
int? mid;
MemberController({this.mid});
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
late Map userStat;
RxString face = ''.obs;
String? heroTag;
late int ownerMid;
bool specialFollowed = false;
// 投稿列表
dynamic userInfo;
RxInt attribute = (-1).obs;
RxString attributeText = '关注'.obs;
RxList<MemberCoinsDataModel> recentCoinsList = <MemberCoinsDataModel>[].obs;
String? wwebid;
extension MemberTabTypeExt on MemberTabType {
String get title => ['默认', '首页', '动态', '投稿', '收藏', '番剧'][index];
}
class MemberControllerNew extends CommonDataController<Data, dynamic>
with GetTickerProviderStateMixin {
MemberControllerNew({required this.mid});
int mid;
RxBool showUname = false.obs;
String? username;
int? ownerMid;
RxBool isFollow = false.obs;
RxInt relation = 1.obs;
TabController? tabController;
late List<Tab> tabs;
List<Tab2>? tab2;
RxInt contributeInitialIndex = 0.obs;
double top = 0;
bool? hasSeasonOrSeries;
final fromViewAid = Get.parameters['from_view_aid'];
@override
void onInit() async {
void onInit() {
super.onInit();
mid = mid ?? int.parse(Get.parameters['mid']!);
userInfo = GStorage.userInfo.get('userInfoCache');
ownerMid = userInfo != null ? userInfo.mid : -1;
try {
face.value = Get.arguments['face'] ?? '';
heroTag = Get.arguments['heroTag'] ?? '';
} catch (_) {}
relationSearch();
ownerMid = Accounts.main.mid;
queryData();
}
// 获取用户信息
Future<Map<String, dynamic>> getInfo() {
return Future.wait([getMemberInfo(), getMemberStat(), getMemberView()])
.then((res) => res[0]);
}
dynamic live;
Future<Map<String, dynamic>> getMemberInfo() async {
wwebid ??= await Utils.getWwebid(mid);
await getMemberStat();
await getMemberView();
var res = await MemberHttp.memberInfo(mid: mid, wwebid: wwebid);
if (res['status']) {
memberInfo.value = res['data'];
face.value = res['data'].face;
}
return res;
}
int? silence;
String? endTime;
// 获取用户状态
Future<Map<String, dynamic>> getMemberStat() async {
var res = await MemberHttp.memberStat(mid: mid);
if (res['status']) {
userStat = res['data'];
}
return res;
}
late final implTabs = const [
'home',
'dynamic',
'contribute',
'favorite',
'bangumi',
];
// 获取用户播放数 获赞数
Future<Map<String, dynamic>> getMemberView() async {
var res = await MemberHttp.memberView(mid: mid!);
if (res['status']) {
userStat.addAll(res['data']);
@override
bool customHandleResponse(bool isRefresh, Success<Data> response) {
Data data = response.response;
username = data.card?.name ?? '';
isFollow.value = data.card?.relation?.isFollow == 1;
relation.value = data.relSpecial == 1 ? 2 : data.relation ?? 1;
tab2 = data.tab2;
live = data.live;
silence = data.card?.silence;
if ((data.ugcSeason?.count != null && data.ugcSeason?.count != 0) ||
data.series?.item?.isNotEmpty == true) {
hasSeasonOrSeries = true;
}
return res;
}
Future delayedUpdateRelation() async {
await Future.delayed(const Duration(milliseconds: 1000), () async {
SmartDialog.showToast('更新状态');
await relationSearch();
memberInfo.update((val) {});
});
}
// 关注/取关up
Future actionRelationMod(BuildContext context) async {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
if (data.card?.endTime != null) {
if (data.card!.endTime == 0) {
endTime = ': 永久封禁';
} else if (data.card!.endTime! >
DateTime.now().millisecondsSinceEpoch ~/ 1000) {
endTime =
':至 ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(data.card!.endTime! * 1000))}';
}
}
if (memberInfo.value.mid == null) {
SmartDialog.showToast('尚未获取到用户信息');
return;
}
if (attribute.value == 128) {
blockUser(context);
return;
}
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('操作'),
actions: [
if (memberInfo.value.isFollowed!) ...[
TextButton(
onPressed: () async {
final res = await MemberHttp.addUsers(
mid, specialFollowed ? '0' : '-10');
SmartDialog.showToast(res['msg']);
if (res['status']) {
specialFollowed = !specialFollowed;
}
Get.back();
},
child: Text(specialFollowed ? '移除特别关注' : '加入特别关注'),
),
TextButton(
onPressed: () async {
await Get.bottomSheet(
GroupPanel(mid: mid),
isScrollControlled: true,
backgroundColor: Theme.of(context).colorScheme.surface,
);
Get.back();
},
child: const Text('设置分组'),
),
],
TextButton(
onPressed: () async {
var res = await VideoHttp.relationMod(
mid: mid!,
act: memberInfo.value.isFollowed! ? 2 : 1,
reSrc: 11,
);
SmartDialog.showToast(res['status'] ? "操作成功" : res['msg']);
if (res['status']) {
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
}
Get.back();
},
child: Text(memberInfo.value.isFollowed! ? '取消关注' : '关注'),
),
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
],
tab2?.retainWhere((item) => implTabs.contains(item.param));
if (tab2?.isNotEmpty == true) {
if (!data.tab!.toJson().values.contains(true) &&
tab2!.first.param == 'home') {
// remove empty home tab
tab2!.removeAt(0);
}
if (tab2!.isNotEmpty) {
int initialIndex = -1;
MemberTabType memberTab = GStorage.memberTab;
if (memberTab != MemberTabType.none) {
initialIndex = tab2!.indexWhere((item) {
return item.param == memberTab.name;
});
}
if (initialIndex == -1) {
if (data.defaultTab == 'video') {
data.defaultTab = 'contribute';
}
initialIndex = tab2!.indexWhere((item) {
return item.param == data.defaultTab;
});
}
tabs = tab2!.map((item) => Tab(text: item.title ?? '')).toList();
tabController = TabController(
vsync: this,
length: tabs.length,
initialIndex: max(0, initialIndex),
);
},
);
await delayedUpdateRelation();
}
// 关系查询
Future relationSearch() async {
if (userInfo == null) return;
if (mid == ownerMid) return;
var res = await UserHttp.hasFollow(mid!);
if (res['status']) {
attribute.value = res['data']['attribute'];
switch (attribute.value) {
case 1:
attributeText.value = '悄悄关注';
memberInfo.value.isFollowed = true;
break;
case 2:
attributeText.value = '已关注';
memberInfo.value.isFollowed = true;
break;
case 6:
attributeText.value = '已互关';
memberInfo.value.isFollowed = true;
break;
case 128:
attributeText.value = '已拉黑';
memberInfo.value.isFollowed = false;
break;
default:
attributeText.value = '关注';
memberInfo.value.isFollowed = false;
}
if (res['data']['special'] == 1) {
specialFollowed = true;
attributeText.value += ' 🔔';
} else {
specialFollowed = false;
}
} else {
SmartDialog.showToast(res['msg']);
}
loadingState.value = response;
return true;
}
// 拉黑用户
@override
bool handleError(String? errMsg) {
tab2 = [
Tab2(title: '动态', param: 'dynamic'),
Tab2(
title: '投稿',
param: 'contribute',
items: [Item(title: '视频', param: 'video')],
),
Tab2(title: '收藏', param: 'favorite'),
Tab2(title: '追番', param: 'bangumi'),
];
tabs = tab2!.map((item) => Tab(text: item.title)).toList();
tabController = TabController(
vsync: this,
length: tabs.length,
);
showUname.value = true;
username = errMsg;
loadingState.value = LoadingState.success(null);
return true;
}
@override
Future<LoadingState<Data>> customGetData() => MemberHttp.space(
mid: mid,
fromViewAid: fromViewAid,
);
Future blockUser(BuildContext context) async {
if (userInfo == null) {
if (ownerMid == 0) {
SmartDialog.showToast('账号未登录');
return;
}
@@ -208,10 +153,10 @@ class MemberController extends GetxController {
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
content: Text(relation.value != -1 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
actions: [
TextButton(
onPressed: () => Get.back(),
onPressed: Get.back,
child: Text(
'点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -220,18 +165,7 @@ class MemberController extends GetxController {
TextButton(
onPressed: () async {
Get.back();
var res = await VideoHttp.relationMod(
mid: mid!,
act: attribute.value != 128 ? 5 : 6,
reSrc: 11,
);
if (res['status']) {
attribute.value = attribute.value != 128 ? 128 : 0;
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';
memberInfo.value.isFollowed = false;
relationSearch();
memberInfo.update((val) {});
}
_onBlock();
},
child: const Text('确认'),
)
@@ -242,41 +176,46 @@ class MemberController extends GetxController {
}
void shareUser() {
Utils.shareText(
'${memberInfo.value.name} - https://space.bilibili.com/$mid');
Utils.shareText('https://space.bilibili.com/$mid');
}
// 请求专栏
Future getMemberSeasons() async {
if (userInfo == null) return;
var res = await MemberHttp.getMemberSeasons(mid, 1, 10);
if (!res['status']) {
SmartDialog.showToast("用户专栏请求异常:${res['msg']}");
void _onBlock() async {
dynamic res = await VideoHttp.relationMod(
mid: mid,
act: relation.value != -1 ? 5 : 6,
reSrc: 11,
);
if (res['status']) {
relation.value = relation.value != -1 ? -1 : 1;
isFollow.value = false;
}
return res;
}
// 请求投币视频
Future getRecentCoinVideo() async {
// if (userInfo == null) return;
// var res = await MemberHttp.getRecentCoinVideo(mid: mid!);
// recentCoinsList.value = res['data'];
// return res;
void onFollow(BuildContext context) async {
if (mid == ownerMid) {
Get.toNamed('/editProfile');
} else if (relation.value == -1) {
_onBlock();
} else {
if (ownerMid == null) {
SmartDialog.showToast('账号未登录');
return;
}
Utils.actionRelationMod(
context: context,
mid: mid,
isFollow: isFollow.value,
callback: (attribute) {
relation.value = attribute;
isFollow.value = attribute != 0;
},
);
}
}
// 跳转查看动态
void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid');
// 跳转查看投稿
void pushArchivesPage() async {
wwebid ??= await Utils.getWwebid(mid);
Get.toNamed('/memberArchive?mid=$mid&wwebid=$wwebid');
}
// 跳转查看专栏
void pushSeasonsPage() {}
// 跳转查看最近投币
void pushRecentCoinsPage() async {
if (recentCoinsList.isNotEmpty) {}
@override
void onClose() {
tabController?.dispose();
super.onClose();
}
}

View File

@@ -1,4 +0,0 @@
library member;
export './controller.dart';
export './view.dart';

View File

@@ -1,19 +1,21 @@
import 'package:PiliPlus/common/widgets/dynamic_sliver_appbar.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/bangumi/member_bangumi.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart';
import 'package:PiliPlus/pages/member/new/content/member_home/member_home.dart';
import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/pages/member/new/widget/user_info_card.dart';
import 'package:PiliPlus/pages/member/view.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/bangumi/member_bangumi.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/favorite/member_favorite.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart';
import 'package:PiliPlus/pages/member/content/member_home/member_home.dart';
import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/pages/member/widget/user_info_card.dart';
import 'package:PiliPlus/pages/member_dynamics/view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
@@ -298,3 +300,124 @@ class _MemberPageNewState extends State<MemberPageNew> {
};
}
}
class ReportPanel extends StatefulWidget {
const ReportPanel({
super.key,
required this.name,
required this.mid,
});
final dynamic name;
final dynamic mid;
@override
State<ReportPanel> createState() => _ReportPanelState();
}
class _ReportPanelState extends State<ReportPanel> {
final List<bool> _reasonList = List.generate(3, (_) => false).toList();
final Set<int> _reason = {};
int? _reasonV2;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'举报: ${widget.name}',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 4),
Text('uid: ${widget.mid}'),
const SizedBox(height: 10),
const Text('举报内容(必选,可多选)'),
...List.generate(
3,
(index) => _checkBoxWidget(
_reasonList[index],
(value) {
setState(() => _reasonList[index] = value);
if (value) {
_reason.add(index + 1);
} else {
_reason.remove(index + 1);
}
},
['头像违规', '昵称违规', '签名违规'][index],
),
),
const Text('举报理由(单选,非必选)'),
...List.generate(
5,
(index) => RadioWidget<int>(
value: index,
groupValue: _reasonV2,
onChanged: (value) {
setState(() => _reasonV2 = value);
},
title: const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
if (_reason.isEmpty) {
SmartDialog.showToast('至少选择一项作为举报内容');
} else {
Get.back();
dynamic result = await MemberHttp.reportMember(
widget.mid,
reason: _reason.join(','),
reasonV2: _reasonV2 != null ? _reasonV2! + 1 : null,
);
if (result['msg'] is String && result['msg'].isNotEmpty) {
SmartDialog.showToast(result['msg']);
} else {
SmartDialog.showToast('举报失败');
}
}
},
child: const Text('确定'),
),
],
),
],
),
);
}
}
Widget _checkBoxWidget(
bool defValue,
ValueChanged onChanged,
String title,
) {
return InkWell(
onTap: () => onChanged(!defValue),
child: Row(
children: [
Checkbox(
value: defValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
}

View File

@@ -1,221 +0,0 @@
import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space/item.dart';
import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
enum MemberTabType { none, home, dynamic, contribute, favorite, bangumi }
extension MemberTabTypeExt on MemberTabType {
String get title => ['默认', '首页', '动态', '投稿', '收藏', '番剧'][index];
}
class MemberControllerNew extends CommonDataController<Data, dynamic>
with GetTickerProviderStateMixin {
MemberControllerNew({required this.mid});
int mid;
RxBool showUname = false.obs;
String? username;
int? ownerMid;
RxBool isFollow = false.obs;
RxInt relation = 1.obs;
TabController? tabController;
late List<Tab> tabs;
List<Tab2>? tab2;
RxInt contributeInitialIndex = 0.obs;
double top = 0;
bool? hasSeasonOrSeries;
final fromViewAid = Get.parameters['from_view_aid'];
@override
void onInit() {
super.onInit();
ownerMid = Accounts.main.mid;
queryData();
}
dynamic live;
int? silence;
String? endTime;
late final implTabs = const [
'home',
'dynamic',
'contribute',
'favorite',
'bangumi',
];
@override
bool customHandleResponse(bool isRefresh, Success<Data> response) {
Data data = response.response;
username = data.card?.name ?? '';
isFollow.value = data.card?.relation?.isFollow == 1;
relation.value = data.relSpecial == 1 ? 2 : data.relation ?? 1;
tab2 = data.tab2;
live = data.live;
silence = data.card?.silence;
if ((data.ugcSeason?.count != null && data.ugcSeason?.count != 0) ||
data.series?.item?.isNotEmpty == true) {
hasSeasonOrSeries = true;
}
if (data.card?.endTime != null) {
if (data.card!.endTime == 0) {
endTime = ': 永久封禁';
} else if (data.card!.endTime! >
DateTime.now().millisecondsSinceEpoch ~/ 1000) {
endTime =
':至 ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(data.card!.endTime! * 1000))}';
}
}
tab2?.retainWhere((item) => implTabs.contains(item.param));
if (tab2?.isNotEmpty == true) {
if (!data.tab!.toJson().values.contains(true) &&
tab2!.first.param == 'home') {
// remove empty home tab
tab2!.removeAt(0);
}
if (tab2!.isNotEmpty) {
int initialIndex = -1;
MemberTabType memberTab = GStorage.memberTab;
if (memberTab != MemberTabType.none) {
initialIndex = tab2!.indexWhere((item) {
return item.param == memberTab.name;
});
}
if (initialIndex == -1) {
if (data.defaultTab == 'video') {
data.defaultTab = 'contribute';
}
initialIndex = tab2!.indexWhere((item) {
return item.param == data.defaultTab;
});
}
tabs = tab2!.map((item) => Tab(text: item.title ?? '')).toList();
tabController = TabController(
vsync: this,
length: tabs.length,
initialIndex: max(0, initialIndex),
);
}
}
loadingState.value = response;
return true;
}
@override
bool handleError(String? errMsg) {
tab2 = [
Tab2(title: '动态', param: 'dynamic'),
Tab2(
title: '投稿',
param: 'contribute',
items: [Item(title: '视频', param: 'video')],
),
Tab2(title: '收藏', param: 'favorite'),
Tab2(title: '追番', param: 'bangumi'),
];
tabs = tab2!.map((item) => Tab(text: item.title)).toList();
tabController = TabController(
vsync: this,
length: tabs.length,
);
showUname.value = true;
username = errMsg;
loadingState.value = LoadingState.success(null);
return true;
}
@override
Future<LoadingState<Data>> customGetData() => MemberHttp.space(
mid: mid,
fromViewAid: fromViewAid,
);
Future blockUser(BuildContext context) async {
if (ownerMid == 0) {
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();
_onBlock();
},
child: const Text('确认'),
)
],
);
},
);
}
void shareUser() {
Utils.shareText('https://space.bilibili.com/$mid');
}
void _onBlock() async {
dynamic res = await VideoHttp.relationMod(
mid: mid,
act: relation.value != -1 ? 5 : 6,
reSrc: 11,
);
if (res['status']) {
relation.value = relation.value != -1 ? -1 : 1;
isFollow.value = false;
}
}
void onFollow(BuildContext context) async {
if (mid == ownerMid) {
Get.toNamed('/editProfile');
} else if (relation.value == -1) {
_onBlock();
} else {
if (ownerMid == null) {
SmartDialog.showToast('账号未登录');
return;
}
Utils.actionRelationMod(
context: context,
mid: mid,
isFollow: isFollow.value,
callback: (attribute) {
relation.value = attribute;
isFollow.value = attribute != 0;
},
);
}
}
@override
void onClose() {
tabController?.dispose();
super.onClose();
}
}

View File

@@ -1,652 +0,0 @@
import 'dart:async';
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/pages/member/index.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'widgets/conis.dart';
import 'widgets/profile.dart';
import 'widgets/seasons.dart';
@Deprecated('Use MemberPageNew instead')
class MemberPage extends StatefulWidget {
const MemberPage({super.key});
@override
State<MemberPage> createState() => _MemberPageState();
}
class _MemberPageState extends State<MemberPage> {
late String heroTag;
late MemberController _memberController;
late Future _futureBuilderFuture;
late Future _memberSeasonsFuture;
late Future _memberCoinsFuture;
final ScrollController _extendNestCtr = ScrollController();
final StreamController<bool> appbarStream = StreamController<bool>();
late int mid;
@override
void initState() {
super.initState();
mid = int.parse(Get.parameters['mid']!);
heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid);
_memberController = Get.put(MemberController(), tag: heroTag);
_futureBuilderFuture = _memberController.getInfo();
_memberSeasonsFuture = _memberController.getMemberSeasons();
_memberCoinsFuture = _memberController.getRecentCoinVideo();
_extendNestCtr.addListener(listener);
}
void listener() {
final double offset = _extendNestCtr.position.pixels;
if (offset > 100) {
appbarStream.add(true);
} else {
appbarStream.add(false);
}
}
@override
void dispose() {
_extendNestCtr.removeListener(listener);
_extendNestCtr.dispose();
appbarStream.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
bool isHorizontal = context.width > context.height;
return Scaffold(
primary: true,
body: Column(
children: [
AppBar(
title: StreamBuilder(
stream: appbarStream.stream.distinct(),
initialData: false,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return AnimatedOpacity(
opacity: snapshot.data ? 1 : 0,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 500),
child: Row(
children: [
Row(
children: [
Obx(
() => NetworkImgLayer(
width: 35,
height: 35,
type: 'avatar',
src: _memberController.face.value,
),
),
const SizedBox(width: 10),
Obx(
() => Text(
_memberController.memberInfo.value.name ?? '',
style: TextStyle(
color:
Theme.of(context).colorScheme.onSurface,
fontSize: 14),
),
),
],
)
],
),
);
},
),
actions: [
IconButton(
tooltip: '搜索',
onPressed: () => Get.toNamed(
'/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'),
icon: const Icon(Icons.search_outlined),
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (_memberController.ownerMid != _memberController.mid) ...[
PopupMenuItem(
onTap: () => _memberController.blockUser(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.block, size: 19),
const SizedBox(width: 10),
Text(_memberController.attribute.value != 128
? '加入黑名单'
: '移除黑名单'),
],
),
)
],
PopupMenuItem(
onTap: () => _memberController.shareUser(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.share_outlined, size: 19),
const SizedBox(width: 10),
Text(_memberController.ownerMid != _memberController.mid
? '分享UP主'
: '分享我的主页'),
],
),
),
if (_memberController.userInfo != null) ...[
const PopupMenuDivider(),
PopupMenuItem(
onTap: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
content: ReportPanel(
name: _memberController.memberInfo.value.name,
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),
],
),
Expanded(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
controller: _extendNestCtr,
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 20,
),
child: Column(
children: [
profileWidget(isHorizontal),
Row(children: [
const Spacer(),
InkWell(
onTap: _memberController.pushDynamicsPage,
child: const Row(
children: [
Text('Ta的动态', style: TextStyle(height: 2)),
SizedBox(width: 5),
Icon(Icons.arrow_forward_ios, size: 19),
],
),
),
const Spacer(),
InkWell(
onTap: _memberController.pushArchivesPage,
child: const Row(
children: [
Text('Ta的投稿', style: TextStyle(height: 2)),
SizedBox(width: 5),
Icon(Icons.arrow_forward_ios, size: 19),
],
),
),
const Spacer(),
InkWell(
onTap: () {},
child: const Row(
children: [
Text('Ta的专栏', style: TextStyle(height: 2)),
SizedBox(width: 5),
],
),
),
const Spacer(),
]),
MediaQuery.removePadding(
removeTop: true,
removeBottom: true,
context: context,
child: Padding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
),
child: FutureBuilder(
future: _memberSeasonsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
Map data = snapshot.data as Map;
if (data['data'].seasonsList.isEmpty) {
return commonWidget('用户没有设置专栏');
} else {
return MemberSeasonsPanel(data: data['data']);
}
} else {
// 请求错误
return const SizedBox();
}
} else {
return const SizedBox();
}
},
),
),
),
/// 收藏
/// 追番
/// 最近投币
Obx(
() => _memberController.recentCoinsList.isNotEmpty
? ListTile(
onTap: () {},
title: const Text('最近投币的视频'),
// trailing: const Icon(Icons.arrow_forward_outlined,
// size: 19),
)
: const SizedBox(),
),
MediaQuery.removePadding(
removeTop: true,
removeBottom: true,
context: context,
child: Padding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
),
child: FutureBuilder(
future: _memberCoinsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
Map data = snapshot.data as Map;
return MemberCoinsPanel(data: data['data']);
} else {
// 请求错误
return const SizedBox();
}
} else {
return const SizedBox();
}
},
),
),
),
// 最近点赞
// ListTile(
// onTap: () {},
// title: const Text('最近点赞的视频'),
// trailing:
// const Icon(Icons.arrow_forward_outlined, size: 19),
// ),
],
),
),
),
),
],
),
);
}
Widget profileWidget(bool isHorizontal) {
return Padding(
padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20),
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
Map data = snapshot.data!;
if (data['status']) {
return Obx(
() => Stack(
alignment: AlignmentDirectional.center,
children: [profilePanelAndDetailInfo(isHorizontal, false)]),
);
} else {
return const SizedBox();
}
} else {
// 骨架屏
return profilePanelAndDetailInfo(isHorizontal, true);
}
},
),
);
}
Widget profilePanelAndDetailInfo(bool isHorizontal, bool loadingStatus) {
if (isHorizontal) {
return Row(
children: [
Expanded(
child: ProfilePanel(
ctr: _memberController, loadingStatus: loadingStatus)),
const SizedBox(width: 20),
Expanded(child: profileDetailInfo()),
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfilePanel(ctr: _memberController, loadingStatus: loadingStatus),
const SizedBox(height: 20),
profileDetailInfo(),
],
);
}
Widget profileDetailInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
_memberController.memberInfo.value.name ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.bold,
color: _memberController.memberInfo.value.vip?.status !=
null &&
_memberController.memberInfo.value.vip!.status! >
0 &&
_memberController.memberInfo.value.vip!.type == 2
? context.vipColor
: Theme.of(context).colorScheme.onSurface,
),
)),
const SizedBox(width: 2),
if (_memberController.memberInfo.value.sex == '')
const Icon(
FontAwesomeIcons.venus,
size: 14,
color: Colors.pink,
semanticLabel: '',
),
if (_memberController.memberInfo.value.sex == '')
const Icon(
FontAwesomeIcons.mars,
size: 14,
color: Colors.blue,
semanticLabel: '',
),
const SizedBox(width: 4),
if (_memberController.memberInfo.value.level != null)
Image.asset(
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
height: 11,
semanticLabel: '等级${_memberController.memberInfo.value.level}',
),
const SizedBox(width: 6),
if (_memberController.memberInfo.value.vip?.status == 1) ...[
if (_memberController
.memberInfo.value.vip?.label?['img_label_uri_hans'] !=
'')
CachedNetworkImage(
imageUrl: (_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans'] as String)
.http2https,
height: 20,
// semanticLabel:
// _memberController.memberInfo.value.vip!.label!['text'],
)
else if (_memberController.memberInfo.value.vip
?.label?['img_label_uri_hans_static'] !=
'')
CachedNetworkImage(
imageUrl: (_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans_static'] as String)
.http2https,
height: 20,
// semanticLabel:
// _memberController.memberInfo.value.vip!.label!['text'],
),
],
const SizedBox(width: 5),
GestureDetector(
onTap: () {
Utils.copyText(_memberController.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: ${_memberController.mid}',
style: TextStyle(
height: 1,
fontSize: 12,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
strutStyle: const StrutStyle(
height: 1,
leading: 0,
fontSize: 12,
),
),
),
),
],
),
if (_memberController.memberInfo.value.official != null &&
_memberController.memberInfo.value.official!['title'] != '') ...[
const SizedBox(height: 6),
Text.rich(
maxLines: 2,
TextSpan(
text: _memberController.memberInfo.value.official!['role'] == 1
? '个人认证:'
: '机构认证:',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
children: [
TextSpan(
text: _memberController.memberInfo.value.official!['title'],
),
],
),
softWrap: true,
),
],
const SizedBox(height: 6),
SelectableText(
_memberController.memberInfo.value.sign ?? '',
),
],
);
}
Widget commonWidget(msg) {
return Padding(
padding: const EdgeInsets.only(
top: 20,
bottom: 30,
),
child: Center(
child: Text(
msg,
style: Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: Theme.of(context).colorScheme.outline),
),
),
);
}
}
class ReportPanel extends StatefulWidget {
const ReportPanel({
super.key,
required this.name,
required this.mid,
});
final dynamic name;
final dynamic mid;
@override
State<ReportPanel> createState() => _ReportPanelState();
}
class _ReportPanelState extends State<ReportPanel> {
final List<bool> _reasonList = List.generate(3, (_) => false).toList();
final Set<int> _reason = {};
int? _reasonV2;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'举报: ${widget.name}',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 4),
Text('uid: ${widget.mid}'),
const SizedBox(height: 10),
const Text('举报内容(必选,可多选)'),
...List.generate(
3,
(index) => _checkBoxWidget(
_reasonList[index],
(value) {
setState(() => _reasonList[index] = value);
if (value) {
_reason.add(index + 1);
} else {
_reason.remove(index + 1);
}
},
['头像违规', '昵称违规', '签名违规'][index],
),
),
const Text('举报理由(单选,非必选)'),
...List.generate(
5,
(index) => RadioWidget<int>(
value: index,
groupValue: _reasonV2,
onChanged: (value) {
setState(() => _reasonV2 = value);
},
title: const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
if (_reason.isEmpty) {
SmartDialog.showToast('至少选择一项作为举报内容');
} else {
Get.back();
dynamic result = await MemberHttp.reportMember(
widget.mid,
reason: _reason.join(','),
reasonV2: _reasonV2 != null ? _reasonV2! + 1 : null,
);
if (result['msg'] is String && result['msg'].isNotEmpty) {
SmartDialog.showToast(result['msg']);
} else {
SmartDialog.showToast('举报失败');
}
}
},
child: const Text('确定'),
),
],
),
],
),
);
}
}
Widget _checkBoxWidget(
bool defValue,
ValueChanged onChanged,
String title,
) {
return InkWell(
onTap: () => onChanged(!defValue),
child: Row(
children: [
Checkbox(
value: defValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
}

View File

@@ -1,31 +0,0 @@
import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/models/member/coin.dart';
import 'package:PiliPlus/pages/member_coin/widgets/item.dart';
class MemberCoinsPanel extends StatelessWidget {
final List<MemberCoinsDataModel>? data;
const MemberCoinsPanel({super.key, this.data});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, boxConstraints) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // Use a fixed count for GridView
crossAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.safeSpace,
childAspectRatio: 0.94,
),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: data!.length,
itemBuilder: (context, i) {
return MemberCoinsItem(coinItem: data![i]);
},
);
},
);
}
}

View File

@@ -1,272 +0,0 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/member/info.dart';
import 'package:PiliPlus/utils/utils.dart';
class ProfilePanel extends StatelessWidget {
final dynamic ctr;
final bool loadingStatus;
const ProfilePanel({
super.key,
required this.ctr,
this.loadingStatus = false,
});
@override
Widget build(BuildContext context) {
MemberInfoModel memberInfo = ctr.memberInfo.value;
return Builder(
builder: ((context) {
return Row(
children: [
Hero(
tag: !loadingStatus ? memberInfo.face : ctr.face.value,
child: Stack(
children: [
GestureDetector(
onTap: () => context.imageView(
imgList: [
SourceModel(
url:
!loadingStatus ? memberInfo.face : ctr.face.value,
)
],
),
child: NetworkImgLayer(
width: 90,
height: 90,
type: 'avatar',
src: !loadingStatus ? memberInfo.face : ctr.face.value,
),
),
if (!loadingStatus &&
memberInfo.liveRoom != null &&
memberInfo.liveRoom!.liveStatus == 1)
Positioned(
bottom: 0,
left: 14,
child: GestureDetector(
onTap: () {
// LiveItemModel liveItem = LiveItemModel.fromJson({
// 'title': memberInfo.liveRoom!.title,
// 'uname': memberInfo.name,
// 'face': memberInfo.face,
// 'roomid': memberInfo.liveRoom!.roomId,
// 'watched_show': memberInfo.liveRoom!.watchedShow,
// });
Get.toNamed(
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
);
},
child: Container(
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius:
const BorderRadius.all(Radius.circular(10)),
),
child: Row(children: [
Image.asset(
'assets/images/live.gif',
height: 10,
),
Text(
' 直播中',
style: TextStyle(
color: Colors.white,
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize),
)
]),
),
),
)
],
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding:
const EdgeInsets.only(top: 10, left: 10, right: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Get.toNamed(
'/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['following'].toString()
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold),
),
Text(
'关注',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
),
InkWell(
onTap: () {
Get.toNamed(
'/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['follower'] != null
? Utils.numFormat(
ctr.userStat!['follower'],
)
: '-'
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold)),
Text(
'粉丝',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
),
InkWell(
onTap: null,
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['likes'] != null
? Utils.numFormat(
ctr.userStat!['likes'],
)
: '-'
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold)),
Text(
'获赞',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
)),
],
),
),
const SizedBox(height: 10),
if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[
Row(
children: [
Obx(
() => Expanded(
child: TextButton(
onPressed: () => ctr.actionRelationMod(context),
style: TextButton.styleFrom(
foregroundColor: ctr.attribute.value == -1
? Colors.transparent
: ctr.attribute.value != 0
? Theme.of(context).colorScheme.outline
: Theme.of(context)
.colorScheme
.onPrimary,
backgroundColor: ctr.attribute.value != 0
? Theme.of(context)
.colorScheme
.onInverseSurface
: Theme.of(context).colorScheme.primary,
),
child: Obx(() => Text(ctr.attributeText.value)),
),
),
),
const SizedBox(width: 8),
Expanded(
child: TextButton(
onPressed: () {
Get.toNamed(
'/whisperDetail',
parameters: {
'talkerId': ctr.mid.toString(),
'name': memberInfo.name!,
'face': memberInfo.face!,
'mid': ctr.mid.toString(),
},
);
},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context)
.colorScheme
.onInverseSurface,
),
child: const Text('发消息'),
),
)
],
)
],
if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[
TextButton(
onPressed: () {
Get.toNamed('/webview', parameters: {
'url': 'https://account.bilibili.com/account/home',
'pageTitle': '个人中心(建议浏览器打开)',
'type': 'url'
});
},
style: TextButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
child: const Text(' 个人中心(web) '),
)
],
if (ctr.ownerMid == -1) ...[
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.outline,
backgroundColor:
Theme.of(context).colorScheme.onInverseSurface,
),
child: const Text(' 未登录 '),
)
]
],
),
),
],
);
}),
);
}
}

View File

@@ -1,86 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/models/member/seasons.dart';
import 'package:PiliPlus/pages/member_seasons/widgets/item.dart';
import '../../../utils/grid.dart';
class MemberSeasonsPanel extends StatelessWidget {
final MemberSeasonsDataModel? data;
const MemberSeasonsPanel({super.key, this.data});
@override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: data!.seasonsList!.length,
itemBuilder: (context, index) {
MemberSeasonsList item = data!.seasonsList![index];
return Padding(
padding: const EdgeInsets.only(bottom: 12, right: 4),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 12, left: 4),
child: Row(
children: [
Text(
item.meta!.name!,
maxLines: 1,
style: Theme.of(context).textTheme.titleSmall!,
),
const SizedBox(width: 10),
PBadge(
stack: 'relative',
size: 'small',
text: item.meta!.total.toString(),
),
SizedBox(
width: 30,
height: 30,
child: IconButton(
tooltip: '前往',
onPressed: () => Get.toNamed(
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
icon: const Icon(
Icons.arrow_forward_ios,
size: 20,
),
),
)
],
),
),
LayoutBuilder(
builder: (context, boxConstraints) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.cardSpace,
maxCrossAxisExtent: Grid.smallCardWidth,
childAspectRatio: 0.94,
),
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: item.archives!.length,
itemBuilder: (context, i) {
return MemberSeasonsItem(seasonItem: item.archives![i]);
},
);
},
),
],
),
);
},
);
}
}