Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef6583de18 |
@@ -15,6 +15,10 @@ abstract class EmailRepository {
|
|||||||
int limit = 50,
|
int limit = 50,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Returns threads from the INBOX of every account, sorted by latest date.
|
||||||
|
/// Inbox mailboxes are matched by role = 'inbox'.
|
||||||
|
Stream<List<EmailThread>> observeAllInboxThreads({int limit = 50});
|
||||||
|
|
||||||
/// Returns all emails belonging to [threadId] in [mailboxPath].
|
/// Returns all emails belonging to [threadId] in [mailboxPath].
|
||||||
Stream<List<Email>> observeEmailsInThread(
|
Stream<List<Email>> observeEmailsInThread(
|
||||||
String accountId,
|
String accountId,
|
||||||
|
|||||||
@@ -95,6 +95,26 @@ class EmailRepositoryImpl implements EmailRepository {
|
|||||||
.map((rows) => rows.map(_threadRowToModel).toList());
|
.map((rows) => rows.map(_threadRowToModel).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<model.EmailThread>> observeAllInboxThreads({int limit = 50}) {
|
||||||
|
final query = _db.select(_db.threads).join([
|
||||||
|
innerJoin(
|
||||||
|
_db.mailboxes,
|
||||||
|
_db.mailboxes.accountId.equalsExp(_db.threads.accountId) &
|
||||||
|
_db.mailboxes.path.equalsExp(_db.threads.mailboxPath),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
query
|
||||||
|
..where(_db.mailboxes.role.equals('inbox'))
|
||||||
|
..orderBy([OrderingTerm.desc(_db.threads.latestDate)])
|
||||||
|
..limit(limit);
|
||||||
|
return query.watch().map(
|
||||||
|
(rows) => rows
|
||||||
|
.map((row) => _threadRowToModel(row.readTable(_db.threads)))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
model.EmailThread _threadRowToModel(ThreadRow row) {
|
model.EmailThread _threadRowToModel(ThreadRow row) {
|
||||||
List<model.EmailAddress> parseAddresses(String json) {
|
List<model.EmailAddress> parseAddresses(String json) {
|
||||||
final list = jsonDecode(json) as List<dynamic>;
|
final list = jsonDecode(json) as List<dynamic>;
|
||||||
|
|||||||
@@ -239,6 +239,10 @@ class EmailDetailNotifier extends AsyncNotifier<(Email?, EmailBody)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final allAccountsProvider = StreamProvider<List<model.Account>>((ref) {
|
||||||
|
return ref.watch(accountRepositoryProvider).observeAccounts();
|
||||||
|
});
|
||||||
|
|
||||||
final accountByIdProvider =
|
final accountByIdProvider =
|
||||||
StreamProvider.autoDispose.family<model.Account?, String>((ref, accountId) {
|
StreamProvider.autoDispose.family<model.Account?, String>((ref, accountId) {
|
||||||
return ref.watch(accountRepositoryProvider).observeAccounts().map(
|
return ref.watch(accountRepositoryProvider).observeAccounts().map(
|
||||||
|
|||||||
+6
-1
@@ -4,6 +4,7 @@ import 'package:sharedinbox/core/models/sieve_script.dart';
|
|||||||
|
|
||||||
import 'package:sharedinbox/ui/screens/about_screen.dart';
|
import 'package:sharedinbox/ui/screens/about_screen.dart';
|
||||||
import 'package:sharedinbox/ui/screens/account_list_screen.dart';
|
import 'package:sharedinbox/ui/screens/account_list_screen.dart';
|
||||||
|
import 'package:sharedinbox/ui/screens/combined_inbox_screen.dart';
|
||||||
import 'package:sharedinbox/ui/screens/account_receive_screen.dart';
|
import 'package:sharedinbox/ui/screens/account_receive_screen.dart';
|
||||||
import 'package:sharedinbox/ui/screens/account_send_screen.dart';
|
import 'package:sharedinbox/ui/screens/account_send_screen.dart';
|
||||||
import 'package:sharedinbox/ui/screens/add_account_screen.dart';
|
import 'package:sharedinbox/ui/screens/add_account_screen.dart';
|
||||||
@@ -24,11 +25,15 @@ import 'package:sharedinbox/ui/screens/user_preferences_screen.dart';
|
|||||||
import 'package:sharedinbox/ui/widgets/undo_shell.dart';
|
import 'package:sharedinbox/ui/widgets/undo_shell.dart';
|
||||||
|
|
||||||
final router = GoRouter(
|
final router = GoRouter(
|
||||||
initialLocation: '/accounts',
|
initialLocation: '/inbox',
|
||||||
routes: [
|
routes: [
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
builder: (ctx, state, child) => UndoShell(child: child),
|
builder: (ctx, state, child) => UndoShell(child: child),
|
||||||
routes: [
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/inbox',
|
||||||
|
builder: (ctx, state) => const CombinedInboxScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/accounts',
|
path: '/accounts',
|
||||||
builder: (ctx, state) => const AccountListScreen(),
|
builder: (ctx, state) => const AccountListScreen(),
|
||||||
|
|||||||
@@ -0,0 +1,393 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import 'package:sharedinbox/core/models/account.dart';
|
||||||
|
import 'package:sharedinbox/core/models/email.dart';
|
||||||
|
import 'package:sharedinbox/core/models/undo_action.dart';
|
||||||
|
import 'package:sharedinbox/di.dart';
|
||||||
|
|
||||||
|
final _dateFmt = DateFormat('MMM d');
|
||||||
|
final _formattedDates = <int, String>{};
|
||||||
|
|
||||||
|
int _dayKey(DateTime dt) => dt.year * 10000 + dt.month * 100 + dt.day;
|
||||||
|
|
||||||
|
String _fmtDate(DateTime dt) =>
|
||||||
|
_formattedDates[_dayKey(dt)] ??= _dateFmt.format(dt);
|
||||||
|
|
||||||
|
class CombinedInboxScreen extends ConsumerStatefulWidget {
|
||||||
|
const CombinedInboxScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<CombinedInboxScreen> createState() =>
|
||||||
|
_CombinedInboxScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CombinedInboxScreenState extends ConsumerState<CombinedInboxScreen> {
|
||||||
|
static const _pageSize = 50;
|
||||||
|
int _limit = _pageSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final accountsAsync = ref.watch(allAccountsProvider);
|
||||||
|
|
||||||
|
return accountsAsync.when(
|
||||||
|
loading: () => const Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
error: (e, _) => Scaffold(
|
||||||
|
body: Center(child: Text('Error: $e')),
|
||||||
|
),
|
||||||
|
data: (accounts) {
|
||||||
|
if (accounts.isEmpty) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (context.mounted) context.go('/accounts');
|
||||||
|
});
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final accountNames = {
|
||||||
|
for (final a in accounts) a.id: a.displayName,
|
||||||
|
};
|
||||||
|
final showAccount = accounts.length > 1;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: _buildAppBar(accounts),
|
||||||
|
drawer: _buildDrawer(context, accounts),
|
||||||
|
body: _buildBody(accountNames, showAccount),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () => context.push('/compose'),
|
||||||
|
child: const Icon(Icons.edit),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PreferredSizeWidget _buildAppBar(List<Account> accounts) {
|
||||||
|
return AppBar(
|
||||||
|
title: const Text('Combined Inbox'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
tooltip: 'Search',
|
||||||
|
onPressed: () => context.push('/search'),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.sync),
|
||||||
|
tooltip: 'Sync all',
|
||||||
|
onPressed: () {
|
||||||
|
for (final a in accounts) {
|
||||||
|
ref.read(syncManagerProvider).syncNow(a.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDrawer(BuildContext context, List<Account> accounts) {
|
||||||
|
return Drawer(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: [
|
||||||
|
const DrawerHeader(
|
||||||
|
decoration: BoxDecoration(color: Colors.blueGrey),
|
||||||
|
child: Text(
|
||||||
|
'sharedinbox.de',
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.manage_accounts),
|
||||||
|
title: const Text('Accounts'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
context.go('/accounts');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.person_add),
|
||||||
|
title: const Text('Add account'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
unawaited(context.push('/accounts/add'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
for (final account in accounts)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.inbox),
|
||||||
|
title: Text(account.displayName),
|
||||||
|
subtitle: Text(account.email),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
unawaited(context.push('/accounts/${account.id}/mailboxes'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.settings),
|
||||||
|
title: const Text('Preferences'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
unawaited(context.push('/accounts/preferences'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.history),
|
||||||
|
title: const Text('Undo Log'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
unawaited(context.push('/accounts/undo-log'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.info_outline),
|
||||||
|
title: const Text('About'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
unawaited(context.push('/accounts/about'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody(Map<String, String> accountNames, bool showAccount) {
|
||||||
|
final emailRepo = ref.watch(emailRepositoryProvider);
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
final accounts = ref.read(allAccountsProvider).value ?? [];
|
||||||
|
for (final a in accounts) {
|
||||||
|
ref.read(syncManagerProvider).syncNow(a.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: StreamBuilder<List<EmailThread>>(
|
||||||
|
stream: emailRepo.observeAllInboxThreads(limit: _limit),
|
||||||
|
builder: (ctx, snap) {
|
||||||
|
if (!snap.hasData) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
final threads = snap.data!;
|
||||||
|
if (threads.isEmpty) {
|
||||||
|
return ListView(
|
||||||
|
children: const [
|
||||||
|
SizedBox(
|
||||||
|
height: 300,
|
||||||
|
child: Center(child: Text('No emails')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _buildThreadList(threads, accountNames, showAccount);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildThreadList(
|
||||||
|
List<EmailThread> threads,
|
||||||
|
Map<String, String> accountNames,
|
||||||
|
bool showAccount,
|
||||||
|
) {
|
||||||
|
final hasMore = threads.length == _limit;
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: threads.length + (hasMore ? 1 : 0),
|
||||||
|
itemBuilder: (ctx, i) {
|
||||||
|
if (i == threads.length) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: () => setState(() => _limit += _pageSize),
|
||||||
|
child: const Text('Load more'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _buildThreadTile(ctx, threads[i], accountNames, showAccount);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildThreadTile(
|
||||||
|
BuildContext ctx,
|
||||||
|
EmailThread t,
|
||||||
|
Map<String, String> accountNames,
|
||||||
|
bool showAccount,
|
||||||
|
) {
|
||||||
|
final senderNames =
|
||||||
|
t.participants.map((a) => a.name ?? a.email).take(3).join(', ');
|
||||||
|
|
||||||
|
final tile = ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
t.hasUnread ? Icons.mail : Icons.mail_outline,
|
||||||
|
color: t.hasUnread ? Theme.of(ctx).colorScheme.primary : null,
|
||||||
|
),
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
senderNames.isEmpty ? '(unknown)' : senderNames,
|
||||||
|
style: t.hasUnread
|
||||||
|
? const TextStyle(fontWeight: FontWeight.bold)
|
||||||
|
: null,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (t.messageCount > 1)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 4),
|
||||||
|
child: Text(
|
||||||
|
'[${t.messageCount}]',
|
||||||
|
style: Theme.of(ctx).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
t.subject ?? '(no subject)',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: t.hasUnread
|
||||||
|
? const TextStyle(fontWeight: FontWeight.bold)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
if (t.preview != null && t.preview!.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
t.preview!,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(ctx).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
if (showAccount)
|
||||||
|
Text(
|
||||||
|
accountNames[t.accountId] ?? t.accountId,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(ctx).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(ctx).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (t.isFlagged)
|
||||||
|
const Icon(Icons.star, color: Colors.amber, size: 16),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
_fmtDate(t.latestDate),
|
||||||
|
style: Theme.of(ctx).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: t.messageCount > 1
|
||||||
|
? () => context.push(
|
||||||
|
'/accounts/${t.accountId}/mailboxes'
|
||||||
|
'/${Uri.encodeComponent(t.mailboxPath)}'
|
||||||
|
'/threads/${Uri.encodeComponent(t.threadId)}',
|
||||||
|
)
|
||||||
|
: () => context.push(
|
||||||
|
'/accounts/${t.accountId}/mailboxes'
|
||||||
|
'/${Uri.encodeComponent(t.mailboxPath)}'
|
||||||
|
'/emails/${Uri.encodeComponent(t.latestEmailId)}',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Dismissible(
|
||||||
|
key: ValueKey('${t.accountId}:${t.threadId}'),
|
||||||
|
background: _swipeBackground(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
color: Colors.green,
|
||||||
|
icon: Icons.archive,
|
||||||
|
label: 'Archive',
|
||||||
|
),
|
||||||
|
secondaryBackground: _swipeBackground(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
color: Colors.red,
|
||||||
|
icon: Icons.delete,
|
||||||
|
label: 'Delete',
|
||||||
|
),
|
||||||
|
onDismissed: (direction) => unawaited(_onSwipeDismissed(t, direction)),
|
||||||
|
child: tile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSwipeDismissed(
|
||||||
|
EmailThread t,
|
||||||
|
DismissDirection direction,
|
||||||
|
) async {
|
||||||
|
final repo = ref.read(emailRepositoryProvider);
|
||||||
|
|
||||||
|
final originalEmails = (await Future.wait(
|
||||||
|
t.emailIds.map((id) => repo.getEmail(id)),
|
||||||
|
))
|
||||||
|
.whereType<Email>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (direction == DismissDirection.startToEnd) {
|
||||||
|
final archive = await ref
|
||||||
|
.read(mailboxRepositoryProvider)
|
||||||
|
.findMailboxByRole(t.accountId, 'archive');
|
||||||
|
if (!mounted || archive == null) return;
|
||||||
|
|
||||||
|
for (final id in t.emailIds) {
|
||||||
|
await repo.moveEmail(id, archive.path);
|
||||||
|
}
|
||||||
|
final action = UndoAction(
|
||||||
|
id: DateTime.now().toIso8601String(),
|
||||||
|
accountId: t.accountId,
|
||||||
|
type: UndoType.move,
|
||||||
|
emailIds: t.emailIds,
|
||||||
|
sourceMailboxPath: t.mailboxPath,
|
||||||
|
destinationMailboxPath: archive.path,
|
||||||
|
originalEmails: originalEmails,
|
||||||
|
);
|
||||||
|
unawaited(ref.read(undoServiceProvider.notifier).pushAction(action));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? lastDestPath;
|
||||||
|
for (final id in t.emailIds) {
|
||||||
|
lastDestPath = await repo.deleteEmail(id);
|
||||||
|
}
|
||||||
|
final action = UndoAction(
|
||||||
|
id: DateTime.now().toIso8601String(),
|
||||||
|
accountId: t.accountId,
|
||||||
|
type: UndoType.delete,
|
||||||
|
emailIds: t.emailIds,
|
||||||
|
sourceMailboxPath: t.mailboxPath,
|
||||||
|
destinationMailboxPath: lastDestPath,
|
||||||
|
originalEmails: originalEmails,
|
||||||
|
);
|
||||||
|
unawaited(ref.read(undoServiceProvider.notifier).pushAction(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _swipeBackground({
|
||||||
|
required AlignmentGeometry alignment,
|
||||||
|
required Color color,
|
||||||
|
required IconData icon,
|
||||||
|
required String label,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
color: color,
|
||||||
|
alignment: alignment,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: Colors.white),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(label, style: const TextStyle(color: Colors.white)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -186,6 +186,10 @@ class _FakeEmails implements EmailRepository {
|
|||||||
}) =>
|
}) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<EmailThread>> observeAllInboxThreads({int limit = 50}) =>
|
||||||
|
Stream.value([]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<Email>> observeEmailsInThread(String a, String m, String t) =>
|
Stream<List<Email>> observeEmailsInThread(String a, String m, String t) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ class FakeEmailRepository implements EmailRepository {
|
|||||||
}) =>
|
}) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
@override
|
@override
|
||||||
|
Stream<List<EmailThread>> observeAllInboxThreads({int limit = 50}) =>
|
||||||
|
Stream.value([]);
|
||||||
|
@override
|
||||||
Stream<List<Email>> observeEmailsInThread(String a, String m, String t) =>
|
Stream<List<Email>> observeEmailsInThread(String a, String m, String t) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -287,6 +287,19 @@ class MockEmailRepository extends _i1.Mock implements _i9.EmailRepository {
|
|||||||
returnValue: _i5.Stream<List<_i3.EmailThread>>.empty(),
|
returnValue: _i5.Stream<List<_i3.EmailThread>>.empty(),
|
||||||
) as _i5.Stream<List<_i3.EmailThread>>);
|
) as _i5.Stream<List<_i3.EmailThread>>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i5.Stream<List<_i3.EmailThread>> observeAllInboxThreads({
|
||||||
|
int? limit = 50,
|
||||||
|
}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#observeAllInboxThreads,
|
||||||
|
[],
|
||||||
|
{#limit: limit},
|
||||||
|
),
|
||||||
|
returnValue: _i5.Stream<List<_i3.EmailThread>>.empty(),
|
||||||
|
) as _i5.Stream<List<_i3.EmailThread>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i5.Stream<List<_i3.Email>> observeEmailsInThread(
|
_i5.Stream<List<_i3.Email>> observeEmailsInThread(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ class _FakeEmails implements EmailRepository {
|
|||||||
}) =>
|
}) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
@override
|
@override
|
||||||
|
Stream<List<EmailThread>> observeAllInboxThreads({int limit = 50}) =>
|
||||||
|
Stream.value([]);
|
||||||
|
@override
|
||||||
Stream<List<Email>> observeEmailsInThread(String a, String m, String t) =>
|
Stream<List<Email>> observeEmailsInThread(String a, String m, String t) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -102,6 +102,9 @@ class _CountingEmails implements EmailRepository {
|
|||||||
}) =>
|
}) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
@override
|
@override
|
||||||
|
Stream<List<EmailThread>> observeAllInboxThreads({int limit = 50}) =>
|
||||||
|
Stream.value([]);
|
||||||
|
@override
|
||||||
Stream<List<Email>> observeEmailsInThread(String a, String m, String t) =>
|
Stream<List<Email>> observeEmailsInThread(String a, String m, String t) =>
|
||||||
Stream.value([]);
|
Stream.value([]);
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -109,6 +109,19 @@ class MockEmailRepository extends _i1.Mock implements _i3.EmailRepository {
|
|||||||
returnValue: _i4.Stream<List<_i2.EmailThread>>.empty(),
|
returnValue: _i4.Stream<List<_i2.EmailThread>>.empty(),
|
||||||
) as _i4.Stream<List<_i2.EmailThread>>);
|
) as _i4.Stream<List<_i2.EmailThread>>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i4.Stream<List<_i2.EmailThread>> observeAllInboxThreads({
|
||||||
|
int? limit = 50,
|
||||||
|
}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#observeAllInboxThreads,
|
||||||
|
[],
|
||||||
|
{#limit: limit},
|
||||||
|
),
|
||||||
|
returnValue: _i4.Stream<List<_i2.EmailThread>>.empty(),
|
||||||
|
) as _i4.Stream<List<_i2.EmailThread>>);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_i4.Stream<List<_i2.Email>> observeEmailsInThread(
|
_i4.Stream<List<_i2.Email>> observeEmailsInThread(
|
||||||
String? accountId,
|
String? accountId,
|
||||||
|
|||||||
@@ -245,6 +245,10 @@ class FakeEmailRepository implements EmailRepository {
|
|||||||
}).toList();
|
}).toList();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<EmailThread>> observeAllInboxThreads({int limit = 50}) =>
|
||||||
|
Stream.value([]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<Email>> observeEmailsInThread(
|
Stream<List<Email>> observeEmailsInThread(
|
||||||
String accountId,
|
String accountId,
|
||||||
|
|||||||
Reference in New Issue
Block a user