diff --git a/assets/locales/en_us.json b/assets/locales/en_us.json index f5f0711..9015c83 100644 --- a/assets/locales/en_us.json +++ b/assets/locales/en_us.json @@ -416,5 +416,11 @@ "holdToSeeDetail": "Long press / Mouse hover to see detail", "subscribe": "Subscribe", "subscribed": "Subscribed", - "unsubscribe": "Unsubscribe" + "unsubscribe": "Unsubscribe", + "preferences": "Preferences", + "notificationPreferences": "Notification preferences", + "notificationTopicPostFeedback": "Post feedbacks", + "notificationTopicPostSubscription": "Post subscriptions", + "preferencesApplied": "Preferences has been applied.", + "save": "Save" } diff --git a/assets/locales/zh_cn.json b/assets/locales/zh_cn.json index 5cb69fa..e5f8662 100644 --- a/assets/locales/zh_cn.json +++ b/assets/locales/zh_cn.json @@ -417,5 +417,11 @@ "holdToSeeDetail": "长按 / 鼠标悬浮来查看详情", "subscribe": "订阅", "subscribed": "已订阅", - "unsubscribe": "取消订阅" + "unsubscribe": "取消订阅", + "preferences": "偏好设置", + "notificationPreferences": "通知偏好设置", + "notificationTopicPostFeedback": "帖子反馈", + "notificationTopicPostSubscription": "订阅源", + "preferencesApplied": "偏好设置已应用", + "save": "保存" } diff --git a/lib/router.dart b/lib/router.dart index 15d8849..5d4de29 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -5,6 +5,7 @@ import 'package:solian/models/realm.dart'; import 'package:solian/screens/about.dart'; import 'package:solian/screens/account.dart'; import 'package:solian/screens/account/friend.dart'; +import 'package:solian/screens/account/preferences/notifications.dart'; import 'package:solian/screens/account/profile_edit.dart'; import 'package:solian/screens/account/profile_page.dart'; import 'package:solian/screens/auth/signin.dart'; @@ -245,6 +246,14 @@ abstract class AppRouter { child: const PersonalizeScreen(), ), ), + GoRoute( + path: '/account/preferences/notifications', + name: 'notificationPreferences', + builder: (context, state) => TitleShell( + state: state, + child: const NotificationPreferencesScreen(), + ), + ), GoRoute( path: '/account/view/:name', name: 'accountProfilePage', diff --git a/lib/screens/account.dart b/lib/screens/account.dart index 6788ce0..676b60b 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -131,6 +131,15 @@ class _AccountScreenState extends State { AppRouter.instance.pushNamed('settings'); }, ), + if (auth.isAuthorized.value) + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 34), + leading: const Icon(Icons.edit_notifications), + title: Text('notificationPreferences'.tr), + onTap: () { + AppRouter.instance.pushNamed('notificationPreferences'); + }, + ), const Divider(thickness: 0.3, height: 1) .paddingSymmetric(vertical: 4), ListTile( diff --git a/lib/screens/account/preferences/notifications.dart b/lib/screens/account/preferences/notifications.dart new file mode 100644 index 0000000..911ebca --- /dev/null +++ b/lib/screens/account/preferences/notifications.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:get/get.dart'; +import 'package:get/get_connect/http/src/exceptions/exceptions.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:solian/exceptions/request.dart'; +import 'package:solian/exts.dart'; +import 'package:solian/providers/auth.dart'; + +class NotificationPreferencesScreen extends StatefulWidget { + const NotificationPreferencesScreen({super.key}); + + @override + State createState() => + _NotificationPreferencesScreenState(); +} + +class _NotificationPreferencesScreenState + extends State { + bool _isBusy = true; + + Map _config = {}; + + final Map _topicMap = { + 'interactive.feedback': 'notificationTopicPostFeedback'.tr, + 'interactive.subscription': 'notificationTopicPostSubscription'.tr, + }; + + Future _getPreferences() async { + setState(() => _isBusy = true); + + final auth = Get.find(); + if (!auth.isAuthorized.value) throw UnauthorizedException(); + + final client = await auth.configureClient('id'); + final resp = await client.get('/preferences/notifications'); + if (resp.statusCode != 200 && resp.statusCode != 404) { + context.showErrorDialog(RequestException(resp)); + } + + if (resp.statusCode == 200) { + _config = resp.body['config'] + .map((k, v) => MapEntry(k, v as bool)) + .cast(); + } + + setState(() => _isBusy = false); + } + + Future _savePreferences() async { + setState(() => _isBusy = true); + + final auth = Get.find(); + if (!auth.isAuthorized.value) throw UnauthorizedException(); + + final client = await auth.configureClient('id'); + final resp = await client.put('/preferences/notifications', { + 'config': _config, + }); + if (resp.statusCode != 200) { + context.showErrorDialog(RequestException(resp)); + } + + context.showSnackbar('preferencesApplied'.tr); + + setState(() => _isBusy = false); + } + + @override + void initState() { + super.initState(); + _getPreferences(); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + if (_isBusy) const LinearProgressIndicator().animate().scaleX(), + ListTile( + tileColor: Theme.of(context).colorScheme.surfaceContainer, + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + leading: const Icon(Icons.save), + title: Text('save'.tr), + enabled: !_isBusy, + onTap: () { + _savePreferences(); + }, + ), + Expanded( + child: ListView.builder( + itemCount: _topicMap.length, + itemBuilder: (context, index) { + final element = _topicMap.entries.elementAt(index); + return CheckboxListTile( + title: Text(element.value), + subtitle: Text( + element.key, + style: GoogleFonts.robotoMono(fontSize: 12), + ), + value: _config[element.key] ?? true, + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + onChanged: (value) { + setState(() { + _config[element.key] = value ?? false; + }); + }, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/account/profile_page.dart b/lib/screens/account/profile_page.dart index fca5203..4381168 100644 --- a/lib/screens/account/profile_page.dart +++ b/lib/screens/account/profile_page.dart @@ -400,7 +400,7 @@ class _AccountProfilePageState extends State { child: AttachmentListEntry( item: item, isDense: true, - parentId: 'album', + parentId: 'album-$index', showMature: _showMature, onReveal: (value) { setState(() => _showMature = value);