Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d431501b74 | |||
| 6f9b08e844 | |||
| 09ef54ef69 | |||
| bfea7201f7 | |||
| 05eeccd83b |
@@ -6,6 +6,21 @@ verzování dle [ajtak standardu](#verzování).
|
|||||||
|
|
||||||
## [Nevydáno]
|
## [Nevydáno]
|
||||||
|
|
||||||
|
## [1.2.2] – 2026-10-11
|
||||||
|
|
||||||
|
### Opraveno
|
||||||
|
- **Přečtený chat se dál ukazoval jako nepřečtený.** Zpráva, která přišla do
|
||||||
|
otevřeného chatu, zůstala po návratu do seznamu tučně s odznakem; teď se
|
||||||
|
přečte hned, když je chat na obrazovce (a appka v popředí). Stažení přílohy
|
||||||
|
nebo dodatečné doručení už nevrátí přečtenou zprávu mezi nepřečtené.
|
||||||
|
- Gesto „odpověď tahem“ při zavírání chatu v ladicím buildu padalo na
|
||||||
|
vytvoření animace nad zrušeným prvkem.
|
||||||
|
- Na iPhonu hlásila dlaždice Push v Nastavení chybu (volala UnifiedPush,
|
||||||
|
který existuje jen na Androidu).
|
||||||
|
|
||||||
|
### Změněno
|
||||||
|
- **Nová ikona aplikace:** Š, které se ve spodní části rozpadá do teček.
|
||||||
|
|
||||||
## [1.2.1] – 2026-10-09
|
## [1.2.1] – 2026-10-09
|
||||||
|
|
||||||
### Opraveno
|
### Opraveno
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 7.6 KiB |
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#0a0e17</color>
|
<color name="ic_launcher_background">#0B0C12</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
Before Width: | Height: | Size: 575 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 27 KiB |
@@ -1,9 +1,9 @@
|
|||||||
# Launcher ikony — kandidát 1 „Š s tečkami" (nokturno-ikona-B2, ember paleta).
|
# Launcher icons: the "Š that fades into dots" mark (variant B, 2026-10).
|
||||||
# Zdroj: sepot-web/koncept/logo/nokturno-ikona-B2.png; regenerace:
|
# Source: assets/icon/icon-1024.png (full) and maskable-fg-512.png (adaptive foreground); regenerate with:
|
||||||
# dart run flutter_launcher_icons
|
# dart run flutter_launcher_icons
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: true
|
android: true
|
||||||
ios: true
|
ios: true
|
||||||
image_path: assets/icon/icon-1024.png
|
image_path: assets/icon/icon-1024.png
|
||||||
adaptive_icon_background: "#0a0e17"
|
adaptive_icon_background: "#0B0C12"
|
||||||
adaptive_icon_foreground: assets/icon/maskable-fg-512.png
|
adaptive_icon_foreground: assets/icon/maskable-fg-512.png
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 571 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 782 B After Width: | Height: | Size: 560 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 817 B |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
@@ -138,10 +138,14 @@ class PushService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Seznam nainstalovaných distributorů (pro výběr v nastavení).
|
/// Seznam nainstalovaných distributorů (pro výběr v nastavení).
|
||||||
Future<List<String>> distributors() => UnifiedPush.getDistributors();
|
/// UnifiedPush existuje jen na Androidu; jinde by volání pluginu vyhodilo
|
||||||
|
/// UnimplementedError (iOS: dlaždice Push v Nastavení).
|
||||||
|
Future<List<String>> distributors() async =>
|
||||||
|
Platform.isAndroid ? UnifiedPush.getDistributors() : const [];
|
||||||
|
|
||||||
/// Aktuálně uložený distributor (null = push vypnutý / nevybraný).
|
/// Aktuálně uložený distributor (null = push vypnutý / nevybraný).
|
||||||
Future<String?> currentDistributor() => UnifiedPush.getDistributor();
|
Future<String?> currentDistributor() async =>
|
||||||
|
Platform.isAndroid ? UnifiedPush.getDistributor() : null;
|
||||||
|
|
||||||
/// Vybere konkrétního distributora a zaregistruje se u něj.
|
/// Vybere konkrétního distributora a zaregistruje se u něj.
|
||||||
Future<void> useDistributor(String distributor) async {
|
Future<void> useDistributor(String distributor) async {
|
||||||
@@ -158,7 +162,7 @@ class PushService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> get isEnabled async =>
|
Future<bool> get isEnabled async =>
|
||||||
(await UnifiedPush.getDistributor()) != null;
|
Platform.isAndroid && (await UnifiedPush.getDistributor()) != null;
|
||||||
|
|
||||||
// ── Režimy (off / up / fcm) ─────────────────────────────────────────
|
// ── Režimy (off / up / fcm) ─────────────────────────────────────────
|
||||||
|
|
||||||
@@ -167,6 +171,7 @@ class PushService {
|
|||||||
Future<String> currentMode() async {
|
Future<String> currentMode() async {
|
||||||
final mode = await PushBootstrap.readMode();
|
final mode = await PushBootstrap.readMode();
|
||||||
if (mode != 'off') return mode;
|
if (mode != 'off') return mode;
|
||||||
|
if (!Platform.isAndroid) return mode;
|
||||||
return (await UnifiedPush.getDistributor()) != null ? 'up' : 'off';
|
return (await UnifiedPush.getDistributor()) != null ? 'up' : 'off';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ class ConversationScreen extends ConsumerStatefulWidget {
|
|||||||
ConsumerState<ConversationScreen> createState() => _ConversationScreenState();
|
ConsumerState<ConversationScreen> createState() => _ConversationScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ConversationScreenState extends ConsumerState<ConversationScreen> {
|
class _ConversationScreenState extends ConsumerState<ConversationScreen>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
final _messageController = TextEditingController();
|
final _messageController = TextEditingController();
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
final _searchController = TextEditingController();
|
final _searchController = TextEditingController();
|
||||||
@@ -133,9 +134,43 @@ class _ConversationScreenState extends ConsumerState<ConversationScreen> {
|
|||||||
_drafts = ref.read(draftsProvider.notifier);
|
_drafts = ref.read(draftsProvider.notifier);
|
||||||
_restoreDraft();
|
_restoreDraft();
|
||||||
_messageController.addListener(_scheduleDraftSave);
|
_messageController.addListener(_scheduleDraftSave);
|
||||||
|
_conversations = ref.read(conversationsProvider.notifier);
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _captureUnreadAndMark());
|
WidgetsBinding.instance.addPostFrameCallback((_) => _captureUnreadAndMark());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Notifier zachycený v initState: v dispose už `ref` použít nejde.
|
||||||
|
late final ConversationsNotifier _conversations;
|
||||||
|
|
||||||
|
/// Zpráva, která přijde do otevřeného chatu, je přečtená, jen když ji
|
||||||
|
/// uživatel opravdu vidí: appka v popředí a tahle obrazovka navrchu
|
||||||
|
/// (ne pod profilem kontaktu nebo náhledem fotky).
|
||||||
|
bool get _isSeenNow =>
|
||||||
|
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed &&
|
||||||
|
(_route?.isCurrent ?? false);
|
||||||
|
|
||||||
|
// Route uložená v didChangeDependencies: lifecycle callback může přijít
|
||||||
|
// i ve chvíli, kdy je element už deaktivovaný a hledat předka nesmí.
|
||||||
|
ModalRoute<Object?>? _route;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_route = ModalRoute.of(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _markIfSeen() {
|
||||||
|
if (!mounted || !_isSeenNow) return;
|
||||||
|
_conversations.markAsRead(widget.contactId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
// Zprávy, které přišly se zhasnutým displejem nad otevřeným chatem,
|
||||||
|
// uživatel uvidí až po návratu do appky.
|
||||||
|
if (state == AppLifecycleState.resumed) _markIfSeen();
|
||||||
|
}
|
||||||
|
|
||||||
/// Zapamatuje si první nepřečtenou zprávu a teprve pak konverzaci označí
|
/// Zapamatuje si první nepřečtenou zprávu a teprve pak konverzaci označí
|
||||||
/// za přečtenou — jinak by řádek „nové zprávy“ nikdy nebylo kam dát.
|
/// za přečtenou — jinak by řádek „nové zprávy“ nikdy nebylo kam dát.
|
||||||
Future<void> _captureUnreadAndMark() async {
|
Future<void> _captureUnreadAndMark() async {
|
||||||
@@ -185,6 +220,10 @@ class _ConversationScreenState extends ConsumerState<ConversationScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
// Odchod z chatu: všechno, co bylo na obrazovce, je přečtené – i zprávy
|
||||||
|
// dorazivší těsně před odchodem, které listener nestihl označit.
|
||||||
|
_conversations.markAsRead(widget.contactId);
|
||||||
_draftTimer?.cancel();
|
_draftTimer?.cancel();
|
||||||
// Odchod z chatu uprostřed úpravy = koncept je ten odložený, ne text
|
// Odchod z chatu uprostřed úpravy = koncept je ten odložený, ne text
|
||||||
// upravované zprávy.
|
// upravované zprávy.
|
||||||
@@ -214,6 +253,17 @@ class _ConversationScreenState extends ConsumerState<ConversationScreen> {
|
|||||||
.where((c) => c.publicIdentity.id == widget.contactId)
|
.where((c) => c.publicIdentity.id == widget.contactId)
|
||||||
.firstOrNull;
|
.firstOrNull;
|
||||||
final conversationAsync = ref.watch(conversationProvider(widget.contactId));
|
final conversationAsync = ref.watch(conversationProvider(widget.contactId));
|
||||||
|
// Nová příchozí zpráva v otevřeném chatu se rovnou čte; jinak by po
|
||||||
|
// návratu do seznamu svítila jako nepřečtená.
|
||||||
|
// První načtení vynechá: to obslouží _captureUnreadAndMark, které si
|
||||||
|
// nejdřív zapamatuje řádek „N nových zpráv“.
|
||||||
|
ref.listen(conversationProvider(widget.contactId), (prev, next) {
|
||||||
|
if (prev?.valueOrNull == null) return;
|
||||||
|
final conv = next.valueOrNull;
|
||||||
|
final myId = ref.read(identityProvider).valueOrNull?.id;
|
||||||
|
if (conv == null || myId == null || conv.unreadCount(myId) == 0) return;
|
||||||
|
_markIfSeen();
|
||||||
|
});
|
||||||
final identity = ref.watch(identityProvider).valueOrNull;
|
final identity = ref.watch(identityProvider).valueOrNull;
|
||||||
// Skupinová konverzace: contactId == gid
|
// Skupinová konverzace: contactId == gid
|
||||||
final group = ref.watch(groupProvider(widget.contactId)).valueOrNull;
|
final group = ref.watch(groupProvider(widget.contactId)).valueOrNull;
|
||||||
@@ -2150,7 +2200,11 @@ class _SwipeToReplyState extends State<_SwipeToReply>
|
|||||||
static const _max = 84.0;
|
static const _max = 84.0;
|
||||||
static const _trigger = 58.0;
|
static const _trigger = 58.0;
|
||||||
|
|
||||||
late final AnimationController _back = AnimationController(
|
// Kontroler vzniká až při prvním tahu. Dřív ho `late final` vytvořil
|
||||||
|
// teprve dispose() u bublin, které nikdo netáhl – nad deaktivovaným
|
||||||
|
// elementem, což v debug buildu padá na hledání TickerMode.
|
||||||
|
AnimationController? _backCtl;
|
||||||
|
AnimationController get _back => _backCtl ??= AnimationController(
|
||||||
vsync: this, duration: const Duration(milliseconds: 180))
|
vsync: this, duration: const Duration(milliseconds: 180))
|
||||||
..addListener(() => setState(() => _dx = _from * (1 - _back.value)));
|
..addListener(() => setState(() => _dx = _from * (1 - _back.value)));
|
||||||
double _dx = 0;
|
double _dx = 0;
|
||||||
@@ -2159,7 +2213,7 @@ class _SwipeToReplyState extends State<_SwipeToReply>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_back.dispose();
|
_backCtl?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,17 @@ class Conversation {
|
|||||||
|
|
||||||
/// Nahraď zprávu se stejným id (změna stavu přílohy, doručenka…).
|
/// Nahraď zprávu se stejným id (změna stavu přílohy, doručenka…).
|
||||||
/// Neexistující id se tiše ignoruje (zpráva mohla být smazána).
|
/// Neexistující id se tiše ignoruje (zpráva mohla být smazána).
|
||||||
|
///
|
||||||
|
/// Přečtení je jednosměrné: aktualizace nesmí vrátit už přečtenou zprávu
|
||||||
|
/// mezi nepřečtené. Volající často drží kopii zprávy načtenou před
|
||||||
|
/// otevřením chatu (stahování přílohy, dodatečné odeslání) a bez téhle
|
||||||
|
/// pojistky by zápis po návratu ze stahování obnovil odznak nepřečtených.
|
||||||
void updateMessage(Message message) {
|
void updateMessage(Message message) {
|
||||||
final index = messages.indexWhere((m) => m.id == message.id);
|
final index = messages.indexWhere((m) => m.id == message.id);
|
||||||
if (index >= 0) messages[index] = message;
|
if (index < 0) return;
|
||||||
|
messages[index] = messages[index].isRead && !message.isRead
|
||||||
|
? message.copyWith(isRead: true)
|
||||||
|
: message;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Označ všechny zprávy jako přečtené
|
/// Označ všechny zprávy jako přečtené
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: sepot
|
name: sepot
|
||||||
description: "Šepot — Soukromý messenger s E2E šifrováním"
|
description: "Šepot — Soukromý messenger s E2E šifrováním"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.2.1+24
|
version: 1.2.2+25
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.10.8
|
sdk: ^3.10.8
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:sepot/models/conversation.dart';
|
||||||
|
import 'package:sepot/models/message.dart';
|
||||||
|
|
||||||
|
/// Read state only moves forward. Updates that carry an older copy of the
|
||||||
|
/// message (attachment download finishing, delivery status) must not flip a
|
||||||
|
/// message the user has already read back to unread.
|
||||||
|
void main() {
|
||||||
|
Message zprava({bool isRead = false}) => Message(
|
||||||
|
id: 'm1',
|
||||||
|
senderId: 'alice',
|
||||||
|
recipientId: 'bob',
|
||||||
|
type: MessageType.text,
|
||||||
|
content: 'hi',
|
||||||
|
timestamp: DateTime.parse('2026-10-10T09:00:00.000'),
|
||||||
|
isRead: isRead,
|
||||||
|
);
|
||||||
|
|
||||||
|
test('stale unread copy does not undo a read message', () {
|
||||||
|
final c = Conversation(contactId: 'alice')..addMessage(zprava());
|
||||||
|
c.markAllRead('bob');
|
||||||
|
|
||||||
|
c.updateMessage(zprava().copyWith(content: 'hi (edited)'));
|
||||||
|
|
||||||
|
expect(c.messages.single.isRead, isTrue);
|
||||||
|
expect(c.messages.single.content, 'hi (edited)');
|
||||||
|
expect(c.unreadCount('bob'), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update can still mark an unread message as read', () {
|
||||||
|
final c = Conversation(contactId: 'alice')..addMessage(zprava());
|
||||||
|
|
||||||
|
c.updateMessage(zprava(isRead: true));
|
||||||
|
|
||||||
|
expect(c.unreadCount('bob'), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Needs the native libsignal library (FFI); skipped on builders without it (--exclude-tags ffi).
|
||||||
|
@Tags(['ffi'])
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Needs the native libsignal library (FFI); skipped on builders without it (--exclude-tags ffi).
|
||||||
|
@Tags(['ffi'])
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Needs the native libsignal library (FFI); skipped on builders without it (--exclude-tags ffi).
|
||||||
|
@Tags(['ffi'])
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Needs the native libsignal library (FFI); skipped on builders without it (--exclude-tags ffi).
|
||||||
|
@Tags(['ffi'])
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Needs the native libsignal library (FFI); skipped on builders without it (--exclude-tags ffi).
|
||||||
|
@Tags(['ffi'])
|
||||||
// Argon2id (64 MiB) ×6 pod zátěží celé sady: víc než výchozích 30 s.
|
// Argon2id (64 MiB) ×6 pod zátěží celé sady: víc než výchozích 30 s.
|
||||||
@Timeout(Duration(minutes: 3))
|
@Timeout(Duration(minutes: 3))
|
||||||
library;
|
library;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Needs the native libsignal library (FFI); skipped on builders without it (--exclude-tags ffi).
|
||||||
|
@Tags(['ffi'])
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
<title>sepot</title>
|
<title>sepot</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
|
|
||||||
|
|
||||||
|
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
||||||
<style id="splash-screen-style">
|
<style id="splash-screen-style">
|
||||||
html {
|
html {
|
||||||
height: 100%
|
height: 100%
|
||||||
@@ -100,7 +103,6 @@
|
|||||||
document.body.style.background = "transparent";
|
document.body.style.background = "transparent";
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<picture id="splash">
|
<picture id="splash">
|
||||||
@@ -108,6 +110,7 @@
|
|||||||
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
|
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
|
||||||
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt="">
|
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt="">
|
||||||
</picture>
|
</picture>
|
||||||
|
|
||||||
<script src="flutter_bootstrap.js" async=""></script>
|
<script src="flutter_bootstrap.js" async=""></script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 32 KiB |