feat: add 'Create new folder' option to Move To Folder dialog (#423)
This commit was merged in pull request #423.
This commit is contained in:
@@ -20,4 +20,8 @@ abstract class MailboxRepository {
|
|||||||
String name,
|
String name,
|
||||||
String role,
|
String role,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Creates a new mailbox named [name] for [accountId] without a special role.
|
||||||
|
/// Returns the newly created [Mailbox].
|
||||||
|
Future<Mailbox> createMailbox(String accountId, String name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,11 +343,23 @@ class MailboxRepositoryImpl implements MailboxRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<model.Mailbox> createMailbox(String accountId, String name) async {
|
||||||
|
final account = (await _accounts.getAccount(accountId))!;
|
||||||
|
final password = await _accounts.getPassword(accountId);
|
||||||
|
switch (account.type) {
|
||||||
|
case account_model.AccountType.imap:
|
||||||
|
return _createMailboxWithRoleImap(account, password, name, null);
|
||||||
|
case account_model.AccountType.jmap:
|
||||||
|
return _createMailboxWithRoleJmap(account, password, name, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<model.Mailbox> _createMailboxWithRoleImap(
|
Future<model.Mailbox> _createMailboxWithRoleImap(
|
||||||
account_model.Account account,
|
account_model.Account account,
|
||||||
String password,
|
String password,
|
||||||
String name,
|
String name,
|
||||||
String role,
|
String? role,
|
||||||
) async {
|
) async {
|
||||||
final client = await _imapConnect(
|
final client = await _imapConnect(
|
||||||
account,
|
account,
|
||||||
@@ -380,7 +392,7 @@ class MailboxRepositoryImpl implements MailboxRepository {
|
|||||||
account_model.Account account,
|
account_model.Account account,
|
||||||
String password,
|
String password,
|
||||||
String name,
|
String name,
|
||||||
String role,
|
String? role,
|
||||||
) async {
|
) async {
|
||||||
final jmapUrl = account.jmapUrl;
|
final jmapUrl = account.jmapUrl;
|
||||||
if (jmapUrl == null || jmapUrl.isEmpty) {
|
if (jmapUrl == null || jmapUrl.isEmpty) {
|
||||||
@@ -398,7 +410,10 @@ class MailboxRepositoryImpl implements MailboxRepository {
|
|||||||
{
|
{
|
||||||
'accountId': jmap.accountId,
|
'accountId': jmap.accountId,
|
||||||
'create': {
|
'create': {
|
||||||
'new-mailbox': {'name': name, 'role': role},
|
'new-mailbox': {
|
||||||
|
'name': name,
|
||||||
|
if (role != null) 'role': role,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'0',
|
'0',
|
||||||
|
|||||||
@@ -563,6 +563,42 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> _promptNewFolderName(BuildContext context) async {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
try {
|
||||||
|
return await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: const Text('Create new folder'),
|
||||||
|
content: TextField(
|
||||||
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
|
decoration: const InputDecoration(hintText: 'Folder name'),
|
||||||
|
textCapitalization: TextCapitalization.words,
|
||||||
|
onSubmitted: (value) {
|
||||||
|
if (value.trim().isNotEmpty) Navigator.pop(ctx, value.trim());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
final name = controller.text.trim();
|
||||||
|
if (name.isNotEmpty) Navigator.pop(ctx, name);
|
||||||
|
},
|
||||||
|
child: const Text('Create'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _moveTo(BuildContext context, Email header) async {
|
Future<void> _moveTo(BuildContext context, Email header) async {
|
||||||
final nextEmailId = await _getNextEmailIdIfNeeded(header);
|
final nextEmailId = await _getNextEmailIdIfNeeded(header);
|
||||||
|
|
||||||
@@ -576,6 +612,8 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
const createNewSentinel = '__create_new__';
|
||||||
|
|
||||||
final chosen = await showModalBottomSheet<String>(
|
final chosen = await showModalBottomSheet<String>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => ListView(
|
builder: (ctx) => ListView(
|
||||||
@@ -593,13 +631,28 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
title: Text(m.name),
|
title: Text(m.name),
|
||||||
onTap: () => Navigator.pop(ctx, m.path),
|
onTap: () => Navigator.pop(ctx, m.path),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.create_new_folder_outlined),
|
||||||
|
title: const Text('Create new folder…'),
|
||||||
|
onTap: () => Navigator.pop(ctx, createNewSentinel),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (chosen == null || !context.mounted) return;
|
if (chosen == null || !context.mounted) return;
|
||||||
|
|
||||||
await ref.read(emailRepositoryProvider).moveEmail(widget.emailId, chosen);
|
String destination = chosen;
|
||||||
|
if (chosen == createNewSentinel) {
|
||||||
|
final name = await _promptNewFolderName(context);
|
||||||
|
if (name == null || !context.mounted) return;
|
||||||
|
final mailbox = await mailboxRepo.createMailbox(header.accountId, name);
|
||||||
|
destination = mailbox.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(emailRepositoryProvider)
|
||||||
|
.moveEmail(widget.emailId, destination);
|
||||||
|
|
||||||
unawaited(
|
unawaited(
|
||||||
ref.read(undoServiceProvider.notifier).pushAction(
|
ref.read(undoServiceProvider.notifier).pushAction(
|
||||||
@@ -609,7 +662,7 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
type: UndoType.move,
|
type: UndoType.move,
|
||||||
emailIds: [widget.emailId],
|
emailIds: [widget.emailId],
|
||||||
sourceMailboxPath: header.mailboxPath,
|
sourceMailboxPath: header.mailboxPath,
|
||||||
destinationMailboxPath: chosen,
|
destinationMailboxPath: destination,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -169,6 +169,15 @@ class _FakeMailboxes implements MailboxRepository {
|
|||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
);
|
);
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailbox(String accountId, String name) async => Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FakeEmails implements EmailRepository {
|
class _FakeEmails implements EmailRepository {
|
||||||
|
|||||||
@@ -239,6 +239,15 @@ class FakeMailboxRepositoryWithInbox implements MailboxRepository {
|
|||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
);
|
);
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailbox(String accountId, String name) async => Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AccountRepositoryWithMissingPlugin implements AccountRepository {
|
class _AccountRepositoryWithMissingPlugin implements AccountRepository {
|
||||||
|
|||||||
@@ -235,6 +235,31 @@ class MockMailboxRepository extends _i1.Mock implements _i8.MailboxRepository {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
) as _i5.Future<_i2.Mailbox>);
|
) as _i5.Future<_i2.Mailbox>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Mailbox> createMailbox(
|
||||||
|
String? accountId,
|
||||||
|
String? name,
|
||||||
|
) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#createMailbox,
|
||||||
|
[
|
||||||
|
accountId,
|
||||||
|
name,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
returnValue: _i5.Future<_i2.Mailbox>.value(_FakeMailbox_0(
|
||||||
|
this,
|
||||||
|
Invocation.method(
|
||||||
|
#createMailbox,
|
||||||
|
[
|
||||||
|
accountId,
|
||||||
|
name,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
) as _i5.Future<_i2.Mailbox>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A class which mocks [EmailRepository].
|
/// A class which mocks [EmailRepository].
|
||||||
|
|||||||
@@ -77,6 +77,15 @@ class _FakeMailboxes implements MailboxRepository {
|
|||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
);
|
);
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailbox(String accountId, String name) async => Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FakeEmails implements EmailRepository {
|
class _FakeEmails implements EmailRepository {
|
||||||
|
|||||||
@@ -67,6 +67,15 @@ class _FakeMailboxes implements MailboxRepository {
|
|||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
);
|
);
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailbox(String accountId, String name) async => Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CountingEmails implements EmailRepository {
|
class _CountingEmails implements EmailRepository {
|
||||||
|
|||||||
@@ -192,6 +192,20 @@ class FakeMailboxRepository implements MailboxRepository {
|
|||||||
_mailboxes.add(mailbox);
|
_mailboxes.add(mailbox);
|
||||||
return mailbox;
|
return mailbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Mailbox> createMailbox(String accountId, String name) async {
|
||||||
|
final mailbox = Mailbox(
|
||||||
|
id: '$accountId:$name',
|
||||||
|
accountId: accountId,
|
||||||
|
path: name,
|
||||||
|
name: name,
|
||||||
|
unreadCount: 0,
|
||||||
|
totalCount: 0,
|
||||||
|
);
|
||||||
|
_mailboxes.add(mailbox);
|
||||||
|
return mailbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeEmailRepository implements EmailRepository {
|
class FakeEmailRepository implements EmailRepository {
|
||||||
|
|||||||
Reference in New Issue
Block a user