feat(avatar): relax the data mapper logic

Relax the data mapper logic by adding a default value instead of throwing exceptions

Fixes #9468

The avatar mapping it too restrictive and should apply defaults
The current mapping logic for avatars is to restrictive and needs to provide defaults instead of throwing exceptions.

diff --git a/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapper.kt b/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapper.kt
index 8a318981902..7bcd58a0524 100644
--- a/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapper.kt
+++ b/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapper.kt
@@ -10,16 +10,36 @@ class DefaultAccountAvatarDataMapper : AccountAvatarDataMapper {
     override fun toDomain(dto: AvatarDto): AccountAvatar {
         return when (dto.avatarType) {
             AvatarTypeDto.MONOGRAM -> AccountAvatar.Monogram(
-                value = dto.avatarMonogram ?: throw IllegalArgumentException("Monogram value is required"),
+                value = dto.avatarMonogram ?: DEFAULT_MONOGRAM,
             )

-            AvatarTypeDto.IMAGE -> AccountAvatar.Image(
-                uri = dto.avatarImageUri ?: throw IllegalArgumentException("Image URI is required"),
-            )
+            AvatarTypeDto.IMAGE -> {
+                val uri = dto.avatarImageUri

-            AvatarTypeDto.ICON -> AccountAvatar.Icon(
-                name = dto.avatarIconName ?: throw IllegalArgumentException("Icon type is required"),
-            )
+                if (uri.isNullOrEmpty()) {
+                    AccountAvatar.Monogram(
+                        value = DEFAULT_MONOGRAM,
+                    )
+                } else {
+                    AccountAvatar.Image(
+                        uri = uri,
+                    )
+                }
+            }
+
+            AvatarTypeDto.ICON -> {
+                val name = dto.avatarIconName
+
+                if (name.isNullOrEmpty()) {
+                    AccountAvatar.Monogram(
+                        value = DEFAULT_MONOGRAM,
+                    )
+                } else {
+                    AccountAvatar.Icon(
+                        name = name,
+                    )
+                }
+            }
         }
     }

@@ -35,4 +55,8 @@ class DefaultAccountAvatarDataMapper : AccountAvatarDataMapper {
             avatarIconName = if (domain is AccountAvatar.Icon) domain.name else null,
         )
     }
+
+    private companion object {
+        const val DEFAULT_MONOGRAM = "XX"
+    }
 }

diff --git a/feature/account/storage/legacy/src/test/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapperTest.kt b/feature/account/storage/legacy/src/test/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapperTest.kt
index 79a8bf239e6..2bc1fd42160 100644
--- a/feature/account/storage/legacy/src/test/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapperTest.kt
+++ b/feature/account/storage/legacy/src/test/kotlin/net/thunderbird/feature/account/storage/legacy/mapper/DefaultAccountAvatarDataMapperTest.kt
@@ -4,7 +4,6 @@ import assertk.assertThat
 import assertk.assertions.isEqualTo
 import assertk.assertions.isNull
 import kotlin.test.Test
-import kotlin.test.assertFailsWith
 import net.thunderbird.feature.account.profile.AccountAvatar
 import net.thunderbird.feature.account.storage.profile.AvatarDto
 import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
@@ -35,7 +34,7 @@ class DefaultAccountAvatarDataMapperTest {
     }

     @Test
-    fun `toDomain should throw IllegalArgumentException for invalid AvatarDto`() {
+    fun `toDomain should return default monogram for invalid AvatarDto`() {
         val avatarTypeDtos = AvatarTypeDto.entries

         avatarTypeDtos.forEach { type ->
@@ -47,9 +46,11 @@ class DefaultAccountAvatarDataMapperTest {
                 avatarIconName = null,
             )

-            assertFailsWith<IllegalArgumentException> {
-                testSubject.toDomain(dto)
-            }
+            // Act
+            val result = testSubject.toDomain(dto)
+
+            // Assert
+            assertDomainMonogram(result, AccountAvatar.Monogram("XX"))
         }
     }

@@ -74,17 +75,17 @@ class DefaultAccountAvatarDataMapperTest {
         }
     }

