diff --git a/lib/http/api.dart b/lib/http/api.dart index 34d51ee3..1614f70c 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -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'; } diff --git a/lib/http/user.dart b/lib/http/user.dart index 861df6ab..ff801c25 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -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> 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']}; + } + } } diff --git a/lib/models_new/space_setting/data.dart b/lib/models_new/space_setting/data.dart new file mode 100644 index 00000000..7dd0a461 --- /dev/null +++ b/lib/models_new/space_setting/data.dart @@ -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 json) => + SpaceSettingData( + privacy: json['privacy'] == null + ? null + : Privacy.fromJson(json['privacy'] as Map), + showNftSwitch: json['show_nft_switch'] as bool?, + exclusiveUrl: json['exclusive_url'] as String?, + ); +} diff --git a/lib/models_new/space_setting/privacy.dart b/lib/models_new/space_setting/privacy.dart new file mode 100644 index 00000000..262bc4bc --- /dev/null +++ b/lib/models_new/space_setting/privacy.dart @@ -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 list1; + List list2; + List list3; + + Privacy({ + required this.list1, + required this.list2, + required this.list3, + }); + + factory Privacy.fromJson(Map 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'], + ), + ], + ); +} diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index c30067b5..4ce348d0 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -135,41 +135,53 @@ class _MemberPageState extends State { ], ), ), - 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), diff --git a/lib/pages/space_setting/controller.dart b/lib/pages/space_setting/controller.dart new file mode 100644 index 00000000..f55444a6 --- /dev/null +++ b/lib/pages/space_setting/controller.dart @@ -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 { + @override + void onInit() { + super.onInit(); + queryData(); + } + + bool? hasMod; + + @override + bool customHandleResponse( + bool isRefresh, Success response) { + loadingState.value = Success(response.response.privacy); + return true; + } + + @override + Future> customGetData() => + UserHttp.spaceSetting(); + + Future 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']); + } + } + } + } +} diff --git a/lib/pages/space_setting/view.dart b/lib/pages/space_setting/view.dart new file mode 100644 index 00000000..b74d9ef4 --- /dev/null +++ b/lib/pages/space_setting/view.dart @@ -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 createState() => _SpaceSettingPageState(); +} + +class _SpaceSettingPageState extends State { + 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 loadingState) { + return switch (loadingState) { + Loading() => const SizedBox.shrink(), + Success(: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( + (Set states) { + if (states.isNotEmpty && states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: item.boolVal, + onChanged: onChanged, + ), + ), + ); + }, + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index d86f37cf..d0997858 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -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()), ]; }