mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-26 05:16:01 +08:00
feat: im settings
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
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