90a1390716
Fixes reported by testers and found in an app review: - First message right after install no longer fails. Before the app finished registering with the relay, the outgoing message could crash on a null delivery address. The profile field is now optional and the send path throws a clear "still connecting, try again shortly" error while registration completes in the background. - The QR code and share link now appear only once the delivery address is ready. Previously a link generated before the first connection lacked routing, so the other side could not send the first message. The screen now shows a connecting state and reactively swaps in the QR when ready. - Incomplete contact links are rejected on import instead of failing at first send, with a clear prompt to ask for a fresh link. - Auto-lock no longer silences the app: after locking and unlocking, messaging is restarted (it previously stayed dead until app restart). - Group messages are marked failed on a non-per-member send error instead of hanging in the "sending" state forever. - Hardened the relay client against a rare duplicate connection when toggling Tor or locking mid-connect, and fromShareString now rejects a delivery address of the wrong length. - Pin unifiedpush below 6.2.0 to drop the webcrypto dependency (its native framework lacks LC_ENCRYPTION_INFO and blocked the iOS upload). - Skip Android release lint (network dependency check hangs on the build host and adds no value for a Flutter app).
77 lines
2.8 KiB
Dart
77 lines
2.8 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:sepot/core/errors.dart';
|
|
import 'package:sepot/models/contact.dart';
|
|
import 'package:sepot/models/identity.dart';
|
|
import 'package:sepot/providers/storage_provider.dart';
|
|
|
|
/// Stav seznamu kontaktů
|
|
class ContactsNotifier extends AsyncNotifier<List<Contact>> {
|
|
@override
|
|
Future<List<Contact>> build() async {
|
|
final storage = ref.read(storageProvider);
|
|
return storage.loadContacts();
|
|
}
|
|
|
|
/// Přidej kontakt z veřejné identity
|
|
Future<Contact> addContact(PublicIdentity identity, {bool verified = false}) async {
|
|
final storage = ref.read(storageProvider);
|
|
final contacts = await future;
|
|
|
|
// Odkaz bez routingu (aid+da) = protistrana ho vytvořila dřív, než měla
|
|
// hotovou registraci. Přidat takový kontakt = časovaná bomba: první
|
|
// odeslání spadne na „nemá doručovací adresu". Odmítnout hned s
|
|
// jasnou hláškou. (Nullable routing má smysl jen na interních příjmových
|
|
// cestách — roster/_findOrCreateContact —, ne při ručním importu.)
|
|
if (identity.accountId == null || identity.deliveryAddress == null) {
|
|
throw const ContactLinkIncompleteError();
|
|
}
|
|
|
|
// Kontrola duplicity
|
|
if (contacts.any((c) => c.publicIdentity.id == identity.id)) {
|
|
throw const ContactAlreadyExistsError();
|
|
}
|
|
|
|
final contact = Contact.fromPublicIdentity(identity, verified: verified);
|
|
await storage.saveContact(contact);
|
|
|
|
state = AsyncData([...contacts, contact]);
|
|
return contact;
|
|
}
|
|
|
|
/// Smaž kontakt
|
|
Future<void> removeContact(String contactId) async {
|
|
final storage = ref.read(storageProvider);
|
|
await storage.deleteContact(contactId);
|
|
|
|
final contacts = await future;
|
|
state = AsyncData(contacts.where((c) => c.id != contactId).toList());
|
|
}
|
|
|
|
// Pozn.: dřívější verifyContact() (slepé přepnutí flagu) odstraněna —
|
|
// ověření jde VÝHRADNĚ přes safety number (ContactVerifyScreen).
|
|
|
|
/// Zablokuj / odblokuj kontakt. Blokovaný: příchozí zprávy se zahodí,
|
|
/// odesílání je v UI zakázané.
|
|
Future<void> setBlocked(String contactId, bool blocked) async {
|
|
final storage = ref.read(storageProvider);
|
|
final contacts = await future;
|
|
final contact = contacts.where((c) => c.id == contactId).firstOrNull;
|
|
if (contact == null) return;
|
|
contact.blocked = blocked;
|
|
await storage.saveContact(contact);
|
|
state = AsyncData([...contacts]);
|
|
}
|
|
|
|
/// Najdi kontakt podle identity ID
|
|
Contact? findByIdentityId(String identityId) {
|
|
final contacts = state.valueOrNull;
|
|
if (contacts == null) return null;
|
|
return contacts.where((c) => c.publicIdentity.id == identityId).firstOrNull;
|
|
}
|
|
}
|
|
|
|
final contactsProvider =
|
|
AsyncNotifierProvider<ContactsNotifier, List<Contact>>(
|
|
ContactsNotifier.new,
|
|
);
|