Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions lib/features/about/screens/about_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class AboutScreen extends StatelessWidget {
Clipboard.setData(
const ClipboardData(text: 'https://mostro.network/docs'),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Link copied to clipboard'),
duration: Duration(seconds: 2),
),
);
},
),
),
Expand All @@ -41,8 +47,8 @@ class AboutScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
final c = colors!;
if (colors == null) throw StateError('AppColors theme extension must be registered');
final c = colors;

return Scaffold(
appBar: AppBar(
Expand Down
23 changes: 23 additions & 0 deletions lib/features/account/providers/privacy_mode_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';

/// In-memory privacy mode flag.
///
/// Wraps `get_privacy_mode()` / `set_privacy_mode()` from the Rust reputation
/// API. UI reads and writes go through this provider so widgets can react
/// to changes without polling.
///
/// TODO(bridge): initialise from `get_privacy_mode()` once the bridge is wired
/// (Phase 18+).
final privacyModeProvider = StateNotifierProvider<PrivacyModeNotifier, bool>(
(ref) => PrivacyModeNotifier(),
);

class PrivacyModeNotifier extends StateNotifier<bool> {
PrivacyModeNotifier() : super(false);

/// Set privacy mode to [enabled] and propagate to the Rust layer.
void setPrivacyMode(bool enabled) {
state = enabled;
// TODO(bridge): call set_privacy_mode(enabled) via FFI (Phase 18+).
}
}
70 changes: 36 additions & 34 deletions lib/features/account/screens/account_screen.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';

import 'package:mostro/core/app_routes.dart';
import 'package:mostro/core/app_theme.dart';
import 'package:mostro/features/account/providers/backup_reminder_provider.dart';
import 'package:mostro/features/account/providers/privacy_mode_provider.dart';

/// Account screen — Route `/key_management`.
///
Expand All @@ -21,9 +24,6 @@ class _AccountScreenState extends ConsumerState<AccountScreen> {
List<String>? _words;
bool _loadingWords = false;

// Privacy mode — placeholder until settings provider is wired in Phase 6
bool _privacyMode = false;

String _maskPhrase(List<String> words) {
if (words.length < 4) return words.join(' ');
final first = words.take(2).join(' ');
Expand Down Expand Up @@ -66,6 +66,7 @@ class _AccountScreenState extends ConsumerState<AccountScreen> {
final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230);
final inputBg = colors?.backgroundInput ?? const Color(0xFF252A3A);
final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6);
final privacyMode = ref.watch(privacyModeProvider);

return Scaffold(
appBar: AppBar(
Expand Down Expand Up @@ -176,33 +177,29 @@ class _AccountScreenState extends ConsumerState<AccountScreen> {
style: theme.textTheme.bodySmall!.copyWith(color: textSec),
),
const SizedBox(height: AppSpacing.md),
Opacity(
opacity: 0.5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_PrivacyOption(
title: 'Reputation Mode',
subtitle: 'Standard privacy with reputation',
selected: !_privacyMode,
green: green,
onTap: null,
),
const SizedBox(height: AppSpacing.sm),
_PrivacyOption(
title: 'Full Privacy Mode',
subtitle: 'Maximum anonymity',
selected: _privacyMode,
green: green,
onTap: null,
),
],
),
),
const SizedBox(height: AppSpacing.sm),
Text(
'Coming soon',
style: theme.textTheme.bodySmall!.copyWith(color: textSec),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_PrivacyOption(
title: 'Reputation Mode',
subtitle: 'Standard privacy with reputation',
selected: !privacyMode,
green: green,
onTap: () => ref
.read(privacyModeProvider.notifier)
.setPrivacyMode(false),
),
const SizedBox(height: AppSpacing.sm),
_PrivacyOption(
title: 'Full Privacy Mode',
subtitle: 'Maximum anonymity',
selected: privacyMode,
green: green,
onTap: () => ref
.read(privacyModeProvider.notifier)
.setPrivacyMode(true),
),
],
),
],
),
Expand Down Expand Up @@ -286,18 +283,23 @@ class _AccountScreenState extends ConsumerState<AccountScreen> {
builder: (_) => AlertDialog(
title: const Text('Generate New User?'),
content: const Text(
'This will permanently replace your current identity. '
'Make sure you have backed up your current secret words.',
'This will create a brand-new identity. Your current secret words '
'will no longer work — make sure they are backed up before continuing.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () {
onPressed: () async {
Navigator.pop(context);
// TODO: wire to create_identity() Rust bridge in Phase 5.
// TODO(bridge): call create_identity() via FFI (Phase 18+).
await ref
.read(backupReminderProvider.notifier)
.showBackupReminder();
if (!context.mounted) return;
context.go(AppRoute.walkthrough);
},
child: const Text('Continue'),
),
Expand Down
6 changes: 2 additions & 4 deletions lib/features/chat/screens/chat_room_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ class _ChatRoomScreenState extends ConsumerState<ChatRoomScreen> {

final room = _resolveRoom();
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');

return Scaffold(
// Keyboard avoidance is handled manually via viewInsets.bottom padding
Expand Down Expand Up @@ -227,8 +226,7 @@ class _AppBarTitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

return Column(
Expand Down
3 changes: 1 addition & 2 deletions lib/features/chat/screens/chat_rooms_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ class ChatRoomsScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

return DefaultTabController(
Expand Down
3 changes: 1 addition & 2 deletions lib/features/chat/widgets/chat_list_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ class ChatListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

final contextLine = room.isSelling
Expand Down
3 changes: 1 addition & 2 deletions lib/features/chat/widgets/encrypted_file_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ class _EncryptedFileMessageState extends State<EncryptedFileMessage> {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

final icon = _iconForMime(widget.mimeType);
Expand Down
3 changes: 1 addition & 2 deletions lib/features/chat/widgets/encrypted_image_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ class EncryptedImageMessage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

return GestureDetector(
Expand Down
6 changes: 2 additions & 4 deletions lib/features/chat/widgets/info_panels.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ class TradeInformationTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

return Card(
Expand Down Expand Up @@ -135,8 +134,7 @@ class UserInformationTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

return Card(
Expand Down
6 changes: 2 additions & 4 deletions lib/features/chat/widgets/message_bubble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ class MessageBubble extends StatelessWidget {
}

final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

final isMine = message.isMine;
Expand Down Expand Up @@ -202,8 +201,7 @@ class _SystemMessage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

return Padding(
Expand Down
3 changes: 1 addition & 2 deletions lib/features/chat/widgets/message_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ class _MessageInputState extends State<MessageInput> {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');

return Container(
decoration: BoxDecoration(
Expand Down
3 changes: 1 addition & 2 deletions lib/features/disputes/screens/dispute_chat_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ class _DisputeChatScreenState extends ConsumerState<DisputeChatScreen> {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');

final dispute = ref.watch(disputeByIdProvider(widget.disputeId));

Expand Down
3 changes: 1 addition & 2 deletions lib/features/disputes/widgets/dispute_list_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ class DisputeListItem extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final textTheme = Theme.of(context).textTheme;

final (statusBg, statusFg, statusLabel) = _statusChip(dispute.status);
Expand Down
3 changes: 1 addition & 2 deletions lib/features/disputes/widgets/dispute_message_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ class _DisputeMessageInputState extends State<DisputeMessageInput> {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');
final l10n = AppLocalizations.of(context);

return Container(
Expand Down
3 changes: 1 addition & 2 deletions lib/features/disputes/widgets/dispute_messages_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ class _DisputeMessagesListState extends State<DisputeMessagesList> {
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');

// Deduplicate by nostrEventId where present.
final seen = <String>{};
Expand Down
3 changes: 1 addition & 2 deletions lib/features/disputes/widgets/disputes_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ class DisputesList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final colors = Theme.of(context).extension<AppColors>();
assert(colors != null, 'AppColors theme extension must be registered');
if (colors == null) return const SizedBox.shrink();
if (colors == null) throw StateError('AppColors theme extension must be registered');

final disputesAsync = ref.watch(userDisputeDataProvider);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class SembastNotificationsStore {
// lose all notifications on page reload (notificationsProviderWithDb
// regresses to in-memory-only behavior). Fix: add sembast_web, switch
// to databaseFactoryWeb, remove the sembast_memory import.
debugPrint(
'[notifications] WARNING: Using in-memory DB on web — '
'notifications will not persist across reloads. '
'Add sembast_web to pubspec.yaml to fix.',
);
db = await databaseFactoryMemory.openDatabase(_dbName);
} else {
final dir = await getApplicationDocumentsDirectory();
Expand Down
Loading