💄 Better authorized device page
This commit is contained in:
@@ -11,6 +11,7 @@ import 'package:island/services/udid.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
import 'package:island/widgets/sites/info_row.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
@@ -19,21 +20,21 @@ import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||
part 'account_devices.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<SnAuthDeviceWithChallenge>> authDevices(Ref ref) async {
|
||||
Future<List<SnAuthDeviceWithSession>> authDevices(Ref ref) async {
|
||||
final resp = await ref
|
||||
.watch(apiClientProvider)
|
||||
.get('/pass/accounts/me/devices');
|
||||
final currentId = await getUdid();
|
||||
final data =
|
||||
resp.data.map<SnAuthDeviceWithChallenge>((e) {
|
||||
final ele = SnAuthDeviceWithChallenge.fromJson(e);
|
||||
resp.data.map<SnAuthDeviceWithSession>((e) {
|
||||
final ele = SnAuthDeviceWithSession.fromJson(e);
|
||||
return ele.copyWith(isCurrent: ele.deviceId == currentId);
|
||||
}).toList();
|
||||
return data;
|
||||
}
|
||||
|
||||
class _DeviceListTile extends StatelessWidget {
|
||||
final SnAuthDeviceWithChallenge device;
|
||||
final SnAuthDeviceWithSession device;
|
||||
final Function(String) updateDeviceLabel;
|
||||
final Function(String) logoutDevice;
|
||||
|
||||
@@ -69,11 +70,12 @@ class _DeviceListTile extends StatelessWidget {
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'lastActiveAt'.tr(
|
||||
args: [device.challenges.first.createdAt.formatSystem()],
|
||||
if (device.sessions.isNotEmpty)
|
||||
Text(
|
||||
'lastActiveAt'.tr(
|
||||
args: [device.sessions.first.createdAt.formatSystem()],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: Icon(switch (device.platform) {
|
||||
@@ -114,26 +116,40 @@ class _DeviceListTile extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text('authDeviceChallenges'.tr()),
|
||||
),
|
||||
for (final challenge in device.challenges)
|
||||
ListTile(
|
||||
minTileHeight: 48,
|
||||
title: Text(DateFormat().format(challenge.createdAt.toLocal())),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(challenge.ipAddress),
|
||||
if (challenge.location != null)
|
||||
Row(
|
||||
spacing: 4,
|
||||
children:
|
||||
[challenge.location?.city, challenge.location?.country]
|
||||
.where((e) => e?.isNotEmpty ?? false)
|
||||
.map((e) => Text(e!))
|
||||
.toList(),
|
||||
...device.sessions
|
||||
.map(
|
||||
(session) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 4,
|
||||
children: [
|
||||
InfoRow(
|
||||
label: 'createdAt'.tr(
|
||||
args: [session.createdAt.toLocal().formatSystem()],
|
||||
),
|
||||
icon: Symbols.join,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
InfoRow(
|
||||
label: 'lastActiveAt'.tr(
|
||||
args: [session.lastGrantedAt.toLocal().formatSystem()],
|
||||
),
|
||||
icon: Symbols.refresh_rounded,
|
||||
),
|
||||
InfoRow(
|
||||
label:
|
||||
'${'location'.tr()} ${session.location?.city ?? 'unknown'.tr()}',
|
||||
icon: Symbols.pin_drop,
|
||||
),
|
||||
InfoRow(
|
||||
label:
|
||||
'${'ipAddress'.tr()} ${session.ipAddress ?? 'unknown'.tr()}',
|
||||
icon: Symbols.dns,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 8),
|
||||
)
|
||||
.expand((element) => [element, const Divider(height: 1)])
|
||||
.toList()
|
||||
..removeLast(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ part of 'account_devices.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$authDevicesHash() => r'35735af4ed75b73fe80c8942e53b3bc26a569c01';
|
||||
String _$authDevicesHash() => r'1af378149286020ec263be178c573ccc247a0cd1';
|
||||
|
||||
/// See also [authDevices].
|
||||
@ProviderFor(authDevices)
|
||||
final authDevicesProvider =
|
||||
AutoDisposeFutureProvider<List<SnAuthDeviceWithChallenge>>.internal(
|
||||
AutoDisposeFutureProvider<List<SnAuthDeviceWithSession>>.internal(
|
||||
authDevices,
|
||||
name: r'authDevicesProvider',
|
||||
debugGetCreateSourceHash:
|
||||
@@ -25,6 +25,6 @@ final authDevicesProvider =
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef AuthDevicesRef =
|
||||
AutoDisposeFutureProviderRef<List<SnAuthDeviceWithChallenge>>;
|
||||
AutoDisposeFutureProviderRef<List<SnAuthDeviceWithSession>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -124,7 +124,7 @@ class FileItem extends HookConsumerWidget {
|
||||
if (confirmed != true) return;
|
||||
}
|
||||
|
||||
await _showEditSheet(context, ref);
|
||||
if (context.mounted) await _showEditSheet(context, ref);
|
||||
}
|
||||
|
||||
Future<void> _showEditSheet(BuildContext context, WidgetRef ref) async {
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class InfoRow extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final String? value;
|
||||
final IconData icon;
|
||||
final bool monospace;
|
||||
final VoidCallback? onTap;
|
||||
@@ -12,7 +12,7 @@ class InfoRow extends StatelessWidget {
|
||||
const InfoRow({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.value,
|
||||
required this.icon,
|
||||
this.monospace = false,
|
||||
this.onTap,
|
||||
@@ -20,14 +20,17 @@ class InfoRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget valueWidget = Text(
|
||||
value,
|
||||
style:
|
||||
monospace
|
||||
? GoogleFonts.robotoMono(fontSize: 14)
|
||||
: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.end,
|
||||
);
|
||||
Widget? valueWidget =
|
||||
value == null
|
||||
? null
|
||||
: Text(
|
||||
value!,
|
||||
style:
|
||||
monospace
|
||||
? GoogleFonts.robotoMono(fontSize: 14)
|
||||
: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.end,
|
||||
);
|
||||
|
||||
if (onTap != null) valueWidget = InkWell(onTap: onTap, child: valueWidget);
|
||||
|
||||
@@ -40,13 +43,16 @@ class InfoRow extends StatelessWidget {
|
||||
flex: 2,
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style:
|
||||
valueWidget == null
|
||||
? null
|
||||
: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Expanded(flex: 3, child: valueWidget),
|
||||
if (valueWidget != null) const Gap(12),
|
||||
if (valueWidget != null) Expanded(flex: 3, child: valueWidget),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user