feat: space setting

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-06-07 21:21:03 +08:00
parent 6ec0d8f589
commit 19e4ae6c04
8 changed files with 390 additions and 32 deletions

View File

@@ -880,4 +880,8 @@ class Api {
static const String coinArc = '${HttpString.appBaseUrl}/x/v2/space/coinarc';
static const String likeArc = '${HttpString.appBaseUrl}/x/v2/space/likearc';
static const String spaceSetting = '/x/space/setting/app';
static const String spaceSettingMod = '/x/space/privacy/batch/modify';
}

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/models/user/stat.dart';
import 'package:PiliPlus/models_new/history/data.dart';
import 'package:PiliPlus/models_new/later/data.dart';
import 'package:PiliPlus/models_new/media_list/data.dart';
import 'package:PiliPlus/models_new/space_setting/data.dart';
import 'package:PiliPlus/models_new/sub/sub/data.dart';
import 'package:PiliPlus/models_new/sub/sub/list.dart';
import 'package:PiliPlus/models_new/video/video_tag/data.dart';
@@ -352,4 +353,34 @@ class UserHttp {
);
return res.data as Map;
}
static Future<LoadingState<SpaceSettingData>> spaceSetting() async {
final res = await Request().get(
Api.spaceSetting,
queryParameters: {
'mid': Accounts.main.mid,
},
);
if (res.data['code'] == 0) {
return Success(SpaceSettingData.fromJson(res.data['data']));
} else {
return Error(res.data['message']);
}
}
static Future spaceSettingMod(data) async {
final res = await Request().post(
Api.spaceSettingMod,
queryParameters: {
'csrf': Accounts.main.csrf,
},
data: data,
options: Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
return {'status': true};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
}

View File

@@ -0,0 +1,18 @@
import 'package:PiliPlus/models_new/space_setting/privacy.dart';
class SpaceSettingData {
Privacy? privacy;
bool? showNftSwitch;
String? exclusiveUrl;
SpaceSettingData({this.privacy, this.showNftSwitch, this.exclusiveUrl});
factory SpaceSettingData.fromJson(Map<String, dynamic> json) =>
SpaceSettingData(
privacy: json['privacy'] == null
? null
: Privacy.fromJson(json['privacy'] as Map<String, dynamic>),
showNftSwitch: json['show_nft_switch'] as bool?,
exclusiveUrl: json['exclusive_url'] as String?,
);
}

View File

@@ -0,0 +1,102 @@
class SpaceSettingModel {
SpaceSettingModel({
required this.name,
required this.key,
required this.value,
this.isReverse = false,
});
String name;
String key;
int? value;
bool isReverse;
bool get boolVal => isReverse ? value == 0 : value == 1;
}
class Privacy {
List<SpaceSettingModel> list1;
List<SpaceSettingModel> list2;
List<SpaceSettingModel> list3;
Privacy({
required this.list1,
required this.list2,
required this.list3,
});
factory Privacy.fromJson(Map<String, dynamic> json) => Privacy(
list1: [
SpaceSettingModel(
name: '公开我的收藏', key: 'fav_video', value: json['fav_video']),
SpaceSettingModel(
name: '公开我的追番追剧', key: 'bangumi', value: json['bangumi']),
SpaceSettingModel(name: '公开我的追漫', key: 'comic', value: json['comic']),
SpaceSettingModel(
name: '公开最近投币的视频',
key: 'coins_video',
value: json['coins_video'],
),
SpaceSettingModel(
name: '公开最近点赞的视频',
key: 'likes_video',
value: json['likes_video'],
),
SpaceSettingModel(
name: '公开最近玩过的游戏',
key: 'played_game',
value: json['played_game'],
),
SpaceSettingModel(
name: '公开拥有的粉丝装扮', key: 'dress_up', value: json['dress_up']),
SpaceSettingModel(
name: '公开我的关注列表',
key: 'disable_following',
value: json['disable_following'],
isReverse: true,
),
SpaceSettingModel(
name: '公开我的粉丝列表',
key: 'disable_show_fans',
value: json['disable_show_fans'],
isReverse: true,
),
],
list2: [
SpaceSettingModel(
name: '公开佩戴的粉丝勋章',
key: 'close_space_medal',
value: json['close_space_medal'],
isReverse: true,
),
SpaceSettingModel(
name: '勋章墙公开显示所有粉丝勋章',
key: 'only_show_wearing',
value: json['only_show_wearing'],
),
SpaceSettingModel(
name: '公开学校信息',
key: 'disable_show_school',
value: json['disable_show_school'],
isReverse: true,
),
],
list3: [
SpaceSettingModel(
name: '投稿视频列表中展现直播回放',
key: 'live_playback',
value: json['live_playback'],
),
SpaceSettingModel(
name: '投稿视频列表中展现包月充电专属视频',
key: 'charge_video',
value: json['charge_video'],
),
SpaceSettingModel(
name: '投稿视频列表中展现课堂视频',
key: 'lesson_video',
value: json['lesson_video'],
),
],
);
}

View File

@@ -135,41 +135,53 @@ class _MemberPageState extends State<MemberPage> {
],
),
),
if (_userController.ownerMid != null &&
_userController.mid != _userController.ownerMid) ...[
const PopupMenuDivider(),
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
content: MemberReportPanel(
name: _userController.username,
mid: _mid,
if (_userController.ownerMid != 0)
if (_userController.mid == _userController.ownerMid)
PopupMenuItem(
onTap: () => Get.toNamed('/spaceSetting'),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.settings_outlined, size: 19),
SizedBox(width: 10),
Text('空间设置'),
],
),
)
else ...[
const PopupMenuDivider(),
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
content: MemberReportPanel(
name: _userController.username,
mid: _mid,
),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 19,
color: theme.colorScheme.error,
),
const SizedBox(width: 10),
Text(
'举报',
style: TextStyle(color: theme.colorScheme.error),
),
],
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 19,
color: theme.colorScheme.error,
),
const SizedBox(width: 10),
Text(
'举报',
style: TextStyle(color: theme.colorScheme.error),
),
],
),
),
],
],
],
),
const SizedBox(width: 4),

