Skip to content

Commit 232f79a

Browse files
committed
feat(message-compose): add sent folder not found confirmation dialog
1 parent 2623d2e commit 232f79a

File tree

8 files changed

+226
-33
lines changed

8 files changed

+226
-33
lines changed

app-common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies {
3737
implementation(projects.feature.account.avatar.impl)
3838
implementation(projects.feature.account.setup)
3939
implementation(projects.feature.mail.account.api)
40+
implementation(projects.feature.mail.message.composer)
4041
implementation(projects.feature.migration.provider)
4142
implementation(projects.feature.notification.api)
4243
implementation(projects.feature.notification.impl)

app-common/src/main/kotlin/net/thunderbird/app/common/feature/AppCommonFeatureModule.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package net.thunderbird.app.common.feature
33
import app.k9mail.feature.launcher.FeatureLauncherExternalContract
44
import app.k9mail.feature.launcher.di.featureLauncherModule
55
import net.thunderbird.app.common.feature.mail.appCommonFeatureMailModule
6+
import net.thunderbird.feature.mail.message.composer.inject.featureMessageComposerModule
67
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract
78
import net.thunderbird.feature.notification.impl.inject.featureNotificationModule
89
import org.koin.android.ext.koin.androidContext
@@ -11,6 +12,7 @@ import org.koin.dsl.module
1112
internal val appCommonFeatureModule = module {
1213
includes(featureLauncherModule)
1314
includes(featureNotificationModule)
15+
includes(featureMessageComposerModule)
1416
includes(appCommonFeatureMailModule)
1517

1618
factory<FeatureLauncherExternalContract.AccountSetupFinishedLauncher> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package net.thunderbird.feature.mail.message.composer.dialog
2+
3+
import androidx.compose.foundation.layout.PaddingValues
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.res.stringResource
8+
import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonText
9+
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
10+
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
11+
import app.k9mail.core.ui.compose.designsystem.organism.BasicDialog
12+
import app.k9mail.core.ui.compose.theme2.MainTheme
13+
import net.thunderbird.feature.mail.message.composer.R
14+
15+
@Composable
16+
fun SentFolderNotFoundConfirmationDialog(
17+
showDialog: Boolean,
18+
onAssignSentFolderClick: () -> Unit,
19+
onSendAndDeleteClick: () -> Unit,
20+
onDismiss: () -> Unit,
21+
modifier: Modifier = Modifier,
22+
) {
23+
if (showDialog) {
24+
BasicDialog(
25+
headlineText = stringResource(R.string.sent_folder_not_found_dialog_title),
26+
supportingText = stringResource(R.string.sent_folder_not_found_dialog_supporting_text),
27+
content = {
28+
ButtonText(
29+
onClick = onAssignSentFolderClick,
30+
text = stringResource(R.string.sent_folder_not_found_dialog_assign_folder_action),
31+
leadingIcon = {
32+
Icon(
33+
imageVector = Icons.Outlined.Folder,
34+
contentDescription = null,
35+
modifier = Modifier.padding(end = MainTheme.spacings.half),
36+
)
37+
},
38+
)
39+
},
40+
buttons = {
41+
ButtonText(
42+
text = stringResource(R.string.sent_folder_not_found_dialog_cancel_action),
43+
onClick = onDismiss,
44+
)
45+
ButtonText(
46+
text = stringResource(R.string.sent_folder_not_found_dialog_send_and_delete_action),
47+
onClick = onSendAndDeleteClick,
48+
color = MainTheme.colors.error,
49+
)
50+
},
51+
onDismissRequest = onDismiss,
52+
contentPadding = PaddingValues(horizontal = MainTheme.spacings.default),
53+
modifier = modifier,
54+
)
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package net.thunderbird.feature.mail.message.composer.dialog
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import android.view.Window
8+
import androidx.compose.ui.platform.ComposeView
9+
import androidx.core.os.bundleOf
10+
import androidx.fragment.app.DialogFragment
11+
import androidx.fragment.app.FragmentManager
12+
import androidx.fragment.app.setFragmentResult
13+
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
14+
import net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragmentFactory.Companion.ACCOUNT_UUID_ARG
15+
import net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragmentFactory.Companion.RESULT_CODE_ASSIGN_SENT_FOLDER_REQUEST_KEY
16+
import net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragmentFactory.Companion.RESULT_CODE_SEND_AND_DELETE_REQUEST_KEY
17+
import org.koin.android.ext.android.inject
18+
19+
class SentFolderNotFoundConfirmationDialogFragment : DialogFragment() {
20+
private val themeProvider: FeatureThemeProvider by inject<FeatureThemeProvider>()
21+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
22+
val accountUuid = requireNotNull(requireArguments().getString(ACCOUNT_UUID_ARG)) {
23+
"The $ACCOUNT_UUID_ARG argument is missing from the arguments bundle."
24+
}
25+
dialog?.requestWindowFeature(Window.FEATURE_NO_TITLE)
26+
return ComposeView(requireContext()).apply {
27+
setContent {
28+
themeProvider.WithTheme {
29+
SentFolderNotFoundConfirmationDialog(
30+
showDialog = true,
31+
onAssignSentFolderClick = {
32+
dismiss()
33+
setFragmentResult(
34+
requestKey = RESULT_CODE_ASSIGN_SENT_FOLDER_REQUEST_KEY,
35+
result = bundleOf(ACCOUNT_UUID_ARG to accountUuid),
36+
)
37+
},
38+
onSendAndDeleteClick = {
39+
dismiss()
40+
setFragmentResult(
41+
requestKey = RESULT_CODE_SEND_AND_DELETE_REQUEST_KEY,
42+
result = bundleOf(ACCOUNT_UUID_ARG to accountUuid),
43+
)
44+
},
45+
onDismiss = ::dismiss,
46+
)
47+
}
48+
}
49+
}
50+
}
51+
52+
companion object Factory : SentFolderNotFoundConfirmationDialogFragmentFactory {
53+
private const val TAG = "SentFolderNotFoundConfirmationDialogFragment"
54+
override fun show(accountUuid: String, fragmentManager: FragmentManager) {
55+
SentFolderNotFoundConfirmationDialogFragment().apply {
56+
arguments = bundleOf(ACCOUNT_UUID_ARG to accountUuid)
57+
show(fragmentManager, TAG)
58+
}
59+
}
60+
}
61+
}
62+
63+
interface SentFolderNotFoundConfirmationDialogFragmentFactory {
64+
companion object {
65+
const val RESULT_CODE_ASSIGN_SENT_FOLDER_REQUEST_KEY =
66+
"SentFolderNotFoundConfirmationDialogFragmentFactory_assign_sent_folder"
67+
const val RESULT_CODE_SEND_AND_DELETE_REQUEST_KEY =
68+
"SentFolderNotFoundConfirmationDialogFragmentFactory_send_and_delete"
69+
const val ACCOUNT_UUID_ARG = "SetupArchiveFolderDialogFragmentFactory_accountUuid"
70+
}
71+
72+
fun show(accountUuid: String, fragmentManager: FragmentManager)
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package net.thunderbird.feature.mail.message.composer.inject
2+
3+
import net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragment
4+
import net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragmentFactory
5+
import org.koin.dsl.module
6+
7+
val featureMessageComposerModule = module {
8+
factory<SentFolderNotFoundConfirmationDialogFragmentFactory> {
9+
SentFolderNotFoundConfirmationDialogFragment.Factory
10+
}
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<string name="sent_folder_not_found_dialog_title">Sent folder not found</string>
4+
<string name="sent_folder_not_found_dialog_supporting_text">To save this email after sending, set a Sent folder in Account Settings.</string>
5+
<string name="sent_folder_not_found_dialog_assign_folder_action">Assign Sent Folder</string>
6+
<string name="sent_folder_not_found_dialog_cancel_action">Cancel</string>
7+
<string name="sent_folder_not_found_dialog_send_and_delete_action">Send and Delete</string>
8+
</resources>

legacy/ui/legacy/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies {
3434
implementation(projects.feature.settings.import)
3535
implementation(projects.feature.telemetry.api)
3636
implementation(projects.feature.mail.message.list)
37+
implementation(projects.feature.mail.message.composer)
3738

3839
compileOnly(projects.mail.protocols.imap)
3940

legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageCompose.java

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.List;
1010
import java.util.Locale;
1111
import java.util.Map;
12+
import java.util.Objects;
1213
import java.util.Set;
1314
import java.util.regex.Pattern;
1415
import android.annotation.SuppressLint;
@@ -58,6 +59,8 @@
5859
import com.bumptech.glide.Glide;
5960
import com.bumptech.glide.load.engine.DiskCacheStrategy;
6061
import com.fsck.k9.activity.compose.MessageComposeInAppNotificationFragment;
62+
import com.fsck.k9.ui.settings.account.AccountSettingsActivity;
63+
import com.fsck.k9.ui.settings.account.AccountSettingsFragment;
6164
import kotlin.Unit;
6265
import net.thunderbird.core.android.account.LegacyAccountDto;
6366
import app.k9mail.legacy.di.DI;
@@ -133,6 +136,7 @@
133136
import net.thunderbird.core.outcome.OutcomeKt;
134137
import net.thunderbird.core.preference.GeneralSettingsManager;
135138
import net.thunderbird.core.ui.theme.manager.ThemeManager;
139+
import net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragmentFactory;
136140
import net.thunderbird.feature.notification.api.command.outcome.CommandExecutionFailed;
137141
import net.thunderbird.feature.notification.api.content.NotificationFactoryCoroutineCompat;
138142
import net.thunderbird.feature.notification.api.content.SentFolderNotFoundNotification;
@@ -147,6 +151,9 @@
147151
import static com.fsck.k9.activity.compose.AttachmentPresenter.REQUEST_CODE_ATTACHMENT_URI;
148152
import static app.k9mail.core.android.common.camera.CameraCaptureHandler.CAMERA_PERMISSION_REQUEST_CODE;
149153
import static app.k9mail.core.android.common.camera.CameraCaptureHandler.REQUEST_IMAGE_CAPTURE;
154+
import static net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragmentFactory.ACCOUNT_UUID_ARG;
155+
import static net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragmentFactory.RESULT_CODE_ASSIGN_SENT_FOLDER_REQUEST_KEY;
156+
import static net.thunderbird.feature.mail.message.composer.dialog.SentFolderNotFoundConfirmationDialogFragmentFactory.RESULT_CODE_SEND_AND_DELETE_REQUEST_KEY;
150157
import static net.thunderbird.feature.notification.api.content.SentFolderNotFoundNotificationKt.SentFolderNotFoundNotification;
151158

152159

@@ -227,7 +234,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
227234
private final NotificationSenderCompat notificationSenderCompat = new NotificationSenderCompat(notificationSender);
228235
private final NotificationDismisser notificationDismisser = DI.get(NotificationDismisser.class);
229236
private final NotificationDismisserCompat notificationDismisserCompat =
230-
new NotificationDismisserCompat(notificationDismisser);
237+
new NotificationDismisserCompat(notificationDismisser);
238+
private final SentFolderNotFoundConfirmationDialogFragmentFactory sentFolderNotFoundDialogFragmentFactory =
239+
DI.get(SentFolderNotFoundConfirmationDialogFragmentFactory.class);
231240

232241
private final Set<Integer> activeInAppNotifications = new HashSet<>();
233242

@@ -285,6 +294,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
285294
private boolean navigateUp;
286295

287296
private boolean sendMessageHasBeenTriggered = false;
297+
private boolean ignoreSentFolderNotAssigned = false;
288298

289299
@Override
290300
public void onCreate(Bundle savedInstanceState) {
@@ -551,6 +561,32 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
551561
setProgressBarIndeterminateVisibility(true);
552562
currentMessageBuilder.reattachCallback(this);
553563
}
564+
setupSentFolderNotFoundDialogResults();
565+
}
566+
567+
private void setupSentFolderNotFoundDialogResults() {
568+
getSupportFragmentManager().setFragmentResultListener(
569+
RESULT_CODE_ASSIGN_SENT_FOLDER_REQUEST_KEY,
570+
this,
571+
(requestKey, result) -> {
572+
if (RESULT_CODE_ASSIGN_SENT_FOLDER_REQUEST_KEY.equals(requestKey)) {
573+
final String accountUuid = result.getString(ACCOUNT_UUID_ARG);
574+
AccountSettingsActivity.start(this,
575+
Objects.requireNonNull(accountUuid),
576+
AccountSettingsFragment.PREFERENCE_FOLDERS);
577+
}
578+
}
579+
);
580+
getSupportFragmentManager().setFragmentResultListener(
581+
RESULT_CODE_SEND_AND_DELETE_REQUEST_KEY,
582+
this,
583+
(requestKey, result) -> {
584+
if (RESULT_CODE_SEND_AND_DELETE_REQUEST_KEY.equals(requestKey)) {
585+
ignoreSentFolderNotAssigned = true;
586+
performSendAfterChecks();
587+
}
588+
}
589+
);
554590
}
555591

556592
private void fetchAccount(Intent intent) {
@@ -582,46 +618,46 @@ protected void onResume() {
582618
private void triggerIfNeededSentFolderNotFoundInAppNotification() {
583619
if (account != null && account.getSentFolderId() == null) {
584620
final SentFolderNotFoundNotification notification = NotificationFactoryCoroutineCompat.create(
585-
continuation -> SentFolderNotFoundNotification(account.getUuid(), continuation)
621+
continuation -> SentFolderNotFoundNotification(account.getUuid(), continuation)
586622
);
587623
notificationSenderCompat.send(notification, outcome -> {
588624
OutcomeKt.handle(
625+
outcome,
626+
success -> {
627+
activeInAppNotifications.add(success.getRawNotificationId());
628+
return Unit.INSTANCE;
629+
},
630+
failure -> {
631+
final Throwable throwable = failure instanceof CommandExecutionFailed<?>
632+
? ((CommandExecutionFailed<?>) failure).getThrowable()
633+
: null;
634+
Log.e(throwable, "Failed to send in-app notification. Failure = " + failure);
635+
return Unit.INSTANCE;
636+
});
637+
});
638+
}
639+
}
640+
641+
private void dismissActiveInAppNotifications() {
642+
for (Integer notificationId : activeInAppNotifications) {
643+
notificationDismisserCompat.dismiss(
644+
notificationId,
645+
outcome -> {
646+
OutcomeKt.handle(
589647
outcome,
590648
success -> {
591-
activeInAppNotifications.add(success.getRawNotificationId());
649+
activeInAppNotifications.remove(success.getRawNotificationId());
592650
return Unit.INSTANCE;
593651
},
594652
failure -> {
595653
final Throwable throwable = failure instanceof CommandExecutionFailed<?>
596654
? ((CommandExecutionFailed<?>) failure).getThrowable()
597655
: null;
598-
Log.e(throwable, "Failed to send in-app notification. Failure = " + failure);
656+
Log.e(throwable, "Failed to dismiss in-app notification. Failure = " + failure);
599657
return Unit.INSTANCE;
600-
});
601-
});
602-
}
603-
}
604-
605-
private void dismissActiveInAppNotifications() {
606-
for (Integer notificationId : activeInAppNotifications) {
607-
notificationDismisserCompat.dismiss(
608-
notificationId,
609-
outcome -> {
610-
OutcomeKt.handle(
611-
outcome,
612-
success -> {
613-
activeInAppNotifications.remove(success.getRawNotificationId());
614-
return Unit.INSTANCE;
615-
},
616-
failure -> {
617-
final Throwable throwable = failure instanceof CommandExecutionFailed<?>
618-
? ((CommandExecutionFailed<?>) failure).getThrowable()
619-
: null;
620-
Log.e(throwable, "Failed to dismiss in-app notification. Failure = " + failure);
621-
return Unit.INSTANCE;
622-
}
623-
);
624-
});
658+
}
659+
);
660+
});
625661
}
626662
}
627663

@@ -833,6 +869,11 @@ public void performSendAfterChecks() {
833869
return;
834870
}
835871

872+
if (!ignoreSentFolderNotAssigned && !account.hasSentFolder()) {
873+
sentFolderNotFoundDialogFragmentFactory.show(account.getUuid(), getSupportFragmentManager());
874+
return;
875+
}
876+
836877
currentMessageBuilder = createMessageBuilder(false);
837878
if (currentMessageBuilder != null) {
838879
sendMessageHasBeenTriggered = true;
@@ -1831,8 +1872,8 @@ private void initializeActionBar() {
18311872

18321873
private void initializeInAppNotificationFragment() {
18331874
if (FeatureFlagProviderCompat
1834-
.provide(featureFlagProvider, "display_in_app_notifications")
1835-
.isDisabledOrUnavailable()) {
1875+
.provide(featureFlagProvider, "display_in_app_notifications")
1876+
.isDisabledOrUnavailable()) {
18361877
return;
18371878
}
18381879

@@ -1843,7 +1884,7 @@ private void initializeInAppNotificationFragment() {
18431884
}
18441885
final FragmentManager fragmentManager = getSupportFragmentManager();
18451886
final Fragment currentFragment = fragmentManager
1846-
.findFragmentByTag(MessageComposeInAppNotificationFragment.FRAGMENT_TAG);
1887+
.findFragmentByTag(MessageComposeInAppNotificationFragment.FRAGMENT_TAG);
18471888

18481889
if (currentFragment != null) {
18491890
return;
@@ -1854,7 +1895,7 @@ private void initializeInAppNotificationFragment() {
18541895
uuids.add(legacyAccountDto.getUuid());
18551896
}
18561897
final MessageComposeInAppNotificationFragment inAppNotificationFragment =
1857-
MessageComposeInAppNotificationFragment.newInstance(uuids);
1898+
MessageComposeInAppNotificationFragment.newInstance(uuids);
18581899
fragmentManager
18591900
.beginTransaction()
18601901
.add(R.id.message_compose_in_app_notifications_container, inAppNotificationFragment,

0 commit comments

Comments
 (0)