fix(app): the phrase typed back is pasted into its fields, never copied out
The fields the recovery phrase is typed back into offered the system's Copy, Cut and Share, and Ctrl+C and Ctrl+X copied what was selected (the short reading's L3), so the phrase could still reach clipboard histories, cloud clipboards or another app. Their menu now offers Paste and Select all alone, and the field's own copy and cut, overridden from above, do nothing. The comment on the recovery codes no longer speaks of Copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
290f584329
commit
87529cd5c6
1 changed files with 162 additions and 37 deletions
|
|
@ -368,22 +368,25 @@ class const PhraseField({
|
|||
// apps while it does, as when it was first shown (the reading of the
|
||||
// fixes).
|
||||
return SecretScreen(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
enabled: enabled,
|
||||
autofocus: autofocus,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
// Nothing typed here is the keyboard's to learn.
|
||||
enableIMEPersonalizedLearning: false,
|
||||
minLines: minLines,
|
||||
maxLines: 5,
|
||||
maxLength: TextLimits.passage,
|
||||
buildCounter: nearLimitCounter,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.passwordPhraseField,
|
||||
hintText: l10n.passwordPhraseFieldHint,
|
||||
errorText: errorText,
|
||||
child: _PasteOnly(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
enabled: enabled,
|
||||
autofocus: autofocus,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
// Nothing typed here is the keyboard's to learn.
|
||||
enableIMEPersonalizedLearning: false,
|
||||
contextMenuBuilder: _PasteOnly.menu,
|
||||
minLines: minLines,
|
||||
maxLines: 5,
|
||||
maxLength: TextLimits.passage,
|
||||
buildCounter: nearLimitCounter,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.passwordPhraseField,
|
||||
hintText: l10n.passwordPhraseFieldHint,
|
||||
errorText: errorText,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -408,25 +411,28 @@ class const PhraseCheckFields({
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
for (var i = 0; i < positions.length; i++) ...[
|
||||
TextField(
|
||||
controller: controllers[i],
|
||||
enabled: enabled,
|
||||
autofocus: i == 0,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
// Nothing typed here is the keyboard's to learn.
|
||||
enableIMEPersonalizedLearning: false,
|
||||
textInputAction: i == positions.length - 1
|
||||
? TextInputAction.done
|
||||
: TextInputAction.next,
|
||||
onSubmitted: i == positions.length - 1
|
||||
? (_) => onDone?.call()
|
||||
: null,
|
||||
maxLength: TextLimits.name,
|
||||
buildCounter: nearLimitCounter,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.passwordPhraseWord(positions[i] + 1),
|
||||
errorText: i == positions.length - 1 ? errorText : null,
|
||||
_PasteOnly(
|
||||
child: TextField(
|
||||
controller: controllers[i],
|
||||
enabled: enabled,
|
||||
autofocus: i == 0,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
// Nothing typed here is the keyboard's to learn.
|
||||
enableIMEPersonalizedLearning: false,
|
||||
contextMenuBuilder: _PasteOnly.menu,
|
||||
textInputAction: i == positions.length - 1
|
||||
? TextInputAction.done
|
||||
: TextInputAction.next,
|
||||
onSubmitted: i == positions.length - 1
|
||||
? (_) => onDone?.call()
|
||||
: null,
|
||||
maxLength: TextLimits.name,
|
||||
buildCounter: nearLimitCounter,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.passwordPhraseWord(positions[i] + 1),
|
||||
errorText: i == positions.length - 1 ? errorText : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
|
@ -437,6 +443,35 @@ class const PhraseCheckFields({
|
|||
}
|
||||
}
|
||||
|
||||
/// [child], a field the recovery phrase is typed into, from which nothing
|
||||
/// is copied, cut or shared (the short reading's L3): its menu offers
|
||||
/// Paste and Select all alone ([menu], which the field takes as its
|
||||
/// `contextMenuBuilder`), and the keys that would copy or cut do nothing.
|
||||
/// The phrase is typed back to show it was kept; a copy of it would reach
|
||||
/// the clipboard's histories as one shown would (`KeepTextButtons`).
|
||||
class const _PasteOnly({required final Widget child}) extends StatelessWidget {
|
||||
/// A field's menu with Paste and Select all alone.
|
||||
static Widget menu(BuildContext context, EditableTextState state) =>
|
||||
AdaptiveTextSelectionToolbar.buttonItems(
|
||||
anchors: state.contextMenuAnchors,
|
||||
buttonItems: [
|
||||
for (final item in state.contextMenuButtonItems)
|
||||
if (item.type == ContextMenuButtonType.paste ||
|
||||
item.type == ContextMenuButtonType.selectAll)
|
||||
item,
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Actions(
|
||||
// The field's own copy and cut can be overridden from above.
|
||||
actions: <Type, Action<Intent>>{
|
||||
CopySelectionTextIntent: DoNothingAction(),
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Ten recovery codes, to keep.
|
||||
class const RecoveryCodesView({required final List<String> codes, super.key})
|
||||
extends StatelessWidget {
|
||||
|
|
@ -459,8 +494,8 @@ class const RecoveryCodesView({required final List<String> codes, super.key})
|
|||
spacing: 24,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
// Not selectable: Copy empties the clipboard again, the
|
||||
// system's own copy wouldn't.
|
||||
// Not selectable: nothing secret is copied, saved or
|
||||
// written down instead ([KeepTextButtons]).
|
||||
for (final code in codes)
|
||||
Text(
|
||||
code,
|
||||
|
|
|
|||
Loading…
Reference in a new issue