Š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:
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:sepot/core/l10n/error_messages.dart';
|
||||
import 'package:sepot/core/l10n/l10n.dart';
|
||||
import 'package:sepot/core/network/relay_client.dart';
|
||||
import 'package:sepot/core/theme/app_theme.dart';
|
||||
import 'package:sepot/providers/messaging_provider.dart';
|
||||
import 'package:sepot/providers/settings_provider.dart';
|
||||
|
||||
/// Přepínač routování přes Tor (SOCKS5/Orbot na `.onion`).
|
||||
///
|
||||
/// Zapnutí skryje IP klienta před relayem — provoz jde přes lokální
|
||||
/// Orbot. Bez běžícího Orbotu se spojení nenaváže (indikátor zůstane
|
||||
/// „připojuji"), na což uživatele upozorníme předem. Přepnutí přestaví
|
||||
/// spojení za běhu, restart appky netřeba.
|
||||
class TorSettingsTile extends ConsumerStatefulWidget {
|
||||
const TorSettingsTile({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<TorSettingsTile> createState() => _TorSettingsTileState();
|
||||
}
|
||||
|
||||
class _TorSettingsTileState extends ConsumerState<TorSettingsTile> {
|
||||
bool _busy = false;
|
||||
|
||||
Future<void> _toggle(bool value) async {
|
||||
if (value && !await _confirmOrbot()) return;
|
||||
setState(() => _busy = true);
|
||||
try {
|
||||
await ref.read(torProvider.notifier).set(value);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.torToggleFailed(describeError(context.l10n, e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _busy = false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tor přes Orbot potřebuje běžící Orbot s aktivním SOCKS proxy.
|
||||
Future<bool> _confirmOrbot() async {
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(ctx.l10n.torConnectionTitle),
|
||||
content: Text(ctx.l10n.torDialogBody),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: Text(ctx.l10n.commonCancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: Text(ctx.l10n.commonTurnOn),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return ok ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enabled = ref.watch(torProvider).valueOrNull ?? false;
|
||||
|
||||
// Stav připojení má smysl ukazovat jen když je Tor zapnutý — pomůže
|
||||
// odhalit chybějící/vypnutý Orbot (uvázne na „připojuji").
|
||||
String subtitle;
|
||||
if (!enabled) {
|
||||
subtitle = context.l10n.torSubtitleOff;
|
||||
} else {
|
||||
final conn = ref.watch(relayConnectionProvider).valueOrNull;
|
||||
subtitle = switch (conn) {
|
||||
RelayConnectionState.connected => context.l10n.torStatusConnected,
|
||||
RelayConnectionState.connecting => context.l10n.torStatusConnecting,
|
||||
_ => context.l10n.torStatusWaiting,
|
||||
};
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: SepotColors.surfaceVariant,
|
||||
),
|
||||
child: Icon(
|
||||
enabled ? Icons.vpn_lock : Icons.public,
|
||||
size: 20,
|
||||
color: enabled ? SepotColors.ember : SepotColors.textSecondary,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
context.l10n.torConnectionTitle,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
),
|
||||
subtitle: Text(
|
||||
subtitle,
|
||||
style: const TextStyle(fontSize: 13, color: SepotColors.textMuted),
|
||||
),
|
||||
trailing: _busy
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: SepotColors.ember,
|
||||
),
|
||||
)
|
||||
: Switch(
|
||||
value: enabled,
|
||||
onChanged: _toggle,
|
||||
activeTrackColor: SepotColors.ember,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user