mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
feat: add verify button to send and receive page
This commit is contained in:
@@ -88,7 +88,11 @@ class _DeviceDetailsPageState extends State<DeviceDetailsPage> with Refena {
|
|||||||
label: t.deviceDetailsPage.verify,
|
label: t.deviceDetailsPage.verify,
|
||||||
filled: false,
|
filled: false,
|
||||||
width: 120,
|
width: 120,
|
||||||
onTap: () async => await context.push(() => VerifyPage(device: device)),
|
onTap: () async => await context.push(
|
||||||
|
() => VerifyPage(
|
||||||
|
fingerprint: CombinedFingerprint.load(context, device.fingerprint),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
+138
-118
@@ -6,12 +6,13 @@ import 'package:localsend_app/config/theme.dart';
|
|||||||
import 'package:localsend_app/gen/strings.g.dart';
|
import 'package:localsend_app/gen/strings.g.dart';
|
||||||
import 'package:localsend_app/model/persistence/color_mode.dart';
|
import 'package:localsend_app/model/persistence/color_mode.dart';
|
||||||
import 'package:localsend_app/pages/receive_options_page.dart';
|
import 'package:localsend_app/pages/receive_options_page.dart';
|
||||||
|
import 'package:localsend_app/pages/verify_page.dart';
|
||||||
|
import 'package:localsend_app/pages/web_share_page.dart';
|
||||||
import 'package:localsend_app/provider/favorites_provider.dart';
|
import 'package:localsend_app/provider/favorites_provider.dart';
|
||||||
import 'package:localsend_app/provider/selection/selected_receiving_files_provider.dart';
|
import 'package:localsend_app/provider/selection/selected_receiving_files_provider.dart';
|
||||||
import 'package:localsend_app/provider/settings_provider.dart';
|
import 'package:localsend_app/provider/settings_provider.dart';
|
||||||
import 'package:localsend_app/util/device_type_ext.dart';
|
import 'package:localsend_app/util/device_type_ext.dart';
|
||||||
import 'package:localsend_app/util/favorites.dart';
|
import 'package:localsend_app/util/favorites.dart';
|
||||||
import 'package:localsend_app/util/ip_helper.dart';
|
|
||||||
import 'package:localsend_app/util/native/platform_check.dart';
|
import 'package:localsend_app/util/native/platform_check.dart';
|
||||||
import 'package:localsend_app/util/native/taskbar_helper.dart';
|
import 'package:localsend_app/util/native/taskbar_helper.dart';
|
||||||
import 'package:localsend_app/util/ui/snackbar.dart';
|
import 'package:localsend_app/util/ui/snackbar.dart';
|
||||||
@@ -20,6 +21,7 @@ import 'package:localsend_app/widget/responsive_list_view.dart';
|
|||||||
import 'package:localsend_isolates/model/device.dart';
|
import 'package:localsend_isolates/model/device.dart';
|
||||||
import 'package:localsend_isolates/model/dto/file_dto.dart';
|
import 'package:localsend_isolates/model/dto/file_dto.dart';
|
||||||
import 'package:localsend_isolates/model/session_status.dart';
|
import 'package:localsend_isolates/model/session_status.dart';
|
||||||
|
import 'package:refena_flutter/addons.dart';
|
||||||
import 'package:refena_flutter/refena_flutter.dart';
|
import 'package:refena_flutter/refena_flutter.dart';
|
||||||
import 'package:routerino/routerino.dart';
|
import 'package:routerino/routerino.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@@ -28,7 +30,7 @@ class ReceivePageVm {
|
|||||||
final SessionStatus? status;
|
final SessionStatus? status;
|
||||||
final Device sender;
|
final Device sender;
|
||||||
|
|
||||||
/// Show hashtag and device model.
|
/// Show verify button and device model.
|
||||||
final bool showSenderInfo;
|
final bool showSenderInfo;
|
||||||
final List<FileDto> files;
|
final List<FileDto> files;
|
||||||
final String? message;
|
final String? message;
|
||||||
@@ -59,8 +61,6 @@ class ReceivePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ReceivePageState extends State<ReceivePage> with Refena {
|
class _ReceivePageState extends State<ReceivePage> with Refena {
|
||||||
bool _showFullIp = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final vm = context.watch(
|
final vm = context.watch(
|
||||||
@@ -110,125 +110,158 @@ class _ReceivePageState extends State<ReceivePage> with Refena {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
if (vm.showSenderInfo && !smallUi)
|
Column(
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
if (vm.showSenderInfo && !smallUi)
|
||||||
child: Icon(vm.sender.deviceType.icon, size: 64),
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
Builder(
|
child: Icon(vm.sender.deviceType.icon, size: 64),
|
||||||
builder: (context) {
|
|
||||||
final alias = senderFavoriteEntry?.alias ?? vm.sender.alias;
|
|
||||||
if (alias.isEmpty) {
|
|
||||||
return Text('', style: TextStyle(fontSize: smallUi ? 32 : 48));
|
|
||||||
}
|
|
||||||
return FittedBox(
|
|
||||||
child: Text(
|
|
||||||
alias,
|
|
||||||
style: TextStyle(fontSize: smallUi ? 32 : 48),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
);
|
Builder(
|
||||||
},
|
builder: (context) {
|
||||||
),
|
final alias = senderFavoriteEntry?.alias ?? vm.sender.alias;
|
||||||
if (vm.showSenderInfo) ...[
|
if (alias.isEmpty) {
|
||||||
const SizedBox(height: 10),
|
return Text('', style: TextStyle(fontSize: smallUi ? 32 : 48));
|
||||||
Row(
|
}
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
return FittedBox(
|
||||||
children: [
|
child: Text(
|
||||||
InkWell(
|
alias,
|
||||||
onTap: () {
|
style: TextStyle(fontSize: smallUi ? 32 : 48),
|
||||||
setState(() {
|
textAlign: TextAlign.center,
|
||||||
_showFullIp = !_showFullIp;
|
),
|
||||||
});
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
if (vm.showSenderInfo && vm.sender.deviceModel != null) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Center(
|
||||||
child: DeviceBadge(
|
child: DeviceBadge(
|
||||||
backgroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.onInverseSurface,
|
|
||||||
label: switch (vm.sender.ip) {
|
|
||||||
String ip => _showFullIp ? ip : '#${ip.visualId}',
|
|
||||||
null => 'WebRTC',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (vm.sender.deviceModel != null) ...[
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
DeviceBadge(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
foregroundColor: Theme.of(context).colorScheme.onInverseSurface,
|
foregroundColor: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
label: vm.sender.deviceModel!,
|
label: vm.sender.deviceModel!,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
],
|
],
|
||||||
),
|
],
|
||||||
],
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
Text(
|
|
||||||
vm.message != null
|
|
||||||
? (vm.isLink ? t.receivePage.subTitleLink : t.receivePage.subTitleMessage)
|
|
||||||
: t.receivePage.subTitle(n: vm.files.length),
|
|
||||||
style: smallUi ? null : Theme.of(context).textTheme.titleLarge,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
if (vm.message != null)
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
children: [
|
Text(
|
||||||
|
vm.message != null
|
||||||
|
? (vm.isLink ? t.receivePage.subTitleLink : t.receivePage.subTitleMessage)
|
||||||
|
: t.receivePage.subTitle(n: vm.files.length),
|
||||||
|
style: smallUi ? null : Theme.of(context).textTheme.titleLarge,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
if (vm.showSenderInfo && vm.message == null)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: SizedBox(
|
child: Row(
|
||||||
height: 100,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Card(
|
children: [
|
||||||
child: SingleChildScrollView(
|
TextButton.icon(
|
||||||
child: Padding(
|
onPressed: () async => await context.push(
|
||||||
padding: const EdgeInsets.all(10),
|
() => VerifyPage(
|
||||||
child: SelectableText(
|
fingerprint: CombinedFingerprint.load(context, vm.sender.fingerprint),
|
||||||
vm.message!,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
icon: Icon(Icons.verified_user),
|
||||||
|
label: Text(t.verifyPage.title),
|
||||||
),
|
),
|
||||||
),
|
TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await context.push(() => ReceiveOptionsPage(vm));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.settings),
|
||||||
|
label: Text(t.receiveOptionsPage.title),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
if (vm.message != null)
|
||||||
Row(
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
Padding(
|
||||||
onPressed: () {
|
padding: const EdgeInsets.only(top: 20),
|
||||||
unawaited(
|
child: SizedBox(
|
||||||
Clipboard.setData(ClipboardData(text: vm.message!)),
|
height: 100,
|
||||||
);
|
child: Card(
|
||||||
if (checkPlatformIsDesktop()) {
|
child: SingleChildScrollView(
|
||||||
context.showSnackBar(t.general.copiedToClipboard);
|
child: Padding(
|
||||||
}
|
padding: const EdgeInsets.all(10),
|
||||||
vm.onAccept();
|
child: SelectableText(
|
||||||
context.pop();
|
vm.message!,
|
||||||
},
|
),
|
||||||
child: Text(t.general.copy),
|
),
|
||||||
),
|
|
||||||
if (vm.isLink)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsetsDirectional.only(start: 20),
|
|
||||||
child: ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
|
||||||
),
|
),
|
||||||
onPressed: () {
|
|
||||||
// ignore: discarded_futures
|
|
||||||
launchUrl(Uri.parse(vm.message!), mode: LaunchMode.externalApplication);
|
|
||||||
vm.onAccept();
|
|
||||||
context.pop();
|
|
||||||
},
|
|
||||||
child: Text(t.general.open),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (vm.showSenderInfo)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(end: 20),
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () async => await context.push(
|
||||||
|
() => VerifyPage(
|
||||||
|
fingerprint: CombinedFingerprint.load(context, vm.sender.fingerprint),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: Icon(Icons.verified_user),
|
||||||
|
label: Text(t.verifyPage.title),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
unawaited(
|
||||||
|
Clipboard.setData(ClipboardData(text: vm.message!)),
|
||||||
|
);
|
||||||
|
if (checkPlatformIsDesktop()) {
|
||||||
|
context.showSnackBar(t.general.copiedToClipboard);
|
||||||
|
}
|
||||||
|
vm.onAccept();
|
||||||
|
context.global.dispatch(NavigateAction.popUntil<WebSharePage>());
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.copy),
|
||||||
|
label: Text(t.general.copy),
|
||||||
|
),
|
||||||
|
if (vm.isLink)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(start: 20),
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
// ignore: discarded_futures
|
||||||
|
launchUrl(Uri.parse(vm.message!), mode: LaunchMode.externalApplication);
|
||||||
|
vm.onAccept();
|
||||||
|
context.global.dispatch(NavigateAction.popUntil<WebSharePage>());
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.open_in_new),
|
||||||
|
label: Text(t.general.open),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -266,7 +299,7 @@ class _Actions extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
vm.onAccept();
|
vm.onAccept();
|
||||||
context.pop();
|
context.global.dispatch(NavigateAction.popUntil<WebSharePage>());
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
label: Text(t.general.close),
|
label: Text(t.general.close),
|
||||||
@@ -289,7 +322,7 @@ class _Actions extends StatelessWidget {
|
|||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
vm.onClose();
|
vm.onClose();
|
||||||
context.pop();
|
context.global.dispatch(NavigateAction.popUntil<WebSharePage>());
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.check_circle),
|
icon: const Icon(Icons.check_circle),
|
||||||
label: Text(t.general.close),
|
label: Text(t.general.close),
|
||||||
@@ -301,19 +334,6 @@ class _Actions extends StatelessWidget {
|
|||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 20),
|
|
||||||
child: TextButton.icon(
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
await context.push(() => ReceiveOptionsPage(vm));
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.settings),
|
|
||||||
label: Text(t.receiveOptionsPage.title),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -325,7 +345,7 @@ class _Actions extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
vm.onDecline();
|
vm.onDecline();
|
||||||
context.pop();
|
context.global.dispatch(NavigateAction.popUntil<WebSharePage>());
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
label: Text(t.general.decline),
|
label: Text(t.general.decline),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:localsend_app/config/theme.dart';
|
import 'package:localsend_app/config/theme.dart';
|
||||||
import 'package:localsend_app/gen/strings.g.dart';
|
import 'package:localsend_app/gen/strings.g.dart';
|
||||||
import 'package:localsend_app/model/state/send/send_session_state.dart';
|
import 'package:localsend_app/model/state/send/send_session_state.dart';
|
||||||
|
import 'package:localsend_app/pages/verify_page.dart';
|
||||||
import 'package:localsend_app/provider/device_info_provider.dart';
|
import 'package:localsend_app/provider/device_info_provider.dart';
|
||||||
import 'package:localsend_app/provider/favorites_provider.dart';
|
import 'package:localsend_app/provider/favorites_provider.dart';
|
||||||
import 'package:localsend_app/provider/file_transfer_provider.dart';
|
import 'package:localsend_app/provider/file_transfer_provider.dart';
|
||||||
@@ -17,6 +18,7 @@ import 'package:localsend_app/widget/list_tile/device_list_tile.dart';
|
|||||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||||
import 'package:localsend_isolates/model/device.dart';
|
import 'package:localsend_isolates/model/device.dart';
|
||||||
import 'package:localsend_isolates/model/session_status.dart';
|
import 'package:localsend_isolates/model/session_status.dart';
|
||||||
|
import 'package:refena_flutter/addons.dart';
|
||||||
import 'package:refena_flutter/refena_flutter.dart';
|
import 'package:refena_flutter/refena_flutter.dart';
|
||||||
import 'package:routerino/routerino.dart';
|
import 'package:routerino/routerino.dart';
|
||||||
|
|
||||||
@@ -137,6 +139,25 @@ class _SendPageState extends State<SendPage> with Refena {
|
|||||||
nameOverride: targetFavoriteEntry?.alias,
|
nameOverride: targetFavoriteEntry?.alias,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
InitialFadeTransition(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
delay: const Duration(milliseconds: 400),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: () async => await context.push(
|
||||||
|
() => VerifyPage(
|
||||||
|
fingerprint: CombinedFingerprint.load(context, targetDevice.fingerprint),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
icon: Icon(Icons.verified_user),
|
||||||
|
label: Text(t.verifyPage.title),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -217,7 +238,7 @@ class _SendPageState extends State<SendPage> with Refena {
|
|||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_cancel();
|
_cancel();
|
||||||
context.pop();
|
context.global.dispatch(NavigateAction.popUntilRoot());
|
||||||
},
|
},
|
||||||
icon: Icon(waiting ? Icons.close : Icons.check_circle),
|
icon: Icon(waiting ? Icons.close : Icons.check_circle),
|
||||||
label: Text(waiting ? t.general.cancel : t.general.close),
|
label: Text(waiting ? t.general.cancel : t.general.close),
|
||||||
|
|||||||
@@ -3,10 +3,30 @@ import 'package:localsend_app/gen/strings.g.dart';
|
|||||||
import 'package:localsend_app/provider/security_provider.dart';
|
import 'package:localsend_app/provider/security_provider.dart';
|
||||||
import 'package:localsend_app/util/fingerprint_alphabet.dart';
|
import 'package:localsend_app/util/fingerprint_alphabet.dart';
|
||||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||||
import 'package:localsend_isolates/model/device.dart';
|
|
||||||
import 'package:refena_flutter/refena_flutter.dart';
|
import 'package:refena_flutter/refena_flutter.dart';
|
||||||
|
|
||||||
enum _VerifyMode {
|
class CombinedFingerprint {
|
||||||
|
final String combined;
|
||||||
|
final List<IconData> icons;
|
||||||
|
|
||||||
|
CombinedFingerprint({
|
||||||
|
required this.combined,
|
||||||
|
required this.icons,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CombinedFingerprint.load(BuildContext context, String fingerprint) {
|
||||||
|
final myFingerprint = context.ref.watch(securityProvider.select((s) => s.certificateHash));
|
||||||
|
final fingerprints = [myFingerprint, fingerprint]..sort();
|
||||||
|
final combined = fingerprints.join();
|
||||||
|
final icons = fingerprintToIcons(combined);
|
||||||
|
return CombinedFingerprint(
|
||||||
|
combined: combined,
|
||||||
|
icons: icons,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VerifyMode {
|
||||||
icons,
|
icons,
|
||||||
raw,
|
raw,
|
||||||
}
|
}
|
||||||
@@ -14,22 +34,19 @@ enum _VerifyMode {
|
|||||||
/// Verifies the identity of a discovered device by comparing the fingerprints
|
/// Verifies the identity of a discovered device by comparing the fingerprints
|
||||||
/// of both sides.
|
/// of both sides.
|
||||||
class VerifyPage extends StatefulWidget {
|
class VerifyPage extends StatefulWidget {
|
||||||
final Device device;
|
final CombinedFingerprint fingerprint;
|
||||||
|
|
||||||
const VerifyPage({required this.device});
|
const VerifyPage({required this.fingerprint});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<VerifyPage> createState() => _VerifyPageState();
|
State<VerifyPage> createState() => _VerifyPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VerifyPageState extends State<VerifyPage> {
|
class _VerifyPageState extends State<VerifyPage> {
|
||||||
_VerifyMode _mode = _VerifyMode.icons;
|
VerifyMode _mode = VerifyMode.icons;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final myFingerprint = context.ref.watch(securityProvider.select((s) => s.certificateHash));
|
|
||||||
final fingerprints = [myFingerprint, widget.device.fingerprint]..sort();
|
|
||||||
final combined = fingerprints.join();
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(t.verifyPage.title),
|
title: Text(t.verifyPage.title),
|
||||||
@@ -38,34 +55,20 @@ class _VerifyPageState extends State<VerifyPage> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: SegmentedButton<_VerifyMode>(
|
child: SegmentedButton<VerifyMode>(
|
||||||
segments: [
|
segments: [
|
||||||
ButtonSegment(value: _VerifyMode.icons, label: Text(t.verifyPage.icons)),
|
ButtonSegment(value: VerifyMode.icons, label: Text(t.verifyPage.icons)),
|
||||||
ButtonSegment(value: _VerifyMode.raw, label: Text(t.verifyPage.raw)),
|
ButtonSegment(value: VerifyMode.raw, label: Text(t.verifyPage.raw)),
|
||||||
],
|
],
|
||||||
selected: {_mode},
|
selected: {_mode},
|
||||||
onSelectionChanged: (selection) => setState(() => _mode = selection.first),
|
onSelectionChanged: (selection) => setState(() => _mode = selection.first),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
switch (_mode) {
|
VerifyWidget(
|
||||||
_VerifyMode.icons => Center(
|
mode: _mode,
|
||||||
child: ConstrainedBox(
|
fingerprint: widget.fingerprint,
|
||||||
constraints: const BoxConstraints(maxWidth: 240),
|
),
|
||||||
child: GridView.count(
|
|
||||||
crossAxisCount: 4,
|
|
||||||
mainAxisSpacing: 10,
|
|
||||||
crossAxisSpacing: 10,
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
children: [
|
|
||||||
for (final icon in fingerprintToIcons(combined)) Icon(icon, size: 32),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_VerifyMode.raw => SelectableText(combined),
|
|
||||||
},
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -79,3 +82,39 @@ class _VerifyPageState extends State<VerifyPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class VerifyWidget extends StatelessWidget {
|
||||||
|
final VerifyMode mode;
|
||||||
|
final CombinedFingerprint fingerprint;
|
||||||
|
|
||||||
|
const VerifyWidget({
|
||||||
|
required this.mode,
|
||||||
|
required this.fingerprint,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
switch (mode) {
|
||||||
|
VerifyMode.icons => Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 240),
|
||||||
|
child: GridView.count(
|
||||||
|
crossAxisCount: 4,
|
||||||
|
mainAxisSpacing: 10,
|
||||||
|
crossAxisSpacing: 10,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
children: [
|
||||||
|
for (final icon in fingerprint.icons) Icon(icon, size: 32),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VerifyMode.raw => SelectableText(fingerprint.combined),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user