mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: im settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -53,6 +53,11 @@ class GrpcUrl {
|
|||||||
static const pinSession = '$im2/PinSession';
|
static const pinSession = '$im2/PinSession';
|
||||||
static const unpinSession = '$im2/UnpinSession';
|
static const unpinSession = '$im2/UnpinSession';
|
||||||
static const deleteSessionList = '$im2/DeleteSessionList';
|
static const deleteSessionList = '$im2/DeleteSessionList';
|
||||||
|
static const getImSettings = '$im2/GetImSettings';
|
||||||
|
static const setImSettings = '$im2/SetImSettings';
|
||||||
|
static const keywordBlockingList = '$im2/KeywordBlockingList';
|
||||||
|
static const keywordBlockingAdd = '$im2/KeywordBlockingAdd';
|
||||||
|
static const keywordBlockingDelete = '$im2/KeywordBlockingDelete';
|
||||||
}
|
}
|
||||||
|
|
||||||
class GrpcRepo {
|
class GrpcRepo {
|
||||||
|
|||||||
@@ -143,4 +143,60 @@ class ImGrpc {
|
|||||||
DeleteSessionListReply.fromBuffer,
|
DeleteSessionListReply.fromBuffer,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<LoadingState<GetImSettingsReply>> getImSettings(
|
||||||
|
{IMSettingType? type}) async {
|
||||||
|
var res = await GrpcRepo.request(
|
||||||
|
GrpcUrl.getImSettings,
|
||||||
|
GetImSettingsReq(
|
||||||
|
type: type,
|
||||||
|
),
|
||||||
|
GetImSettingsReply.fromBuffer,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
return LoadingState.success(res['data']);
|
||||||
|
} else {
|
||||||
|
return LoadingState.error(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future setImSettings({PbMap<int, Setting>? settings}) {
|
||||||
|
return GrpcRepo.request(
|
||||||
|
GrpcUrl.setImSettings,
|
||||||
|
SetImSettingsReq(
|
||||||
|
settings: settings,
|
||||||
|
),
|
||||||
|
SetImSettingsReply.fromBuffer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<LoadingState<KeywordBlockingListReply>>
|
||||||
|
keywordBlockingList() async {
|
||||||
|
var res = await GrpcRepo.request(
|
||||||
|
GrpcUrl.keywordBlockingList,
|
||||||
|
KeywordBlockingListReq(),
|
||||||
|
KeywordBlockingListReply.fromBuffer,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
return LoadingState.success(res['data']);
|
||||||
|
} else {
|
||||||
|
return LoadingState.error(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future keywordBlockingAdd(String keyword) {
|
||||||
|
return GrpcRepo.request(
|
||||||
|
GrpcUrl.keywordBlockingAdd,
|
||||||
|
KeywordBlockingAddReq(keyword: keyword),
|
||||||
|
KeywordBlockingAddReply.fromBuffer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future keywordBlockingDelete(String keyword) {
|
||||||
|
return GrpcRepo.request(
|
||||||
|
GrpcUrl.keywordBlockingDelete,
|
||||||
|
KeywordBlockingDeleteReq(keyword: keyword),
|
||||||
|
KeywordBlockingDeleteReply.fromBuffer,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class ContactPage extends StatefulWidget {
|
class ContactPage extends StatefulWidget {
|
||||||
const ContactPage({super.key});
|
const ContactPage({super.key, this.isFromSelct = true});
|
||||||
|
|
||||||
|
final bool isFromSelct;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ContactPage> createState() => _ContactPageState();
|
State<ContactPage> createState() => _ContactPageState();
|
||||||
@@ -45,7 +47,10 @@ class _ContactPageState extends State<ContactPage>
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
UserModel? userModel = await Get.dialog(
|
UserModel? userModel = await Get.dialog(
|
||||||
FollowSearchPage(mid: mid),
|
FollowSearchPage(
|
||||||
|
mid: mid,
|
||||||
|
isFromSelct: widget.isFromSelct,
|
||||||
|
),
|
||||||
useSafeArea: false,
|
useSafeArea: false,
|
||||||
transitionDuration: const Duration(milliseconds: 120),
|
transitionDuration: const Duration(milliseconds: 120),
|
||||||
);
|
);
|
||||||
@@ -61,8 +66,14 @@ class _ContactPageState extends State<ContactPage>
|
|||||||
body: tabBarView(
|
body: tabBarView(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
children: [
|
children: [
|
||||||
FollowChildPage(mid: mid, onSelect: onSelect),
|
FollowChildPage(
|
||||||
FansPage(mid: mid, onSelect: onSelect),
|
mid: mid,
|
||||||
|
onSelect: widget.isFromSelct ? onSelect : null,
|
||||||
|
),
|
||||||
|
FansPage(
|
||||||
|
mid: mid,
|
||||||
|
onSelect: widget.isFromSelct ? onSelect : null,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class _FansPageState extends State<FansPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: widget.onSelect != null
|
appBar: widget.mid != null
|
||||||
? null
|
? null
|
||||||
: AppBar(title: Text(isOwner ? '我的粉丝' : '$name的粉丝')),
|
: AppBar(title: Text(isOwner ? '我的粉丝' : '$name的粉丝')),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class FollowSearchPage extends CommonSearchPage {
|
class FollowSearchPage extends CommonSearchPage {
|
||||||
const FollowSearchPage({super.key, this.mid});
|
const FollowSearchPage({
|
||||||
|
super.key,
|
||||||
|
this.mid,
|
||||||
|
this.isFromSelct,
|
||||||
|
});
|
||||||
|
|
||||||
final int? mid;
|
final int? mid;
|
||||||
|
final bool? isFromSelct;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FollowSearchPage> createState() => _FollowSearchPageState();
|
State<FollowSearchPage> createState() => _FollowSearchPageState();
|
||||||
@@ -37,7 +42,7 @@ class _FollowSearchPageState extends CommonSearchPageState<FollowSearchPage,
|
|||||||
}
|
}
|
||||||
return FollowItem(
|
return FollowItem(
|
||||||
item: list[index],
|
item: list[index],
|
||||||
onSelect: widget.mid != null
|
onSelect: widget.mid != null && widget.isFromSelct != false
|
||||||
? (userModel) {
|
? (userModel) {
|
||||||
Get.back(result: userModel);
|
Get.back(result: userModel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||||
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart'
|
||||||
|
show IMSettingType;
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/msg/msgfeed_at_me.dart';
|
import 'package:PiliPlus/models/msg/msgfeed_at_me.dart';
|
||||||
import 'package:PiliPlus/pages/msg_feed_top/at_me/controller.dart';
|
import 'package:PiliPlus/pages/msg_feed_top/at_me/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/whisper_settings/view.dart';
|
||||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -27,6 +30,18 @@ class _AtMePageState extends State<AtMePage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('@我的'),
|
title: const Text('@我的'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.to(
|
||||||
|
const WhisperSettingsPage(
|
||||||
|
imSettingType: IMSettingType.SETTING_TYPE_OLD_AT_ME),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(size: 22, Icons.settings),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: refreshIndicator(
|
body: refreshIndicator(
|
||||||
onRefresh: _atMeController.onRefresh,
|
onRefresh: _atMeController.onRefresh,
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||||
import 'package:PiliPlus/common/widgets/pair.dart';
|
import 'package:PiliPlus/common/widgets/pair.dart';
|
||||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||||
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart'
|
||||||
|
show IMSettingType;
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/msg/msgfeed_like_me.dart';
|
import 'package:PiliPlus/models/msg/msgfeed_like_me.dart';
|
||||||
import 'package:PiliPlus/pages/msg_feed_top/like_me/controller.dart';
|
import 'package:PiliPlus/pages/msg_feed_top/like_me/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/whisper_settings/view.dart';
|
||||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -27,6 +30,18 @@ class _LikeMePageState extends State<LikeMePage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('收到的赞'),
|
title: const Text('收到的赞'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.to(
|
||||||
|
const WhisperSettingsPage(
|
||||||
|
imSettingType: IMSettingType.SETTING_TYPE_OLD_RECEIVE_LIKE),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(size: 22, Icons.settings),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: refreshIndicator(
|
body: refreshIndicator(
|
||||||
onRefresh: _likeMeController.onRefresh,
|
onRefresh: _likeMeController.onRefresh,
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||||
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart'
|
||||||
|
show IMSettingType;
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart';
|
import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart';
|
||||||
import 'package:PiliPlus/pages/msg_feed_top/reply_me/controller.dart';
|
import 'package:PiliPlus/pages/msg_feed_top/reply_me/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/whisper_settings/view.dart';
|
||||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -25,7 +28,21 @@ class _ReplyMePageState extends State<ReplyMePage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('回复我的')),
|
appBar: AppBar(
|
||||||
|
title: const Text('回复我的'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.to(
|
||||||
|
const WhisperSettingsPage(
|
||||||
|
imSettingType: IMSettingType.SETTING_TYPE_OLD_REPLY_ME),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(size: 22, Icons.settings),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: refreshIndicator(
|
body: refreshIndicator(
|
||||||
onRefresh: _replyMeController.onRefresh,
|
onRefresh: _replyMeController.onRefresh,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
|||||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||||
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart';
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart';
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/pages/contact/view.dart';
|
||||||
import 'package:PiliPlus/pages/whisper/controller.dart';
|
import 'package:PiliPlus/pages/whisper/controller.dart';
|
||||||
import 'package:PiliPlus/pages/whisper/widgets/item.dart';
|
import 'package:PiliPlus/pages/whisper/widgets/item.dart';
|
||||||
|
import 'package:PiliPlus/pages/whisper_settings/view.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';
|
||||||
@@ -41,7 +43,64 @@ class _WhisperPageState extends State<WhisperPage> {
|
|||||||
Icons.cleaning_services,
|
Icons.cleaning_services,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
PopupMenuButton(
|
||||||
|
itemBuilder: (context) {
|
||||||
|
return [
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () {
|
||||||
|
Get.to(const WhisperSettingsPage(
|
||||||
|
imSettingType: IMSettingType.SETTING_TYPE_NEED_ALL,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(size: 19, Icons.settings),
|
||||||
|
Text(' 消息设置'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(
|
||||||
|
'/whisperDetail',
|
||||||
|
parameters: {
|
||||||
|
'talkerId': '844424930131966',
|
||||||
|
'name': 'UP主小助手',
|
||||||
|
'face':
|
||||||
|
'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(size: 18, Icons.live_tv),
|
||||||
|
Text(' UP主小助手'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// PopupMenuItem(
|
||||||
|
// onTap: () {},
|
||||||
|
// child: const Row(
|
||||||
|
// children: [
|
||||||
|
// Icon(size: 19, Icons.notifications_none),
|
||||||
|
// Text(' 应援团消息助手'),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () {
|
||||||
|
Get.to(const ContactPage(isFromSelct: false));
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(size: 19, Icons.account_box_outlined),
|
||||||
|
Text(' 通讯录'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: refreshIndicator(
|
body: refreshIndicator(
|
||||||
|
|||||||
58
lib/pages/whisper_block/controller.dart
Normal file
58
lib/pages/whisper_block/controller.dart
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart';
|
||||||
|
import 'package:PiliPlus/grpc/im.dart';
|
||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class WhisperBlockController extends CommonListController<
|
||||||
|
KeywordBlockingListReply, KeywordBlockingItem> {
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
queryData();
|
||||||
|
}
|
||||||
|
|
||||||
|
RxInt count = 0.obs;
|
||||||
|
int? listLimit;
|
||||||
|
int? charLimit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<KeywordBlockingItem>? getDataList(KeywordBlockingListReply response) {
|
||||||
|
count.value = response.items.length;
|
||||||
|
listLimit = response.listLimit;
|
||||||
|
charLimit = response.charLimit;
|
||||||
|
return response.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LoadingState<KeywordBlockingListReply>> customGetData() =>
|
||||||
|
ImGrpc.keywordBlockingList();
|
||||||
|
|
||||||
|
Future<void> onAdd(String keyword) async {
|
||||||
|
var res = await ImGrpc.keywordBlockingAdd(keyword);
|
||||||
|
if (res['status']) {
|
||||||
|
Get.back();
|
||||||
|
loadingState
|
||||||
|
..value.data!.add(KeywordBlockingItem(keyword: keyword))
|
||||||
|
..refresh();
|
||||||
|
count.value += 1;
|
||||||
|
SmartDialog.showToast('添加成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onRemove(KeywordBlockingItem item) async {
|
||||||
|
var res = await ImGrpc.keywordBlockingDelete(item.keyword);
|
||||||
|
if (res['status']) {
|
||||||
|
loadingState
|
||||||
|
..value.data!.remove(item)
|
||||||
|
..refresh();
|
||||||
|
count.value -= 1;
|
||||||
|
SmartDialog.showToast('删除成功');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
219
lib/pages/whisper_block/view.dart
Normal file
219
lib/pages/whisper_block/view.dart
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
|
||||||
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
|
||||||
|
show KeywordBlockingItem;
|
||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||||
|
import 'package:PiliPlus/pages/whisper_block/controller.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class WhisperBlockPage extends StatefulWidget {
|
||||||
|
const WhisperBlockPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WhisperBlockPage> createState() => _WhisperBlockPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WhisperBlockPageState extends State<WhisperBlockPage> {
|
||||||
|
final _controller = Get.put(WhisperBlockController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
appBar: AppBar(title: const Text('消息屏蔽词')),
|
||||||
|
body: Obx(() => _buildBody(theme, _controller.loadingState.value)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody(
|
||||||
|
ThemeData theme, LoadingState<List<KeywordBlockingItem>?> loadingState) {
|
||||||
|
return switch (loadingState) {
|
||||||
|
Loading() => loadingWidget,
|
||||||
|
Success() => loadingState.response?.isNotEmpty == true
|
||||||
|
? Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'点击屏蔽词即可删除',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_controller.listLimit != null)
|
||||||
|
Obx(
|
||||||
|
() => Text(
|
||||||
|
'${_controller.count.value}/${_controller.listLimit}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 12,
|
||||||
|
runSpacing: 12,
|
||||||
|
children: loadingState.response!
|
||||||
|
.map((e) => SearchText(
|
||||||
|
text: e.keyword,
|
||||||
|
onTap: (keyword) {
|
||||||
|
showConfirmDialog(
|
||||||
|
context: context,
|
||||||
|
title: '删除屏蔽词?',
|
||||||
|
content: '该屏蔽词将不再生效',
|
||||||
|
onConfirm: () {
|
||||||
|
_controller.onRemove(e);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 25,
|
||||||
|
right: 25,
|
||||||
|
bottom: MediaQuery.paddingOf(context).bottom + 10,
|
||||||
|
),
|
||||||
|
child: FilledButton.tonal(
|
||||||
|
onPressed: _onAdd,
|
||||||
|
child: const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [Icon(Icons.add, size: 22), Text('添加消息屏蔽词')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: SizedBox.expand(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
SvgPicture.asset("assets/images/error.svg", height: 156),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
const Text(
|
||||||
|
'还未添加屏蔽词',
|
||||||
|
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
const Text('添加后,将不再接受包含屏蔽词的消息'),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
FilledButton.tonal(
|
||||||
|
onPressed: _onAdd,
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
visualDensity: VisualDensity.compact),
|
||||||
|
child: const Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.add, size: 22),
|
||||||
|
Text('添加'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(flex: 2),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Error() => scrollErrorWidget(
|
||||||
|
errMsg: loadingState.errMsg,
|
||||||
|
onReload: _controller.onReload,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onAdd() {
|
||||||
|
String keyword = '';
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
enableDrag: false,
|
||||||
|
useSafeArea: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12) +
|
||||||
|
EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.paddingOf(context).bottom +
|
||||||
|
MediaQuery.viewInsetsOf(context).bottom),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'添加消息屏蔽词',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: Get.back,
|
||||||
|
child: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextFormField(
|
||||||
|
autofocus: true,
|
||||||
|
maxLength: _controller.charLimit,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
hintText: '请输入',
|
||||||
|
hintStyle: const TextStyle(fontSize: 14),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: theme.colorScheme.onInverseSurface,
|
||||||
|
),
|
||||||
|
onChanged: (value) => keyword = value,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
FilledButton.tonal(
|
||||||
|
onPressed: () {
|
||||||
|
if (keyword.isNotEmpty) {
|
||||||
|
_controller.onAdd(keyword);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [Icon(Icons.add, size: 22), Text('添加消息屏蔽词')],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
lib/pages/whisper_settings/controller.dart
Normal file
45
lib/pages/whisper_settings/controller.dart
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
|
||||||
|
show GetImSettingsReply, IMSettingType, Setting;
|
||||||
|
import 'package:PiliPlus/grpc/im.dart';
|
||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/pages/common/common_data_controller.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart' show PbMap;
|
||||||
|
|
||||||
|
class WhisperSettingsController
|
||||||
|
extends CommonDataController<GetImSettingsReply, PbMap<int, Setting>> {
|
||||||
|
WhisperSettingsController({
|
||||||
|
required this.imSettingType,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IMSettingType imSettingType;
|
||||||
|
|
||||||
|
RxString title = ''.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
queryData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool customHandleResponse(
|
||||||
|
bool isRefresh, Success<GetImSettingsReply> response) {
|
||||||
|
title.value = response.response.pageTitle;
|
||||||
|
loadingState.value = LoadingState.success(response.response.settings);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LoadingState<GetImSettingsReply>> customGetData() =>
|
||||||
|
ImGrpc.getImSettings(type: imSettingType);
|
||||||
|
|
||||||
|
Future<bool> onSet(PbMap<int, Setting> settings) async {
|
||||||
|
var res = await ImGrpc.setImSettings(settings: settings);
|
||||||
|
if (!res['status']) {
|
||||||
|
SmartDialog.showToast('err: ${res['msg']}');
|
||||||
|
}
|
||||||
|
return res['status'];
|
||||||
|
}
|
||||||
|
}
|
||||||
186
lib/pages/whisper_settings/view.dart
Normal file
186
lib/pages/whisper_settings/view.dart
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
|
||||||
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
|
||||||
|
show IMSettingType, Setting;
|
||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/pages/whisper_block/view.dart';
|
||||||
|
import 'package:PiliPlus/pages/whisper_settings/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/whisper_settings/widgets/item.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart' show PbMap;
|
||||||
|
|
||||||
|
class WhisperSettingsPage extends StatefulWidget {
|
||||||
|
const WhisperSettingsPage({
|
||||||
|
super.key,
|
||||||
|
required this.imSettingType,
|
||||||
|
this.onUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
final IMSettingType imSettingType;
|
||||||
|
final ValueChanged<PbMap<int, Setting>>? onUpdate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WhisperSettingsPage> createState() => _WhisperSettingsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WhisperSettingsPageState extends State<WhisperSettingsPage> {
|
||||||
|
late final WhisperSettingsController _controller = Get.put(
|
||||||
|
WhisperSettingsController(imSettingType: widget.imSettingType),
|
||||||
|
tag: widget.imSettingType.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Obx(() => Text(_controller.title.value)),
|
||||||
|
),
|
||||||
|
body: Obx(() => _buildBody(theme, _controller.loadingState.value)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody(
|
||||||
|
ThemeData theme, LoadingState<PbMap<int, Setting>> loadingState) {
|
||||||
|
return switch (loadingState) {
|
||||||
|
Loading() => const SizedBox.shrink(),
|
||||||
|
Success<PbMap<int, Setting>>() => Builder(builder: (context) {
|
||||||
|
final keys = loadingState.response.keys.toList()..sort();
|
||||||
|
return ListView.separated(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.paddingOf(context).bottom + 80),
|
||||||
|
itemCount: keys.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final key = keys[index];
|
||||||
|
final item = loadingState.response[key]!;
|
||||||
|
return ImSettingsItem(
|
||||||
|
item: item,
|
||||||
|
onSet: () async {
|
||||||
|
PbMap<int, Setting> settings = PbMap<int, Setting>(
|
||||||
|
loadingState.response.keyFieldType,
|
||||||
|
loadingState.response.valueFieldType,
|
||||||
|
)..[key] = item;
|
||||||
|
final res = await _controller.onSet(settings);
|
||||||
|
if (res) {
|
||||||
|
widget.onUpdate?.call(settings);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
onRedirect: () {
|
||||||
|
if (item.redirect.settingPage.hasParentSettingType()) {
|
||||||
|
Get.to(
|
||||||
|
WhisperSettingsPage(
|
||||||
|
imSettingType:
|
||||||
|
item.redirect.settingPage.parentSettingType,
|
||||||
|
onUpdate: (value) {
|
||||||
|
_controller.loadingState
|
||||||
|
..value
|
||||||
|
.data[key]
|
||||||
|
?.redirect
|
||||||
|
.settingPage
|
||||||
|
.subSettings
|
||||||
|
.addAll(value)
|
||||||
|
..refresh();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
preventDuplicates: false,
|
||||||
|
);
|
||||||
|
} else if (item.redirect.hasWindowSelect()) {
|
||||||
|
String? selected;
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: item.redirect.windowSelect.item.map(
|
||||||
|
(e) {
|
||||||
|
if (e.selected) {
|
||||||
|
selected ??= e.text;
|
||||||
|
}
|
||||||
|
return ListTile(
|
||||||
|
dense: true,
|
||||||
|
onTap: () async {
|
||||||
|
if (!e.selected) {
|
||||||
|
Get.back();
|
||||||
|
for (var j
|
||||||
|
in item.redirect.windowSelect.item) {
|
||||||
|
j.selected = false;
|
||||||
|
}
|
||||||
|
item.redirect.selectedSummary = e.text;
|
||||||
|
e.selected = true;
|
||||||
|
_controller.loadingState.refresh();
|
||||||
|
PbMap<int, Setting> settings =
|
||||||
|
PbMap<int, Setting>(
|
||||||
|
loadingState.response.keyFieldType,
|
||||||
|
loadingState.response.valueFieldType,
|
||||||
|
)..[key] = item;
|
||||||
|
final res =
|
||||||
|
await _controller.onSet(settings);
|
||||||
|
if (!res) {
|
||||||
|
for (var j in item
|
||||||
|
.redirect.windowSelect.item) {
|
||||||
|
j.selected = j.text == selected;
|
||||||
|
}
|
||||||
|
item.redirect.selectedSummary =
|
||||||
|
selected!;
|
||||||
|
_controller.loadingState.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: Text(
|
||||||
|
e.text,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: e.selected
|
||||||
|
? theme.colorScheme.primary
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (item.redirect.otherPage.hasUrl()) {
|
||||||
|
if (item.redirect.title == '黑名单') {
|
||||||
|
Get.toNamed('/blackListPage');
|
||||||
|
} else if (item.redirect.otherPage.url.startsWith('http')) {
|
||||||
|
Get.toNamed('/webview',
|
||||||
|
parameters: {'url': item.redirect.otherPage.url});
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(item.redirect.otherPage.url);
|
||||||
|
}
|
||||||
|
} else if (item.redirect.settingPage.hasUrl()) {
|
||||||
|
if (item.redirect.title == '消息屏蔽词') {
|
||||||
|
Get.to(const WhisperBlockPage());
|
||||||
|
} else if (item.redirect.settingPage.url
|
||||||
|
.startsWith('http')) {
|
||||||
|
Get.toNamed('/webview',
|
||||||
|
parameters: {'url': item.redirect.settingPage.url});
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(item.redirect.settingPage.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
height: 1,
|
||||||
|
color: theme.colorScheme.outline.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Error() => scrollErrorWidget(
|
||||||
|
errMsg: loadingState.errMsg,
|
||||||
|
onReload: _controller.onReload,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
165
lib/pages/whisper_settings/widgets/item.dart
Normal file
165
lib/pages/whisper_settings/widgets/item.dart
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
|
||||||
|
show SelectItem, Setting, SettingSwitch;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ImSettingsItem extends StatelessWidget {
|
||||||
|
const ImSettingsItem({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
required this.onSet,
|
||||||
|
required this.onRedirect,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Setting item;
|
||||||
|
final Future<bool> Function() onSet;
|
||||||
|
final VoidCallback onRedirect;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
void rebuild() {
|
||||||
|
if (context.mounted) {
|
||||||
|
(context as Element).markNeedsBuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleStyle = TextStyle(fontSize: 14);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final outline = theme.colorScheme.outline;
|
||||||
|
final subtitleStyle = TextStyle(fontSize: 13, color: outline);
|
||||||
|
|
||||||
|
if (item.hasSwitch_1()) {
|
||||||
|
Future<void> onChanged() async {
|
||||||
|
item.switch_1.switchOn = !item.switch_1.switchOn;
|
||||||
|
rebuild();
|
||||||
|
if (!await onSet()) {
|
||||||
|
item.switch_1.switchOn = !item.switch_1.switchOn;
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
dense: true,
|
||||||
|
onTap: onChanged,
|
||||||
|
title: Text(
|
||||||
|
item.switch_1.title,
|
||||||
|
style: titleStyle,
|
||||||
|
),
|
||||||
|
subtitle: item.switch_1.hasSubtitle()
|
||||||
|
? Text(item.switch_1.subtitle, style: subtitleStyle)
|
||||||
|
: null,
|
||||||
|
trailing: Transform.scale(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
scale: 0.8,
|
||||||
|
child: Switch(
|
||||||
|
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((states) {
|
||||||
|
if (states.isNotEmpty && states.first == WidgetState.selected) {
|
||||||
|
return const Icon(Icons.done);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
value: item.switch_1.switchOn,
|
||||||
|
onChanged: (value) => onChanged(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.hasRedirect()) {
|
||||||
|
SelectItem? selected;
|
||||||
|
SettingSwitch? sw1tch;
|
||||||
|
if (item.redirect.settingPage.subSettings.isNotEmpty) {
|
||||||
|
for (var subItem in item.redirect.settingPage.subSettings.values) {
|
||||||
|
if (subItem.hasSelect()) {
|
||||||
|
for (var i in subItem.select.item) {
|
||||||
|
if (i.selected) {
|
||||||
|
selected = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (subItem.hasSwitch_1()) {
|
||||||
|
if (subItem.switch_1.switchOn) {
|
||||||
|
sw1tch = subItem.switch_1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ListTile(
|
||||||
|
dense: true,
|
||||||
|
onTap: onRedirect,
|
||||||
|
title: Text(
|
||||||
|
item.redirect.title,
|
||||||
|
style: titleStyle,
|
||||||
|
),
|
||||||
|
subtitle: item.redirect.hasSubtitle()
|
||||||
|
? Text(item.redirect.subtitle, style: subtitleStyle)
|
||||||
|
: null,
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (selected != null)
|
||||||
|
Text(
|
||||||
|
selected.text,
|
||||||
|
style: TextStyle(fontSize: 13, color: outline),
|
||||||
|
)
|
||||||
|
else if (sw1tch != null)
|
||||||
|
Text(
|
||||||
|
sw1tch.title,
|
||||||
|
style: TextStyle(fontSize: 13, color: outline),
|
||||||
|
)
|
||||||
|
else if (item.redirect.hasSelectedSummary())
|
||||||
|
Text(
|
||||||
|
item.redirect.selectedSummary,
|
||||||
|
style: TextStyle(fontSize: 13, color: outline),
|
||||||
|
),
|
||||||
|
Icon(color: outline, Icons.keyboard_arrow_right),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.hasSelect()) {
|
||||||
|
String? selected;
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: item.select.item.map((e) {
|
||||||
|
if (e.selected) {
|
||||||
|
selected ??= e.text;
|
||||||
|
}
|
||||||
|
return ListTile(
|
||||||
|
dense: true,
|
||||||
|
onTap: () async {
|
||||||
|
if (!e.selected) {
|
||||||
|
for (var i in item.select.item) {
|
||||||
|
i.selected = false;
|
||||||
|
}
|
||||||
|
e.selected = true;
|
||||||
|
rebuild();
|
||||||
|
|
||||||
|
if (await onSet()) {
|
||||||
|
selected = e.text;
|
||||||
|
} else {
|
||||||
|
for (var i in item.select.item) {
|
||||||
|
i.selected = i.text == selected;
|
||||||
|
}
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: Text(e.text, style: titleStyle),
|
||||||
|
trailing: e.selected
|
||||||
|
? Icon(
|
||||||
|
size: 20,
|
||||||
|
Icons.check,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user