mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-30 23:58:13 +08:00
feat: login devices
Closes #1140 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -948,4 +948,7 @@ class Api {
|
|||||||
|
|
||||||
static const String liveLikeReport =
|
static const String liveLikeReport =
|
||||||
'${HttpString.liveBaseUrl}/xlive/app-ucenter/v1/like_info_v3/like/likeReportV3';
|
'${HttpString.liveBaseUrl}/xlive/app-ucenter/v1/like_info_v3/like/likeReportV3';
|
||||||
|
|
||||||
|
static const String loginDevices =
|
||||||
|
'${HttpString.passBaseUrl}/x/safecenter/user_login_devices';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import 'dart:convert';
|
|||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
import 'package:PiliPlus/http/api.dart';
|
import 'package:PiliPlus/http/api.dart';
|
||||||
import 'package:PiliPlus/http/init.dart';
|
import 'package:PiliPlus/http/init.dart';
|
||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/login/model.dart';
|
import 'package:PiliPlus/models/login/model.dart';
|
||||||
|
import 'package:PiliPlus/models_new/login_devices/data.dart';
|
||||||
|
import 'package:PiliPlus/utils/accounts.dart';
|
||||||
import 'package:PiliPlus/utils/accounts/account.dart';
|
import 'package:PiliPlus/utils/accounts/account.dart';
|
||||||
import 'package:PiliPlus/utils/app_sign.dart';
|
import 'package:PiliPlus/utils/app_sign.dart';
|
||||||
import 'package:PiliPlus/utils/login_utils.dart';
|
import 'package:PiliPlus/utils/login_utils.dart';
|
||||||
@@ -499,4 +502,31 @@ class LoginHttp {
|
|||||||
);
|
);
|
||||||
return {'status': res.data['code'] == 0, 'msg': res.data['message']};
|
return {'status': res.data['code'] == 0, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<LoadingState<LoginDevicesData>> loginDevices() async {
|
||||||
|
final account = Accounts.main;
|
||||||
|
final buvid = LoginUtils.buvid;
|
||||||
|
final params = {
|
||||||
|
'local_id': buvid,
|
||||||
|
'buvid': buvid,
|
||||||
|
'device_name': 'android',
|
||||||
|
'device_platform': 'android',
|
||||||
|
'csrf': account.csrf,
|
||||||
|
'mobi_app': 'android_hd',
|
||||||
|
'platform': 'android',
|
||||||
|
'access_key': account.accessKey,
|
||||||
|
'statistics': Constants.statistics,
|
||||||
|
'ts': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
};
|
||||||
|
AppSign.appSign(params);
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.loginDevices,
|
||||||
|
queryParameters: params,
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return Success(LoginDevicesData.fromJson(res.data['data']));
|
||||||
|
} else {
|
||||||
|
return Error(res.data['message']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
lib/models_new/login_devices/data.dart
Normal file
14
lib/models_new/login_devices/data.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:PiliPlus/models_new/login_devices/device.dart';
|
||||||
|
|
||||||
|
class LoginDevicesData {
|
||||||
|
List<LoginDevice>? devices;
|
||||||
|
|
||||||
|
LoginDevicesData({this.devices});
|
||||||
|
|
||||||
|
factory LoginDevicesData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
LoginDevicesData(
|
||||||
|
devices: (json['devices'] as List<dynamic>?)
|
||||||
|
?.map((e) => LoginDevice.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
32
lib/models_new/login_devices/device.dart
Normal file
32
lib/models_new/login_devices/device.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
class LoginDevice {
|
||||||
|
int? mid;
|
||||||
|
String? localId;
|
||||||
|
String? deviceName;
|
||||||
|
String? devicePlatform;
|
||||||
|
bool? isCurrentDevice;
|
||||||
|
String? latestLoginAt;
|
||||||
|
String? source;
|
||||||
|
int? origin;
|
||||||
|
|
||||||
|
LoginDevice({
|
||||||
|
this.mid,
|
||||||
|
this.localId,
|
||||||
|
this.deviceName,
|
||||||
|
this.devicePlatform,
|
||||||
|
this.isCurrentDevice,
|
||||||
|
this.latestLoginAt,
|
||||||
|
this.source,
|
||||||
|
this.origin,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory LoginDevice.fromJson(Map<String, dynamic> json) => LoginDevice(
|
||||||
|
mid: json['mid'] as int?,
|
||||||
|
localId: json['local_id'] as String?,
|
||||||
|
deviceName: json['device_name'] as String?,
|
||||||
|
devicePlatform: json['device_platform'] as String?,
|
||||||
|
isCurrentDevice: json['is_current_device'] as bool?,
|
||||||
|
latestLoginAt: json['latest_login_at'] as String?,
|
||||||
|
source: json['source'] as String?,
|
||||||
|
origin: json['origin'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
23
lib/pages/login_devices/controller.dart
Normal file
23
lib/pages/login_devices/controller.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/http/login.dart';
|
||||||
|
import 'package:PiliPlus/models_new/login_devices/data.dart';
|
||||||
|
import 'package:PiliPlus/models_new/login_devices/device.dart';
|
||||||
|
import 'package:PiliPlus/pages/common/common_list_controller.dart';
|
||||||
|
|
||||||
|
class LoginDevicesController
|
||||||
|
extends CommonListController<LoginDevicesData, LoginDevice> {
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
queryData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<LoginDevice>? getDataList(LoginDevicesData response) {
|
||||||
|
return response.devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LoadingState<LoginDevicesData>> customGetData() =>
|
||||||
|
LoginHttp.loginDevices();
|
||||||
|
}
|
||||||
97
lib/pages/login_devices/view.dart
Normal file
97
lib/pages/login_devices/view.dart
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart';
|
||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/models_new/login_devices/device.dart';
|
||||||
|
import 'package:PiliPlus/pages/login_devices/controller.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class LoginDevicesPage extends StatefulWidget {
|
||||||
|
const LoginDevicesPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginDevicesPage> createState() => LloginDevicesPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LloginDevicesPageState extends State<LoginDevicesPage> {
|
||||||
|
final _controller = Get.put(LoginDevicesController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('登录设备')),
|
||||||
|
body: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 650),
|
||||||
|
child: refreshIndicator(
|
||||||
|
onRefresh: _controller.onRefresh,
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
ViewSliverSafeArea(
|
||||||
|
sliver: MediaQuery.removeViewPadding(
|
||||||
|
context: context,
|
||||||
|
removeLeft: true,
|
||||||
|
removeRight: true,
|
||||||
|
child: Obx(
|
||||||
|
() => _buildBody(
|
||||||
|
colorScheme,
|
||||||
|
_controller.loadingState.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody(
|
||||||
|
ColorScheme colorScheme,
|
||||||
|
LoadingState<List<LoginDevice>?> loadingState,
|
||||||
|
) {
|
||||||
|
late final divider = Divider(
|
||||||
|
height: 1,
|
||||||
|
color: colorScheme.outline.withValues(alpha: 0.1),
|
||||||
|
);
|
||||||
|
return switch (loadingState) {
|
||||||
|
Loading() => const SliverToBoxAdapter(),
|
||||||
|
Success<List<LoginDevice>?>(:var response) =>
|
||||||
|
response?.isNotEmpty == true
|
||||||
|
? SliverList.separated(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _buildItem(colorScheme, response[index]);
|
||||||
|
},
|
||||||
|
itemCount: response!.length,
|
||||||
|
separatorBuilder: (_, _) => divider,
|
||||||
|
)
|
||||||
|
: HttpError(onReload: _controller.onReload),
|
||||||
|
Error(:var errMsg) => HttpError(
|
||||||
|
errMsg: errMsg,
|
||||||
|
onReload: _controller.onReload,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItem(ColorScheme colorScheme, LoginDevice item) {
|
||||||
|
final style = TextStyle(fontSize: 13, color: colorScheme.outline);
|
||||||
|
return ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(
|
||||||
|
item.deviceName ?? '',
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
'${item.latestLoginAt} ${item.source}',
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
trailing: item.isCurrentDevice == true
|
||||||
|
? Text('(本机)', style: style)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import 'package:PiliPlus/models_new/space/space/data.dart';
|
|||||||
import 'package:PiliPlus/pages/coin_log/controller.dart';
|
import 'package:PiliPlus/pages/coin_log/controller.dart';
|
||||||
import 'package:PiliPlus/pages/exp_log/controller.dart';
|
import 'package:PiliPlus/pages/exp_log/controller.dart';
|
||||||
import 'package:PiliPlus/pages/log_table/view.dart';
|
import 'package:PiliPlus/pages/log_table/view.dart';
|
||||||
|
import 'package:PiliPlus/pages/login_devices/view.dart';
|
||||||
import 'package:PiliPlus/pages/login_log/controller.dart';
|
import 'package:PiliPlus/pages/login_log/controller.dart';
|
||||||
import 'package:PiliPlus/pages/member/controller.dart';
|
import 'package:PiliPlus/pages/member/controller.dart';
|
||||||
import 'package:PiliPlus/pages/member/widget/user_info_card.dart';
|
import 'package:PiliPlus/pages/member/widget/user_info_card.dart';
|
||||||
@@ -190,6 +191,17 @@ class _MemberPageState extends State<MemberPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () => Get.to(const LoginDevicesPage()),
|
||||||
|
child: const Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.devices, size: 18),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Text('登录设备'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () => Get.to(
|
onTap: () => Get.to(
|
||||||
const LogPage(),
|
const LogPage(),
|
||||||
|
|||||||
Reference in New Issue
Block a user