初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
@@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hgdj/hj_model/video_model.dart';
import 'package:hgdj/hj_utils/api_service/common_service.dart';
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
import 'package:hgdj/tools_base/toast.dart';
import 'package:hgdj/tools_base/widget/swipe_action_item.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../../hj_model/mine/follow_user_list_model.dart';
import 'mine_follow_sub_page.dart';
import 'widget/follow_list_items.dart';
abstract class MineFollowSubLogic extends GetxController
with GetTickerProviderStateMixin {
final int? uid;
MineFollowSubLogic(this.uid);
RxInt loadCount = 0.obs;
final dataSource = [];
RefreshController? refreshController;
int pageNumber = 1;
bool isLoading = true;
bool isGridStyle = false;
@override
void onReady() {
super.onReady();
fetchPageData();
}
@mustCallSuper
fetchPageData({bool isRefresh = true}) {
if (isRefresh) {
pageNumber = 1;
}
}
Widget instanceChildItem(int index);
}
class ActressController extends MineFollowSubLogic {
ActressController(super.uid);
@override
fetchPageData({bool isRefresh = true}) async {
super.fetchPageData(isRefresh: isRefresh);
final retModel = await MineService.fetchCollectList<FollowUserModel>(
'actress',
page: pageNumber,
size: 10,
uid: uid,
);
isLoading = false;
if (isRefresh) {
dataSource.clear();
refreshController?.refreshCompleted();
}
retModel?.hasNext ?? false
? refreshController?.loadComplete()
: refreshController?.loadNoData();
dataSource.addAll(retModel?.list ?? []);
pageNumber += 1;
update();
}
@override
Widget instanceChildItem(int index) {
return FollowActressItem(dataSource[index]);
}
}
class BloggerController extends MineFollowSubLogic {
BloggerController(super.uid);
@override
fetchPageData({bool isRefresh = true}) async {
super.fetchPageData(isRefresh: isRefresh);
final retModel = await CommonService.getFollowUsers(
pageNumber: pageNumber,
pageSize: 10,
uid: uid,
);
isLoading = false;
if (isRefresh) {
dataSource.clear();
refreshController?.refreshCompleted();
}
retModel?.hasNext ?? false
? refreshController?.loadComplete()
: refreshController?.loadNoData();
dataSource.addAll(retModel?.list ?? []);
pageNumber += 1;
update();
}
@override
Widget instanceChildItem(int index) {
final model = dataSource[index] as FollowUserModel;
// 用 ObjectKey 而不是 ValueKey(uid)uid 是可空的,两条 uid 都为 null 就是重复 key
// Flutter 会直接抛 Duplicate keys 白屏。绑对象身份天然唯一,且能让展开状态不串项
return SwipeActionItem(
key: ObjectKey(model),
actionText: '取消关注',
onAction: () => _unfollow(model),
child: FollowBloggerItem(model),
);
}
/// 正在取关的项:按钮收起动画那 200ms 里还能再点一次,防重复请求
final _unfollowing = <FollowUserModel>{};
/// 左滑取关:成功才从列表摘掉(接口内部会 emit CollectStatusModel 同步其他页面)
Future<void> _unfollow(FollowUserModel model) async {
if (!_unfollowing.add(model)) return;
try {
final ok = await MineService.getFollow(model.uid, false);
if (!ok) return showToast('取消关注失败,请重试');
dataSource.remove(model); // 按对象移除,不用 index——异步期间列表可能已刷新
update();
} finally {
_unfollowing.remove(model);
}
}
}
class TopicController extends MineFollowSubLogic {
TopicController(super.uid);
@override
bool get isGridStyle => true;
@override
fetchPageData({bool isRefresh = true}) async {
super.fetchPageData(isRefresh: isRefresh);
final retModel = await MineService.fetchCollectList<TagsBean>("tag",
page: pageNumber, size: 10, uid: uid);
isLoading = false;
if (isRefresh) {
dataSource.clear();
refreshController?.refreshCompleted();
}
retModel?.hasNext ?? false
? refreshController?.loadComplete()
: refreshController?.loadNoData();
dataSource.addAll(retModel?.list ?? []);
pageNumber += 1;
update();
}
@override
Widget instanceChildItem(int index) {
return FollowTopicItem(dataSource[index]);
}
}
MineFollowSubLogic instanceController(FollowType type, {int? uid}) {
switch (type) {
case FollowType.user:
return BloggerController(uid);
case FollowType.topic:
return TopicController(uid);
case FollowType.actress:
return ActressController(uid);
default:
throw 'type 没有定义';
}
}
@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../tools_base/loading/loading_center_widget.dart';
import '../../../tools_base/refresh/pull_refresh.dart';
import '../../../tools_base/widget/swipe_action_item.dart';
import 'mine_follow_sub_logic.dart';
enum FollowType {
user,
topic,
actress;
}
class MineFollowSubPage extends StatelessWidget {
final FollowType pageType; // 0 博主, 1 粉丝, 2 话题
final int? uid;
MineFollowSubPage({super.key, required this.pageType, this.uid});
@override
Widget build(BuildContext context) {
return Scaffold(
// 用默认 globalglobal:false 下 widget 销毁不会 Get.deleteonClose 永远不触发,
// 往 logic 里加 Timer/订阅就会静默泄漏。本页单实例(入口只在「我的」,链路不成环)故不加 tag;
// 将来若做成多 pageType 并存的 TabBarView,得按 pageType 补 tag,否则三个 tab 抢同一个实例
body: GetBuilder<MineFollowSubLogic>(
init: instanceController(pageType, uid: uid),
builder: (logic) {
return pullYsRefresh(
onRefresh: (refreshController) => logic.fetchPageData(),
onLoading: (refreshController) => logic.fetchPageData(isRefresh: false),
onInit: (ctr) => logic.refreshController = ctr,
child: () {
if (logic.isLoading) return const LoadingCenterWidget();
if (logic.dataSource.isEmpty)
return CErrorWidget(
retryOnTap: () {
logic.fetchPageData();
},
);
if (logic.isGridStyle)
return GridView.builder(
padding: EdgeInsets.only(left: 16, right: 16, top: 12),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 5,
childAspectRatio: 165 / 143,
),
itemCount: logic.dataSource.length,
itemBuilder: (BuildContext context, int index) {
return logic.instanceChildItem(index);
},
);
// 一滚动就收起左滑展开的那项,免得滑走了还留着个张开的
return NotificationListener<ScrollStartNotification>(
onNotification: (_) {
SwipeActionItem.closeOpened();
return false;
},
child: ListView.builder(
padding: EdgeInsets.only(top: 12),
itemCount: logic.dataSource.length,
itemBuilder: (BuildContext context, int index) {
return logic.instanceChildItem(index);
},
),
);
}());
}),
);
}
}
@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'mine_follow_sub_page.dart';
class MineFollowingPage extends StatelessWidget {
const MineFollowingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('我的关注')),
body: MineFollowSubPage(pageType: FollowType.user),
);
}
}
@@ -0,0 +1,294 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:hgdj/assets_tool/images.dart';
import 'package:hgdj/hj_model/mine/follow_user_list_model.dart';
import 'package:hgdj/hj_model/video_model.dart';
import 'package:hgdj/hj_utils/api_service/mine_service.dart';
import 'package:hgdj/hj_utils/screen.dart';
import 'package:hgdj/routers/jump_router.dart';
import 'package:hgdj/tools_base/widget/net_image_widget.dart';
import '../../../community/community_tag_page/community_tag_page.dart';
import 'package:hgdj/extension/extensions.dart';
/// 关注列表 - 女优项
class FollowActressItem extends StatefulWidget {
final FollowUserModel model;
const FollowActressItem(this.model, {super.key});
@override
State<FollowActressItem> createState() => _FollowActressItemState();
}
class _FollowActressItemState extends State<FollowActressItem> {
@override
Widget build(BuildContext context) {
return InkWell(
enableFeedback: false,
onTap: () {
// type:1 原跳女优主页 ActressMainPage(已下线),pushToPersonCenter 内 type==1 已 return null
// → 当前点击不跳转,待产品确认替代页(objcId 与用户中心 uid 非同一体系,不能直接跳 UserCenterPage
pushToPersonCenter(widget.model.objcId, type: 1);
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: 18.w),
padding: EdgeInsets.symmetric(horizontal: 13.w, vertical: 16.h),
decoration: BoxDecoration(
color: Color(0xff242424),
borderRadius: BorderRadius.all(Radius.circular(6)),
),
child: Row(
children: [
NetworkImageLoader(
imageUrl: widget.model.portrait ?? "",
width: 43,
height: 43,
borderRadius: 6,
),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
widget.model.name ?? "",
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
Text(
"粉丝: ${widget.model.fans?.countStr}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 10,
color: Color(0xff666666),
fontWeight: FontWeight.w400,
),
),
],
),
),
GestureDetector(
onTap: () async {
final isFollow = widget.model.hasFollow ?? false;
final res = await MineService.postCollect(
widget.model.objcId, 'actresss', !isFollow);
if (res) {
widget.model.hasFollow = !isFollow;
setState(() {});
}
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 11.w, vertical: 5.h),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: .3),
borderRadius: BorderRadius.circular(40)),
child: Row(
children: [
if (widget.model.hasFollow == true) ...[
Image.asset('collect_red.png'.commonImgPath, width: 16.w),
7.w.sizeBoxW,
],
Text(
widget.model.hasFollow == true ? '已关注' : '关注',
style: TextStyle(color: Colors.white, fontSize: 14),
)
],
),
),
),
],
),
),
);
}
}
/// 关注列表 - 博主项
class FollowBloggerItem extends StatefulWidget {
final FollowUserModel model;
const FollowBloggerItem(this.model, {super.key});
@override
State<FollowBloggerItem> createState() => _FollowBloggerItemState();
}
class _FollowBloggerItemState extends State<FollowBloggerItem> {
@override
Widget build(BuildContext context) {
return InkWell(
enableFeedback: false,
onTap: () {
pushToPersonCenter(widget.model.uid ?? 0);
},
child: Container(
padding: EdgeInsets.only(bottom: 18, left: 16, right: 16),
child: Row(
children: [
NetworkImageLoader(
imageUrl: widget.model.portrait ?? "",
width: 60,
height: 60,
borderRadius: 30,
),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
widget.model.name ?? "",
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 16,
color: Color(0xE5FFFFFF),
fontWeight: FontWeight.w500,
),
),
6.sizeBoxH,
Row(
children: [
Flexible(
child: Text(
'${widget.model.totalWorks ?? 0} 作品',
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 12,
color: Color(0x8CFFFFFF),
fontWeight: FontWeight.w400),
),
),
SizedBox(width: 12),
Flexible(
child: Text(
'${widget.model.fans?.countStr} 粉丝',
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 12,
color: Color(0x8CFFFFFF),
fontWeight: FontWeight.w400),
),
),
],
)
],
),
),
SizedBox(width: 12),
Image.asset(
'arrow_right_grey.webp'.commonImgPath,
color: Color(0xffDCDCDC),
width: 24,
)
],
),
),
);
}
}
/// 关注列表 - 话题项
class FollowTopicItem extends StatefulWidget {
final TagsBean model;
const FollowTopicItem(this.model, {super.key});
@override
State<FollowTopicItem> createState() => _FollowTopicItemState();
}
class _FollowTopicItemState extends State<FollowTopicItem> {
@override
Widget build(BuildContext context) {
return InkWell(
enableFeedback: false,
onTap: () {
Get.to(() => CommunityTagDetailPage(model: widget.model));
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Stack(
alignment: Alignment.center,
children: [
NetworkImageLoader(
imageUrl: widget.model.coverImg ?? "",
width: double.infinity,
height: double.infinity,
borderRadius: 8,
),
Container(
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: .6),
borderRadius: BorderRadius.circular(8)),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"#${widget.model.name}",
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
Text(
'${widget.model.videoCount.countStr}个帖子',
style: TextStyle(color: Color(0xffcccccc), fontSize: 10),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
],
),
),
SizedBox(height: 12),
GestureDetector(
onTap: () async {
final isCollected = widget.model.hasCollected ?? false;
final res = await MineService.postCollect(
widget.model.id, "tag", !isCollected);
if (res) {
widget.model.hasCollected = !isCollected;
setState(() {});
}
},
child: Container(
width: 108,
height: 27,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Color(0xFFF68804),
borderRadius: BorderRadius.circular(6)),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.model.hasCollected == true ? '已关注' : '关注',
style: TextStyle(color: Colors.white, fontSize: 14),
)
],
),
),
),
],
),
);
}
}