5 Commits

Author SHA1 Message Date
Julius Joska d431501b74 docs(changelog): note the iOS push settings fix in 1.2.2 2026-10-11 16:12:46 +02:00
Julius Joska 6f9b08e844 fix(push): do not call UnifiedPush outside Android
UnifiedPush only exists on Android. On iOS getDistributor() and
getDistributors() throw UnimplementedError, which surfaced as an uncaught
error from the Push tile in Settings. Report an empty state instead.
2026-10-11 16:12:41 +02:00
Julius Joska 09ef54ef69 release: 1.2.2 (25)
New app icon: the Š that fades into dots. Launcher icons, adaptive icon
foreground and splash regenerated from assets/icon.
2026-10-11 15:31:53 +02:00
Julius Joska bfea7201f7 fix(chat): clear unread state for messages read in an open chat
Messages arriving while a conversation was open were never marked as read,
so the chat list kept showing them as unread after going back. The screen
now marks incoming messages read while it is on top and the app is in the
foreground, again when the app returns to the foreground, and on leaving.

Read state is now monotonic in Conversation.updateMessage, so a stale copy
of a message (attachment download, delayed resend) can no longer flip it
back to unread.

Also create the swipe-to-reply animation controller lazily instead of in
dispose().
2026-10-11 15:31:53 +02:00
Julius Joska 05eeccd83b test: tag suites that need the native libsignal library 2026-10-09 15:15:34 +02:00
82 changed files with 147 additions and 13 deletions
+15
View File
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 27 KiB

+3 -3
View File
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 571 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 26 KiB

+8 -3
View File
@@ -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();
} }
+9 -1
View File
@@ -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 -1
View File
@@ -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
+37
View File
@@ -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);
});
}
+2
View File
@@ -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';
+2
View File
@@ -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';
+2
View File
@@ -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';
+2
View File
@@ -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';
+4 -1
View File
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 32 KiB