mod: 个人主页
This commit is contained in:
@ -1,44 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
|
||||
class ArchiveController extends GetxController {
|
||||
ArchiveController(this.mid);
|
||||
int? mid;
|
||||
int pn = 1;
|
||||
int count = 0;
|
||||
RxMap<String, String> currentOrder = <String, String>{}.obs;
|
||||
List<Map<String, String>> orderList = [
|
||||
{'type': 'pubdate', 'label': '最新发布'},
|
||||
{'type': 'click', 'label': '最多播放'},
|
||||
{'type': 'stow', 'label': '最多收藏'},
|
||||
];
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
mid ??= int.parse(Get.parameters['mid']!);
|
||||
print('🐶🐶: $mid');
|
||||
currentOrder.value = orderList.first;
|
||||
}
|
||||
|
||||
// 获取用户投稿
|
||||
Future getMemberArchive() async {
|
||||
var res = await MemberHttp.memberArchive(
|
||||
mid: mid, pn: pn, order: currentOrder['type']!);
|
||||
if (res['status']) {
|
||||
count = res['data'].page['count'];
|
||||
pn += 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
toggleSort() async {
|
||||
pn = 1;
|
||||
int index = orderList.indexOf(currentOrder.value);
|
||||
if (index == orderList.length - 1) {
|
||||
currentOrder.value = orderList.first;
|
||||
} else {
|
||||
currentOrder.value = orderList[index + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
library archive_panel;
|
||||
|
||||
export './controller.dart';
|
||||
export 'index.dart';
|
||||
@ -1,240 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loading_more_list/loading_more_list.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||
import 'package:pilipala/models/member/archive.dart';
|
||||
import 'package:pilipala/pages/member/archive/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
|
||||
|
||||
class ArchivePanel extends StatefulWidget {
|
||||
final int? mid;
|
||||
const ArchivePanel({super.key, this.mid});
|
||||
|
||||
@override
|
||||
State<ArchivePanel> createState() => _ArchivePanelState();
|
||||
}
|
||||
|
||||
class _ArchivePanelState extends State<ArchivePanel>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
DateTime lastRefreshTime = DateTime.now();
|
||||
late final LoadMoreListSource source;
|
||||
late final ArchiveController _archiveController;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
print('🐶🐶: ${widget.mid}');
|
||||
_archiveController = Get.put(ArchiveController(widget.mid),
|
||||
tag: Utils.makeHeroTag(widget.mid));
|
||||
source = LoadMoreListSource(_archiveController);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return PullToRefreshNotification(
|
||||
onRefresh: () async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return true;
|
||||
},
|
||||
maxDragOffset: 50,
|
||||
child: GlowNotificationWidget(
|
||||
Column(
|
||||
children: <Widget>[
|
||||
// 下拉刷新指示器
|
||||
// PullToRefreshContainer(
|
||||
// (PullToRefreshScrollNotificationInfo? info) {
|
||||
// return PullToRefreshHeader(info, lastRefreshTime);
|
||||
// },
|
||||
// ),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 14, top: 8, bottom: 8, right: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('排序方式'),
|
||||
SizedBox(
|
||||
height: 35,
|
||||
width: 85,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
// _archiveController.order = 'click';
|
||||
// _archiveController.pn = 1;
|
||||
_archiveController.toggleSort();
|
||||
source.refresh(true);
|
||||
// LoadMoreListSource().loadData();
|
||||
},
|
||||
child: Obx(
|
||||
() => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
_archiveController.currentOrder['label']!,
|
||||
key: ValueKey<String>(
|
||||
_archiveController.currentOrder['label']!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: LoadingMoreList<VListItemModel>(
|
||||
ListConfig<VListItemModel>(
|
||||
sourceList: source,
|
||||
itemBuilder:
|
||||
(BuildContext c, VListItemModel item, int index) {
|
||||
if (index == 0) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 6),
|
||||
VideoCardH(videoItem: item)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return VideoCardH(videoItem: item);
|
||||
}
|
||||
},
|
||||
indicatorBuilder: _buildIndicator,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
showGlowLeading: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIndicator(BuildContext context, IndicatorStatus status) {
|
||||
TextStyle style =
|
||||
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
|
||||
Widget? widget;
|
||||
switch (status) {
|
||||
case IndicatorStatus.none:
|
||||
widget = Container(height: 0.0);
|
||||
break;
|
||||
case IndicatorStatus.loadingMoreBusying:
|
||||
widget = Text('加载中...', style: style);
|
||||
widget = _setbackground(false, widget, height: 60.0);
|
||||
break;
|
||||
case IndicatorStatus.fullScreenBusying:
|
||||
widget = Text('加载中...', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
break;
|
||||
case IndicatorStatus.error:
|
||||
|
||||
/// TODO 异常逻辑
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(false, widget);
|
||||
|
||||
widget = GestureDetector(
|
||||
onTap: () {},
|
||||
child: widget,
|
||||
);
|
||||
|
||||
break;
|
||||
case IndicatorStatus.fullScreenError:
|
||||
|
||||
/// TODO 异常逻辑
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
widget = GestureDetector(
|
||||
onTap: () {},
|
||||
child: widget,
|
||||
);
|
||||
break;
|
||||
case IndicatorStatus.noMoreLoad:
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(false, widget, height: 60.0);
|
||||
break;
|
||||
case IndicatorStatus.empty:
|
||||
widget = Text('用户没有投稿', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
break;
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget _setbackground(bool full, Widget widget, {double height = 100}) {
|
||||
widget = Padding(
|
||||
padding: height == double.infinity
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: height,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
alignment: Alignment.center,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget getIndicator(BuildContext context) {
|
||||
final TargetPlatform platform = Theme.of(context).platform;
|
||||
return platform == TargetPlatform.iOS
|
||||
? const CupertinoActivityIndicator(
|
||||
animating: true,
|
||||
radius: 16.0,
|
||||
)
|
||||
: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
||||
late ArchiveController ctr;
|
||||
LoadMoreListSource(this.ctr);
|
||||
bool forceRefresh = false;
|
||||
|
||||
@override
|
||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||
bool isSuccess = false;
|
||||
var res = await ctr.getMemberArchive();
|
||||
if (res['status']) {
|
||||
if (ctr.pn == 2) {
|
||||
clear();
|
||||
}
|
||||
addAll(res['data'].list.vlist);
|
||||
}
|
||||
if (length < res['data'].page['count']) {
|
||||
isSuccess = true;
|
||||
} else {
|
||||
isSuccess = false;
|
||||
}
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> refresh([bool clearBeforeRequest = false]) async {
|
||||
// _hasMore = true;
|
||||
// pageindex = 1;
|
||||
// //force to refresh list when you don't want clear list before request
|
||||
// //for the case, if your list already has 20 items.
|
||||
forceRefresh = !clearBeforeRequest;
|
||||
var result = await super.refresh(clearBeforeRequest);
|
||||
|
||||
forceRefresh = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import 'package:pilipala/http/member.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/member/archive.dart';
|
||||
import 'package:pilipala/models/member/coin.dart';
|
||||
import 'package:pilipala/models/member/info.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@ -20,9 +21,10 @@ class MemberController extends GetxController {
|
||||
late int ownerMid;
|
||||
// 投稿列表
|
||||
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||
var userInfo;
|
||||
dynamic userInfo;
|
||||
RxInt attribute = (-1).obs;
|
||||
RxString attributeText = '关注'.obs;
|
||||
RxList<MemberCoinsDataModel> recentCoinsList = <MemberCoinsDataModel>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -55,14 +57,6 @@ class MemberController extends GetxController {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Future getMemberCardInfo() async {
|
||||
// var res = await MemberHttp.memberCardInfo(mid: mid);
|
||||
// if (res['status']) {
|
||||
// print(userStat);
|
||||
// }
|
||||
// return res;
|
||||
// }
|
||||
|
||||
// 关注/取关up
|
||||
Future actionRelationMod() async {
|
||||
if (userInfo == null) {
|
||||
@ -173,4 +167,35 @@ class MemberController extends GetxController {
|
||||
void shareUser() {
|
||||
Share.share('${memberInfo.value.name} - 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']}");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 请求投币视频
|
||||
Future getRecentCoinVideo() async {
|
||||
if (userInfo == null) return;
|
||||
var res = await MemberHttp.getRecentCoinVideo(mid: mid);
|
||||
recentCoinsList.value = res['data'];
|
||||
return res;
|
||||
}
|
||||
|
||||
// 跳转查看动态
|
||||
void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid');
|
||||
|
||||
// 跳转查看投稿
|
||||
void pushArchivesPage() => Get.toNamed('/memberArchive?mid=$mid');
|
||||
|
||||
// 跳转查看专栏
|
||||
void pushSeasonsPage() {}
|
||||
// 跳转查看最近投币
|
||||
void pushRecentCoinsPage() async {
|
||||
if (recentCoinsList.isNotEmpty) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
|
||||
class MemberDynamicPanelController extends GetxController {
|
||||
MemberDynamicPanelController(this.mid);
|
||||
int? mid;
|
||||
String offset = '';
|
||||
int count = 0;
|
||||
bool hasMore = true;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
mid ??= int.parse(Get.parameters['mid']!);
|
||||
}
|
||||
|
||||
Future getMemberDynamic() async {
|
||||
if (!hasMore) {
|
||||
return {'status': false};
|
||||
}
|
||||
var res = await MemberHttp.memberDynamic(
|
||||
offset: offset,
|
||||
mid: mid,
|
||||
);
|
||||
if (res['status']) {
|
||||
offset = res['data'].offset;
|
||||
hasMore = res['data'].hasMore;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
library dynamic_panel;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
@ -1,152 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loading_more_list/loading_more_list.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/pages/dynamics/widgets/dynamic_panel.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class MemberDynamicPanel extends StatefulWidget {
|
||||
final int? mid;
|
||||
const MemberDynamicPanel({super.key, this.mid});
|
||||
|
||||
@override
|
||||
State<MemberDynamicPanel> createState() => _MemberDynamicPanelState();
|
||||
}
|
||||
|
||||
class _MemberDynamicPanelState extends State<MemberDynamicPanel>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
DateTime lastRefreshTime = DateTime.now();
|
||||
late final LoadMoreListSource source;
|
||||
late final MemberDynamicPanelController _dynamicController;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dynamicController = Get.put(MemberDynamicPanelController(widget.mid),
|
||||
tag: Utils.makeHeroTag(widget.mid));
|
||||
source = LoadMoreListSource(_dynamicController);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return LoadingMoreList<DynamicItemModel>(
|
||||
ListConfig<DynamicItemModel>(
|
||||
sourceList: source,
|
||||
itemBuilder: (BuildContext c, DynamicItemModel item, int index) {
|
||||
return DynamicPanel(item: item);
|
||||
},
|
||||
indicatorBuilder: _buildIndicator,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIndicator(BuildContext context, IndicatorStatus status) {
|
||||
TextStyle style =
|
||||
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
|
||||
Widget? widget;
|
||||
switch (status) {
|
||||
case IndicatorStatus.none:
|
||||
widget = Container(height: 0.0);
|
||||
break;
|
||||
case IndicatorStatus.loadingMoreBusying:
|
||||
widget = Text('加载中...', style: style);
|
||||
widget = _setbackground(false, widget, height: 60.0);
|
||||
break;
|
||||
case IndicatorStatus.fullScreenBusying:
|
||||
widget = Text('加载中...', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
break;
|
||||
case IndicatorStatus.error:
|
||||
|
||||
/// TODO 异常逻辑
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(false, widget);
|
||||
|
||||
widget = GestureDetector(
|
||||
onTap: () {},
|
||||
child: widget,
|
||||
);
|
||||
|
||||
break;
|
||||
case IndicatorStatus.fullScreenError:
|
||||
|
||||
/// TODO 异常逻辑
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
widget = GestureDetector(
|
||||
onTap: () {},
|
||||
child: widget,
|
||||
);
|
||||
break;
|
||||
case IndicatorStatus.noMoreLoad:
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(false, widget, height: 60.0);
|
||||
break;
|
||||
case IndicatorStatus.empty:
|
||||
widget = Text('用户没有投稿', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
break;
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget _setbackground(bool full, Widget widget, {double height = 100}) {
|
||||
widget = Padding(
|
||||
padding: height == double.infinity
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: height,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
alignment: Alignment.center,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget getIndicator(BuildContext context) {
|
||||
final TargetPlatform platform = Theme.of(context).platform;
|
||||
return platform == TargetPlatform.iOS
|
||||
? const CupertinoActivityIndicator(
|
||||
animating: true,
|
||||
radius: 16.0,
|
||||
)
|
||||
: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
||||
late MemberDynamicPanelController ctr;
|
||||
LoadMoreListSource(this.ctr);
|
||||
|
||||
@override
|
||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||
bool isSuccess = false;
|
||||
var res = await ctr.getMemberDynamic();
|
||||
if (res['status']) {
|
||||
addAll(res['data'].items);
|
||||
}
|
||||
try {
|
||||
if (res['data'].hasMore) {
|
||||
isSuccess = true;
|
||||
} else {
|
||||
isSuccess = false;
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/pages/member/archive/view.dart';
|
||||
import 'package:pilipala/pages/member/dynamic/index.dart';
|
||||
import 'package:pilipala/pages/member/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import 'widgets/conis.dart';
|
||||
import 'widgets/profile.dart';
|
||||
import 'widgets/seasons.dart';
|
||||
|
||||
class MemberPage extends StatefulWidget {
|
||||
const MemberPage({super.key});
|
||||
@ -23,9 +23,10 @@ class _MemberPageState extends State<MemberPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late String heroTag;
|
||||
late MemberController _memberController;
|
||||
Future? _futureBuilderFuture;
|
||||
late Future _futureBuilderFuture;
|
||||
late Future _memberSeasonsFuture;
|
||||
late Future _memberCoinsFuture;
|
||||
final ScrollController _extendNestCtr = ScrollController();
|
||||
late TabController _tabController;
|
||||
final StreamController<bool> appbarStream = StreamController<bool>();
|
||||
late int mid;
|
||||
|
||||
@ -35,12 +36,13 @@ class _MemberPageState extends State<MemberPage>
|
||||
mid = int.parse(Get.parameters['mid']!);
|
||||
heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid);
|
||||
_memberController = Get.put(MemberController(), tag: heroTag);
|
||||
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
|
||||
_futureBuilderFuture = _memberController.getInfo();
|
||||
_memberSeasonsFuture = _memberController.getMemberSeasons();
|
||||
_memberCoinsFuture = _memberController.getRecentCoinVideo();
|
||||
_extendNestCtr.addListener(
|
||||
() {
|
||||
double offset = _extendNestCtr.position.pixels;
|
||||
if (offset > 230) {
|
||||
if (offset > 100) {
|
||||
appbarStream.add(true);
|
||||
} else {
|
||||
appbarStream.add(false);
|
||||
@ -59,183 +61,222 @@ class _MemberPageState extends State<MemberPage>
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
primary: true,
|
||||
body: ExtendedNestedScrollView(
|
||||
controller: _extendNestCtr,
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return <Widget>[
|
||||
SliverAppBar(
|
||||
pinned: false,
|
||||
primary: true,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 1,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
expandedHeight: 290,
|
||||
titleSpacing: 0,
|
||||
title: StreamBuilder(
|
||||
stream: appbarStream.stream,
|
||||
initialData: false,
|
||||
builder: (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,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
AppBar(
|
||||
title: StreamBuilder(
|
||||
stream: appbarStream.stream,
|
||||
initialData: false,
|
||||
builder: (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
|
||||
.onBackground,
|
||||
fontSize: 14),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Obx(
|
||||
() => Text(
|
||||
_memberController.memberInfo.value.name ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground,
|
||||
fontSize: 14),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSearch?mid=${Get.parameters['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(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.block, size: 19),
|
||||
const SizedBox(width: 10),
|
||||
Text(_memberController.attribute.value != 128
|
||||
? '加入黑名单'
|
||||
: '移除黑名单'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSearch?mid=${Get.parameters['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.shareUser(),
|
||||
onTap: () => _memberController.blockUser(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.share_outlined, size: 19),
|
||||
const Icon(Icons.block, size: 19),
|
||||
const SizedBox(width: 10),
|
||||
Text(_memberController.ownerMid !=
|
||||
_memberController.mid
|
||||
? '分享UP主'
|
||||
: '分享我的主页'),
|
||||
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主'
|
||||
: '分享我的主页'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: _extendNestCtr,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 20,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
child: Column(
|
||||
children: [
|
||||
profileWidget(),
|
||||
|
||||
/// 动态链接
|
||||
ListTile(
|
||||
onTap: _memberController.pushDynamicsPage,
|
||||
title: const Text('Ta的动态'),
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
),
|
||||
|
||||
/// 视频
|
||||
ListTile(
|
||||
onTap: _memberController.pushArchivesPage,
|
||||
title: const Text('Ta的投稿'),
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
),
|
||||
|
||||
/// 专栏
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
title: const Text('Ta的专栏'),
|
||||
),
|
||||
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 commenWidget('用户没有设置专栏');
|
||||
} else {
|
||||
return MemberSeasonsPanel(data: data['data']);
|
||||
}
|
||||
} else {
|
||||
// 请求错误
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// 收藏
|
||||
|
||||
/// 追番
|
||||
/// 最近投币
|
||||
Obx(
|
||||
() => _memberController.face.value != ''
|
||||
? Positioned.fill(
|
||||
bottom: 10,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.fitWidth,
|
||||
image: NetworkImage(
|
||||
_memberController.face.value),
|
||||
alignment: Alignment.topCenter,
|
||||
isAntiAlias: true,
|
||||
),
|
||||
),
|
||||
foregroundDecoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0.44),
|
||||
Theme.of(context).colorScheme.background,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: const [0.0, 0.46],
|
||||
),
|
||||
),
|
||||
),
|
||||
() => _memberController.recentCoinsList.isNotEmpty
|
||||
? ListTile(
|
||||
onTap: () {},
|
||||
title: const Text('最近投币的视频'),
|
||||
// trailing: const Icon(Icons.arrow_forward_outlined,
|
||||
// size: 19),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: 20,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
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();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
profileWidget(),
|
||||
// 最近点赞
|
||||
// ListTile(
|
||||
// onTap: () {},
|
||||
// title: const Text('最近点赞的视频'),
|
||||
// trailing:
|
||||
// const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
pinnedHeaderSliverHeightBuilder: () {
|
||||
return MediaQuery.of(context).padding.top + kToolbarHeight;
|
||||
},
|
||||
onlyOneScrollInBody: true,
|
||||
body: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: TabBar(controller: _tabController, tabs: const [
|
||||
Tab(text: '主页'),
|
||||
Tab(text: '动态'),
|
||||
Tab(text: '投稿'),
|
||||
]),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
const Text('主页'),
|
||||
MemberDynamicPanel(mid: mid),
|
||||
ArchivePanel(mid: mid),
|
||||
],
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget profileWidget() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 18, right: 18),
|
||||
padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20),
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
@ -250,7 +291,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
profile(_memberController),
|
||||
const SizedBox(height: 14),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
@ -260,7 +301,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.titleMedium!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
)),
|
||||
const SizedBox(width: 2),
|
||||
@ -332,29 +373,11 @@ class _MemberPageState extends State<MemberPage>
|
||||
softWrap: true,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: 6),
|
||||
if (_memberController.memberInfo.value.sign != '')
|
||||
SelectableText(
|
||||
_memberController.memberInfo.value.sign!,
|
||||
maxLines: _memberController
|
||||
.memberInfo.value.official!['title'] !=
|
||||
''
|
||||
? 1
|
||||
: 2,
|
||||
style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: SelectableText(_memberController
|
||||
.memberInfo.value.sign!),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -371,4 +394,22 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget commenWidget(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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
31
lib/pages/member/widgets/conis.dart
Normal file
31
lib/pages/member/widgets/conis.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/models/member/coin.dart';
|
||||
import 'package:pilipala/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]);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,7 @@ Widget profile(ctr, {loadingStatus = false}) {
|
||||
return Builder(
|
||||
builder: ((context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 3 * MediaQuery.of(context).padding.top),
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Hero(
|
||||
@ -78,7 +78,8 @@ Widget profile(ctr, {loadingStatus = false}) {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
padding:
|
||||
const EdgeInsets.only(top: 10, left: 10, right: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
|
||||
85
lib/pages/member/widgets/seasons.dart
Normal file
85
lib/pages/member/widgets/seasons.dart
Normal file
@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/models/member/seasons.dart';
|
||||
import 'package:pilipala/pages/member_seasons/widgets/item.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(),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
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: item.archives!.length,
|
||||
itemBuilder: (context, i) {
|
||||
return MemberSeasonsItem(seasonItem: item.archives![i]);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user