feat: 黑名单查看、屏蔽黑名单用户推荐/最热 #22
This commit is contained in:
@ -278,4 +278,7 @@ class Api {
|
|||||||
// 我的订阅
|
// 我的订阅
|
||||||
static const String bangumiFollow =
|
static const String bangumiFollow =
|
||||||
'/x/space/bangumi/follow/list?type=1&follow_status=0&pn=1&ps=15&ts=1691544359969';
|
'/x/space/bangumi/follow/list?type=1&follow_status=0&pn=1&ps=15&ts=1691544359969';
|
||||||
|
|
||||||
|
// 黑名单
|
||||||
|
static const String blackLst = '/x/relation/blacks';
|
||||||
}
|
}
|
||||||
|
|||||||
26
lib/http/black.dart
Normal file
26
lib/http/black.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
import 'package:pilipala/models/user/black.dart';
|
||||||
|
|
||||||
|
class BlackHttp {
|
||||||
|
static Future blackList({required int pn, int? ps}) async {
|
||||||
|
var res = await Request().get(Api.blackLst, data: {
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps ?? 50,
|
||||||
|
're_version': 0,
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': BlackListDataModel.fromJson(res.data['data'])
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -66,8 +66,13 @@ class VideoHttp {
|
|||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
List<RecVideoItemAppModel> list = [];
|
List<RecVideoItemAppModel> list = [];
|
||||||
|
List<int> blackMidsList =
|
||||||
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
for (var i in res.data['data']['items']) {
|
for (var i in res.data['data']['items']) {
|
||||||
if (i['card_goto'] != 'ad_av') {
|
// 屏蔽推广和拉黑用户
|
||||||
|
if (i['card_goto'] != 'ad_av' &&
|
||||||
|
(i['args'] != null &&
|
||||||
|
!blackMidsList.contains(i['args']['up_mid']))) {
|
||||||
list.add(RecVideoItemAppModel.fromJson(i));
|
list.add(RecVideoItemAppModel.fromJson(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,8 +94,12 @@ class VideoHttp {
|
|||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
List<HotVideoItemModel> list = [];
|
List<HotVideoItemModel> list = [];
|
||||||
|
List<int> blackMidsList =
|
||||||
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
for (var i in res.data['data']['list']) {
|
for (var i in res.data['data']['list']) {
|
||||||
list.add(HotVideoItemModel.fromJson(i));
|
if (!blackMidsList.contains(i['owner']['mid'])) {
|
||||||
|
list.add(HotVideoItemModel.fromJson(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {'status': true, 'data': list};
|
return {'status': true, 'data': list};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
37
lib/models/user/black.dart
Normal file
37
lib/models/user/black.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
class BlackListDataModel {
|
||||||
|
BlackListDataModel({
|
||||||
|
this.list,
|
||||||
|
this.total,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<BlackListItem>? list;
|
||||||
|
int? total;
|
||||||
|
|
||||||
|
BlackListDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
list = json['list']
|
||||||
|
.map<BlackListItem>((e) => BlackListItem.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
total = json['total'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlackListItem {
|
||||||
|
BlackListItem({
|
||||||
|
this.face,
|
||||||
|
this.mid,
|
||||||
|
this.mtime,
|
||||||
|
this.uname,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? face;
|
||||||
|
int? mid;
|
||||||
|
int? mtime;
|
||||||
|
String? uname;
|
||||||
|
|
||||||
|
BlackListItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
face = json['face'];
|
||||||
|
mid = json['mid'];
|
||||||
|
mtime = json['mtime'];
|
||||||
|
uname = json['uname'];
|
||||||
|
}
|
||||||
|
}
|
||||||
156
lib/pages/blacklist/index.dart
Normal file
156
lib/pages/blacklist/index.dart
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/black.dart';
|
||||||
|
import 'package:pilipala/models/user/black.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
class BlackListPage extends StatefulWidget {
|
||||||
|
const BlackListPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BlackListPage> createState() => _BlackListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BlackListPageState extends State<BlackListPage> {
|
||||||
|
final BlackListController _blackListController =
|
||||||
|
Get.put(BlackListController());
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Future? _futureBuilderFuture;
|
||||||
|
bool _isLoadingMore = false;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_futureBuilderFuture = _blackListController.queryBlacklist();
|
||||||
|
scrollController.addListener(
|
||||||
|
() async {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
|
if (!_isLoadingMore) {
|
||||||
|
_isLoadingMore = true;
|
||||||
|
await _blackListController.queryBlacklist(type: 'onLoad');
|
||||||
|
_isLoadingMore = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
List<int> blackMidsList =
|
||||||
|
_blackListController.blackList.map<int>((e) => e.mid!).toList();
|
||||||
|
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
titleSpacing: 0,
|
||||||
|
centerTitle: false,
|
||||||
|
title: Obx(
|
||||||
|
() => Text(
|
||||||
|
'黑名单管理 (${_blackListController.blackList.length} / 5000)',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: () async => await _blackListController.queryBlacklist(),
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
var data = snapshot.data;
|
||||||
|
if (data['status']) {
|
||||||
|
List<BlackListItem> list = _blackListController.blackList;
|
||||||
|
return Obx(
|
||||||
|
() => list.length == 1
|
||||||
|
? const SizedBox()
|
||||||
|
: ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: list.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return ListTile(
|
||||||
|
onTap: () {},
|
||||||
|
leading: NetworkImgLayer(
|
||||||
|
width: 45,
|
||||||
|
height: 45,
|
||||||
|
type: 'avatar',
|
||||||
|
src: list[index].face,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
list[index].uname!,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
Utils.dateFormat(list[index].mtime),
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.outline),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
dense: true,
|
||||||
|
// trailing: TextButton(
|
||||||
|
// onPressed: () {},
|
||||||
|
// child: const Text('移除'),
|
||||||
|
// ),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => _blackListController.queryBlacklist(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlackListController extends GetxController {
|
||||||
|
int currentPage = 1;
|
||||||
|
int pageSize = 50;
|
||||||
|
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
||||||
|
|
||||||
|
Future queryBlacklist({type = 'init'}) async {
|
||||||
|
if (type == 'init') {
|
||||||
|
currentPage = 1;
|
||||||
|
}
|
||||||
|
var result = await BlackHttp.blackList(pn: currentPage, ps: pageSize);
|
||||||
|
if (result['status']) {
|
||||||
|
if (type == 'init') {
|
||||||
|
blackList.value = result['data'].list;
|
||||||
|
} else {
|
||||||
|
blackList.addAll(result['data'].list);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage += 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
lib/pages/setting/privacy_setting.dart
Normal file
58
lib/pages/setting/privacy_setting.dart
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
class PrivacySetting extends StatefulWidget {
|
||||||
|
const PrivacySetting({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PrivacySetting> createState() => _PrivacySettingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PrivacySettingState extends State<PrivacySetting> {
|
||||||
|
bool userLogin = false;
|
||||||
|
Box user = GStrorage.user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
userLogin = user.get(UserBoxKey.userLogin) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
|
||||||
|
TextStyle subTitleStyle = Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.copyWith(color: Theme.of(context).colorScheme.outline);
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: false,
|
||||||
|
titleSpacing: 0,
|
||||||
|
title: Text(
|
||||||
|
'隐私设置',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
if (!userLogin) {
|
||||||
|
SmartDialog.showToast('登录后查看');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.toNamed('/blackListPage');
|
||||||
|
},
|
||||||
|
dense: false,
|
||||||
|
title: Text('黑名单管理', style: titleStyle),
|
||||||
|
subtitle: Text('已拉黑用户', style: subTitleStyle),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,6 +19,11 @@ class SettingPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
|
ListTile(
|
||||||
|
onTap: () => Get.toNamed('/privacySetting'),
|
||||||
|
dense: false,
|
||||||
|
title: const Text('隐私设置'),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => Get.toNamed('/playSetting'),
|
onTap: () => Get.toNamed('/playSetting'),
|
||||||
dense: false,
|
dense: false,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/pages/blacklist/index.dart';
|
||||||
import 'package:pilipala/pages/dynamics/deatil/index.dart';
|
import 'package:pilipala/pages/dynamics/deatil/index.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
import 'package:pilipala/pages/dynamics/index.dart';
|
||||||
import 'package:pilipala/pages/fan/index.dart';
|
import 'package:pilipala/pages/fan/index.dart';
|
||||||
@ -15,6 +16,7 @@ import 'package:pilipala/pages/preview/index.dart';
|
|||||||
import 'package:pilipala/pages/search/index.dart';
|
import 'package:pilipala/pages/search/index.dart';
|
||||||
import 'package:pilipala/pages/searchResult/index.dart';
|
import 'package:pilipala/pages/searchResult/index.dart';
|
||||||
import 'package:pilipala/pages/setting/play_setting.dart';
|
import 'package:pilipala/pages/setting/play_setting.dart';
|
||||||
|
import 'package:pilipala/pages/setting/privacy_setting.dart';
|
||||||
import 'package:pilipala/pages/setting/style_setting.dart';
|
import 'package:pilipala/pages/setting/style_setting.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||||
@ -73,7 +75,12 @@ class Routes {
|
|||||||
|
|
||||||
// 播放设置
|
// 播放设置
|
||||||
GetPage(name: '/playSetting', page: () => const PlaySetting()),
|
GetPage(name: '/playSetting', page: () => const PlaySetting()),
|
||||||
|
// 外观设置
|
||||||
GetPage(name: '/styleSetting', page: () => const StyleSetting()),
|
GetPage(name: '/styleSetting', page: () => const StyleSetting()),
|
||||||
|
// 隐私设置
|
||||||
|
GetPage(name: '/privacySetting', page: () => const PrivacySetting()),
|
||||||
|
|
||||||
|
//
|
||||||
|
GetPage(name: '/blackListPage', page: () => const BlackListPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,6 +117,8 @@ class SettingBoxKey {
|
|||||||
|
|
||||||
static const String danmakuEnable = 'danmakuEnable';
|
static const String danmakuEnable = 'danmakuEnable';
|
||||||
static const String fullScreenMode = 'fullScreenMode';
|
static const String fullScreenMode = 'fullScreenMode';
|
||||||
|
|
||||||
|
static const String blackMidsList = 'blackMidsList';
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalCacheKey {
|
class LocalCacheKey {
|
||||||
|
|||||||
Reference in New Issue
Block a user