feat: introduce Local Filters / Remote Filters terminology (#109)

- Rename 'Local email filters' → 'Local Filters' and 'Server email
  filters' → 'Remote Filters' in AppBar titles
- Update banner text on each filter page to focus on the current type
  and mention that the other type exists separately
- Add 'Remote Filters' and 'Local Filters' as two distinct drawer
  entries so both types are discoverable from the navigation
- Add widget tests verifying titles and banner text for both pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas SharedInbox
2026-05-16 01:49:11 +02:00
co-authored by Claude Sonnet 4.6
parent 67880929bc
commit dc8c1cb08d
3 changed files with 93 additions and 8 deletions
+7 -6
View File
@@ -138,7 +138,7 @@ class _SieveScriptsScreenState extends ConsumerState<SieveScriptsScreen> {
return Scaffold(
appBar: AppBar(
title: Text(
widget.isLocal ? 'Local email filters' : 'Server email filters',
widget.isLocal ? 'Local Filters' : 'Remote Filters',
),
),
body: _buildBody(),
@@ -178,7 +178,7 @@ class _SieveScriptsScreenState extends ConsumerState<SieveScriptsScreen> {
Expanded(
child: scripts.isEmpty
? const Center(
child: Text('No Sieve scripts. Tap + to create one.'),
child: Text('No filters yet. Tap + to create one.'),
)
: RefreshIndicator(
onRefresh: _load,
@@ -208,10 +208,11 @@ class _SieveSourceBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
final text = isLocal
? 'These scripts run locally on this device. '
'Server email filters are separate and independent.'
: 'These scripts run on the mail server (ManageSieve / JMAP). '
'Local email filters are separate and independent.';
? 'Local Filters run Sieve scripts directly on this device. '
'Remote Filters, which run on the mail server, are configured separately.'
: 'Remote Filters run Sieve scripts on the mail server '
'(ManageSieve or JMAP). '
'Local Filters, which run on this device, are configured separately.';
return Container(
width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHighest,
+10 -2
View File
@@ -70,13 +70,21 @@ class FolderDrawer extends ConsumerWidget {
},
),
ListTile(
leading: const Icon(Icons.filter_list),
title: const Text('Email filters'),
leading: const Icon(Icons.dns),
title: const Text('Remote Filters'),
onTap: () {
Navigator.pop(context);
unawaited(context.push('/accounts/$accountId/sieve'));
},
),
ListTile(
leading: const Icon(Icons.phone_android),
title: const Text('Local Filters'),
onTap: () {
Navigator.pop(context);
unawaited(context.push('/accounts/$accountId/sieve/local'));
},
),
const Divider(height: 1),
Expanded(
child: StreamBuilder(
@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:sharedinbox/core/models/sieve_script.dart';
import 'package:sharedinbox/data/db/local_sieve_repository.dart';
import 'package:sharedinbox/data/jmap/sieve_repository.dart';
import 'package:sharedinbox/di.dart';
import 'package:sharedinbox/ui/screens/sieve_scripts_screen.dart';
import '../unit/db_test_helper.dart';
import 'helpers.dart';
class _FakeSieveRepository extends SieveRepository {
_FakeSieveRepository() : super(FakeAccountRepository(), http.Client());
@override
Future<List<SieveScript>> listScripts(String accountId) async => [];
}
void main() {
configureSqliteForTests();
testWidgets('Remote Filters page shows correct title and banner', (
tester,
) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
sieveRepositoryProvider.overrideWith(
(ref) => _FakeSieveRepository(),
),
],
child: const MaterialApp(
home: SieveScriptsScreen(accountId: 'acc-1'),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Remote Filters'), findsOneWidget);
expect(
find.textContaining('Remote Filters run Sieve scripts'),
findsOneWidget,
);
expect(find.textContaining('Local Filters'), findsOneWidget);
});
testWidgets('Local Filters page shows correct title and banner', (
tester,
) async {
final db = openTestDatabase();
addTearDown(db.close);
await tester.pumpWidget(
ProviderScope(
overrides: [
localSieveRepositoryProvider.overrideWith(
(ref) => LocalSieveRepository(db),
),
],
child: const MaterialApp(
home: SieveScriptsScreen(accountId: 'acc-1', isLocal: true),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Local Filters'), findsOneWidget);
expect(
find.textContaining('Local Filters run Sieve scripts'),
findsOneWidget,
);
expect(find.textContaining('Remote Filters'), findsOneWidget);
});
}