fluffychat/lib/pages/bootstrap_dialog.dart

350 lines
13 KiB
Dart
Raw Normal View History

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
2021-10-26 16:50:34 +00:00
import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
2021-05-15 10:06:13 +00:00
import 'package:future_loading_dialog/future_loading_dialog.dart';
2021-10-26 16:50:34 +00:00
import 'package:matrix/encryption.dart';
import 'package:matrix/encryption/utils/bootstrap.dart';
import 'package:matrix/matrix.dart';
2021-10-26 16:50:34 +00:00
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/adaptive_flat_button.dart';
2021-02-13 13:26:03 +00:00
import 'key_verification_dialog.dart';
class BootstrapDialog extends StatefulWidget {
2021-02-13 13:26:03 +00:00
final bool wipe;
2021-03-06 09:03:01 +00:00
final Client client;
2021-01-23 10:57:47 +00:00
const BootstrapDialog({
Key key,
2021-02-13 13:26:03 +00:00
this.wipe = false,
2021-03-06 09:03:01 +00:00
@required this.client,
2021-01-23 10:57:47 +00:00
}) : super(key: key);
2021-01-19 14:46:43 +00:00
Future<bool> show(BuildContext context) => PlatformInfos.isCupertinoStyle
2021-02-24 11:17:23 +00:00
? showCupertinoDialog(
context: context,
builder: (context) => this,
2021-04-09 13:53:26 +00:00
barrierDismissible: true,
2021-05-23 13:02:36 +00:00
useRootNavigator: false,
2021-02-24 11:17:23 +00:00
)
: showDialog(
context: context,
builder: (context) => this,
2021-04-09 13:53:26 +00:00
barrierDismissible: true,
2021-05-23 13:02:36 +00:00
useRootNavigator: false,
2021-02-24 11:17:23 +00:00
);
2021-01-19 14:46:43 +00:00
@override
_BootstrapDialogState createState() => _BootstrapDialogState();
}
class _BootstrapDialogState extends State<BootstrapDialog> {
2021-02-06 11:55:27 +00:00
final TextEditingController _recoveryKeyTextEditingController =
TextEditingController();
Bootstrap bootstrap;
2021-02-06 11:55:27 +00:00
String _recoveryKeyInputError;
bool _recoveryKeyInputLoading = false;
String titleText;
bool _recoveryKeyStored = false;
2021-02-13 13:26:03 +00:00
bool _wipe;
2021-02-06 11:55:27 +00:00
void _createBootstrap(bool wipe) {
setState(() {
_wipe = wipe;
titleText = null;
_recoveryKeyStored = false;
2021-03-06 09:03:01 +00:00
bootstrap = widget.client.encryption
2021-02-06 11:55:27 +00:00
.bootstrap(onUpdate: () => setState(() => null));
});
}
@override
Widget build(BuildContext context) {
2021-02-13 13:26:03 +00:00
_wipe ??= widget.wipe;
final buttons = <AdaptiveFlatButton>[];
2021-06-22 15:56:15 +00:00
Widget body = PlatformInfos.isCupertinoStyle
2021-10-14 16:09:30 +00:00
? const CupertinoActivityIndicator()
: const LinearProgressIndicator();
2021-02-24 11:17:23 +00:00
titleText = L10n.of(context).loadingPleaseWait;
2021-02-06 11:55:27 +00:00
if (bootstrap == null) {
2021-10-10 09:40:08 +00:00
titleText = L10n.of(context).setupChatBackup;
body = Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/backup.png', fit: BoxFit.contain),
Text(L10n.of(context).setupChatBackupDescription),
],
);
2021-02-06 11:55:27 +00:00
buttons.add(AdaptiveFlatButton(
2021-02-27 06:53:34 +00:00
label: L10n.of(context).next,
2021-02-06 11:55:27 +00:00
onPressed: () => _createBootstrap(false),
));
} else if (bootstrap.newSsssKey?.recoveryKey != null &&
_recoveryKeyStored == false) {
final key = bootstrap.newSsssKey.recoveryKey;
2021-02-24 11:17:23 +00:00
titleText = L10n.of(context).securityKey;
2021-10-14 14:57:01 +00:00
return Scaffold(
appBar: AppBar(
2021-10-14 14:56:08 +00:00
centerTitle: true,
leading: IconButton(
2021-10-14 16:09:30 +00:00
icon: const Icon(Icons.close),
2021-10-14 14:56:08 +00:00
onPressed: Navigator.of(context).pop,
2021-10-10 11:35:16 +00:00
),
2021-10-14 14:56:08 +00:00
title: Text(L10n.of(context).securityKey),
),
2021-10-14 14:57:01 +00:00
body: Center(
2021-10-14 14:56:08 +00:00
child: ConstrainedBox(
constraints:
2021-10-14 16:09:30 +00:00
const BoxConstraints(maxWidth: FluffyThemes.columnWidth * 1.5),
2021-10-14 14:56:08 +00:00
child: ListView(
padding: const EdgeInsets.all(16.0),
children: [
TextField(
minLines: 4,
maxLines: 4,
readOnly: true,
controller: TextEditingController(text: key),
2021-10-10 11:35:16 +00:00
),
2021-10-14 14:56:08 +00:00
const SizedBox(height: 16),
ElevatedButton.icon(
2021-10-14 16:09:30 +00:00
icon: const Icon(Icons.copy_outlined),
2021-10-14 14:56:08 +00:00
label: Text(L10n.of(context).copyToClipboard),
onPressed: () => Clipboard.setData(ClipboardData(text: key)),
),
const SizedBox(height: 16),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).secondaryHeaderColor,
onPrimary: Theme.of(context).primaryColor,
),
2021-10-14 16:09:30 +00:00
icon: const Icon(Icons.check_outlined),
2021-10-14 14:56:08 +00:00
label: Text(L10n.of(context).iWroteDownTheKey),
onPressed: () => setState(() => _recoveryKeyStored = true),
),
],
),
2021-10-10 11:35:16 +00:00
),
2021-02-06 11:55:27 +00:00
),
);
} else {
switch (bootstrap.state) {
case BootstrapState.loading:
break;
case BootstrapState.askWipeSsss:
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.wipeSsss(_wipe),
);
break;
2021-07-24 09:45:27 +00:00
case BootstrapState.askBadSsss:
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.ignoreBadSecrets(true),
);
break;
2021-02-06 11:55:27 +00:00
case BootstrapState.askUseExistingSsss:
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.useExistingSsss(!_wipe),
);
break;
case BootstrapState.askUnlockSsss:
2021-07-24 09:45:27 +00:00
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.unlockedSsss(),
);
break;
2021-02-06 11:55:27 +00:00
case BootstrapState.askNewSsss:
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.newSsss(),
);
break;
case BootstrapState.openExistingSsss:
_recoveryKeyStored = true;
2021-10-14 14:56:08 +00:00
return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: IconButton(
2021-10-14 16:09:30 +00:00
icon: const Icon(Icons.close),
2021-10-14 14:56:08 +00:00
onPressed: Navigator.of(context).pop,
2021-10-10 09:40:08 +00:00
),
2021-10-14 14:56:08 +00:00
title: Text(L10n.of(context).pleaseEnterSecurityKey),
),
body: Center(
child: ConstrainedBox(
2021-10-17 12:15:29 +00:00
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5),
2021-10-14 14:56:08 +00:00
child: ListView(
padding: const EdgeInsets.all(16.0),
children: [
TextField(
minLines: 4,
maxLines: 4,
autofocus: true,
autocorrect: false,
autofillHints: _recoveryKeyInputLoading
? null
: [AutofillHints.password],
controller: _recoveryKeyTextEditingController,
decoration: InputDecoration(
hintText: 'Abc123 Def456',
labelText: L10n.of(context).securityKey,
errorText: _recoveryKeyInputError,
),
2021-10-10 11:35:16 +00:00
),
2021-10-14 14:56:08 +00:00
const SizedBox(height: 16),
ElevatedButton.icon(
2021-10-14 16:09:30 +00:00
icon: const Icon(Icons.lock_open_outlined),
2021-10-14 14:56:08 +00:00
label: Text(L10n.of(context).unlockChatBackup),
onPressed: () async {
setState(() {
_recoveryKeyInputError = null;
_recoveryKeyInputLoading = true;
});
try {
await bootstrap.newSsssKey.unlock(
keyOrPassphrase:
_recoveryKeyTextEditingController.text,
);
await bootstrap.openExistingSsss();
} catch (e, s) {
Logs().w('Unable to unlock SSSS', e, s);
setState(() => _recoveryKeyInputError =
L10n.of(context).oopsSomethingWentWrong);
} finally {
setState(() => _recoveryKeyInputLoading = false);
}
}),
const SizedBox(height: 16),
Row(children: [
2021-10-14 16:09:30 +00:00
const Expanded(child: Divider()),
2021-10-14 14:56:08 +00:00
Padding(
padding: const EdgeInsets.all(12.0),
child: Text(L10n.of(context).or),
),
2021-10-14 16:09:30 +00:00
const Expanded(child: Divider()),
2021-10-14 14:56:08 +00:00
]),
const SizedBox(height: 16),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).secondaryHeaderColor,
onPrimary: Theme.of(context).primaryColor,
),
2021-10-17 12:15:29 +00:00
icon:
const Icon(Icons.transfer_within_a_station_outlined),
2021-10-14 14:56:08 +00:00
label: Text(L10n.of(context).transferFromAnotherDevice),
2021-10-10 11:35:16 +00:00
onPressed: () async {
2021-10-14 14:56:08 +00:00
final req = await showFutureLoadingDialog(
context: context,
future: () => widget
.client.userDeviceKeys[widget.client.userID]
.startVerification(),
);
if (req.error != null) return;
await KeyVerificationDialog(request: req.result)
.show(context);
Navigator.of(context, rootNavigator: false).pop();
},
2021-10-10 11:35:16 +00:00
),
2021-10-14 14:56:08 +00:00
const SizedBox(height: 16),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).secondaryHeaderColor,
onPrimary: Colors.red,
),
2021-10-14 16:09:30 +00:00
icon: const Icon(Icons.delete_outlined),
2021-10-14 14:56:08 +00:00
label: Text(L10n.of(context).securityKeyLost),
onPressed: () async {
if (OkCancelResult.ok ==
await showOkCancelAlertDialog(
useRootNavigator: false,
context: context,
title: L10n.of(context).securityKeyLost,
message: L10n.of(context).wipeChatBackup,
okLabel: L10n.of(context).ok,
cancelLabel: L10n.of(context).cancel,
isDestructiveAction: true,
)) {
_createBootstrap(true);
}
},
)
],
),
2021-10-10 11:35:16 +00:00
),
2021-10-10 09:40:08 +00:00
),
);
2021-02-06 11:55:27 +00:00
case BootstrapState.askWipeCrossSigning:
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.wipeCrossSigning(_wipe),
);
break;
case BootstrapState.askSetupCrossSigning:
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.askSetupCrossSigning(
setupMasterKey: true,
setupSelfSigningKey: true,
setupUserSigningKey: true,
),
);
break;
case BootstrapState.askWipeOnlineKeyBackup:
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.wipeOnlineKeyBackup(_wipe),
);
break;
case BootstrapState.askSetupOnlineKeyBackup:
WidgetsBinding.instance.addPostFrameCallback(
(_) => bootstrap.askSetupOnlineKeyBackup(true),
);
break;
case BootstrapState.error:
2021-02-24 11:17:23 +00:00
titleText = L10n.of(context).oopsSomethingWentWrong;
2021-10-14 16:09:30 +00:00
body = const Icon(Icons.error_outline, color: Colors.red, size: 40);
2021-02-06 11:55:27 +00:00
buttons.add(AdaptiveFlatButton(
2021-02-27 06:53:34 +00:00
label: L10n.of(context).close,
2021-02-24 11:17:23 +00:00
onPressed: () =>
Navigator.of(context, rootNavigator: false).pop<bool>(false),
2021-02-06 11:55:27 +00:00
));
break;
case BootstrapState.done:
2021-02-24 11:17:23 +00:00
titleText = L10n.of(context).everythingReady;
2021-06-22 15:56:15 +00:00
body = Column(
mainAxisSize: MainAxisSize.min,
children: [
2021-10-10 09:40:08 +00:00
Image.asset('assets/backup.png', fit: BoxFit.contain),
Text(L10n.of(context).yourChatBackupHasBeenSetUp),
2021-06-22 15:56:15 +00:00
],
2021-02-06 11:55:27 +00:00
);
buttons.add(AdaptiveFlatButton(
2021-02-27 06:53:34 +00:00
label: L10n.of(context).close,
2021-02-24 11:17:23 +00:00
onPressed: () =>
Navigator.of(context, rootNavigator: false).pop<bool>(false),
2021-02-06 11:55:27 +00:00
));
break;
}
}
2021-01-23 10:57:47 +00:00
final title = Text(titleText);
if (PlatformInfos.isCupertinoStyle) {
return CupertinoAlertDialog(
title: title,
content: body,
actions: buttons,
);
}
return AlertDialog(
title: title,
content: body,
actions: buttons,
);
}
}