Šepot — end-to-end encrypted messenger

Initial public release. Anonymous on-device identity (no phone number,
e-mail, or password), Signal-protocol cryptography (X3DH + PQXDH, Double
Ratchet, sealed sender) via libsignal, a metadata-minimizing relay,
group chats, disappearing messages, encrypted local storage, optional
Tor transport, and a UI localized into Czech, English, German, Russian,
and Italian.

Monorepo: Flutter client (app/), Rust relay + wire types (rust/),
protobuf definitions (proto/), self-host deploy (deploy/).

Licensed under AGPL-3.0 with an app-store distribution exception (§7).
This commit is contained in:
Julius Joska
2026-07-11 17:06:39 +02:00
commit 8081d98c67
309 changed files with 44102 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
import 'dart:ui';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
/// Ruční volba jazyka aplikace. `null` = řídit se systémem.
///
/// Hodnota žije v OS keystore (NE v SQLCipher DB) — PIN lock a onboarding
/// se zobrazují před odemčením úložiště a musí už být ve zvoleném jazyce.
/// Jazyk není citlivý údaj; keystore je tu jen kvůli dostupnosti před
/// unlockem (stejný vzor jako režim pushů v PushBootstrap).
class LocaleNotifier extends AsyncNotifier<Locale?> {
static const _key = 'app_locale';
static const _store = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);
@override
Future<Locale?> build() => readStored();
/// Přečte uloženou volbu i bez Riverpodu — pro headless start (push
/// budíček skládá notifikaci před postavením widget tree).
static Future<Locale?> readStored() async {
try {
final code = await _store.read(key: _key);
return (code == null || code.isEmpty) ? null : Locale(code);
} catch (_) {
// Keystore nedostupný (např. těsně po restartu) → systémový jazyk.
return null;
}
}
/// Nastaví jazyk aplikace; `null` vrátí řízení systému.
Future<void> set(Locale? locale) async {
if (locale == null) {
await _store.delete(key: _key);
} else {
await _store.write(key: _key, value: locale.languageCode);
}
state = AsyncData(locale);
}
}
final localeProvider =
AsyncNotifierProvider<LocaleNotifier, Locale?>(LocaleNotifier.new);