View File

@@ -0,0 +1,46 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models_new/space_setting/data.dart';
import 'package:PiliPlus/models_new/space_setting/privacy.dart';
import 'package:PiliPlus/pages/common/common_data_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class SpaceSettingController
extends CommonDataController<SpaceSettingData, Privacy?> {
@override
void onInit() {
super.onInit();
queryData();
}
bool? hasMod;
@override
bool customHandleResponse(
bool isRefresh, Success<SpaceSettingData> response) {
loadingState.value = Success(response.response.privacy);
return true;
}
@override
Future<LoadingState<SpaceSettingData>> customGetData() =>
UserHttp.spaceSetting();
Future<void> onMod() async {
if (hasMod == true && loadingState.value.isSuccess) {
Privacy? data = loadingState.value.data;
if (data != null) {
var res = await UserHttp.spaceSettingMod(
{
for (var e in data.list1) ...{e.key: e.value},
for (var e in data.list2) ...{e.key: e.value},
for (var e in data.list3) ...{e.key: e.value},
},
);
if (!res['status']) {
SmartDialog.showToast(res['msg']);
}
}
}
}
}

View File

@@ -0,0 +1,143 @@
import 'dart:math';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/space_setting/privacy.dart';
import 'package:PiliPlus/pages/space_setting/controller.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class SpaceSettingPage extends StatefulWidget {
const SpaceSettingPage({super.key});
@override
State<SpaceSettingPage> createState() => _SpaceSettingPageState();
}
class _SpaceSettingPageState extends State<SpaceSettingPage> {
final _controller = Get.put(SpaceSettingController());
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('空间设置'),
),
body: Obx(() => _buildBody(theme, _controller.loadingState.value)),
);
}
@override
void dispose() {
_controller.onMod();
super.dispose();
}
Widget _buildBody(ThemeData theme, LoadingState<Privacy?> loadingState) {
return switch (loadingState) {
Loading() => const SizedBox.shrink(),
Success<Privacy?>(:var response) => response == null
? scrollErrorWidget(onReload: _controller.onReload)
: Builder(
builder: (context) {
final padding = MediaQuery.paddingOf(context);
final divider = Divider(
height: 1,
indent: max(16, padding.left),
color: theme.colorScheme.outline.withValues(alpha: 0.1),
);
final dividerL = SliverToBoxAdapter(
child: Divider(
height: 12,
thickness: 12,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
);
return CustomScrollView(
slivers: [
dividerL,
SliverList.separated(
itemCount: response.list1.length,
itemBuilder: (context, index) {
return _item(response.list1[index]);
},
separatorBuilder: (context, index) => divider,
),
dividerL,
SliverList.separated(
itemCount: response.list2.length,
itemBuilder: (context, index) {
return _item(response.list2[index]);
},
separatorBuilder: (context, index) => divider,
),
dividerL,
SliverList.separated(
itemCount: response.list3.length,
itemBuilder: (context, index) {
return _item(response.list3[index]);
},
separatorBuilder: (context, index) => divider,
),
dividerL,
SliverToBoxAdapter(
child: SizedBox(
height: padding.bottom + 80,
),
),
],
);
},
),
Error(:var errMsg) => scrollErrorWidget(
errMsg: errMsg,
onReload: _controller.onReload,
),
};
}
Widget _item(SpaceSettingModel item) {
return Builder(
builder: (context) {
void onChanged([bool? value]) {
_controller.hasMod ??= true;
value ??= !item.boolVal;
item.value = item.isReverse
? value
? 0
: 1
: value
? 1
: 0;
(context as Element).markNeedsBuild();
}
return ListTile(
dense: true,
onTap: onChanged,
title: Text(
item.name,
style: const TextStyle(fontSize: 14),
),
trailing: Transform.scale(
alignment: Alignment.centerRight,
scale: 0.8,
child: Switch(
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
(Set<WidgetState> states) {
if (states.isNotEmpty && states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null;
}),
value: item.boolVal,
onChanged: onChanged,
),
),
);
},
);
}
}

View File

@@ -49,6 +49,7 @@ import 'package:PiliPlus/pages/setting/style_setting.dart';
import 'package:PiliPlus/pages/setting/video_setting.dart';
import 'package:PiliPlus/pages/setting/view.dart';
import 'package:PiliPlus/pages/settings_search/view.dart';
import 'package:PiliPlus/pages/space_setting/view.dart';
import 'package:PiliPlus/pages/sponsor_block/view.dart';
import 'package:PiliPlus/pages/subscription/view.dart';
import 'package:PiliPlus/pages/subscription_detail/view.dart';
@@ -175,6 +176,7 @@ class Routes {
CustomGetPage(name: '/articleList', page: () => const ArticleListPage()),
CustomGetPage(name: '/barSetting', page: () => const BarSetPage()),
CustomGetPage(name: '/upowerRank', page: () => const UpowerRankPage()),
CustomGetPage(name: '/spaceSetting', page: () => const SpaceSettingPage()),
];
}