641d70b76e
Settings crashed with a TypeError: the profile card passed a SepotFingerprint object where the localized string formatter expects a String (hidden behind a `dynamic` parameter). Also fixes RangeError in avatar initials (empty names, double spaces) and switches initials to grapheme clusters so emoji don't get split. New photo preview screen: a captured or picked photo no longer sends immediately — it opens in a preview with freehand drawing (7 colours, undo), draggable text overlay, 90° rotation and a caption field. Nothing leaves the device until Send is tapped. Incoming-content hardening: decryption and content processing are now separated in the delivery path. Once an envelope is decrypted the ratchet has advanced, so redelivery can never help — permanently malformed content (FormatException/TypeError) is acked and dropped instead of poisoning the queue on every reconnect. Attachment metadata now validates base64 of the crypto fields up front, and a corrupt inline thumbnail no longer breaks bubble rendering. PNG without real transparency (screenshots, edited photos) is now re-encoded as JPEG — several times smaller on the wire. Tests: 116 (new widget tests for Settings and the photo preview, AttachmentMeta wire validation).
60 lines
2.0 KiB
Dart
60 lines
2.0 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:sepot/features/settings/settings_screen.dart';
|
|
import 'package:sepot/l10n/generated/app_localizations.dart';
|
|
import 'package:sepot/models/identity.dart';
|
|
import 'package:sepot/providers/identity_provider.dart';
|
|
|
|
/// Obrazovka Nastavení se musí vykreslit bez výjimky v build —
|
|
/// pád tady znamená pro uživatele „Tahle obrazovka se nevykreslila".
|
|
class _FakeIdentityNotifier extends IdentityNotifier {
|
|
_FakeIdentityNotifier(this._identity);
|
|
final LocalIdentity? _identity;
|
|
|
|
@override
|
|
Future<LocalIdentity?> build() async => _identity;
|
|
}
|
|
|
|
LocalIdentity _identityWithName(String name) => LocalIdentity(
|
|
displayName: name,
|
|
identityPrivateKey: Uint8List(32),
|
|
identityPublicKey: Uint8List(33),
|
|
createdAt: DateTime.utc(2026, 1, 1),
|
|
);
|
|
|
|
Widget _app(LocalIdentity? identity) => ProviderScope(
|
|
overrides: [
|
|
identityProvider.overrideWith(() => _FakeIdentityNotifier(identity)),
|
|
],
|
|
child: MaterialApp(
|
|
locale: const Locale('cs'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: const SettingsScreen(),
|
|
),
|
|
);
|
|
|
|
void main() {
|
|
testWidgets('vykreslí se s běžnou identitou', (tester) async {
|
|
await tester.pumpWidget(_app(_identityWithName('Alice')));
|
|
await tester.pump();
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.text('Alice'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('vykreslí se s prázdným jménem identity', (tester) async {
|
|
await tester.pumpWidget(_app(_identityWithName('')));
|
|
await tester.pump();
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('vykreslí se bez identity', (tester) async {
|
|
await tester.pumpWidget(_app(null));
|
|
await tester.pump();
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|