()) }
+ single { K9JobManager(workManager = get(), preferences = get(), mailSyncWorkerManager = get()) }
factory { MailSyncWorkerManager(workManager = get(), clock = get(), context = get()) }
+ factory { (parameters: WorkerParameters) ->
+ MailSyncWorker(messagingController = get(), preferences = get(), context = get(), parameters)
+ }
}
diff --git a/app/core/src/main/java/com/fsck/k9/job/WorkManagerConfigurationProvider.kt b/app/core/src/main/java/com/fsck/k9/job/WorkManagerConfigurationProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..54d973fee18e7ec439dc0a8c1d541a77ac8c3067
--- /dev/null
+++ b/app/core/src/main/java/com/fsck/k9/job/WorkManagerConfigurationProvider.kt
@@ -0,0 +1,12 @@
+package com.fsck.k9.job
+
+import androidx.work.Configuration
+import androidx.work.WorkerFactory
+
+class WorkManagerConfigurationProvider(private val workerFactory: WorkerFactory) {
+ fun getConfiguration(): Configuration {
+ return Configuration.Builder()
+ .setWorkerFactory(workerFactory)
+ .build()
+ }
+}
diff --git a/app/core/src/main/java/com/fsck/k9/job/WorkManagerProvider.kt b/app/core/src/main/java/com/fsck/k9/job/WorkManagerProvider.kt
deleted file mode 100644
index f31fbaaf7b2fa815ef8aa33cd6a9f104c0354751..0000000000000000000000000000000000000000
--- a/app/core/src/main/java/com/fsck/k9/job/WorkManagerProvider.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.fsck.k9.job
-
-import android.content.Context
-import androidx.work.Configuration
-import androidx.work.WorkManager
-import androidx.work.WorkerFactory
-
-class WorkManagerProvider(private val context: Context, private val workerFactory: WorkerFactory) {
- fun getWorkManager(): WorkManager {
- val configuration = Configuration.Builder()
- .setWorkerFactory(workerFactory)
- .build()
-
- WorkManager.initialize(context, configuration)
-
- return WorkManager.getInstance(context)
- }
-}
diff --git a/app/core/src/main/java/com/fsck/k9/message/extractors/TextPartFinder.java b/app/core/src/main/java/com/fsck/k9/message/extractors/TextPartFinder.java
deleted file mode 100644
index 2168da54f109f4384e90bdc1b4bc3e0a081db9f7..0000000000000000000000000000000000000000
--- a/app/core/src/main/java/com/fsck/k9/message/extractors/TextPartFinder.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.fsck.k9.message.extractors;
-
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.fsck.k9.mail.Body;
-import com.fsck.k9.mail.BodyPart;
-import com.fsck.k9.mail.Multipart;
-import com.fsck.k9.mail.Part;
-
-import static com.fsck.k9.mail.internet.MimeUtility.isSameMimeType;
-
-
-public class TextPartFinder {
- @Nullable
- public Part findFirstTextPart(@NonNull Part part) {
- String mimeType = part.getMimeType();
- Body body = part.getBody();
-
- if (body instanceof Multipart) {
- Multipart multipart = (Multipart) body;
- if (isSameMimeType(mimeType, "multipart/alternative")) {
- return findTextPartInMultipartAlternative(multipart);
- } else {
- return findTextPartInMultipart(multipart);
- }
- } else if (isSameMimeType(mimeType, "text/plain") || isSameMimeType(mimeType, "text/html")) {
- return part;
- }
-
- return null;
- }
-
- private Part findTextPartInMultipartAlternative(Multipart multipart) {
- Part htmlPart = null;
-
- for (BodyPart bodyPart : multipart.getBodyParts()) {
- String mimeType = bodyPart.getMimeType();
- Body body = bodyPart.getBody();
-
- if (body instanceof Multipart) {
- Part candidatePart = findFirstTextPart(bodyPart);
- if (candidatePart != null) {
- if (isSameMimeType(candidatePart.getMimeType(), "text/html")) {
- htmlPart = candidatePart;
- } else {
- return candidatePart;
- }
- }
- } else if (isSameMimeType(mimeType, "text/plain")) {
- return bodyPart;
- } else if (isSameMimeType(mimeType, "text/html") && htmlPart == null) {
- htmlPart = bodyPart;
- }
- }
-
- if (htmlPart != null) {
- return htmlPart;
- }
-
- return null;
- }
-
- private Part findTextPartInMultipart(Multipart multipart) {
- for (BodyPart bodyPart : multipart.getBodyParts()) {
- String mimeType = bodyPart.getMimeType();
- Body body = bodyPart.getBody();
-
- if (body instanceof Multipart) {
- Part candidatePart = findFirstTextPart(bodyPart);
- if (candidatePart != null) {
- return candidatePart;
- }
- } else if (isSameMimeType(mimeType, "text/plain") || isSameMimeType(mimeType, "text/html")) {
- return bodyPart;
- }
- }
-
- return null;
- }
-}
diff --git a/app/core/src/main/java/com/fsck/k9/message/extractors/TextPartFinder.kt b/app/core/src/main/java/com/fsck/k9/message/extractors/TextPartFinder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cca3d79c298bd5d5e6f06484ccac872315728091
--- /dev/null
+++ b/app/core/src/main/java/com/fsck/k9/message/extractors/TextPartFinder.kt
@@ -0,0 +1,68 @@
+package com.fsck.k9.message.extractors
+
+import com.fsck.k9.mail.MimeType.Companion.toMimeType
+import com.fsck.k9.mail.MimeType.Companion.toMimeTypeOrNull
+import com.fsck.k9.mail.Multipart
+import com.fsck.k9.mail.Part
+
+private val TEXT_PLAIN = "text/plain".toMimeType()
+private val TEXT_HTML = "text/html".toMimeType()
+private val MULTIPART_ALTERNATIVE = "multipart/alternative".toMimeType()
+
+class TextPartFinder {
+ fun findFirstTextPart(part: Part): Part? {
+ val mimeType = part.mimeType.toMimeTypeOrNull()
+ val body = part.body
+
+ return if (body is Multipart) {
+ if (mimeType == MULTIPART_ALTERNATIVE) {
+ findTextPartInMultipartAlternative(body)
+ } else {
+ findTextPartInMultipart(body)
+ }
+ } else if (mimeType == TEXT_PLAIN || mimeType == TEXT_HTML) {
+ part
+ } else {
+ null
+ }
+ }
+
+ private fun findTextPartInMultipartAlternative(multipart: Multipart): Part? {
+ var htmlPart: Part? = null
+
+ for (bodyPart in multipart.bodyParts) {
+ val mimeType = bodyPart.mimeType.toMimeTypeOrNull()
+ val body = bodyPart.body
+
+ if (body is Multipart) {
+ val candidatePart = findFirstTextPart(bodyPart) ?: continue
+ if (mimeType == TEXT_PLAIN) {
+ return candidatePart
+ }
+
+ htmlPart = candidatePart
+ } else if (mimeType == TEXT_PLAIN) {
+ return bodyPart
+ } else if (mimeType == TEXT_HTML && htmlPart == null) {
+ htmlPart = bodyPart
+ }
+ }
+
+ return htmlPart
+ }
+
+ private fun findTextPartInMultipart(multipart: Multipart): Part? {
+ for (bodyPart in multipart.bodyParts) {
+ val mimeType = bodyPart.mimeType.toMimeTypeOrNull()
+ val body = bodyPart.body
+
+ if (body is Multipart) {
+ return findFirstTextPart(bodyPart) ?: continue
+ } else if (mimeType == TEXT_PLAIN || mimeType == TEXT_HTML) {
+ return bodyPart
+ }
+ }
+
+ return null
+ }
+}
diff --git a/app/core/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.java b/app/core/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.java
deleted file mode 100644
index 632231a00a45250d916a5e834de138367e027223..0000000000000000000000000000000000000000
--- a/app/core/src/main/java/com/fsck/k9/message/html/BitcoinUriParser.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.fsck.k9.message.html;
-
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-
-class BitcoinUriParser implements UriParser {
- private static final Pattern BITCOIN_URI_PATTERN =
- Pattern.compile("bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?");
-
- @Nullable
- @Override
- public UriMatch parseUri(@NotNull CharSequence text, int startPos) {
- Matcher matcher = BITCOIN_URI_PATTERN.matcher(text);
-
- if (!matcher.find(startPos) || matcher.start() != startPos) {
- return null;
- }
-
- int startIndex = matcher.start();
- int endIndex = matcher.end();
- CharSequence uri = text.subSequence(startIndex, endIndex);
- return new UriMatch(startIndex, endIndex, uri);
- }
-}
diff --git a/app/core/src/main/java/com/fsck/k9/message/html/EmailTextToHtml.kt b/app/core/src/main/java/com/fsck/k9/message/html/EmailTextToHtml.kt
index c14b569fc41dbd77ad19c3bff6fa747a20804af1..fc487a75c23db56b197536cfecf68cfbb05e9141 100644
--- a/app/core/src/main/java/com/fsck/k9/message/html/EmailTextToHtml.kt
+++ b/app/core/src/main/java/com/fsck/k9/message/html/EmailTextToHtml.kt
@@ -22,7 +22,7 @@ class EmailTextToHtml private constructor(private val text: String) {
}
private fun appendHtmlPrefix() {
- html.append("")
+ html.append("")
}
private fun appendHtmlSuffix() {
diff --git a/app/core/src/main/java/com/fsck/k9/message/html/EthereumUriParser.java b/app/core/src/main/java/com/fsck/k9/message/html/EthereumUriParser.java
deleted file mode 100644
index 70f5878a23cddeda747a9a5a4b791c61898ec330..0000000000000000000000000000000000000000
--- a/app/core/src/main/java/com/fsck/k9/message/html/EthereumUriParser.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.fsck.k9.message.html;
-
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-
-/**
- * Parses ERC-67 URIs
- * https://github.com/ethereum/EIPs/issues/67
- */
-class EthereumUriParser implements UriParser {
- private static final Pattern ETHEREUM_URI_PATTERN =
- Pattern.compile("ethereum:0x[0-9a-fA-F]*(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?");
-
- @Nullable
- @Override
- public UriMatch parseUri(@NotNull CharSequence text, int startPos) {
- Matcher matcher = ETHEREUM_URI_PATTERN.matcher(text);
-
- if (!matcher.find(startPos) || matcher.start() != startPos) {
- return null;
- }
-
- int startIndex = matcher.start();
- int endIndex = matcher.end();
- CharSequence uri = text.subSequence(startIndex, endIndex);
- return new UriMatch(startIndex, endIndex, uri);
- }
-}
diff --git a/app/core/src/main/java/com/fsck/k9/message/html/GenericUriParser.kt b/app/core/src/main/java/com/fsck/k9/message/html/GenericUriParser.kt
new file mode 100644
index 0000000000000000000000000000000000000000..14cede4cdc26480a4a6e63841d4ca588d3d568df
--- /dev/null
+++ b/app/core/src/main/java/com/fsck/k9/message/html/GenericUriParser.kt
@@ -0,0 +1,34 @@
+package com.fsck.k9.message.html
+
+import java.util.regex.Pattern
+
+/**
+ * Matches the URI generic syntax.
+ *
+ * See [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986).
+ */
+class GenericUriParser : UriParser {
+ override fun parseUri(text: CharSequence, startPos: Int): UriMatch? {
+ val matcher = PATTERN.matcher(text)
+ if (!matcher.find(startPos) || matcher.start() != startPos) return null
+
+ val startIndex = matcher.start()
+ val endIndex = matcher.end()
+ val uri = text.subSequence(startIndex, endIndex)
+
+ return UriMatch(startIndex, endIndex, uri)
+ }
+
+ companion object {
+ private const val SCHEME = "[a-zA-Z][a-zA-Z0-9+.\\-]*"
+ private const val AUTHORITY = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:\\[\\]@]*"
+ private const val PATH = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/]*"
+ private const val QUERY = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/?]*"
+ private const val FRAGMENT = "[a-zA-Z0-9\\-._~%!\$&'()*+,;=:@/?]*"
+
+ // This regular expression matches more than allowed by the generic URI syntax. So we might end up linkifying
+ // text that is not a proper URI. We leave apps actually handling the URI when the user clicks on such a link
+ // to deal with this case.
+ private val PATTERN = Pattern.compile("$SCHEME:(?://$AUTHORITY)?(?:$PATH)?(?:\\?$QUERY)?(?:#$FRAGMENT)?")
+ }
+}
diff --git a/app/core/src/main/java/com/fsck/k9/message/html/TextToHtml.kt b/app/core/src/main/java/com/fsck/k9/message/html/TextToHtml.kt
index 286c88c2d623022e45a542264d44af2aadd40234..ad0b5e6f2e68f203a2cd1ce4750949552f78575f 100644
--- a/app/core/src/main/java/com/fsck/k9/message/html/TextToHtml.kt
+++ b/app/core/src/main/java/com/fsck/k9/message/html/TextToHtml.kt
@@ -8,6 +8,8 @@ class TextToHtml private constructor(
private val retainOriginalWhitespace: Boolean
) {
fun appendAsHtmlFragment() {
+ appendHtmlPrefix()
+
val modifications = HTML_MODIFIERS
.flatMap { it.findModifications(text) }
.sortedBy { it.startIndex }
@@ -52,6 +54,16 @@ class TextToHtml private constructor(
}
appendHtmlEncoded(currentIndex, text.length)
+
+ appendHtmlSuffix()
+ }
+
+ private fun appendHtmlPrefix() {
+ html.append("""""")
+ }
+
+ private fun appendHtmlSuffix() {
+ html.append("
")
}
private fun appendHtmlEncoded(startIndex: Int, endIndex: Int) {
diff --git a/app/core/src/main/java/com/fsck/k9/message/html/UriMatcher.kt b/app/core/src/main/java/com/fsck/k9/message/html/UriMatcher.kt
index 5dea02560e4928e60caeffd8a5930f8d3755fbc6..44329acfdb6a7b7bb15b867b4697e5878f6d078f 100644
--- a/app/core/src/main/java/com/fsck/k9/message/html/UriMatcher.kt
+++ b/app/core/src/main/java/com/fsck/k9/message/html/UriMatcher.kt
@@ -3,12 +3,14 @@ package com.fsck.k9.message.html
object UriMatcher {
private val SUPPORTED_URIS = run {
val httpUriParser = HttpUriParser()
+ val genericUriParser = GenericUriParser()
mapOf(
- "ethereum:" to EthereumUriParser(),
- "bitcoin:" to BitcoinUriParser(),
"http:" to httpUriParser,
"https:" to httpUriParser,
- "rtsp:" to httpUriParser
+ "mailto:" to genericUriParser,
+ "matrix:" to genericUriParser,
+ "rtsp:" to httpUriParser,
+ "xmpp:" to genericUriParser,
)
}
diff --git a/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationController.kt b/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationController.kt
index c29c7791f533c08c9c248d1d339111ba16b611df..21c850da54053865d046e0528c317d89736053a9 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationController.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationController.kt
@@ -14,6 +14,7 @@ internal class NewMailNotificationController(
private val summaryNotificationCreator: SummaryNotificationCreator,
private val singleMessageNotificationCreator: SingleMessageNotificationCreator
) {
+ @Synchronized
fun restoreNewMailNotifications(accounts: List) {
for (account in accounts) {
val notificationData = newMailNotificationManager.restoreNewMailNotifications(account)
@@ -24,6 +25,7 @@ internal class NewMailNotificationController(
}
}
+ @Synchronized
fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean) {
val notificationData = newMailNotificationManager.addNewMailNotification(account, message, silent)
@@ -32,6 +34,7 @@ internal class NewMailNotificationController(
}
}
+ @Synchronized
fun removeNewMailNotifications(
account: Account,
clearNewMessageState: Boolean,
@@ -48,6 +51,7 @@ internal class NewMailNotificationController(
}
}
+ @Synchronized
fun clearNewMailNotifications(account: Account, clearNewMessageState: Boolean) {
val cancelNotificationIds = newMailNotificationManager.clearNewMailNotifications(account, clearNewMessageState)
diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationActionService.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationActionService.kt
index d70ea87778326e3c891c99868bf21dc387dd459c..5dfa9a8e30a1e7529a3cd30144622cca926f9ed3 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/NotificationActionService.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationActionService.kt
@@ -11,22 +11,44 @@ import com.fsck.k9.controller.MessageReference
import com.fsck.k9.controller.MessageReferenceHelper
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.mail.Flag
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
+import org.koin.core.qualifier.named
import timber.log.Timber
class NotificationActionService : Service() {
private val preferences: Preferences by inject()
private val messagingController: MessagingController by inject()
+ private val coroutineScope: CoroutineScope by inject(named("AppCoroutineScope"))
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Timber.i("NotificationActionService started with startId = %d", startId)
- val accountUuid = intent.getStringExtra(EXTRA_ACCOUNT_UUID) ?: error("Missing account UUID")
+ startHandleCommand(intent, startId)
+
+ return START_NOT_STICKY
+ }
+
+ private fun startHandleCommand(intent: Intent, startId: Int) {
+ coroutineScope.launch(Dispatchers.IO) {
+ handleCommand(intent)
+ stopSelf(startId)
+ }
+ }
+
+ private fun handleCommand(intent: Intent) {
+ val accountUuid = intent.getStringExtra(EXTRA_ACCOUNT_UUID)
+ if (accountUuid == null) {
+ Timber.w("Missing account UUID.")
+ return
+ }
val account = preferences.getAccount(accountUuid)
if (account == null) {
Timber.w("Could not find account for notification action.")
- return START_NOT_STICKY
+ return
}
when (intent.action) {
@@ -38,8 +60,6 @@ class NotificationActionService : Service() {
}
cancelNotifications(intent, account)
-
- return START_NOT_STICKY
}
override fun onBind(intent: Intent): IBinder? {
diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationDataStore.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationDataStore.kt
index de9f8c94bea4cbe209a290961d3dcd28acb9ea23..058f229e84fe1fab8e0317b1f163a5746cced036 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/NotificationDataStore.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationDataStore.kt
@@ -15,6 +15,11 @@ internal const val MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS = 8
internal class NotificationDataStore {
private val notificationDataMap = mutableMapOf()
+ @Synchronized
+ fun isAccountInitialized(account: Account): Boolean {
+ return notificationDataMap[account.uuid] != null
+ }
+
@Synchronized
fun initializeAccount(
account: Account,
diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationRepository.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationRepository.kt
index ddad7bb965d3be14e3cb9ad18d20eb618c926172..05d26a6a369ca085dcc7609053863cd8aa749964 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/NotificationRepository.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationRepository.kt
@@ -15,14 +15,14 @@ internal class NotificationRepository(
@Synchronized
fun restoreNotifications(account: Account): NotificationData? {
+ if (notificationDataStore.isAccountInitialized(account)) return null
+
val localStore = localStoreProvider.getInstance(account)
val (activeNotificationMessages, inactiveNotificationMessages) = localStore.notificationMessages.partition {
it.notificationId != null
}
- if (activeNotificationMessages.isEmpty()) return null
-
val activeNotifications = activeNotificationMessages.map { notificationMessage ->
val content = notificationContentCreator.createFromMessage(account, notificationMessage.message)
NotificationHolder(notificationMessage.notificationId!!, notificationMessage.timestamp, content)
@@ -33,11 +33,19 @@ internal class NotificationRepository(
InactiveNotificationHolder(notificationMessage.timestamp, content)
}
- return notificationDataStore.initializeAccount(account, activeNotifications, inactiveNotifications)
+ val notificationData = notificationDataStore.initializeAccount(
+ account,
+ activeNotifications,
+ inactiveNotifications
+ )
+
+ return if (notificationData.activeNotifications.isNotEmpty()) notificationData else null
}
@Synchronized
fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult? {
+ restoreNotifications(account)
+
return notificationDataStore.addNotification(account, content, timestamp)?.also { result ->
persistNotificationDataStoreChanges(
account = account,
@@ -53,6 +61,8 @@ internal class NotificationRepository(
clearNewMessageState: Boolean = true,
selector: (List) -> List
): RemoveNotificationsResult? {
+ restoreNotifications(account)
+
return notificationDataStore.removeNotifications(account, selector)?.also { result ->
persistNotificationDataStoreChanges(
account = account,
diff --git a/app/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java b/app/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java
index 93dd1d23ca022a4e5a0b049899ab4783f23d98d7..cb4bfeb8e8af4fb5515d0155900cf4af274f61b7 100644
--- a/app/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java
+++ b/app/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java
@@ -76,18 +76,6 @@ public class GeneralSettingsDescriptions {
s.put("enableSensitiveLogging", Settings.versions(
new V(1, new BooleanSetting(false))
));
- s.put("fontSizeAccountDescription", Settings.versions(
- new V(1, new FontSizeSetting(FontSizes.FONT_DEFAULT))
- ));
- s.put("fontSizeAccountName", Settings.versions(
- new V(1, new FontSizeSetting(FontSizes.FONT_DEFAULT))
- ));
- s.put("fontSizeFolderName", Settings.versions(
- new V(1, new FontSizeSetting(FontSizes.FONT_DEFAULT))
- ));
- s.put("fontSizeFolderStatus", Settings.versions(
- new V(1, new FontSizeSetting(FontSizes.FONT_DEFAULT))
- ));
s.put("fontSizeMessageComposeInput", Settings.versions(
new V(5, new FontSizeSetting(FontSizes.FONT_DEFAULT))
));
@@ -283,6 +271,9 @@ public class GeneralSettingsDescriptions {
s.put("swipeLeftAction", Settings.versions(
new V(83, new EnumSetting<>(SwipeAction.class, SwipeAction.ToggleRead))
));
+ s.put("showComposeButtonOnMessageList", Settings.versions(
+ new V(85, new BooleanSetting(true))
+ ));
SETTINGS = Collections.unmodifiableMap(s);
diff --git a/app/core/src/main/java/com/fsck/k9/preferences/Settings.java b/app/core/src/main/java/com/fsck/k9/preferences/Settings.java
index a761d6fa90cb69eadd005e582b393089d1b33c9d..7f1274952bc118327fedcc9f8a6e1f1ad3d4f279 100644
--- a/app/core/src/main/java/com/fsck/k9/preferences/Settings.java
+++ b/app/core/src/main/java/com/fsck/k9/preferences/Settings.java
@@ -36,7 +36,7 @@ public class Settings {
*
* @see SettingsExporter
*/
- public static final int VERSION = 83;
+ public static final int VERSION = 85;
static Map validate(int version, Map> settings,
Map importedSettings, boolean useDefaultValues) {
diff --git a/app/core/src/main/java/com/fsck/k9/search/LocalSearch.java b/app/core/src/main/java/com/fsck/k9/search/LocalSearch.java
index 59e400cbba1535e267c41abdb208c8c8dad53e10..52be56ade78fc83dff1a0a84dbfc0900f7f77c7d 100644
--- a/app/core/src/main/java/com/fsck/k9/search/LocalSearch.java
+++ b/app/core/src/main/java/com/fsck/k9/search/LocalSearch.java
@@ -241,7 +241,7 @@ public class LocalSearch implements SearchSpecification {
}
///////////////////////////////////////////////////////////////
- // Public accesor methods
+ // Public accessor methods
///////////////////////////////////////////////////////////////
/**
* TODO THIS HAS TO GO!!!!
diff --git a/app/core/src/test/java/com/fsck/k9/TestApp.kt b/app/core/src/test/java/com/fsck/k9/TestApp.kt
index b365bd4ddc5c2190f0740ad49585b8d54d791a4c..7f6f0f88677f81a1870667c19c1a20f4467a9eeb 100644
--- a/app/core/src/test/java/com/fsck/k9/TestApp.kt
+++ b/app/core/src/test/java/com/fsck/k9/TestApp.kt
@@ -1,6 +1,7 @@
package com.fsck.k9
import android.app.Application
+import androidx.work.WorkManager
import com.fsck.k9.backend.BackendManager
import com.fsck.k9.controller.ControllerExtension
import com.fsck.k9.crypto.EncryptionExtractor
@@ -36,4 +37,5 @@ val testModule = module {
single { mock() }
single { mock() }
single(named("controllerExtensions")) { emptyList() }
+ single { mock() }
}
diff --git a/app/core/src/test/java/com/fsck/k9/mailstore/MessageViewInfoExtractorTest.java b/app/core/src/test/java/com/fsck/k9/mailstore/MessageViewInfoExtractorTest.java
index 16fce8e4de9a0ba27f054d708320c9e8d6987719..6de424caa23d3d96ad977dc9cd7ccfd5a8b72d05 100644
--- a/app/core/src/test/java/com/fsck/k9/mailstore/MessageViewInfoExtractorTest.java
+++ b/app/core/src/test/java/com/fsck/k9/mailstore/MessageViewInfoExtractorTest.java
@@ -60,7 +60,7 @@ import static org.mockito.Mockito.when;
@SuppressWarnings("WeakerAccess")
public class MessageViewInfoExtractorTest extends K9RobolectricTest {
public static final String BODY_TEXT = "K-9 Mail rocks :>";
- public static final String BODY_TEXT_HTML = "K-9 Mail rocks :>";
+ public static final String BODY_TEXT_HTML = "K-9 Mail rocks :>
";
public static final String BODY_TEXT_FLOWED = "K-9 Mail rocks :> \r\nflowed line\r\nnot flowed line";
public static final String SUBJECT = "sabject";
public static final String PROTECTED_SUBJECT = "protected subject";
@@ -126,8 +126,8 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
ViewableExtractedText container = messageViewInfoExtractor.extractTextFromViewables(outputViewableParts);
String expectedHtml =
- "" +
- "K-9 Mail rocks :>" +
+ "" +
+ BODY_TEXT_HTML +
"
";
assertEquals(BODY_TEXT, container.text);
@@ -153,8 +153,10 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
String expectedText = "K-9 Mail rocks :> flowed line\r\n" +
"not flowed line";
String expectedHtml =
- "" +
+ "" +
+ "" +
"K-9 Mail rocks :> flowed line
not flowed line" +
+ "
" +
"
";
assertEquals(expectedText, container.text);
@@ -214,13 +216,17 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
"------------------------------------------------------------------------\r\n\r\n" +
bodyText2;
String expectedHtml =
- "" +
+ "" +
+ "" +
bodyText1 +
+ "
" +
"
" +
"" +
- "" +
+ "" +
+ "" +
bodyText2 +
+ "
" +
"
";
@@ -280,7 +286,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
"\r\n" +
innerBodyText;
String expectedHtml =
- "" +
+ "" +
BODY_TEXT_HTML +
"
" +
"Subject" +
"" +
"" +
- "
" +
+ "" +
+ "" +
innerBodyText +
+ "
" +
"
";
assertEquals(expectedText, container.text);
@@ -356,12 +364,20 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
String expectedHtmlText = "
" +
"Subject: | (No subject) |
" +
"
" +
- "text body of first message
" +
+ "" +
+ "" +
+ "text body of first message
" +
+ "
" +
+ "
" +
"" +
"" +
"Subject: | subject of second message |
" +
"
" +
- "text part of second message
";
+ "" +
+ "" +
+ "text part of second message
" +
+ "
" +
+ "
";
assertEquals(4, outputViewableParts.size());
@@ -389,7 +405,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
false);
- assertEquals("text
", messageViewInfo.text);
+ assertEquals("text
", messageViewInfo.text);
assertSame(attachmentViewInfo, messageViewInfo.attachments.get(0));
assertNull(messageViewInfo.cryptoResultAnnotation);
assertTrue(messageViewInfo.extraAttachments.isEmpty());
@@ -411,7 +427,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
false);
- assertEquals("text
", messageViewInfo.text);
+ assertEquals("text
", messageViewInfo.text);
assertSame(annotation, messageViewInfo.cryptoResultAnnotation);
assertSame(message, messageViewInfo.message);
assertSame(message, messageViewInfo.rootPart);
@@ -436,7 +452,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
false);
- assertEquals("replacement text
", messageViewInfo.text);
+ assertEquals("replacement text
", messageViewInfo.text);
assertSame(annotation, messageViewInfo.cryptoResultAnnotation);
assertSame(message, messageViewInfo.message);
assertSame(replacementPart, messageViewInfo.rootPart);
@@ -464,7 +480,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
false);
- assertEquals("text
", messageViewInfo.text);
+ assertEquals("text
", messageViewInfo.text);
assertSame(annotation, messageViewInfo.cryptoResultAnnotation);
assertEquals("extra text", messageViewInfo.extraText);
assertTrue(messageViewInfo.attachments.isEmpty());
@@ -494,7 +510,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
false);
- assertEquals("text
", messageViewInfo.text);
+ assertEquals("text
", messageViewInfo.text);
assertSame(annotation, messageViewInfo.cryptoResultAnnotation);
assertSame(attachmentViewInfo, messageViewInfo.extraAttachments.get(0));
assertTrue(messageViewInfo.attachments.isEmpty());
@@ -535,7 +551,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
true);
assertSame(openPgpResultAnnotation, messageViewInfo.cryptoResultAnnotation);
- assertEquals("encrypted text
", messageViewInfo.text);
+ assertEquals("encrypted text
", messageViewInfo.text);
assertTrue(messageViewInfo.attachments.isEmpty());
assertTrue(messageViewInfo.extraAttachments.isEmpty());
}
@@ -562,7 +578,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
true);
assertSame(openPgpResultAnnotation, messageViewInfo.cryptoResultAnnotation);
- assertEquals("encrypted text
", messageViewInfo.text);
+ assertEquals("encrypted text
", messageViewInfo.text);
assertEquals(PROTECTED_SUBJECT, messageViewInfo.subject);
assertTrue(messageViewInfo.attachments.isEmpty());
assertTrue(messageViewInfo.extraAttachments.isEmpty());
@@ -580,7 +596,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, null,
false);
- assertEquals("text
", messageViewInfo.text);
+ assertEquals("text
", messageViewInfo.text);
assertNull(messageViewInfo.cryptoResultAnnotation);
assertTrue(messageViewInfo.attachments.isEmpty());
assertTrue(messageViewInfo.extraAttachments.isEmpty());
@@ -604,7 +620,7 @@ public class MessageViewInfoExtractorTest extends K9RobolectricTest {
MessageViewInfo messageViewInfo = messageViewInfoExtractor.extractMessageForView(message, null,
false);
- assertEquals("text
", messageViewInfo.text);
+ assertEquals("text
", messageViewInfo.text);
assertNull(messageViewInfo.cryptoResultAnnotation);
assertSame(mock, messageViewInfo.attachments.get(0));
assertTrue(messageViewInfo.extraAttachments.isEmpty());
diff --git a/app/core/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.kt b/app/core/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.kt
index 67eafbbef77f7a2c0d734c89ea980825ac162fc0..2178cb42838605d0399cbd0b72b0fba5e6d20537 100644
--- a/app/core/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.kt
+++ b/app/core/src/test/java/com/fsck/k9/message/TextBodyBuilderTest.kt
@@ -12,14 +12,14 @@ class TextBodyBuilderTest(val testData: TestData) {
companion object {
private const val MESSAGE_TEXT = "my message\r\nwith two lines"
- private const val MESSAGE_TEXT_HTML = "my message
with two lines"
+ private const val MESSAGE_TEXT_HTML = "my message
with two lines
"
private const val QUOTED_TEXT = ">quoted text\r\n>-- \r\n>Other signature"
private const val QUOTED_HTML_BODY = "quoted text
"
private const val QUOTED_HTML_TAGS_END = "