feat: show member article

This commit is contained in:
bggRGjQaUbCoE
2024-10-19 10:38:29 +08:00
parent 8c26ef4ff6
commit d04a72c462
34 changed files with 1098 additions and 8 deletions

View File

@@ -1,12 +1,22 @@
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/space_article/item.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart';
import 'package:PiliPalaX/utils/app_scheme.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MemberArticle extends StatefulWidget {
const MemberArticle({
super.key,
required this.heroTag,
required this.mid,
});
final String? heroTag;
final int mid;
@override
State<MemberArticle> createState() => _MemberArticleState();
@@ -17,11 +27,91 @@ class _MemberArticleState extends State<MemberArticle>
@override
bool get wantKeepAlive => true;
late final _controller = Get.put(
MemberArticleCtr(mid: widget.mid),
tag: widget.heroTag,
);
@override
Widget build(BuildContext context) {
super.build(context);
return Center(
child: Text('Article'),
);
return Obx(() => _buildBody(_controller.loadingState.value));
}
_buildBody(LoadingState loadingState) {
return loadingState is Success
? MediaQuery.removePadding(
context: context,
removeTop: true,
child: RefreshIndicator(
onRefresh: () async {
await _controller.onRefresh();
},
child: ListView.separated(
itemCount: loadingState.response.length,
itemBuilder: (_, index) {
if (index == loadingState.response.length - 1) {
_controller.onLoadMore();
}
Item item = loadingState.response[index];
return ListTile(
dense: true,
onTap: () {
PiliScheme.routePush(Uri.parse(item.uri ?? ''));
},
leading: item.originImageUrls?.isNotEmpty == true
? Container(
margin: const EdgeInsets.symmetric(vertical: 6),
child: LayoutBuilder(
builder: (_, constraints) {
return NetworkImgLayer(
radius: 6,
src: item.originImageUrls!.first,
width: constraints.maxHeight *
StyleString.aspectRatio,
height: constraints.maxHeight,
);
},
),
)
: null,
title: Text(
item.title ?? '',
style: TextStyle(
fontSize: 15,
),
),
subtitle: item.summary?.isNotEmpty == true
? Text(
item.summary!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.outline,
),
)
: null,
);
},
separatorBuilder: (_, index) => Divider(height: 1),
),
),
)
: loadingState is Error
? Center(
child: CustomScrollView(
shrinkWrap: true,
slivers: [
HttpError(
errMsg: loadingState.errMsg,
fn: _controller.onReload,
),
],
),
)
: Center(
child: CircularProgressIndicator(),
);
}
}

View File

@@ -0,0 +1,50 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/http/member.dart';
import 'package:PiliPalaX/models/space_article/data.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
class MemberArticleCtr extends CommonController {
MemberArticleCtr({
required this.mid,
});
final int mid;
bool isEnd = false;
int count = -1;
@override
void onInit() {
super.onInit();
queryData();
}
@override
Future onRefresh() async {
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) {
Data data = response.response;
if (currentPage == 1) {
count = data.count ?? -1;
} else if (loadingState.value is Success) {
data.item?.insertAll(0, (loadingState.value as Success).response);
}
isEnd = (data.item?.length ?? -1) >= count;
loadingState.value = LoadingState.success(data.item);
return true;
}
@override
Future<LoadingState> customGetData() =>
MemberHttp.spaceArticle(mid: mid, page: currentPage);
}

View File

@@ -89,7 +89,10 @@ class _MemberContributeState extends State<MemberContribute>
heroTag: widget.heroTag,
mid: widget.mid,
),
'article' => MemberArticle(heroTag: widget.heroTag),
'article' => MemberArticle(
heroTag: widget.heroTag,
mid: widget.mid,
),
'audio' => MemberAudio(heroTag: widget.heroTag),
'season_video' => MemberVideo(
type: ContributeType.season,

View File

@@ -1,6 +1,7 @@
import 'dart:math';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.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';
@@ -13,6 +14,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../../../../utils/app_scheme.dart';
class MemberHome extends StatefulWidget {
const MemberHome({super.key, this.heroTag});
@@ -93,7 +96,7 @@ class _MemberHomeState extends State<MemberHome>
true) ...[
_videoHeader(
title: '最近点赞的视频',
param: 'coinArchive',
param: 'likeArchive',
count: loadingState.response.likeArchive.count,
),
// TODO
@@ -105,7 +108,53 @@ class _MemberHomeState extends State<MemberHome>
param1: 'article',
count: loadingState.response.article.count,
),
// TODO
SliverToBoxAdapter(
child: ListTile(
dense: true,
onTap: () {
PiliScheme.routePush(Uri.parse(
loadingState.response.article.item.first.uri ?? ''));
},
leading: loadingState.response.article.item.first
.originImageUrls?.isNotEmpty ==
true
? Container(
margin: const EdgeInsets.symmetric(vertical: 6),
child: LayoutBuilder(
builder: (_, constraints) {
return NetworkImgLayer(
radius: 6,
src: loadingState.response.article.item.first
.originImageUrls!.first,
width: constraints.maxHeight *
StyleString.aspectRatio,
height: constraints.maxHeight,
);
},
),
)
: null,
title: Text(
loadingState.response.article.item.first.title ?? '',
style: TextStyle(
fontSize: 15,
),
),
subtitle: loadingState.response.article.item.first.summary
?.isNotEmpty ==
true
? Text(
loadingState.response.article.item.first.summary!,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.outline,
),
)
: null,
),
),
],
if (loadingState.response?.audios?.item?.isNotEmpty == true) ...[
_videoHeader(