mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-14 21:24:02 +08:00
create fav tag from fav panel
Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
@@ -593,7 +593,7 @@ abstract final class MemberHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<LoadingState<void>> createFollowTag(Object tagName) async {
|
static Future<LoadingState<int>> createFollowTag(String tagName) async {
|
||||||
final res = await Request().post(
|
final res = await Request().post(
|
||||||
Api.createFollowTag,
|
Api.createFollowTag,
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
@@ -607,7 +607,7 @@ abstract final class MemberHttp {
|
|||||||
options: Options(contentType: Headers.formUrlEncodedContentType),
|
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return const Success(null);
|
return Success(res.data['data']['tagid']);
|
||||||
} else {
|
} else {
|
||||||
return Error(res.data['message']);
|
return Error(res.data['message']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,4 +17,10 @@ class MemberTagItemModel {
|
|||||||
tagid = json['tagid'];
|
tagid = json['tagid'];
|
||||||
tip = json['tip'];
|
tip = json['tip'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MemberTagItemModel.fromCreate(({int tagid, String tagName}) res) {
|
||||||
|
tagid = res.tagid;
|
||||||
|
name = res.tagName;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,36 +45,35 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
tabs
|
tabs
|
||||||
..assign(MemberTagItemModel(name: '全部关注'))
|
..assign(MemberTagItemModel(name: '全部关注'))
|
||||||
..addAll(response);
|
..addAll(response);
|
||||||
int initialIndex = 0;
|
onInitTab();
|
||||||
if (tabController != null) {
|
|
||||||
initialIndex = tabController!.index.clamp(0, tabs.length - 1);
|
|
||||||
tabController!.dispose();
|
|
||||||
}
|
|
||||||
tabController = TabController(
|
|
||||||
initialIndex: initialIndex,
|
|
||||||
length: tabs.length,
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
followState.value = Success(tabs.hashCode);
|
followState.value = Success(tabs.hashCode);
|
||||||
} else {
|
} else {
|
||||||
followState.value = res;
|
followState.value = res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void onInitTab() {
|
||||||
void onClose() {
|
int initialIndex = 0;
|
||||||
tabController?.dispose();
|
if (tabController != null) {
|
||||||
super.onClose();
|
initialIndex = tabController!.index.clamp(0, tabs.length - 1);
|
||||||
|
tabController!.dispose();
|
||||||
|
}
|
||||||
|
tabController = TabController(
|
||||||
|
initialIndex: initialIndex,
|
||||||
|
length: tabs.length,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onCreateTag(String tagName) async {
|
void onCreateFavTag(({int tagid, String tagName}) res) {
|
||||||
final res = await MemberHttp.createFollowTag(tagName);
|
if (isClosed) return;
|
||||||
if (res.isSuccess) {
|
if (followState.value.isSuccess) {
|
||||||
|
tabs.add(MemberTagItemModel.fromCreate(res));
|
||||||
|
onInitTab();
|
||||||
|
followState.refresh();
|
||||||
|
} else {
|
||||||
followState.value = LoadingState.loading();
|
followState.value = LoadingState.loading();
|
||||||
queryFollowUpTags();
|
queryFollowUpTags();
|
||||||
SmartDialog.showToast('创建成功');
|
|
||||||
} else {
|
|
||||||
res.toast();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,14 +88,22 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onDelTag(int tagid) async {
|
Future<void> onDelTag(int index, int tagid) async {
|
||||||
final res = await MemberHttp.delFollowTag(tagid);
|
final res = await MemberHttp.delFollowTag(tagid);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
followState.value = LoadingState.loading();
|
tabs.removeAt(index);
|
||||||
queryFollowUpTags();
|
onInitTab();
|
||||||
|
followState.refresh();
|
||||||
SmartDialog.showToast('删除成功');
|
SmartDialog.showToast('删除成功');
|
||||||
} else {
|
} else {
|
||||||
res.toast();
|
res.toast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
tabController?.dispose();
|
||||||
|
tabController = null;
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:PiliPlus/pages/follow/child/child_controller.dart';
|
|||||||
import 'package:PiliPlus/pages/follow/child/child_view.dart';
|
import 'package:PiliPlus/pages/follow/child/child_view.dart';
|
||||||
import 'package:PiliPlus/pages/follow/controller.dart';
|
import 'package:PiliPlus/pages/follow/controller.dart';
|
||||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||||
|
import 'package:PiliPlus/utils/request_utils.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' show LengthLimitingTextInputFormatter;
|
import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter;
|
||||||
@@ -45,57 +46,62 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
appBar: AppBar(
|
appBar: _buildAppBar,
|
||||||
title: _followController.isOwner
|
|
||||||
? const Text('我的关注')
|
|
||||||
: Obx(() {
|
|
||||||
final name = _followController.name.value;
|
|
||||||
if (name != null) return Text('$name的关注');
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}),
|
|
||||||
actions: _followController.isOwner
|
|
||||||
? [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _onCreateTag,
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
tooltip: '新建分组',
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => Get.toNamed(
|
|
||||||
'/followSearch',
|
|
||||||
arguments: {
|
|
||||||
'mid': _followController.mid,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.search_outlined),
|
|
||||||
tooltip: '搜索',
|
|
||||||
),
|
|
||||||
PopupMenuButton(
|
|
||||||
icon: const Icon(Icons.more_vert),
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
PopupMenuItem(
|
|
||||||
onTap: () => Get.toNamed('/blackListPage'),
|
|
||||||
child: const Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.block, size: 19),
|
|
||||||
SizedBox(width: 10),
|
|
||||||
Text('黑名单管理'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
body: _followController.isOwner
|
body: _followController.isOwner
|
||||||
? Obx(() => _buildBody(_followController.followState.value))
|
? Obx(() => _buildBody(_followController.followState.value))
|
||||||
: _childPage(),
|
: _childPage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreferredSizeWidget get _buildAppBar => AppBar(
|
||||||
|
title: _followController.isOwner
|
||||||
|
? const Text('我的关注')
|
||||||
|
: Obx(() {
|
||||||
|
final name = _followController.name.value;
|
||||||
|
if (name != null) return Text('$name的关注');
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}),
|
||||||
|
actions: _followController.isOwner
|
||||||
|
? [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => RequestUtils.createFavTag(
|
||||||
|
context,
|
||||||
|
_followController.onCreateFavTag,
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
tooltip: '新建分组',
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/followSearch',
|
||||||
|
arguments: {
|
||||||
|
'mid': _followController.mid,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
tooltip: '搜索',
|
||||||
|
),
|
||||||
|
PopupMenuButton(
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () => Get.toNamed('/blackListPage'),
|
||||||
|
child: const Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.block, size: 19),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Text('黑名单管理'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
Widget _childPage([MemberTagItemModel? item]) => FollowChildPage(
|
Widget _childPage([MemberTagItemModel? item]) => FollowChildPage(
|
||||||
tag: _tag,
|
tag: _tag,
|
||||||
controller: _followController,
|
controller: _followController,
|
||||||
@@ -223,7 +229,8 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
title: const Text('删除分组'),
|
title: const Text('删除分组'),
|
||||||
content: const Text('删除后,该分组下的用户依旧保留?'),
|
content: const Text('删除后,该分组下的用户依旧保留?'),
|
||||||
onConfirm: () => _followController.onDelTag(item.tagid!),
|
onConfirm: () =>
|
||||||
|
_followController.onDelTag(index, item.tagid!),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -237,22 +244,4 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onCreateTag() {
|
|
||||||
String tagName = '';
|
|
||||||
showConfirmDialog(
|
|
||||||
context: context,
|
|
||||||
title: const Text('新建分组'),
|
|
||||||
content: TextFormField(
|
|
||||||
autofocus: true,
|
|
||||||
initialValue: tagName,
|
|
||||||
onChanged: (value) => tagName = value,
|
|
||||||
inputFormatters: [
|
|
||||||
LengthLimitingTextInputFormatter(16),
|
|
||||||
],
|
|
||||||
decoration: const InputDecoration(border: OutlineInputBorder()),
|
|
||||||
),
|
|
||||||
onConfirm: () => _followController.onCreateTag(tagName),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:PiliPlus/models/member/tags.dart';
|
|||||||
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
|
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
|
||||||
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
||||||
import 'package:PiliPlus/utils/feed_back.dart';
|
import 'package:PiliPlus/utils/feed_back.dart';
|
||||||
|
import 'package:PiliPlus/utils/request_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -26,7 +27,7 @@ class GroupPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _GroupPanelState extends State<GroupPanel> {
|
class _GroupPanelState extends State<GroupPanel> {
|
||||||
LoadingState<List<MemberTagItemModel>> loadingState = LoadingState.loading();
|
LoadingState<List<MemberTagItemModel>> loadingState = LoadingState.loading();
|
||||||
RxBool showDefaultBtn = true.obs;
|
final RxBool showDefaultBtn = true.obs;
|
||||||
late final Set<int> tags = widget.tags == null
|
late final Set<int> tags = widget.tags == null
|
||||||
? {}
|
? {}
|
||||||
: Set<int>.from(widget.tags!);
|
: Set<int>.from(widget.tags!);
|
||||||
@@ -34,10 +35,10 @@ class _GroupPanelState extends State<GroupPanel> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_query();
|
_queryFollowUpTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _query() {
|
void _queryFollowUpTags() {
|
||||||
MemberHttp.followUpTags().then((res) {
|
MemberHttp.followUpTags().then((res) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
loadingState = res..dataOrNull?.removeFirstWhere((e) => e.tagid == 0);
|
loadingState = res..dataOrNull?.removeFirstWhere((e) => e.tagid == 0);
|
||||||
@@ -116,7 +117,7 @@ class _GroupPanelState extends State<GroupPanel> {
|
|||||||
Error(:final errMsg) => scrollErrorWidget(
|
Error(:final errMsg) => scrollErrorWidget(
|
||||||
controller: widget.scrollController,
|
controller: widget.scrollController,
|
||||||
errMsg: errMsg,
|
errMsg: errMsg,
|
||||||
onReload: _query,
|
onReload: _queryFollowUpTags,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -125,8 +126,8 @@ class _GroupPanelState extends State<GroupPanel> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: .end,
|
||||||
children: <Widget>[
|
children: [
|
||||||
AppBar(
|
AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
@@ -135,6 +136,21 @@ class _GroupPanelState extends State<GroupPanel> {
|
|||||||
icon: const Icon(Icons.close_outlined),
|
icon: const Icon(Icons.close_outlined),
|
||||||
),
|
),
|
||||||
title: const Text('设置关注分组'),
|
title: const Text('设置关注分组'),
|
||||||
|
actions: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () =>
|
||||||
|
RequestUtils.createFavTag(context, _onCreateFavTag),
|
||||||
|
icon: Icon(Icons.add, color: theme.colorScheme.primary),
|
||||||
|
label: const Text('新建分组'),
|
||||||
|
style: const ButtonStyle(
|
||||||
|
visualDensity: .compact,
|
||||||
|
padding: WidgetStatePropertyAll(
|
||||||
|
.symmetric(horizontal: 18, vertical: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Expanded(child: _buildBody),
|
Expanded(child: _buildBody),
|
||||||
Divider(
|
Divider(
|
||||||
@@ -142,20 +158,28 @@ class _GroupPanelState extends State<GroupPanel> {
|
|||||||
color: theme.disabledColor.withValues(alpha: 0.08),
|
color: theme.disabledColor.withValues(alpha: 0.08),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: .only(
|
||||||
right: 20,
|
right: 20,
|
||||||
top: 12,
|
top: 12,
|
||||||
bottom: MediaQuery.viewPaddingOf(context).bottom + 12,
|
bottom: MediaQuery.viewPaddingOf(context).bottom + 12,
|
||||||
),
|
),
|
||||||
child: FilledButton.tonal(
|
child: FilledButton.tonal(
|
||||||
onPressed: onSave,
|
onPressed: onSave,
|
||||||
style: FilledButton.styleFrom(
|
style: const ButtonStyle(visualDensity: .compact),
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
),
|
|
||||||
child: Obx(() => Text(showDefaultBtn.value ? '保存至默认分组' : '保存')),
|
child: Obx(() => Text(showDefaultBtn.value ? '保存至默认分组' : '保存')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onCreateFavTag(({int tagid, String tagName}) res) {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (loadingState case Success(:final response)) {
|
||||||
|
response.add(MemberTagItemModel.fromCreate(res));
|
||||||
|
setState(() {});
|
||||||
|
} else {
|
||||||
|
_queryFollowUpTags();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||||
import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart';
|
import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart';
|
||||||
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
||||||
show ReplyInfo;
|
show ReplyInfo;
|
||||||
@@ -37,6 +38,7 @@ import 'package:PiliPlus/utils/storage_pref.dart';
|
|||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter;
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart';
|
import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart';
|
||||||
@@ -99,6 +101,35 @@ abstract final class RequestUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> createFavTag(
|
||||||
|
BuildContext context,
|
||||||
|
ValueChanged<({int tagid, String tagName})> onSuccess,
|
||||||
|
) async {
|
||||||
|
String tagName = '';
|
||||||
|
final onCreate = await showConfirmDialog(
|
||||||
|
context: context,
|
||||||
|
title: const Text('新建分组'),
|
||||||
|
content: TextFormField(
|
||||||
|
autofocus: true,
|
||||||
|
initialValue: tagName,
|
||||||
|
onChanged: (value) => tagName = value,
|
||||||
|
inputFormatters: [
|
||||||
|
LengthLimitingTextInputFormatter(16),
|
||||||
|
],
|
||||||
|
decoration: const InputDecoration(border: OutlineInputBorder()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (onCreate) {
|
||||||
|
final res = await MemberHttp.createFollowTag(tagName);
|
||||||
|
if (res case Success(:final response)) {
|
||||||
|
onSuccess((tagid: response, tagName: tagName));
|
||||||
|
SmartDialog.showToast('创建成功');
|
||||||
|
} else {
|
||||||
|
res.toast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> actionRelationMod({
|
static Future<void> actionRelationMod({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required dynamic mid,
|
required dynamic mid,
|
||||||
@@ -188,17 +219,13 @@ abstract final class RequestUtils {
|
|||||||
expand: false,
|
expand: false,
|
||||||
snapSizes: [maxChildSize],
|
snapSizes: [maxChildSize],
|
||||||
initialChildSize: maxChildSize,
|
initialChildSize: maxChildSize,
|
||||||
builder:
|
builder: (context, scrollController) {
|
||||||
(
|
return GroupPanel(
|
||||||
BuildContext context,
|
mid: mid,
|
||||||
ScrollController scrollController,
|
tags: followStatus!.tag,
|
||||||
) {
|
scrollController: scrollController,
|
||||||
return GroupPanel(
|
);
|
||||||
mid: mid,
|
},
|
||||||
tags: followStatus!.tag,
|
|
||||||
scrollController: scrollController,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user