feat: encrypted-history prompt, incoming-call alerts, message edit, panic button (v1.7.0+11)
P1 key_restore_prompt: offer recovery-key restore right after login P2 incoming calls: mount overlay in app.dart + detect MSC3401 call.member (Element X signalling) — calls were previously invisible P3 message edit: editMessage/EditMessage + Edit dialog (reactions/delete were already wired) P5 panic button: new feature/panic module — hold-to-send alert text + m.location to a configured room; alert sent before geolocation so a denied permission never blocks it All verified end-to-end in headless Chromium against production build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
80
lib/features/profile/presentation/key_restore_prompt.dart
Normal file
80
lib/features/profile/presentation/key_restore_prompt.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
// Version: 1.0.0 | Created: 2026-07-04
|
||||
// Post-login prompt: if the account has secret storage (key backup) and this
|
||||
// fresh device cannot read encrypted history yet, offer the recovery-key
|
||||
// restore straight away instead of leaving it buried in Profile > Security.
|
||||
//
|
||||
// Shown at most once per login session. "Later" keeps the old behaviour
|
||||
// (restore manually from the Profile tab).
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'key_restore_dialog.dart';
|
||||
|
||||
bool _promptedThisSession = false;
|
||||
|
||||
/// Reset when a new login happens (called from the login flow if needed).
|
||||
void resetKeyRestorePrompt() => _promptedThisSession = false;
|
||||
|
||||
/// Checks whether the freshly logged-in session needs a key restore and,
|
||||
/// if so, asks the user. Safe to call multiple times; only fires once.
|
||||
Future<void> maybePromptKeyRestore(BuildContext context, Client client) async {
|
||||
if (_promptedThisSession) return;
|
||||
_promptedThisSession = true;
|
||||
|
||||
try {
|
||||
// Wait for the first sync so account data (secret storage config) exists.
|
||||
if (client.prevBatch == null) {
|
||||
await client.onSync.stream.first.timeout(const Duration(seconds: 30));
|
||||
}
|
||||
} on TimeoutException {
|
||||
debugPrint('[KeyRestorePrompt] first sync timed out — skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
final enc = client.encryption;
|
||||
if (enc == null) return;
|
||||
|
||||
// No secret storage on the account → nothing to restore from.
|
||||
final hasSecretStorage =
|
||||
client.accountData.containsKey('m.secret_storage.default_key');
|
||||
if (!hasSecretStorage) return;
|
||||
|
||||
// Keys already cached (or session verified) → nothing to do.
|
||||
var cached = false;
|
||||
try {
|
||||
cached = await enc.keyManager.isCached();
|
||||
} on Exception catch (e) {
|
||||
debugPrint('[KeyRestorePrompt] isCached failed: $e');
|
||||
}
|
||||
if (cached && !client.isUnknownSession) return;
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
final wantsRestore = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Restore encrypted messages?'),
|
||||
content: const Text(
|
||||
'This device cannot read your older encrypted messages yet. '
|
||||
'Enter your recovery key now to unlock them, or do it later '
|
||||
'from Profile > Security & Privacy.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(false),
|
||||
child: const Text('Later'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
child: const Text('Enter recovery key'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (wantsRestore != true || !context.mounted) return;
|
||||
await showKeyRestoreDialog(context, client);
|
||||
}
|
||||
Reference in New Issue
Block a user