mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
feat: create/update/del follow tag
opt: owner follow page Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -47,6 +47,8 @@
|
|||||||
|
|
||||||
## feat
|
## feat
|
||||||
|
|
||||||
|
- [x] 创建/修改/删除关注分组
|
||||||
|
- [x] 移除粉丝
|
||||||
- [x] 直播弹幕发送表情
|
- [x] 直播弹幕发送表情
|
||||||
- [x] 收藏夹排序
|
- [x] 收藏夹排序
|
||||||
- [x] 稍后再看`未看`/`未看完`/`已看完`分类
|
- [x] 稍后再看`未看`/`未看完`/`已看完`分类
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:get/get.dart';
|
|||||||
void showConfirmDialog({
|
void showConfirmDialog({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required String title,
|
required String title,
|
||||||
String? content,
|
dynamic content,
|
||||||
required VoidCallback onConfirm,
|
required VoidCallback onConfirm,
|
||||||
}) {
|
}) {
|
||||||
showDialog(
|
showDialog(
|
||||||
@@ -12,7 +12,11 @@ void showConfirmDialog({
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
content: content == null ? null : Text(content),
|
content: content is String
|
||||||
|
? Text(content)
|
||||||
|
: content is Widget
|
||||||
|
? content
|
||||||
|
: null,
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: Get.back,
|
onPressed: Get.back,
|
||||||
|
|||||||
@@ -286,10 +286,6 @@ class Api {
|
|||||||
// order_type 排序规则 最近访问传空,最常访问传 attention
|
// order_type 排序规则 最近访问传空,最常访问传 attention
|
||||||
static const String followings = '/x/relation/followings';
|
static const String followings = '/x/relation/followings';
|
||||||
|
|
||||||
// 指定分类的关注
|
|
||||||
// https://api.bilibili.com/x/relation/tag?mid=17340771&tagid=-10&pn=1&ps=20
|
|
||||||
static const String tagFollowings = '/x/relation/tag';
|
|
||||||
|
|
||||||
// 搜索follow
|
// 搜索follow
|
||||||
static const followSearch = '/x/relation/followings/search';
|
static const followSearch = '/x/relation/followings/search';
|
||||||
|
|
||||||
@@ -469,6 +465,12 @@ class Api {
|
|||||||
// 获取指定分组下的up
|
// 获取指定分组下的up
|
||||||
static const String followUpGroup = '/x/relation/tag';
|
static const String followUpGroup = '/x/relation/tag';
|
||||||
|
|
||||||
|
static const String createFollowTag = '/x/relation/tag/create';
|
||||||
|
|
||||||
|
static const String updateFollowTag = '/x/relation/tag/update';
|
||||||
|
|
||||||
|
static const String delFollowTag = '/x/relation/tag/del';
|
||||||
|
|
||||||
// 获取未读私信数
|
// 获取未读私信数
|
||||||
// https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread
|
// https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread
|
||||||
static const String msgUnread =
|
static const String msgUnread =
|
||||||
|
|||||||
@@ -4,8 +4,12 @@ import '../models/follow/result.dart';
|
|||||||
import 'index.dart';
|
import 'index.dart';
|
||||||
|
|
||||||
class FollowHttp {
|
class FollowHttp {
|
||||||
static Future followings(
|
static Future followings({
|
||||||
{int? vmid, int? pn, int? ps, String? orderType}) async {
|
int? vmid,
|
||||||
|
int? pn,
|
||||||
|
int? ps,
|
||||||
|
String orderType = '',
|
||||||
|
}) async {
|
||||||
var res = await Request().get(Api.followings, queryParameters: {
|
var res = await Request().get(Api.followings, queryParameters: {
|
||||||
'vmid': vmid,
|
'vmid': vmid,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
@@ -23,8 +27,12 @@ class FollowHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<LoadingState<List<FollowItemModel>?>> followingsNew(
|
static Future<LoadingState<FollowDataModel>> followingsNew({
|
||||||
{int? vmid, int? pn, int? ps, String? orderType}) async {
|
int? vmid,
|
||||||
|
int? pn,
|
||||||
|
int? ps,
|
||||||
|
String orderType = '', // ''=>最近关注,'attention'=>最常访问
|
||||||
|
}) async {
|
||||||
var res = await Request().get(Api.followings, queryParameters: {
|
var res = await Request().get(Api.followings, queryParameters: {
|
||||||
'vmid': vmid,
|
'vmid': vmid,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
@@ -35,7 +43,8 @@ class FollowHttp {
|
|||||||
|
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return LoadingState.success(
|
return LoadingState.success(
|
||||||
FollowDataModel.fromJson(res.data['data']).list);
|
FollowDataModel.fromJson(res.data['data']),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return LoadingState.error(res.data['message']);
|
return LoadingState.error(res.data['message']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -470,8 +470,8 @@ class MemberHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': (res.data['data'] as List?)
|
'data': res.data['data']
|
||||||
?.map<MemberTagItemModel>((e) => MemberTagItemModel.fromJson(e))
|
.map<MemberTagItemModel>((e) => MemberTagItemModel.fromJson(e))
|
||||||
.toList()
|
.toList()
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -525,7 +525,7 @@ class MemberHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取某分组下的up
|
// 获取某分组下的up
|
||||||
static Future<LoadingState<List<FollowItemModel>?>> followUpGroup(
|
static Future<LoadingState<FollowDataModel>> followUpGroup(
|
||||||
int? mid,
|
int? mid,
|
||||||
int? tagid,
|
int? tagid,
|
||||||
int? pn,
|
int? pn,
|
||||||
@@ -541,14 +541,82 @@ class MemberHttp {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return LoadingState.success((res.data['data'] as List?)
|
return LoadingState.success(FollowDataModel(
|
||||||
?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
list: (res.data['data'] as List?)
|
||||||
.toList());
|
?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||||
|
.toList()));
|
||||||
} else {
|
} else {
|
||||||
return LoadingState.error(res.data['message']);
|
return LoadingState.error(res.data['message']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future createFollowTag(tagName) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.createFollowTag,
|
||||||
|
queryParameters: {
|
||||||
|
'x-bili-device-req-json':
|
||||||
|
'{"platform":"web","device":"pc","spmid":"333.1387"}',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
'tag': tagName,
|
||||||
|
'csrf': Accounts.main.csrf,
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future updateFollowTag(tagid, name) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.updateFollowTag,
|
||||||
|
queryParameters: {
|
||||||
|
'x-bili-device-req-json':
|
||||||
|
'{"platform":"web","device":"pc","spmid":"333.1387"}',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
'tagid': tagid,
|
||||||
|
'name': name,
|
||||||
|
'csrf': Accounts.main.csrf,
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future delFollowTag(tagid) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.delFollowTag,
|
||||||
|
queryParameters: {
|
||||||
|
'x-bili-device-req-json':
|
||||||
|
'{"platform":"web","device":"pc","spmid":"333.1387"}',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
'tagid': tagid,
|
||||||
|
'csrf': Accounts.main.csrf,
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取up置顶
|
// 获取up置顶
|
||||||
static Future getTopVideo(String? vmid) async {
|
static Future getTopVideo(String? vmid) async {
|
||||||
var res = await Request().get(Api.getTopVideoApi);
|
var res = await Request().get(Api.getTopVideoApi);
|
||||||
|
|||||||
@@ -3,13 +3,25 @@ import 'package:PiliPlus/http/loading_state.dart';
|
|||||||
import 'package:PiliPlus/http/member.dart';
|
import 'package:PiliPlus/http/member.dart';
|
||||||
import 'package:PiliPlus/models/follow/result.dart';
|
import 'package:PiliPlus/models/follow/result.dart';
|
||||||
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/follow/controller.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
enum OrderType { def, attention }
|
||||||
|
|
||||||
|
extension OrderTypeExt on OrderType {
|
||||||
|
String get type => const ['', 'attention'][index];
|
||||||
|
String get title => const ['最近关注', '最常访问'][index];
|
||||||
|
}
|
||||||
|
|
||||||
class FollowChildController
|
class FollowChildController
|
||||||
extends CommonListController<List<FollowItemModel>?, FollowItemModel> {
|
extends CommonListController<FollowDataModel, FollowItemModel> {
|
||||||
FollowChildController(this.mid, this.tagid);
|
FollowChildController(this.controller, this.mid, this.tagid);
|
||||||
|
final FollowController controller;
|
||||||
final int? tagid;
|
final int? tagid;
|
||||||
final int mid;
|
final int mid;
|
||||||
|
|
||||||
|
late final Rx<OrderType> orderType = OrderType.def.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
@@ -17,12 +29,35 @@ class FollowChildController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LoadingState<List<FollowItemModel>?>> customGetData() {
|
List<FollowItemModel>? getDataList(FollowDataModel response) {
|
||||||
|
return response.list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool customHandleResponse(bool isRefresh, Success<FollowDataModel> response) {
|
||||||
|
try {
|
||||||
|
if (controller.isOwner &&
|
||||||
|
tagid == null &&
|
||||||
|
isRefresh &&
|
||||||
|
controller.followState.value is Success) {
|
||||||
|
controller.tabs[0].count = response.response.total;
|
||||||
|
controller.tabs.refresh();
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LoadingState<FollowDataModel>> customGetData() {
|
||||||
if (tagid != null) {
|
if (tagid != null) {
|
||||||
return MemberHttp.followUpGroup(mid, tagid, currentPage, 20);
|
return MemberHttp.followUpGroup(mid, tagid, currentPage, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FollowHttp.followingsNew(
|
return FollowHttp.followingsNew(
|
||||||
vmid: mid, pn: currentPage, ps: 20, orderType: 'attention');
|
vmid: mid,
|
||||||
|
pn: currentPage,
|
||||||
|
ps: 20,
|
||||||
|
orderType: orderType.value.type,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,21 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
|||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/follow/result.dart';
|
import 'package:PiliPlus/models/follow/result.dart';
|
||||||
import 'package:PiliPlus/pages/follow/child_controller.dart';
|
import 'package:PiliPlus/pages/follow/child_controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/follow/controller.dart';
|
||||||
import 'package:PiliPlus/pages/follow/widgets/follow_item.dart';
|
import 'package:PiliPlus/pages/follow/widgets/follow_item.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class FollowChildPage extends StatefulWidget {
|
class FollowChildPage extends StatefulWidget {
|
||||||
const FollowChildPage({super.key, required this.mid, this.tagid});
|
const FollowChildPage({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.mid,
|
||||||
|
this.tagid,
|
||||||
|
});
|
||||||
|
|
||||||
|
final FollowController controller;
|
||||||
final int mid;
|
final int mid;
|
||||||
final int? tagid;
|
final int? tagid;
|
||||||
|
|
||||||
@@ -22,30 +29,50 @@ class FollowChildPage extends StatefulWidget {
|
|||||||
class _FollowChildPageState extends State<FollowChildPage>
|
class _FollowChildPageState extends State<FollowChildPage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
late final _followController = Get.put(
|
late final _followController = Get.put(
|
||||||
FollowChildController(widget.mid, widget.tagid),
|
FollowChildController(widget.controller, widget.mid, widget.tagid),
|
||||||
tag: Utils.generateRandomString(8));
|
tag: Utils.generateRandomString(8));
|
||||||
late final _isOwner = widget.tagid != null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return refreshIndicator(
|
if (widget.controller.isOwner && widget.tagid == null) {
|
||||||
onRefresh: () async {
|
return Scaffold(
|
||||||
await _followController.onRefresh();
|
backgroundColor: Colors.transparent,
|
||||||
},
|
body: _child,
|
||||||
child: CustomScrollView(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
onPressed: () {
|
||||||
slivers: [
|
_followController
|
||||||
SliverPadding(
|
..orderType.value =
|
||||||
padding: EdgeInsets.only(
|
_followController.orderType.value == OrderType.def
|
||||||
bottom: MediaQuery.paddingOf(context).bottom + 80),
|
? OrderType.attention
|
||||||
sliver: Obx(() => _buildBody(_followController.loadingState.value)),
|
: OrderType.def
|
||||||
),
|
..onReload();
|
||||||
],
|
},
|
||||||
),
|
icon: const Icon(Icons.format_list_bulleted, size: 20),
|
||||||
);
|
label: Obx(() => Text(_followController.orderType.value.title)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget get _child => refreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
await _followController.onRefresh();
|
||||||
|
},
|
||||||
|
child: CustomScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
slivers: [
|
||||||
|
SliverPadding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.paddingOf(context).bottom + 80),
|
||||||
|
sliver:
|
||||||
|
Obx(() => _buildBody(_followController.loadingState.value)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Widget _buildBody(LoadingState<List<FollowItemModel>?> loadingState) {
|
Widget _buildBody(LoadingState<List<FollowItemModel>?> loadingState) {
|
||||||
return switch (loadingState) {
|
return switch (loadingState) {
|
||||||
Loading() => SliverList.builder(
|
Loading() => SliverList.builder(
|
||||||
@@ -63,7 +90,7 @@ class _FollowChildPageState extends State<FollowChildPage>
|
|||||||
}
|
}
|
||||||
return FollowItem(
|
return FollowItem(
|
||||||
item: loadingState.response![index],
|
item: loadingState.response![index],
|
||||||
isOwner: _isOwner,
|
isOwner: widget.controller.isOwner,
|
||||||
callback: (attr) {
|
callback: (attr) {
|
||||||
List<FollowItemModel> list =
|
List<FollowItemModel> list =
|
||||||
(_followController.loadingState.value as Success)
|
(_followController.loadingState.value as Success)
|
||||||
@@ -86,5 +113,5 @@ class _FollowChildPageState extends State<FollowChildPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => widget.tagid != null;
|
bool get wantKeepAlive => widget.controller.tabController != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import 'package:PiliPlus/http/loading_state.dart';
|
|||||||
import 'package:PiliPlus/http/member.dart';
|
import 'package:PiliPlus/http/member.dart';
|
||||||
import 'package:PiliPlus/models/member/tags.dart';
|
import 'package:PiliPlus/models/member/tags.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
|
|
||||||
class FollowController extends GetxController
|
class FollowController extends GetxController with GetTickerProviderStateMixin {
|
||||||
with GetSingleTickerProviderStateMixin {
|
|
||||||
late int mid;
|
late int mid;
|
||||||
String? name;
|
String? name;
|
||||||
late bool isOwner;
|
late bool isOwner;
|
||||||
|
|
||||||
late final Rx<LoadingState<List<MemberTagItemModel>?>> followState =
|
late final Rx<LoadingState> followState = LoadingState.loading().obs;
|
||||||
LoadingState<List<MemberTagItemModel>?>.loading().obs;
|
late final RxList<MemberTagItemModel> tabs = <MemberTagItemModel>[].obs;
|
||||||
TabController? tabController;
|
TabController? tabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,12 +33,20 @@ class FollowController extends GetxController
|
|||||||
Future queryFollowUpTags() async {
|
Future queryFollowUpTags() async {
|
||||||
var res = await MemberHttp.followUpTags();
|
var res = await MemberHttp.followUpTags();
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
|
tabs.clear();
|
||||||
|
tabs.addAll(res['data']);
|
||||||
|
tabs.insert(0, MemberTagItemModel(name: '全部关注'));
|
||||||
|
int initialIndex = 0;
|
||||||
|
if (tabController != null) {
|
||||||
|
initialIndex = tabController!.index.clamp(0, tabs.length - 1);
|
||||||
|
tabController!.dispose();
|
||||||
|
}
|
||||||
tabController = TabController(
|
tabController = TabController(
|
||||||
initialIndex: 0,
|
initialIndex: initialIndex,
|
||||||
length: res['data'].length,
|
length: tabs.length,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
followState.value = LoadingState.success(res['data']);
|
followState.value = LoadingState.success(tabs.hashCode);
|
||||||
} else {
|
} else {
|
||||||
followState.value = LoadingState.error(res['msg']);
|
followState.value = LoadingState.error(res['msg']);
|
||||||
}
|
}
|
||||||
@@ -49,4 +57,37 @@ class FollowController extends GetxController
|
|||||||
tabController?.dispose();
|
tabController?.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future onCreateTag(String tagName) async {
|
||||||
|
final res = await MemberHttp.createFollowTag(tagName);
|
||||||
|
if (res['status']) {
|
||||||
|
followState.value = LoadingState.loading();
|
||||||
|
queryFollowUpTags();
|
||||||
|
SmartDialog.showToast('创建成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future onUpdateTag(int index, tagid, String tagName) async {
|
||||||
|
final res = await MemberHttp.updateFollowTag(tagid, tagName);
|
||||||
|
if (res['status']) {
|
||||||
|
tabs[index].name = tagName;
|
||||||
|
tabs.refresh();
|
||||||
|
SmartDialog.showToast('修改成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future onDelTag(tagid) async {
|
||||||
|
final res = await MemberHttp.delFollowTag(tagid);
|
||||||
|
if (res['status']) {
|
||||||
|
followState.value = LoadingState.loading();
|
||||||
|
queryFollowUpTags();
|
||||||
|
SmartDialog.showToast('删除成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:PiliPlus/common/widgets/dialog.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
@@ -5,6 +6,7 @@ import 'package:PiliPlus/models/member/tags.dart';
|
|||||||
import 'package:PiliPlus/pages/follow/child_view.dart';
|
import 'package:PiliPlus/pages/follow/child_view.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
@@ -16,8 +18,10 @@ class FollowPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FollowPageState extends State<FollowPage> {
|
class _FollowPageState extends State<FollowPage> {
|
||||||
final FollowController _followController =
|
final FollowController _followController = Get.put(
|
||||||
Get.put(FollowController(), tag: Utils.generateRandomString(8));
|
FollowController(),
|
||||||
|
tag: Utils.generateRandomString(8),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -28,6 +32,11 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
),
|
),
|
||||||
actions: _followController.isOwner
|
actions: _followController.isOwner
|
||||||
? [
|
? [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _onCreateTag,
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
tooltip: '新建分组',
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => Get.toNamed(
|
onPressed: () => Get.toNamed(
|
||||||
'/followSearch',
|
'/followSearch',
|
||||||
@@ -60,50 +69,171 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
),
|
),
|
||||||
body: _followController.isOwner
|
body: _followController.isOwner
|
||||||
? Obx(() => _buildBody(_followController.followState.value))
|
? Obx(() => _buildBody(_followController.followState.value))
|
||||||
: FollowChildPage(mid: _followController.mid),
|
: FollowChildPage(
|
||||||
|
controller: _followController, mid: _followController.mid),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody(LoadingState<List<MemberTagItemModel>?> loadingState) {
|
bool _isCustomTag(tagid) {
|
||||||
|
return tagid != null && tagid != 0 && tagid != -10 && tagid != -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody(LoadingState loadingState) {
|
||||||
return switch (loadingState) {
|
return switch (loadingState) {
|
||||||
Loading() => loadingWidget,
|
Loading() => loadingWidget,
|
||||||
Success() => loadingState.response?.isNotEmpty == true
|
Success() => Column(
|
||||||
? Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
SafeArea(
|
||||||
SafeArea(
|
top: false,
|
||||||
top: false,
|
bottom: false,
|
||||||
bottom: false,
|
child: TabBar(
|
||||||
child: TabBar(
|
isScrollable: true,
|
||||||
isScrollable: true,
|
tabAlignment: TabAlignment.start,
|
||||||
tabAlignment: TabAlignment.start,
|
controller: _followController.tabController,
|
||||||
controller: _followController.tabController,
|
tabs: List.generate(_followController.tabs.length, (index) {
|
||||||
tabs: loadingState.response!
|
return Obx(() {
|
||||||
.map((item) => Tab(text: item.name))
|
final item = _followController.tabs[index];
|
||||||
.toList(),
|
int? count = item.count;
|
||||||
),
|
if (_isCustomTag(item.tagid)) {
|
||||||
|
return GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
_onHandleTag(index, item);
|
||||||
|
},
|
||||||
|
child: Tab(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${item.name}${count != null ? '($count)' : ''} ',
|
||||||
|
),
|
||||||
|
Icon(Icons.menu, size: 18),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Tab(
|
||||||
|
text: '${item.name}${count != null ? '($count)' : ''}');
|
||||||
|
});
|
||||||
|
}).toList(),
|
||||||
|
onTap: (value) {
|
||||||
|
if (!_followController.tabController!.indexIsChanging) {
|
||||||
|
final item = _followController.tabs[value];
|
||||||
|
if (_isCustomTag(item.tagid)) {
|
||||||
|
_onHandleTag(value, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: tabBarView(
|
||||||
|
controller: _followController.tabController,
|
||||||
|
children: _followController.tabs
|
||||||
|
.map(
|
||||||
|
(item) => FollowChildPage(
|
||||||
|
controller: _followController,
|
||||||
|
mid: _followController.mid,
|
||||||
|
tagid: item.tagid,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: Material(
|
),
|
||||||
color: Colors.transparent,
|
],
|
||||||
child: tabBarView(
|
),
|
||||||
controller: _followController.tabController,
|
Error() => FollowChildPage(
|
||||||
children: loadingState.response!
|
controller: _followController, mid: _followController.mid),
|
||||||
.map(
|
|
||||||
(item) => FollowChildPage(
|
|
||||||
mid: _followController.mid,
|
|
||||||
tagid: item.tagid,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: FollowChildPage(mid: _followController.mid),
|
|
||||||
Error() => FollowChildPage(mid: _followController.mid),
|
|
||||||
_ => throw UnimplementedError(),
|
_ => throw UnimplementedError(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onHandleTag(int index, MemberTagItemModel item) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
String tagName = item.name!;
|
||||||
|
showConfirmDialog(
|
||||||
|
context: context,
|
||||||
|
title: '编辑分组名称',
|
||||||
|
content: TextFormField(
|
||||||
|
autofocus: true,
|
||||||
|
initialValue: tagName,
|
||||||
|
onChanged: (value) => tagName = value,
|
||||||
|
inputFormatters: [
|
||||||
|
LengthLimitingTextInputFormatter(16),
|
||||||
|
],
|
||||||
|
decoration:
|
||||||
|
const InputDecoration(border: OutlineInputBorder()),
|
||||||
|
),
|
||||||
|
onConfirm: () {
|
||||||
|
if (tagName.isNotEmpty) {
|
||||||
|
_followController.onUpdateTag(
|
||||||
|
index, item.tagid, tagName);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
dense: true,
|
||||||
|
title: const Text(
|
||||||
|
'修改名称',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
showConfirmDialog(
|
||||||
|
context: context,
|
||||||
|
title: '删除分组',
|
||||||
|
content: '删除后,该分组下的用户依旧保留?',
|
||||||
|
onConfirm: () {
|
||||||
|
_followController.onDelTag(item.tagid);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
dense: true,
|
||||||
|
title: const Text(
|
||||||
|
'删除分组',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onCreateTag() {
|
||||||
|
String tagName = '';
|
||||||
|
showConfirmDialog(
|
||||||
|
context: context,
|
||||||
|
title: '新建分组',
|
||||||
|
content: TextFormField(
|
||||||
|
autofocus: true,
|
||||||
|
initialValue: tagName,
|
||||||
|
onChanged: (value) => tagName = value,
|
||||||
|
inputFormatters: [
|
||||||
|
LengthLimitingTextInputFormatter(16),
|
||||||
|
],
|
||||||
|
decoration: const InputDecoration(border: OutlineInputBorder()),
|
||||||
|
),
|
||||||
|
onConfirm: () {
|
||||||
|
_followController.onCreateTag(tagName);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user