Skip to content
Closed
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
93 changes: 18 additions & 75 deletions apps/client/lib/src/screens/settings/about_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import '../../theme/echo_theme.dart';
import '../../version.dart';
import '../../widgets/confirm_dialog.dart';
import '../../widgets/feedback_dialog.dart';
import '../../widgets/settings/settings_list_tile.dart';
import '../../widgets/input_dialog.dart';

class AboutSection extends ConsumerStatefulWidget {
Expand Down Expand Up @@ -554,52 +555,20 @@ class _AboutSectionState extends ConsumerState<AboutSection> {
const SizedBox(height: 12),
_buildServersList(),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: Icon(
Icons.add_circle_outline,
color: context.textSecondary,
size: 22,
),
title: Text(
'Add server',
style: TextStyle(color: context.textPrimary, fontSize: 15),
),
subtitle: Text(
'Verifies the URL before adding it to your list.',
style: TextStyle(color: context.textMuted, fontSize: 12),
),
trailing: Icon(
Icons.chevron_right,
color: context.textMuted,
size: 20,
),
SettingsListTile(
icon: Icons.add_circle_outline,
title: 'Add server',
subtitle: 'Verifies the URL before adding it to your list.',
onTap: _showAddServerDialog,
),
const SizedBox(height: 16),
Divider(color: context.border),
const SizedBox(height: 16),
// Debug logs entry (absorbed from former Debug section).
ListTile(
contentPadding: EdgeInsets.zero,
leading: Icon(
Icons.bug_report_outlined,
color: context.textSecondary,
size: 22,
),
title: Text(
'Debug Logs',
style: TextStyle(color: context.textPrimary, fontSize: 15),
),
subtitle: Text(
'View recent in-app log entries.',
style: TextStyle(color: context.textMuted, fontSize: 12),
),
trailing: Icon(
Icons.chevron_right,
color: context.textMuted,
size: 20,
),
SettingsListTile(
icon: Icons.bug_report_outlined,
title: 'Debug Logs',
subtitle: 'View recent in-app log entries.',
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
Expand All @@ -611,47 +580,21 @@ class _AboutSectionState extends ConsumerState<AboutSection> {
const SizedBox(height: 8),
// Beta-prep #4c: surface the feedback dialog from About so testers
// have a one-tap path to report issues without leaving the app.
ListTile(
SettingsListTile(
key: const Key('about-send-feedback'),
contentPadding: EdgeInsets.zero,
leading: Icon(
Icons.feedback_outlined,
color: context.textSecondary,
size: 22,
),
title: Text(
'Send feedback',
style: TextStyle(color: context.textPrimary, fontSize: 15),
),
subtitle: Text(
'Report a bug or share a suggestion.',
style: TextStyle(color: context.textMuted, fontSize: 12),
),
trailing: Icon(
Icons.chevron_right,
color: context.textMuted,
size: 20,
),
icon: Icons.feedback_outlined,
title: 'Send feedback',
subtitle: 'Report a bug or share a suggestion.',
onTap: () => showFeedbackDialog(context),
),
// Privacy link — opens the GitHub-rendered docs/PRIVACY.md so the
// canonical text isn't bundled in every app build.
ListTile(
SettingsListTile(
key: const Key('about-privacy-link'),
contentPadding: EdgeInsets.zero,
leading: Icon(
Icons.privacy_tip_outlined,
color: context.textSecondary,
size: 22,
),
title: Text(
'Privacy',
style: TextStyle(color: context.textPrimary, fontSize: 15),
),
subtitle: Text(
'What Echo stores, what it does not, and where the data lives.',
style: TextStyle(color: context.textMuted, fontSize: 12),
),
icon: Icons.privacy_tip_outlined,
title: 'Privacy',
subtitle:
'What Echo stores, what it does not, and where the data lives.',
trailing: Icon(
Icons.open_in_new,
color: context.textMuted,
Expand Down
16 changes: 5 additions & 11 deletions apps/client/lib/src/screens/settings/data_storage_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../../services/message_cache.dart';
import '../../services/toast_service.dart';
import '../../theme/echo_theme.dart';
import '../../widgets/confirm_dialog.dart';
import '../../widgets/settings/settings_list_tile.dart';

class DataStorageSection extends ConsumerStatefulWidget {
const DataStorageSection({super.key});
Expand Down Expand Up @@ -155,17 +156,10 @@ class _DataStorageSectionState extends ConsumerState<DataStorageSection> {
),
const Divider(height: 24),
// Cache size
ListTile(
contentPadding: EdgeInsets.zero,
leading: Icon(Icons.storage, color: context.textSecondary),
title: Text(
'Message Cache',
style: TextStyle(color: context.textPrimary, fontSize: 14),
),
subtitle: Text(
'Estimated size: $_cacheSize',
style: TextStyle(color: context.textMuted, fontSize: 12),
),
SettingsListTile(
icon: Icons.storage,
title: 'Message Cache',
subtitle: 'Estimated size: $_cacheSize',
trailing: OutlinedButton(
onPressed: _clearMessageCache,
child: const Text('Clear'),
Expand Down
74 changes: 74 additions & 0 deletions apps/client/lib/src/widgets/settings/settings_list_tile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';

import '../../theme/echo_theme.dart';

/// Standardised settings row: leading icon + title + optional subtitle +
/// optional trailing widget (defaults to a chevron when [onTap] is set).
///
/// Use this instead of building a raw [ListTile] with `contentPadding: EdgeInsets.zero`,
/// muted icon and chevron, theme-aware text styles, etc. Every settings
/// section in the app uses the same recipe; this widget bakes it in so
/// callers stay focused on what the row does.
///
/// For card-style rows with a colored icon badge (the redesigned settings
/// home list) use [CardRow] from `widgets/settings/card_row.dart` instead.
class SettingsListTile extends StatelessWidget {
/// Leading icon.
final IconData icon;

/// Primary row label.
final String title;

/// Optional supporting line shown under the title in muted text.
final String? subtitle;

/// Optional trailing widget. When omitted AND [onTap] is non-null,
/// renders a chevron-right; when both are omitted, nothing trails the row.
final Widget? trailing;

/// Tap handler. When null the row renders without a chevron and is inert.
final VoidCallback? onTap;

/// When true, renders the row in destructive style: danger color on icon
/// and title, no default chevron.
final bool destructive;

const SettingsListTile({
super.key,
required this.icon,
required this.title,
this.subtitle,
this.trailing,
this.onTap,
this.destructive = false,
});

@override
Widget build(BuildContext context) {
final iconColor = destructive ? EchoTheme.danger : context.textSecondary;
final titleColor = destructive ? EchoTheme.danger : context.textPrimary;

Widget? resolvedTrailing = trailing;
if (resolvedTrailing == null && onTap != null && !destructive) {
resolvedTrailing = Icon(
Icons.chevron_right,
color: context.textMuted,
size: 20,
);
}

return ListTile(
contentPadding: EdgeInsets.zero,
leading: Icon(icon, color: iconColor, size: 22),
title: Text(title, style: TextStyle(color: titleColor, fontSize: 15)),
subtitle: subtitle == null
? null
: Text(
subtitle!,
style: TextStyle(color: context.textMuted, fontSize: 12),
),
trailing: resolvedTrailing,
onTap: onTap,
);
}
}
Loading