From d6587cf3b67258ccd8cdc4bc6f60dc642f6aa65a Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Sun, 23 Mar 2025 13:46:26 +0800 Subject: [PATCH] feat: logout (#497) * feat: logout * update api type --- lib/pages/about/index.dart | 4 + lib/pages/login/controller.dart | 4 +- lib/pages/main/controller.dart | 4 +- lib/pages/setting/view.dart | 142 +++++++++--------- lib/pages/setting/widgets/model.dart | 6 +- .../setting/widgets/multi_select_dialog.dart | 59 ++++---- lib/utils/accounts/account.dart | 10 +- .../accounts/account_manager/account_mgr.dart | 10 +- lib/utils/extension.dart | 4 +- lib/utils/login.dart | 2 +- lib/utils/storage.dart | 23 ++- 11 files changed, 139 insertions(+), 129 deletions(-) diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index 90c54d48..105b761e 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -13,6 +13,7 @@ import 'package:PiliPlus/models/github/latest.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/utils.dart'; import '../../utils/cache_manage.dart'; +import '../mine/controller.dart'; class AboutPage extends StatefulWidget { const AboutPage({super.key, this.showAppBar}); @@ -302,6 +303,9 @@ Commit Hash: ${BuildConfig.commitHash}''', .putAll(res) .then((_) => Accounts.refresh()) .then((_) { + MineController.anonymity.value = + !Accounts.get(AccountType.heartbeat) + .isLogin; if (Accounts.main.isLogin) { return LoginUtils.onLoginMain(); } diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index a7daef06..abda135b 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -646,7 +646,9 @@ class LoginPageController extends GetxController LoginAccount(BiliCookieJar.fromList(cookieInfo), tokenInfo['access_token'], tokenInfo['refresh_token']) .onChange(), - AnonymousAccount().logout().then((i) => Request.buvidActive(i)) + AnonymousAccount() + .delete() + .then((_) => Request.buvidActive(AnonymousAccount())) ]); if (Accounts.main.isLogin) { SmartDialog.showToast('登录成功'); diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index df782010..f8dc6fc8 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -35,7 +35,7 @@ class MainController extends GetxController { late int homeIndex = -1; late DynamicBadgeMode msgBadgeMode = GStorage.msgBadgeMode; - late List msgUnReadTypes = GStorage.msgUnReadTypeV2; + late Set msgUnReadTypes = GStorage.msgUnReadTypeV2.toSet(); late final RxString msgUnReadCount = ''.obs; late int lastCheckUnreadAt = 0; @@ -83,7 +83,7 @@ class MainController extends GetxController { try { bool shouldCheckPM = msgUnReadTypes.contains(MsgUnReadType.pm); bool shouldCheckFeed = - ([...msgUnReadTypes]..remove(MsgUnReadType.pm)).isNotEmpty; + shouldCheckPM ? msgUnReadTypes.length > 1 : msgUnReadTypes.isNotEmpty; List res = await Future.wait([ if (shouldCheckPM) _queryPMUnread(), if (shouldCheckFeed) _queryMsgFeedUnread(), diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index bca1f8b8..7e9dff2a 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -7,13 +7,15 @@ import 'package:PiliPlus/pages/setting/privacy_setting.dart'; import 'package:PiliPlus/pages/setting/recommend_setting.dart'; import 'package:PiliPlus/pages/setting/style_setting.dart'; import 'package:PiliPlus/pages/setting/video_setting.dart'; +import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/extension.dart'; -import 'package:PiliPlus/utils/login.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'widgets/multi_select_dialog.dart'; + class _SettingsModel { final String name; final String title; @@ -37,7 +39,7 @@ class SettingPage extends StatefulWidget { class _SettingPageState extends State { late String _type = 'privacySetting'; - final RxBool _isLogin = Accounts.main.isLogin.obs; + final RxBool _noAccount = Accounts.accountMode.isEmpty.obs; TextStyle get _titleStyle => Theme.of(context).textTheme.titleMedium!; TextStyle get _subTitleStyle => Theme.of(context) .textTheme @@ -173,8 +175,15 @@ class _SettingPageState extends State { leading: const Icon(Icons.switch_account_outlined), title: const Text('设置账号模式'), ), - // TODO: 多账号登出 - _buildLoginItem, + Obx( + () => _noAccount.value + ? const SizedBox.shrink() + : ListTile( + leading: const Icon(Icons.logout_outlined), + onTap: () => _logoutDialog(context), + title: Text('退出登录', style: _titleStyle), + ), + ), ListTile( tileColor: _getTileColor(_items.last.name), onTap: () => _toPage(_items.last.name), @@ -186,70 +195,69 @@ class _SettingPageState extends State { ); } - Widget get _buildLoginItem => Obx( - () => _isLogin.value.not - ? const SizedBox.shrink() - : ListTile( - leading: const Icon(Icons.logout_outlined), - onTap: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('提示'), - content: const Text('确认要退出登录吗'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '点错了', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () { - Get.back(); - _isLogin.value = false; - LoginUtils.onLogoutMain(); - final account = Accounts.main; - Accounts.accountMode - .removeWhere((_, a) => a == account); - account.logout().then((_) => Accounts.refresh()); - }, - child: Text( - '仅登出', - style: TextStyle( - color: Theme.of(context).colorScheme.error, - ), - ), - ), - TextButton( - onPressed: () async { - SmartDialog.showLoading(); - final res = await LoginHttp.logout(Accounts.main); - if (res['status']) { - await Accounts.main.logout(); - await LoginUtils.onLogoutMain(); - _isLogin.value = false; - SmartDialog.dismiss(); - Get.back(); - } else { - SmartDialog.dismiss(); - SmartDialog.showToast(res['msg'].toString()); - } - }, - child: const Text('确认'), - ) - ], - ); - }, - ); - }, - title: Text('退出登录', style: _titleStyle), + Future _logoutDialog(BuildContext context) async { + final result = await showDialog>( + context: context, + builder: (context) { + return MultiSelectDialog( + title: '选择要登出的账号uid', + initValues: Iterable.empty(), + values: {for (var i in Accounts.account.values) i: i.mid.toString()}, + ); + }, + ); + if (!context.mounted || result.isNullOrEmpty) return; + Future logout() { + _noAccount.value = result!.length == Accounts.account.length; + return Accounts.deleteAll(result); + } + + await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: Text( + "确认要退出以下账号登录吗\n\n${result!.map((i) => i.mid.toString()).join('\n')}"), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '点错了', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), ), - ); + TextButton( + onPressed: () { + Get.back(); + logout(); + }, + child: Text( + '仅登出', + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ), + TextButton( + onPressed: () async { + SmartDialog.showLoading(); + final res = await LoginHttp.logout(Accounts.main); + if (res['status']) { + SmartDialog.dismiss(); + logout(); + Get.back(); + } else { + SmartDialog.dismiss(); + SmartDialog.showToast(res['msg'].toString()); + } + }, + child: const Text('确认'), + ) + ], + ); + }); + } Widget get _buildSearchItem => Padding( padding: const EdgeInsets.only(left: 16, right: 16, bottom: 5), diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index 97610bb9..8c063d5e 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -344,15 +344,13 @@ List get styleSettings => [ SettingsModel( settingsType: SettingsType.normal, onTap: (setState) async { - List? result = await showDialog( + final result = await showDialog>( context: Get.context!, builder: (context) { return MultiSelectDialog( title: '消息未读类型', initValues: GStorage.msgUnReadTypeV2, - values: MsgUnReadType.values.map((e) { - return {'title': e.title, 'value': e}; - }).toList(), + values: {for (var i in MsgUnReadType.values) i: i.title}, ); }, ); diff --git a/lib/pages/setting/widgets/multi_select_dialog.dart b/lib/pages/setting/widgets/multi_select_dialog.dart index 4a011a0e..0f487f07 100644 --- a/lib/pages/setting/widgets/multi_select_dialog.dart +++ b/lib/pages/setting/widgets/multi_select_dialog.dart @@ -2,16 +2,15 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; class MultiSelectDialog extends StatefulWidget { - final List initValues; + final Iterable initValues; final String title; - final List values; + final Map values; - const MultiSelectDialog({ - super.key, - required this.initValues, - required this.values, - required this.title, - }); + const MultiSelectDialog( + {super.key, + required this.initValues, + required this.values, + required this.title}); @override State> createState() => _MultiSelectDialogState(); @@ -36,30 +35,24 @@ class _MultiSelectDialogState extends State> { return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, - children: List.generate( - widget.values.length, - (index) { - bool isChecked = - _tempValues.contains(widget.values[index]['value']); - return CheckboxListTile( - dense: true, - value: isChecked, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - widget.values[index]['title'], - style: Theme.of(context).textTheme.titleMedium!, - ), - onChanged: (value) { - if (isChecked) { - _tempValues.remove(widget.values[index]['value']); - } else { - _tempValues.add(widget.values[index]['value']); - } - setState(() {}); - }, - ); - }, - ), + children: widget.values.entries.map((i) { + bool isChecked = _tempValues.contains(i.key); + return CheckboxListTile( + dense: true, + value: isChecked, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + i.value, + style: Theme.of(context).textTheme.titleMedium!, + ), + onChanged: (value) { + isChecked + ? _tempValues.remove(i.key) + : _tempValues.add(i.key); + setState(() {}); + }, + ); + }).toList(), ), ); }), @@ -75,7 +68,7 @@ class _MultiSelectDialogState extends State> { ), ), TextButton( - onPressed: () => Get.back(result: _tempValues.toList()), + onPressed: () => Get.back(result: _tempValues), child: const Text('确定'), ), ], diff --git a/lib/utils/accounts/account.dart b/lib/utils/accounts/account.dart index 5445c838..ff0e88e8 100644 --- a/lib/utils/accounts/account.dart +++ b/lib/utils/accounts/account.dart @@ -16,7 +16,7 @@ abstract class Account { bool activited = false; - Future logout(); + Future delete(); Future onChange(); Map? toJson(); @@ -55,10 +55,7 @@ class LoginAccount implements Account { bool activited = false; @override - Future logout() async { - await Future.wait([cookieJar.deleteAll(), _box.delete(_midStr)]); - return AnonymousAccount(); - } + Future delete() => _box.delete(_midStr); @override Future onChange() => _box.put(_midStr, this); @@ -121,10 +118,9 @@ class AnonymousAccount implements Account { bool activited = false; @override - Future logout() async { + Future delete() async { await cookieJar.deleteAll(); activited = false; - return this; } @override diff --git a/lib/utils/accounts/account_manager/account_mgr.dart b/lib/utils/accounts/account_manager/account_mgr.dart index b5733eeb..b0a2be1f 100644 --- a/lib/utils/accounts/account_manager/account_mgr.dart +++ b/lib/utils/accounts/account_manager/account_mgr.dart @@ -19,23 +19,22 @@ final _setCookieReg = RegExp('(?<=)(,)(?=[^;]+?=)'); class AccountManager extends Interceptor { static final Map> apiTypeSet = { AccountType.heartbeat: { - Api.videoUrl, Api.videoIntro, - Api.relatedList, Api.replyList, Api.replyReplyList, - Api.searchSuggest, - Api.searchByType, Api.heartBeat, Api.ab2c, Api.bangumiInfo, Api.liveRoomInfo, + Api.liveRoomInfoH5, Api.onlineTotal, Api.dynamicDetail, Api.aiConclusion, Api.getSeasonDetailApi, Api.liveRoomDmToken, Api.liveRoomDmPrefetch, + Api.searchByType, + Api.memberDynamicSearch }, AccountType.recommend: { Api.recommendListWeb, @@ -43,10 +42,11 @@ class AccountManager extends Interceptor { Api.feedDislike, Api.feedDislikeCancel, Api.hotList, + Api.relatedList, Api.hotSearchList, // 不同账号搜索结果可能不一样 Api.searchDefault, Api.searchSuggest, - Api.searchByType + Api.liveList, }, AccountType.video: {Api.videoUrl, Api.bangumiVideoUrl} }; diff --git a/lib/utils/extension.dart b/lib/utils/extension.dart index f9bca916..fc10f537 100644 --- a/lib/utils/extension.dart +++ b/lib/utils/extension.dart @@ -21,9 +21,11 @@ extension ScrollControllerExt on ScrollController { } } -extension ListExt on List? { +extension IterableExt on Iterable? { bool get isNullOrEmpty => this == null || this!.isEmpty; +} +extension ListExt on List? { T? getOrNull(int index) { if (isNullOrEmpty) { return null; diff --git a/lib/utils/login.dart b/lib/utils/login.dart index 9fd8b30f..27855f6b 100644 --- a/lib/utils/login.dart +++ b/lib/utils/login.dart @@ -105,7 +105,7 @@ class LoginUtils { } catch (_) {} } else { // 获取用户信息失败 - await Accounts.set(AccountType.main, await account.logout()); + await Accounts.deleteAll({account}); SmartDialog.showNotify( msg: '登录失败,请检查cookie是否正确,${result['message']}', notifyType: NotifyType.warning); diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 84553120..1dcab52a 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -862,9 +862,8 @@ class Accounts { for (var i in AccountType.values) { accountMode[i] = AnonymousAccount(); } - if (!AnonymousAccount().activited) { - Request.buvidActive(AnonymousAccount()); - } + await AnonymousAccount().delete(); + Request.buvidActive(AnonymousAccount()); } static Future close() async { @@ -872,6 +871,16 @@ class Accounts { account.close(); } + static Future deleteAll(Set accounts) async { + var isloginMain = Accounts.main.isLogin; + Accounts.accountMode + .updateAll((_, a) => accounts.contains(a) ? AnonymousAccount() : a); + await Future.wait(accounts.map((i) => i.delete())); + if (isloginMain && !Accounts.main.isLogin) { + await LoginUtils.onLogoutMain(); + } + } + static Future set(AccountType key, Account account) async { await (accountMode[key]?..type.remove(key))?.onChange(); accountMode[key] = account..type.add(key); @@ -879,11 +888,9 @@ class Accounts { if (!account.activited) await Request.buvidActive(account); switch (key) { case AccountType.main: - if (account.isLogin) { - await LoginUtils.onLoginMain(); - } else { - await LoginUtils.onLogoutMain(); - } + await (account.isLogin + ? LoginUtils.onLoginMain() + : LoginUtils.onLogoutMain()); break; case AccountType.heartbeat: MineController.anonymity.value = !account.isLogin;