feat: logout (#497)

* feat: logout

* update api type
This commit is contained in:
My-Responsitories
2025-03-23 13:46:26 +08:00
committed by GitHub
parent 7c3e3cb1f8
commit d6587cf3b6
11 changed files with 139 additions and 129 deletions

View File

@@ -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();
}

View File

@@ -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('登录成功');

View File

@@ -35,7 +35,7 @@ class MainController extends GetxController {
late int homeIndex = -1;
late DynamicBadgeMode msgBadgeMode = GStorage.msgBadgeMode;
late List<MsgUnReadType> msgUnReadTypes = GStorage.msgUnReadTypeV2;
late Set<MsgUnReadType> 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(),

View File

@@ -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<SettingPage> {
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<SettingPage> {
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<SettingPage> {
);
}
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<void> _logoutDialog(BuildContext context) async {
final result = await showDialog<Set<LoginAccount>>(
context: context,
builder: (context) {
return MultiSelectDialog<LoginAccount>(
title: '选择要登出的账号uid',
initValues: Iterable.empty(),
values: {for (var i in Accounts.account.values) i: i.mid.toString()},
);
},
);
if (!context.mounted || result.isNullOrEmpty) return;
Future<void> 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),

View File

@@ -344,15 +344,13 @@ List<SettingsModel> get styleSettings => [
SettingsModel(
settingsType: SettingsType.normal,
onTap: (setState) async {
List<MsgUnReadType>? result = await showDialog(
final result = await showDialog<Set<MsgUnReadType>>(
context: Get.context!,
builder: (context) {
return MultiSelectDialog<MsgUnReadType>(
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},
);
},
);

View File

@@ -2,16 +2,15 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MultiSelectDialog<T> extends StatefulWidget {
final List<T> initValues;
final Iterable<T> initValues;
final String title;
final List<dynamic> values;
final Map<T, String> 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<MultiSelectDialog<T>> createState() => _MultiSelectDialogState<T>();
@@ -36,30 +35,24 @@ class _MultiSelectDialogState<T> extends State<MultiSelectDialog<T>> {
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<T> extends State<MultiSelectDialog<T>> {
),
),
TextButton(
onPressed: () => Get.back(result: _tempValues.toList()),
onPressed: () => Get.back(result: _tempValues),
child: const Text('确定'),
),
],

View File

@@ -16,7 +16,7 @@ abstract class Account {
bool activited = false;
Future<AnonymousAccount> logout();
Future<void> delete();
Future<void> onChange();
Map<String, dynamic>? toJson();
@@ -55,10 +55,7 @@ class LoginAccount implements Account {
bool activited = false;
@override
Future<AnonymousAccount> logout() async {
await Future.wait([cookieJar.deleteAll(), _box.delete(_midStr)]);
return AnonymousAccount();
}
Future<void> delete() => _box.delete(_midStr);
@override
Future<void> onChange() => _box.put(_midStr, this);
@@ -121,10 +118,9 @@ class AnonymousAccount implements Account {
bool activited = false;
@override
Future<AnonymousAccount> logout() async {
Future<void> delete() async {
await cookieJar.deleteAll();
activited = false;
return this;
}
@override

View File

@@ -19,23 +19,22 @@ final _setCookieReg = RegExp('(?<=)(,)(?=[^;]+?=)');
class AccountManager extends Interceptor {
static final Map<AccountType, Set<String>> 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}
};

View File

@@ -21,9 +21,11 @@ extension ScrollControllerExt on ScrollController {
}
}
extension ListExt<T> on List<T>? {
extension IterableExt<T> on Iterable<T>? {
bool get isNullOrEmpty => this == null || this!.isEmpty;
}
extension ListExt<T> on List<T>? {
T? getOrNull(int index) {
if (isNullOrEmpty) {
return null;

View File

@@ -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);

View File

@@ -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<void> close() async {
@@ -872,6 +871,16 @@ class Accounts {
account.close();
}
static Future<void> deleteAll(Set<Account> 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<void> 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;