-    private fun assertDomainMonogram(actual: AccountAvatar.Monogram, expected: AccountAvatar) {
+    private fun assertDomainMonogram(actual: AccountAvatar, expected: AccountAvatar) {
         require(expected is AccountAvatar.Monogram) { "Expected AccountAvatar to be of type Monogram" }
         assertThat(actual).isEqualTo(expected)
     }

-    private fun assertDomainImage(actual: AccountAvatar.Image, expected: AccountAvatar) {
+    private fun assertDomainImage(actual: AccountAvatar, expected: AccountAvatar) {
         require(expected is AccountAvatar.Image) { "Expected AccountAvatar to be of type Image" }
         assertThat(actual).isEqualTo(expected)
     }

-    private fun assertDomainIcon(actual: AccountAvatar.Icon, expected: AccountAvatar) {
+    private fun assertDomainIcon(actual: AccountAvatar, expected: AccountAvatar) {
         require(expected is AccountAvatar.Icon) { "Expected AccountAvatar to be of type Icon" }
         assertThat(actual).isEqualTo(expected)
     }


 - Fixes #9422
###  Implementation Highlights:
- Migrated `isHideUserAgent` from `K9` to `PrivacySettings`.
- Delegated `isHideUserAgent` value updates to `PrivacySettingsManager`.
-  Replaced static reference of `isHideUserAgent` with access via `generalSettings.privacySettings.isHideUserAgent`.
- Updated tests to reflect the migration of `isHideUserAgent`.

Migrate K9.isHideUserAgent to PreferenceDataStore
The current implementation in Thunderbird Android directly accesses the static field `K9.isHideUserAgent` to determine whether the **User-Agent** header should be removed from outgoing emails. This approach bypasses the modern PreferenceDataStore, which is the preferred mechanism for managing application settings.

This task involves refactoring all direct references to `K9.isHideUserAgent` and replacing them with proper usage of PreferenceDataStore. This will improve consistency, testability, and alignment with the app’s current architecture for preference management.

./gradlew :legacy:core:testDebugUnitTest --tests "com.fsck.k9.notification.SendFailedNotificationControllerTest" --tests "com.fsck.k9.notification.NotificationContentCreatorTest" --tests "com.fsck.k9.notification.CertificateErrorNotificationControllerTest" --tests "com.fsck.k9.message.quote.QuoteDateFormatterTest" --tests "com.fsck.k9.notification.SummaryNotificationDataCreatorTest" --tests "com.fsck.k9.message.MessageBuilderTest" --tests "com.fsck.k9.helper.MessageHelperTest" --tests "com.fsck.k9.notification.NewMailNotificationManagerTest" --tests "com.fsck.k9.notification.AuthenticationErrorNotificationControllerTest" :legacy:ui:legacy:testDebugUnitTest --tests "com.fsck.k9.message.PgpMessageBuilderTest" :feature:mail:message:list:testDebugUnitTest --tests "net.thunderbird.feature.mail.message.list.domain.usecase.BuildSwipeActionsTest"

diff --git a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/PrivacySettings.kt b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/PrivacySettings.kt
index 8ad058942a9..1f871ad4696 100644
--- a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/PrivacySettings.kt
+++ b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/PrivacySettings.kt
@@ -2,4 +2,5 @@ package net.thunderbird.core.preference.privacy

 data class PrivacySettings(
     val isHideTimeZone: Boolean,
+    val isHideUserAgent: Boolean,
 )
diff --git a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/PrivacySettingsManager.kt b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/PrivacySettingsManager.kt
index 248acfb7b0b..e631a08ff22 100644
--- a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/PrivacySettingsManager.kt
+++ b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/PrivacySettingsManager.kt
@@ -4,4 +4,5 @@ interface PrivacySettingsManager {
     val privacySettings: PrivacySettings

     fun setIsHideTimeZone(isHideTimeZone: Boolean)
+    fun setIsHideUserAgent(isHideUserAgent: Boolean)
 }
diff --git a/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/DefaultPrivacySettingsManager.kt b/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/DefaultPrivacySettingsManager.kt
index 2d088755001..5ba5bd7d4b3 100644
--- a/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/DefaultPrivacySettingsManager.kt
+++ b/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/DefaultPrivacySettingsManager.kt
@@ -10,4 +10,9 @@ class DefaultPrivacySettingsManager(
         val privacySettings = preferenceManager.getConfig()
         preferenceManager.save(privacySettings.copy(isHideTimeZone = isHideTimeZone))
     }
+
+    override fun setIsHideUserAgent(isHideUserAgent: Boolean) {
+        val privacySettings = preferenceManager.getConfig()
+        preferenceManager.save(privacySettings.copy(isHideUserAgent = isHideUserAgent))
+    }
 }
diff --git a/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/DefaultPrivacySettingsPreferenceManager.kt b/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/DefaultPrivacySettingsPreferenceManager.kt
index 4f9c5664b8a..b1dab2446b8 100644
--- a/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/DefaultPrivacySettingsPreferenceManager.kt
+++ b/core/preference/impl/src/commonMain/kotlin/net/thunderbird/core/preference/privacy/DefaultPrivacySettingsPreferenceManager.kt
@@ -65,12 +65,14 @@ class DefaultPrivacySettingsPreferenceManager(

     private fun loadConfig(): PrivacySettings = PrivacySettings(
         isHideTimeZone = storage.getBoolean(KEY_HIDE_TIME_ZONE, false),
+        isHideUserAgent = storage.getBoolean(KEY_HIDE_USER_AGENT, false),
     )

     private fun writeConfig(config: PrivacySettings) {
         scope.launch(ioDispatcher) {
             mutex.withLock {
                 storageEditor.putBoolean(KEY_HIDE_TIME_ZONE, config.isHideTimeZone)
+                storageEditor.putBoolean(KEY_HIDE_USER_AGENT, config.isHideUserAgent)
                 storageEditor.commit()
             }
         }
@@ -78,5 +80,6 @@ class DefaultPrivacySettingsPreferenceManager(

     companion object {
         private const val KEY_HIDE_TIME_ZONE = "hideTimeZone"
+        private const val KEY_HIDE_USER_AGENT = "hideUserAgent"
     }
 }
diff --git a/legacy/core/src/main/java/com/fsck/k9/K9.kt b/legacy/core/src/main/java/com/fsck/k9/K9.kt
index dc4b24affed..6e45ac4d618 100644
--- a/legacy/core/src/main/java/com/fsck/k9/K9.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/K9.kt
@@ -197,9 +197,6 @@ object K9 : KoinComponent {

     var isNotificationDuringQuietTimeEnabled = true

-    @JvmStatic
-    var isHideUserAgent = false
-
     @get:Synchronized
     @set:Synchronized
     @JvmStatic
@@ -296,7 +293,6 @@ object K9 : KoinComponent {
             storage.getEnum("messageViewPostDeleteAction", PostRemoveNavigation.ReturnToMessageList)
         messageViewPostMarkAsUnreadNavigation =
             storage.getEnum("messageViewPostMarkAsUnreadAction", PostMarkAsUnreadNavigation.ReturnToMessageList)
-        isHideUserAgent = storage.getBoolean("hideUserAgent", false)

         isConfirmDelete = storage.getBoolean("confirmDelete", false)
         isConfirmDiscardMessage = storage.getBoolean("confirmDiscardMessage", true)
@@ -369,7 +365,6 @@ object K9 : KoinComponent {
         editor.putInt("registeredNameColor", contactNameColor)
         editor.putEnum("messageViewPostDeleteAction", messageViewPostRemoveNavigation)
         editor.putEnum("messageViewPostMarkAsUnreadAction", messageViewPostMarkAsUnreadNavigation)
-        editor.putBoolean("hideUserAgent", isHideUserAgent)

         editor.putString("language", k9Language)

diff --git a/legacy/core/src/main/java/com/fsck/k9/message/MessageBuilder.java b/legacy/core/src/main/java/com/fsck/k9/message/MessageBuilder.java
index a85433aa2ce..0d81aa46294 100644
--- a/legacy/core/src/main/java/com/fsck/k9/message/MessageBuilder.java
+++ b/legacy/core/src/main/java/com/fsck/k9/message/MessageBuilder.java
@@ -34,6 +34,7 @@
 import com.fsck.k9.mail.internet.TextBody;
 import com.fsck.k9.mailstore.TempFileBody;
 import com.fsck.k9.message.quote.InsertableHtmlContent;
+import net.thunderbird.core.preference.GeneralSettingsManager;
 import org.apache.james.mime4j.util.MimeUtil;

@@ -72,11 +73,17 @@ public abstract class MessageBuilder {
     private boolean isDraft;
     private boolean isPgpInlineEnabled;

+    private GeneralSettingsManager settingsManager;
+
     protected MessageBuilder(MessageIdGenerator messageIdGenerator,
-            BoundaryGenerator boundaryGenerator, CoreResourceProvider resourceProvider) {
+            BoundaryGenerator boundaryGenerator,
+            CoreResourceProvider resourceProvider,
+            GeneralSettingsManager settingsManager
+        ) {
         this.messageIdGenerator = messageIdGenerator;
         this.boundaryGenerator = boundaryGenerator;
         this.resourceProvider = resourceProvider;
+        this.settingsManager = settingsManager;
     }

     /**
@@ -110,7 +117,7 @@ private void buildHeader(MimeMessage message) throws MessagingException {
             message.setHeader("Return-Receipt-To", from.toEncodedString());
         }

-        if (!K9.isHideUserAgent()) {
+        if (!settingsManager.getSettings().getPrivacy().isHideUserAgent()) {
             String encodedUserAgent = MimeHeaderEncoder.encode("User-Agent", resourceProvider.userAgent());
             message.setHeader("User-Agent", encodedUserAgent);
         }
diff --git a/legacy/core/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java b/legacy/core/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java
index 30ab57f4e77..0828a1480fc 100644
--- a/legacy/core/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java
+++ b/legacy/core/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java
@@ -37,6 +37,7 @@
 import com.fsck.k9.mail.internet.MimeUtility;
 import com.fsck.k9.mail.internet.TextBody;
 import com.fsck.k9.mailstore.BinaryMemoryBody;
+import net.thunderbird.core.preference.GeneralSettingsManager;
 import org.apache.commons.io.IOUtils;
 import org.apache.james.mime4j.util.MimeUtil;
 import org.openintents.openpgp.OpenPgpError;
@@ -67,15 +68,16 @@ public static PgpMessageBuilder newInstance() {
         AutocryptOperations autocryptOperations = AutocryptOperations.getInstance();
         AutocryptOpenPgpApiInteractor autocryptOpenPgpApiInteractor = AutocryptOpenPgpApiInteractor.getInstance();
         CoreResourceProvider resourceProvider = DI.get(CoreResourceProvider.class);
+        GeneralSettingsManager settingsManager = DI.get(GeneralSettingsManager.class);
         return new PgpMessageBuilder(messageIdGenerator, boundaryGenerator, autocryptOperations,
-                autocryptOpenPgpApiInteractor, resourceProvider);
+                autocryptOpenPgpApiInteractor, resourceProvider, settingsManager);
     }

     @VisibleForTesting
     PgpMessageBuilder(MessageIdGenerator messageIdGenerator, BoundaryGenerator boundaryGenerator,
             AutocryptOperations autocryptOperations, AutocryptOpenPgpApiInteractor autocryptOpenPgpApiInteractor,
-            CoreResourceProvider resourceProvider) {
-        super(messageIdGenerator, boundaryGenerator, resourceProvider);
+            CoreResourceProvider resourceProvider, GeneralSettingsManager settingsManager) {
+        super(messageIdGenerator, boundaryGenerator, resourceProvider, settingsManager);

         this.autocryptOperations = autocryptOperations;
         this.autocryptOpenPgpApiInteractor = autocryptOpenPgpApiInteractor;
diff --git a/legacy/core/src/main/java/com/fsck/k9/message/SimpleMessageBuilder.java b/legacy/core/src/main/java/com/fsck/k9/message/SimpleMessageBuilder.java
index 77986c484df..71ec26d84a5 100644
--- a/legacy/core/src/main/java/com/fsck/k9/message/SimpleMessageBuilder.java
+++ b/legacy/core/src/main/java/com/fsck/k9/message/SimpleMessageBuilder.java
@@ -10,6 +10,7 @@
 import com.fsck.k9.mail.MessagingException;
 import com.fsck.k9.mail.internet.MessageIdGenerator;
 import com.fsck.k9.mail.internet.MimeMessage;
+import net.thunderbird.core.preference.GeneralSettingsManager;

 public class SimpleMessageBuilder extends MessageBuilder {
@@ -18,13 +19,18 @@ public static SimpleMessageBuilder newInstance() {
         MessageIdGenerator messageIdGenerator = MessageIdGenerator.getInstance();
         BoundaryGenerator boundaryGenerator = BoundaryGenerator.getInstance();
         CoreResourceProvider resourceProvider = DI.get(CoreResourceProvider.class);
-        return new SimpleMessageBuilder(messageIdGenerator, boundaryGenerator, resourceProvider);
+        GeneralSettingsManager settingsManager = DI.get(GeneralSettingsManager.class);
+        return new SimpleMessageBuilder(messageIdGenerator, boundaryGenerator, resourceProvider, settingsManager);
     }

     @VisibleForTesting
-    SimpleMessageBuilder(MessageIdGenerator messageIdGenerator, BoundaryGenerator boundaryGenerator,
-            CoreResourceProvider resourceProvider) {
-        super(messageIdGenerator, boundaryGenerator, resourceProvider);
+    SimpleMessageBuilder(
+        MessageIdGenerator messageIdGenerator,
+        BoundaryGenerator boundaryGenerator,
+        CoreResourceProvider resourceProvider,
+        GeneralSettingsManager settingsManager
+        ) {
+        super(messageIdGenerator, boundaryGenerator, resourceProvider, settingsManager);
     }

     @Override
diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt b/legacy/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt
index fb75804a665..e1313f65287 100644
--- a/legacy/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/preferences/RealGeneralSettingsManager.kt
@@ -240,6 +240,13 @@ internal class RealGeneralSettingsManager(
             .persist()
     }

+    override fun setIsHideUserAgent(isHideUserAgent: Boolean) {
+        privacySettingsManager.setIsHideUserAgent(isHideUserAgent)
+        getSettings()
+            .copy(privacy = privacySettings)
+            .persist()
+    }
+
     private fun writeSettings(editor: StorageEditor, settings: GeneralSettings) {
         editor.putBoolean("showRecentChanges", settings.showRecentChanges)
         editor.putEnum("theme", settings.appTheme)
diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt
index 14e64a741f4..6930839035f 100644
--- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt
+++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt
@@ -46,7 +46,7 @@ class GeneralSettingsDataStore(
             "messageview_autofit_width" -> generalSettingsManager.getSettings().isAutoFitWidth
             "quiet_time_enabled" -> generalSettingsManager.getSettings().isQuietTimeEnabled
             "disable_notifications_during_quiet_time" -> !K9.isNotificationDuringQuietTimeEnabled
-            "privacy_hide_useragent" -> K9.isHideUserAgent
+            "privacy_hide_useragent" -> generalSettingsManager.getSettings().privacy.isHideUserAgent
             "privacy_hide_timezone" -> generalSettingsManager.getSettings().privacy.isHideTimeZone
             "debug_logging" -> K9.isDebugLoggingEnabled
             "sync_debug_logging" -> K9.isSyncLoggingEnabled
@@ -86,7 +86,7 @@ class GeneralSettingsDataStore(
             "messageview_autofit_width" -> setIsAutoFitWidth(isAutoFitWidth = value)
             "quiet_time_enabled" -> setIsQuietTimeEnabled(isQuietTimeEnabled = value)
             "disable_notifications_during_quiet_time" -> K9.isNotificationDuringQuietTimeEnabled = !value
-            "privacy_hide_useragent" -> K9.isHideUserAgent = value
+            "privacy_hide_useragent" -> setIsHideUserAgent(isHideUserAgent = value)
             "privacy_hide_timezone" -> setIsHideTimeZone(isHideTimeZone = value)
             "debug_logging" -> K9.isDebugLoggingEnabled = value
             "sync_debug_logging" -> K9.isSyncLoggingEnabled = value
@@ -388,6 +388,13 @@ class GeneralSettingsDataStore(
         )
     }

+    private fun setIsHideUserAgent(isHideUserAgent: Boolean) {
+        skipSaveSettings = true
+        generalSettingsManager.setIsHideUserAgent(
+            isHideUserAgent = isHideUserAgent,
+        )
+    }
+
     private fun appThemeToString(theme: AppTheme) = when (theme) {
         AppTheme.LIGHT -> "light"
         AppTheme.DARK -> "dark"

diff --git a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActionsTest.kt b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActionsTest.kt
index 1cf0aecb62f..8f70c61a29b 100644
--- a/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActionsTest.kt
+++ b/feature/mail/message/list/src/test/kotlin/net/thunderbird/feature/mail/message/list/domain/usecase/BuildSwipeActionsTest.kt
@@ -57,8 +57,10 @@ class BuildSwipeActionsTest {
             quietTimeEnds = "7:00",
             isQuietTime = false,
             isQuietTimeEnabled = false,
-            privacy = PrivacySettings(isHideTimeZone = false),
-
+            privacy = PrivacySettings(
+                isHideTimeZone = false,
+                isHideUserAgent = false,
+            ),
         )

     @Test
@@ -457,6 +459,10 @@ private class FakeGeneralSettingsManager(
     override fun setIsHideTimeZone(isHideTimeZone: Boolean) = error(
         "not implemented",
     )
+
+    override fun setIsHideUserAgent(isHideUserAgent: Boolean) = error(
+        "not implemented",
+    )
 }

 private class FakeStorage(
diff --git a/legacy/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt b/legacy/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt
index f9160543fdf..145f67b437f 100644
--- a/legacy/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/helper/MessageHelperTest.kt
@@ -65,7 +65,10 @@ class MessageHelperTest : RobolectricTest() {
                 isQuietTimeEnabled = false,
                 quietTimeEnds = "7:00",
                 quietTimeStarts = "7:00",
-                privacy = PrivacySettings(isHideTimeZone = false),
+                privacy = PrivacySettings(
+                    isHideTimeZone = false,
+                    isHideUserAgent = false,
+                ),
             ),
         )
     }
diff --git a/legacy/core/src/test/java/com/fsck/k9/message/MessageBuilderTest.java b/legacy/core/src/test/java/com/fsck/k9/message/MessageBuilderTest.java
index ecbad6f57a9..be4d4b40642 100644
--- a/legacy/core/src/test/java/com/fsck/k9/message/MessageBuilderTest.java
+++ b/legacy/core/src/test/java/com/fsck/k9/message/MessageBuilderTest.java
@@ -12,6 +12,8 @@
 import java.util.List;
 import java.util.Map;

+import androidx.annotation.NonNull;
+import kotlinx.coroutines.flow.Flow;
 import net.thunderbird.core.android.testing.RobolectricTest;
 import net.thunderbird.core.android.account.QuoteStyle;
 import com.fsck.k9.CoreResourceProvider;
@@ -31,6 +33,12 @@
 import com.fsck.k9.message.quote.InsertableHtmlContent;
 import net.thunderbird.core.logging.legacy.Log;
 import net.thunderbird.core.logging.testing.TestLogger;
+import net.thunderbird.core.preference.AppTheme;
+import net.thunderbird.core.preference.BackgroundSync;
+import net.thunderbird.core.preference.GeneralSettings;
+import net.thunderbird.core.preference.GeneralSettingsManager;
+import net.thunderbird.core.preference.SubTheme;
+import net.thunderbird.core.preference.privacy.PrivacySettings;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -197,6 +205,183 @@ public class MessageBuilderTest extends RobolectricTest {
     private BoundaryGenerator boundaryGenerator;
     private CoreResourceProvider resourceProvider = new TestCoreResourceProvider();
     private Callback callback;
+    private final GeneralSettingsManager fakeSettingsManager = new GeneralSettingsManager() {
+        @Override
+        public void setIsHideTimeZone(boolean isHideTimeZone) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @NonNull
+        @Override
+        public PrivacySettings getPrivacySettings() {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsQuietTimeEnabled(boolean isQuietTimeEnabled) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setQuietTimeStarts(@NonNull String quietTimeStarts) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setQuietTimeEnds(@NonNull String quietTimeEnds) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @NonNull
+        @Override
+        public GeneralSettings getSettings() {
+            return new GeneralSettings(
+                BackgroundSync.NEVER,
+                false,
+                AppTheme.FOLLOW_SYSTEM,
+                SubTheme.USE_GLOBAL,
+                SubTheme.USE_GLOBAL,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                false,
+                "07:00",
+                "07:00",
+                false,
+                false,
+                new PrivacySettings(false, false)
+            );
+        }
+
+        @NonNull
+        @Override
+        public Flow<GeneralSettings> getSettingsFlow() {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setShowRecentChanges(boolean showRecentChanges) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setAppTheme(@NonNull AppTheme appTheme) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setMessageViewTheme(@NonNull SubTheme subTheme) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setMessageComposeTheme(@NonNull SubTheme subTheme) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setFixedMessageViewTheme(boolean fixedMessageViewTheme) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsShowUnifiedInbox(boolean isShowUnifiedInbox) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsShowStarredCount(boolean isShowStarredCount) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsShowMessageListStars(boolean isShowMessageListStars) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsShowAnimations(boolean isShowAnimations) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsShowCorrespondentNames(boolean isShowCorrespondentNames) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setSetupArchiveShouldNotShowAgain(boolean shouldShowSetupArchiveFolderDialog) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsMessageListSenderAboveSubject(boolean isMessageListSenderAboveSubject) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsShowContactName(boolean isShowContactName) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsShowContactPicture(boolean isShowContactPicture) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsChangeContactNameColor(boolean isChangeContactNameColor) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsColorizeMissingContactPictures(boolean isColorizeMissingContactPictures) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsUseBackgroundAsUnreadIndicator(boolean isUseBackgroundAsUnreadIndicator) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsShowComposeButtonOnMessageList(boolean isShowComposeButtonOnMessageList) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsThreadedViewEnabled(boolean isThreadedViewEnabled) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsUseMessageViewFixedWidthFont(boolean isUseMessageViewFixedWidthFont) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsAutoFitWidth(boolean isAutoFitWidth) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public void setIsHideUserAgent(boolean isHideUserAgent) {
+            throw new UnsupportedOperationException("not implemented");
+        }
+    };

     @Before
@@ -345,7 +530,7 @@ public void build_detachAndReattach_shouldSucceed() throws MessagingException {

     @Test
     public void buildWithException_shouldThrow() throws MessagingException {
-        MessageBuilder messageBuilder = new SimpleMessageBuilder(messageIdGenerator, boundaryGenerator, resourceProvider) {
+        MessageBuilder messageBuilder = new SimpleMessageBuilder(messageIdGenerator, boundaryGenerator, resourceProvider, fakeSettingsManager) {
             @Override
             protected void buildMessageInternal() {
                 queueMessageBuildException(new MessagingException("expected error"));
@@ -361,7 +546,7 @@ protected void buildMessageInternal() {
     @Test
     public void buildWithException_detachAndReattach_shouldThrow() throws MessagingException {
         Callback anotherCallback = mock(Callback.class);
-        MessageBuilder messageBuilder = new SimpleMessageBuilder(messageIdGenerator, boundaryGenerator, resourceProvider) {
+        MessageBuilder messageBuilder = new SimpleMessageBuilder(messageIdGenerator, boundaryGenerator, resourceProvider, fakeSettingsManager) {
             @Override
             protected void buildMessageInternal() {
                 queueMessageBuildException(new MessagingException("expected error"));
@@ -436,7 +621,7 @@ public boolean isInternalAttachment() {

     private MessageBuilder createSimpleMessageBuilder() {
         Identity identity = createIdentity();
-        return new SimpleMessageBuilder(messageIdGenerator, boundaryGenerator, resourceProvider)
+        return new SimpleMessageBuilder(messageIdGenerator, boundaryGenerator, resourceProvider, fakeSettingsManager)
                 .setSubject(TEST_SUBJECT)
                 .setSentDate(SENT_DATE)
                 .setHideTimeZone(true)
diff --git a/legacy/core/src/test/java/com/fsck/k9/message/quote/QuoteDateFormatterTest.kt b/legacy/core/src/test/java/com/fsck/k9/message/quote/QuoteDateFormatterTest.kt
index 27a626eabba..997bd8bc272 100644
--- a/legacy/core/src/test/java/com/fsck/k9/message/quote/QuoteDateFormatterTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/message/quote/QuoteDateFormatterTest.kt
@@ -30,7 +30,10 @@ class QuoteDateFormatterTest {
         messageViewTheme = SubTheme.USE_GLOBAL,
         messageComposeTheme = SubTheme.USE_GLOBAL,
         fixedMessageViewTheme = false,
-        privacy = PrivacySettings(isHideTimeZone = false),
+        privacy = PrivacySettings(
+            isHideTimeZone = false,
+            isHideUserAgent = false,
+        ),
         isAutoFitWidth = false,
         isThreadedViewEnabled = false,
         isUseMessageViewFixedWidthFont = false,
diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/AuthenticationErrorNotificationControllerTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/AuthenticationErrorNotificationControllerTest.kt
index b593cd00fbf..8953df7a2b9 100644
--- a/legacy/core/src/test/java/com/fsck/k9/notification/AuthenticationErrorNotificationControllerTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/notification/AuthenticationErrorNotificationControllerTest.kt
@@ -149,7 +149,10 @@ class AuthenticationErrorNotificationControllerTest : RobolectricTest() {
                     quietTimeStarts = "7:00",
                     quietTimeEnds = "7:00",
                     isQuietTimeEnabled = false,
-                    privacy = PrivacySettings(isHideTimeZone = false),
+                    privacy = PrivacySettings(
+                        isHideTimeZone = false,
+                        isHideUserAgent = false,
+                    ),
                 )
             },
         ) {
diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/CertificateErrorNotificationControllerTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/CertificateErrorNotificationControllerTest.kt
index 282f3b48966..2898088354e 100644
--- a/legacy/core/src/test/java/com/fsck/k9/notification/CertificateErrorNotificationControllerTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/notification/CertificateErrorNotificationControllerTest.kt
@@ -149,7 +149,10 @@ class CertificateErrorNotificationControllerTest : RobolectricTest() {
                 quietTimeStarts = "7:00",
                 quietTimeEnds = "7:00",
                 isQuietTimeEnabled = false,
-                privacy = PrivacySettings(isHideTimeZone = false),
+                privacy = PrivacySettings(
+                    isHideTimeZone = false,
+                    isHideUserAgent = false,
+                ),
             )
         },
     ) {
diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt
index 536c4851e2d..294ec651e99 100644
--- a/legacy/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt
@@ -79,7 +79,10 @@ class NewMailNotificationManagerTest {
                     quietTimeStarts = "7:00",
                     quietTimeEnds = "7:00",
                     isQuietTimeEnabled = false,
-                    privacy = PrivacySettings(isHideTimeZone = false),
+                    privacy = PrivacySettings(
+                        isHideTimeZone = false,
+                        isHideUserAgent = false,
+                    ),
                 )
             },
         ),
diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt
index 188ee045aaa..e1666bb04bc 100644
--- a/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt
@@ -175,7 +175,10 @@ class NotificationContentCreatorTest : RobolectricTest() {
                     quietTimeStarts = "7:00",
                     quietTimeEnds = "7:00",
                     isQuietTimeEnabled = false,
-                    privacy = PrivacySettings(isHideTimeZone = false),
+                    privacy = PrivacySettings(
+                        isHideTimeZone = false,
+                        isHideUserAgent = false,
+                    ),
                 )
             },
         )
diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/SendFailedNotificationControllerTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/SendFailedNotificationControllerTest.kt
index 0cc1cd394e2..b5445be8826 100644
--- a/legacy/core/src/test/java/com/fsck/k9/notification/SendFailedNotificationControllerTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/notification/SendFailedNotificationControllerTest.kt
@@ -66,7 +66,10 @@ class SendFailedNotificationControllerTest : RobolectricTest() {
                 quietTimeStarts = "7:00",
                 quietTimeEnds = "7:00",
                 isQuietTimeEnabled = false,
-                privacy = PrivacySettings(isHideTimeZone = false),
+                privacy = PrivacySettings(
+                    isHideTimeZone = false,
+                    isHideUserAgent = false,
+                ),
             )
         },
     )
diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt
index 915fbc2e23b..0588f75a633 100644
--- a/legacy/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt
@@ -58,7 +58,10 @@ class SummaryNotificationDataCreatorTest {
         quietTimeStarts = "0:00",
         quietTimeEnds = "23:59",
         isQuietTimeEnabled = false,
-        privacy = PrivacySettings(isHideTimeZone = false),
+        privacy = PrivacySettings(
+            isHideTimeZone = false,
+            isHideUserAgent = false,
+        ),
     )
     private val notificationDataCreator = SummaryNotificationDataCreator(
         singleMessageNotificationDataCreator = SingleMessageNotificationDataCreator(),
diff --git a/legacy/ui/legacy/src/test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt b/legacy/ui/legacy/src/test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt
index 6050742351c..6f2cd48b9ad 100644
--- a/legacy/ui/legacy/src/test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt
+++ b/legacy/ui/legacy/src/test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt
@@ -46,10 +46,17 @@ import com.fsck.k9.message.quote.InsertableHtmlContent
 import com.fsck.k9.view.RecipientSelectView
 import java.io.OutputStream
 import java.util.Date
+import kotlinx.coroutines.flow.Flow
 import net.thunderbird.core.android.account.Identity
 import net.thunderbird.core.android.account.QuoteStyle
 import net.thunderbird.core.logging.legacy.Log
 import net.thunderbird.core.logging.testing.TestLogger
+import net.thunderbird.core.preference.AppTheme
+import net.thunderbird.core.preference.BackgroundSync
+import net.thunderbird.core.preference.GeneralSettings
+import net.thunderbird.core.preference.GeneralSettingsManager
+import net.thunderbird.core.preference.SubTheme
+import net.thunderbird.core.preference.privacy.PrivacySettings
 import org.apache.james.mime4j.util.MimeUtil
 import org.junit.Before
 import org.junit.Test
@@ -774,6 +781,7 @@ class PgpMessageBuilderTest : K9RobolectricTest() {
                 AutocryptOperations.getInstance(),
                 autocryptOpenPgpApiInteractor,
                 resourceProvider,
+                fakeGeneralSettingsManager,
             )
             builder.setOpenPgpApi(openPgpApi)

@@ -837,5 +845,105 @@ class PgpMessageBuilderTest : K9RobolectricTest() {
                 }
             }
         }
+
+        private val fakeGeneralSettingsManager = object : GeneralSettingsManager {
+            override fun getSettings() = GeneralSettings(
+                backgroundSync = BackgroundSync.NEVER,
+                showRecentChanges = false,
+                appTheme = AppTheme.FOLLOW_SYSTEM,
+                messageViewTheme = SubTheme.USE_GLOBAL,
+                messageComposeTheme = SubTheme.USE_GLOBAL,
+                fixedMessageViewTheme = false,
+                isShowUnifiedInbox = false,
+                isShowStarredCount = false,
+                isShowMessageListStars = false,
+                isShowAnimations = false,
+                isShowCorrespondentNames = false,
+                shouldShowSetupArchiveFolderDialog = false,
+                isMessageListSenderAboveSubject = false,
+                isShowContactName = false,
+                isShowContactPicture = false,
+                isChangeContactNameColor = false,
+                isColorizeMissingContactPictures = false,
+                isUseBackgroundAsUnreadIndicator = false,
+                isShowComposeButtonOnMessageList = false,
+                isThreadedViewEnabled = false,
+                isUseMessageViewFixedWidthFont = false,
+                isAutoFitWidth = false,
+                privacy = PrivacySettings(isHideUserAgent = false, isHideTimeZone = false),
+                quietTimeEnds = "07:00",
+                quietTimeStarts = "07:00",
+                isQuietTimeEnabled = false,
+                isQuietTime = false,
+            )
+
+            override fun getSettingsFlow(): Flow<GeneralSettings> = error("not implemented")
+
+            override fun setShowRecentChanges(showRecentChanges: Boolean) = error("not implemented")
+
+            override fun setAppTheme(appTheme: AppTheme) = error("not implemented")
+
+            override fun setMessageViewTheme(subTheme: SubTheme) = error("not implemented")
+
+            override fun setMessageComposeTheme(subTheme: SubTheme) = error("not implemented")
+
+            override fun setFixedMessageViewTheme(fixedMessageViewTheme: Boolean) = error("not implemented")
+
+            override fun setIsShowUnifiedInbox(isShowUnifiedInbox: Boolean) = error("not implemented")
+
+            override fun setIsShowStarredCount(isShowStarredCount: Boolean) = error("not implemented")
+
+            override fun setIsShowMessageListStars(isShowMessageListStars: Boolean) = error("not implemented")
+
+            override fun setIsShowAnimations(isShowAnimations: Boolean) = error("not implemented")
+
+            override fun setIsShowCorrespondentNames(isShowCorrespondentNames: Boolean) = error("not implemented")
+
+            override fun setSetupArchiveShouldNotShowAgain(shouldShowSetupArchiveFolderDialog: Boolean) = error(
+                "not implemented",
+            )
+
+            override fun setIsMessageListSenderAboveSubject(isMessageListSenderAboveSubject: Boolean) = error(
+                "not implemented",
+            )
+
+            override fun setIsShowContactName(isShowContactName: Boolean) = error("not implemented")
+
+            override fun setIsShowContactPicture(isShowContactPicture: Boolean) = error("not implemented")
+
+            override fun setIsChangeContactNameColor(isChangeContactNameColor: Boolean) = error("not implemented")
+
+            override fun setIsColorizeMissingContactPictures(isColorizeMissingContactPictures: Boolean) = error(
+                "not implemented",
+            )
+
+            override fun setIsUseBackgroundAsUnreadIndicator(isUseBackgroundAsUnreadIndicator: Boolean) = error(
+                "not implemented",
+            )
+
+            override fun setIsShowComposeButtonOnMessageList(isShowComposeButtonOnMessageList: Boolean) = error(
+                "not implemented",
+            )
+
+            override fun setIsThreadedViewEnabled(isThreadedViewEnabled: Boolean) = error("not implemented")
+
+            override fun setIsUseMessageViewFixedWidthFont(isUseMessageViewFixedWidthFont: Boolean) = error(
+                "not implemented",
+            )
+
+            override fun setIsAutoFitWidth(isAutoFitWidth: Boolean) = error("not implemented")
+            override fun setQuietTimeEnds(quietTimeEnds: String) = error("not implemented")
+
+            override fun setQuietTimeStarts(quietTimeStarts: String) = error("not implemented")
+
+            override fun setIsQuietTimeEnabled(isQuietTimeEnabled: Boolean) = error("not implemented")
+
+            override val privacySettings: PrivacySettings
+                get() = error("not implemented")
+
+            override fun setIsHideTimeZone(isHideTimeZone: Boolean) = error("not implemented")
+
+            override fun setIsHideUserAgent(isHideUserAgent: Boolean) = error("not implemented")
+        }
     }
 }


Propose a solution for #3172, initially covering long URL on mobile phone screens

Fixes #3172

This update improves readability by ensuring that long words and links in email messages wrap properly in the WebView, avoiding horizontal scrolling. In DisplayHtml.kt, the cssStylePre method now includes CSS rules for <a> tags (word-break and overflow-wrap). Additionally, the HTML and CSS generation methods have been refactored to use multiline string literals for better message clarity.

Wrap long lines when displaying message (text size gets too small)
K9 doesn't warp links, which makes some e-mails very wide:
![screenshot_20180208-131048](https://user-images.githubusercontent.com/5685512/36012611-7e192f60-0d5f-11e8-9f28-c7e2559efeac.png)
***
compared to:
![screenshot_20180208-131057](https://user-images.githubusercontent.com/5685512/36012615-7fa039fa-0d5f-11e8-8a5a-b14fc63a9502.png)

another example, this time not links, but a large number of underscores (I already decoded the email from quoted printable):
[mail.zip](https://github.com/k9mail/k-9/files/1709841/mail.zip)

The lines marked with red on the right, have been correctly wrapped by K9.
The divider at the top (this e-mail was forwarded by k9 to myself again. it's the divider between your own message and the forwarded message that K9 adds) stretches to the same exact full width, but the underscores are wider than full width and therefore stretch the viewport

![screenshot_20180209-060913](https://user-images.githubusercontent.com/5685512/36013123-b30be084-0d62-11e8-87c8-7f47e8001b75.png)
We could add
`word-wrap: break-word;`
to the HTML of each mail to achieve this, I think.

https://www.w3schools.com/cssref/css3_pr_word-wrap.asp

@cketti Sorry to bother you, but what do you think?
Any updates on this? Solving this issue would greatly improve the UX, as currently I need to zoom in every time to read the text if the email contains even a remotely lengthy non-breakable string, which gets increasingly likely in a long chain of replies.
Side note: it seems to me that some word-breaking is performed, but it isn't applied to fixed-width text, or long strings like URLs, so replies in long chains are sometimes ridiculously malformed (to the point where the text becomes vertical instead of horizontal, i.e. 1 character per line).
@cketti any idea what a good fix for this might be?
+1
@cketti If you point me in the direction of a fix, I can work on this.
Really need this.  Every day we get emails with long links and it makes the rest of the email hard/impossible to read.
What is the expected target behavior here? If you go to Settings > General > Display, at the bottom there is a setting "Auto-fit messages". If you disable it, then a bugzilla email with a long pushloghtml link will show zoomed in, but you might actually have to zoom out or scroll a bit to read all content.

If you have the same email on Thunderbird desktop it simply has a horizontal scrollbar. So I guess this would be about changing the default for auto-fit?

Further notes from conversation with Ryan: We should do something about images as well, otherwise they either clip or make it zoom out. Then let's see if we can change the default setting on beta with a feature flag and see how the reception is.

https://searchfox.org/comm-central/source/mail/themes/shared/mail/messageBody.css sets max-width: 100% on images if shrinktofit is enabled, which appears to be the case by default. It also forces overflow:auto on the root node.

(Side note it would be cool if we had a system to change defaults, so that we can migrate users without having to think about the implications every time. But that is for another issue.)
This could be a good first bug, though requires some very targeted exploratory testing with different emails in order to find edge cases. So far we have:

* Long link in one line
* Normal text, then a paragraph with an image that exceeds 100% width
Good starting points are:
* legacy/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
* legacy/core/src/main/java/com/fsck/k9/message/html/DisplayHtml.kt
To add yet another use case to consider here: When zooming in, the text becomes larger but the lines don't wrap. So there is a fair portion of horizontal scrolling involved.
Please, assign this to me.
> Please, assign this to me.

Feel free to work on it, considering the comments and code pointers added earlier.
> Good starting points are:
>
> * legacy/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
> * legacy/core/src/main/java/com/fsck/k9/message/html/DisplayHtml.kt

DisplayHtml doesn't affect non-null HTML text."

./gradlew :legacy:core:testDebugUnitTest --tests "com.fsck.k9.message.html.DisplayHtmlTest"

diff --git a/legacy/core/src/main/java/com/fsck/k9/message/html/DisplayHtml.kt b/legacy/core/src/main/java/com/fsck/k9/message/html/DisplayHtml.kt
index a912b0940db..d098b20e486 100644
--- a/legacy/core/src/main/java/com/fsck/k9/message/html/DisplayHtml.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/message/html/DisplayHtml.kt
@@ -1,26 +1,65 @@
 package com.fsck.k9.message.html

 import app.k9mail.html.cleaner.HtmlHeadProvider
+import org.intellij.lang.annotations.Language

 class DisplayHtml(private val settings: HtmlSettings) : HtmlHeadProvider {
     override val headHtml: String
         get() {
-            return """<meta name="viewport" content="width=device-width"/>""" +
-                cssStylePre() +
-                cssStyleSignature()
+            @Language("HTML")
+            val html = """
+                <meta name="viewport" content="width=device-width"/>
+                ${cssStyleGlobal()}
+                ${cssStylePre()}
+                ${cssStyleSignature()}
+            """.trimIndent()
+
+            return html
         }

     fun wrapStatusMessage(status: CharSequence): String {
-        return wrapMessageContent("
$status
")
+        @Language("HTML")
+        val html = """
+
$status

+        """.trimIndent()
+
+        return wrapMessageContent(html)
     }

+    @Language("HTML")
     fun wrapMessageContent(messageContent: CharSequence): String {
         // Include a meta tag so the WebView will not use a fixed viewport width of 980 px
-        return "<meta name=\"viewport\" content=\"width=device-width\"/>" +
-            cssStylePre() +
-            "" +
-            messageContent +
-            ""
+        return """
+
+
+                    $headHtml
+
+
+                    $messageContent
+
+
+        """.trimIndent()
+    }
+
+    /**
+     * Dynamically generates a CSS style block that applies global rules to all elements (`*` selector).
+     *
+     * The style enforces word-breaking and overflow wrapping to prevent content overflow
+     * and ensures long text strings break correctly without causing horizontal scrolling.
+     *
+     * @return A `<style>` element string that can be dynamically injected into the HTML `<head>`
+     * to apply these global styles when rendering messages.
+     */
+    @Language("HTML")
+    private fun cssStyleGlobal(): String {
+        return """
+
+        """.trimIndent()
     }

     /**
@@ -31,15 +70,30 @@ class DisplayHtml(private val settings: HtmlSettings) : HtmlHeadProvider {
      * @return A `<style>` element that can be dynamically included in the HTML `<head>` element when messages are
      * displayed.
      */
+    @Language("HTML")
     private fun cssStylePre(): String {
         val font = if (settings.useFixedWidthFont) "monospace" else "sans-serif"

-        return ""
+        return """
+
+        """.trimIndent()
     }

+    @Language("HTML")
     private fun cssStyleSignature(): String {
-        return """"""
+        return """
+
+        """.trimIndent()
     }
 }

diff --git a/legacy/core/src/test/java/com/fsck/k9/message/html/DisplayHtmlTest.kt b/legacy/core/src/test/java/com/fsck/k9/message/html/DisplayHtmlTest.kt
index 69620ae002c..f26b972da6a 100644
--- a/legacy/core/src/test/java/com/fsck/k9/message/html/DisplayHtmlTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/message/html/DisplayHtmlTest.kt
@@ -2,13 +2,15 @@ package com.fsck.k9.message.html

 import assertk.Assert
 import assertk.assertThat
+import assertk.assertions.contains
 import assertk.assertions.hasSize
 import assertk.assertions.isEqualTo
 import org.jsoup.Jsoup
 import org.junit.Test

 class DisplayHtmlTest {
-    val displayHtml = DisplayHtml(HtmlSettings(useDarkMode = false, useFixedWidthFont = false))
+    val htmlSettings = HtmlSettings(useDarkMode = false, useFixedWidthFont = false)
+    val displayHtml = DisplayHtml(htmlSettings)

     @Test
     fun wrapMessageContent_addsViewportMetaElement() {
@@ -25,10 +27,45 @@ class DisplayHtmlTest {
     }

     @Test
-    fun wrapMessageContent_addsPreCSS() {
+    fun wrapMessageContent_addsPreCSSStyles() {
         val html = displayHtml.wrapMessageContent("Some text")

-        assertThat(html).containsHtmlElement("head > style")
+        assertThat(html).containsHtmlElement("head > style", 3)
+    }
+
+    @Test
+    fun wrapMessageContent_addsGlobalStyleRules() {
+        val html = displayHtml.wrapMessageContent("test")
+
+        assertThat(html).containsStyleRulesFor(
+            selector = "*",
+            "word-break: break-word;",
+            "overflow-wrap: break-word;",
+        )
+    }
+
+    @Test
+    fun wrapMessageContent_addsPreCSS() {
+        val html = displayHtml.wrapMessageContent("test")
+        val expectedFont = if (htmlSettings.useFixedWidthFont) "monospace" else "sans-serif"
+
+        assertThat(html).containsStyleRulesFor(
+            selector = "pre.${EmailTextToHtml.K9MAIL_CSS_CLASS}",
+            "white-space: pre-wrap;",
+            "word-wrap: break-word;",
+            "font-family: $expectedFont;",
+            "margin-top: 0px;",
+        )
+    }
+
+    @Test
+    fun wrapMessageContent_addsSignatureStyleRules() {
+        val html = displayHtml.wrapMessageContent("test")
+
+        assertThat(html).containsStyleRulesFor(
+            selector = ".k9mail-signature",
+            "opacity: 0.5;",
+        )
     }

     @Test
@@ -40,8 +77,26 @@ class DisplayHtmlTest {
         assertThat(html).bodyText().isEqualTo(content)
     }

-    private fun Assert<String>.containsHtmlElement(cssQuery: String) = given { actual ->
-        assertThat(actual).htmlElements(cssQuery).hasSize(1)
+    private fun Assert<String>.containsStyleRulesFor(selector: String, vararg expectedRules: String) = given { html ->
+        val styleContent = Jsoup.parse(html)
+            .select("style")
+            .joinToString("\n") { it.data() }
+
+        val selectorPattern = Regex.escape(selector).replace("\\*", "\\\\*")
+        val selectorBlock = Regex("$selectorPattern\\s*\\{([^}]*)\\}", RegexOption.MULTILINE)
+            .find(styleContent)
+            ?.groupValues?.get(1)
+            ?.trim()
+
+        requireNotNull(selectorBlock) { "No style block found for selector: $selector" }
+
+        expectedRules.forEach { rule ->
+            assertThat(selectorBlock).contains(rule)
+        }
+    }
+
+    private fun Assert<String>.containsHtmlElement(cssQuery: String, expectedCount: Int = 1) = given { actual ->
+        assertThat(actual).htmlElements(cssQuery).hasSize(expectedCount)
     }

     private fun Assert<String>.htmlElements(cssQuery: String) = transform { html ->