Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
8955bb7
feat: Add signature to rich editor
solrubado Feb 3, 2026
d2ed990
feat: Add signature and quotes to richHtlmEditorWebView
solrubado Feb 11, 2026
a802d2c
feat: Add everything into the mainwebview
solrubado Feb 12, 2026
5b0f5e6
feat: Add everything in the same webview and add button to hide quotes
solrubado Feb 12, 2026
f7a6f36
feat: Changes in quote toggle button and in webview focus
solrubado Feb 13, 2026
73019fa
refactor: Remove logs, unnused icon and use jsoupwithlogs
solrubado Feb 16, 2026
6e2580b
fix: Add signature before quotes if they exist
solrubado Feb 16, 2026
75c23ac
feat: Show images on forward
solrubado Feb 16, 2026
7385a79
fix: Use images cids and remove loadWithOverviewMode on new message
solrubado Feb 16, 2026
4d59bb0
refactor: Remove unnecesary log
solrubado Feb 16, 2026
d5957bf
refactor: Remove unused function
solrubado Feb 16, 2026
7143acd
refactor: Fix identation in style css
solrubado Feb 16, 2026
1b1bb1e
refactor: Fix sonar issues
solrubado Feb 16, 2026
cbe1862
feat: Add textview placeholder and change signature with js
solrubado Feb 25, 2026
306f489
feat: Remove splitting of mail body if it's a draft and fix save snap…
solrubado Feb 25, 2026
86f88c9
fix: Fix cid images and webclient for attachments
solrubado Feb 26, 2026
eb7f4fa
feat: Change 3 dots for text button and fix ai PR suggestions
solrubado Mar 2, 2026
cf78338
fix: Fix sonar issues and add more margin to signature
solrubado Mar 2, 2026
38a7e31
refactor: Refactor for PR
solrubado Mar 3, 2026
f438f6a
fix: Remove unnecesary space
solrubado Mar 3, 2026
698b303
fix: Fix PR comments
solrubado Mar 9, 2026
6610621
fix: Fix PR comments
solrubado Mar 10, 2026
8002a56
fix: Horizontal scroll in messages
solrubado Mar 12, 2026
416fc8f
fix: Add horizontal scroll in messages
solrubado Mar 12, 2026
bc6da2d
feat: Add a EditorWebViewClient implementation as well
LunarX Mar 12, 2026
834b836
fix: Fix snapshot and horizontal scroll in wide emails
solrubado Mar 13, 2026
9ee38c3
fix: Change library version and fixes for configuration changes
solrubado Mar 16, 2026
a1728b7
refactor: Changes for PR
solrubado Mar 16, 2026
59d42f5
fix: Make sure webview is ready before evaluating js
solrubado Mar 16, 2026
6f90567
fix: Remove unnecesary margins in css and add margin to placeholder
solrubado Mar 16, 2026
2fdb8e8
refactor: Add shimmering comment and check quotes visibility after init
solrubado Mar 16, 2026
10ed7ba
refactor: Fix new lines for PR
solrubado Mar 16, 2026
47d5106
fix: Remove unnused function
solrubado Mar 16, 2026
e6e5616
feat: Remove images from inline attachment if they are deleted from q…
solrubado Mar 17, 2026
99e04ca
fix: Fix sonar issue
solrubado Mar 17, 2026
c876e3d
refactor: Refactor for PR
solrubado Mar 18, 2026
c5b636f
refactor: Add comment on munge script and add missing semicolons in q…
solrubado Mar 18, 2026
11a6d51
fix: Fix signature update
solrubado Mar 18, 2026
0ed1219
fix: Remove spellcheck in signature
solrubado Mar 18, 2026
30d89f8
fix: Fix bodyHasQuotes functions
solrubado Mar 20, 2026
f6f4ca0
fix: Fix empty body logic and refactor
solrubado Mar 25, 2026
2bdb58a
fix: Take into account blank characters when displaying placeholder
solrubado Mar 25, 2026
7eef5d9
refactor: Use globalThis instead of window to follow standards
solrubado Mar 25, 2026
dc85c55
refactor: Remove unused strings
solrubado Mar 25, 2026
2e05850
refactor: Extract javascript bridges to their own file
solrubado Mar 25, 2026
7281147
fix: Clean webview client arguments and change inline images observer…
solrubado Mar 25, 2026
e928f8b
fix: Empty body sanitization, return kdoc and combine flows for quote…
solrubado Mar 25, 2026
3605976
fix: Initialize webview client settings as soon as possible
solrubado Mar 25, 2026
97c17d4
fix: Add signature ID used for replacing signature
solrubado Mar 25, 2026
771c02c
fix: Fix snapshots that contain carriage returns
solrubado Mar 25, 2026
9669516
fix: Use correct draft instance when saving snapshot
solrubado Mar 25, 2026
2126294
fix: Make delete inline images script check the whole body
solrubado Mar 26, 2026
717074c
fix: Signature replacement related to empty signature
solrubado Mar 26, 2026
3ae1335
refactor: Move setupToggleQuotesButton to initUi
solrubado Mar 26, 2026
45dc06a
fix: Use initial body to check placeholder visibility and sanitize in…
solrubado Mar 26, 2026
70ff071
docs: Add comment on new line after quotes
solrubado Mar 26, 2026
706a2c2
refactor: Body has empty quotes should avoid multiple parsing
solrubado Mar 26, 2026
33ce1d1
fix: Extra padding on top of new message
solrubado Mar 26, 2026
d7eccbd
fix: Fix adding signature outside of editor when replacing a dummy si…
solrubado Mar 26, 2026
9556b85
refactor: Correct methods' order in initDraftAndViewModel
solrubado Mar 27, 2026
d196f5e
fix: Add quotes to webview only when the user toggles their visibility
solrubado Mar 27, 2026
b0e6dc7
fix: Fix adding quotes if they are null
solrubado Mar 30, 2026
d784428
fix: Fix channel consumption
solrubado Mar 30, 2026
370376c
fix: Fix body with null signature
solrubado Mar 30, 2026
c953928
fix: Add editor signature id if it is an existing draft
solrubado Mar 30, 2026
178d00b
fix: Remove useless isBodyBlank jsoup parse
solrubado Mar 30, 2026
f2180be
fix: Only use 2 jsoup parse instead of 3
solrubado Mar 30, 2026
222b8e7
fix: Not using body with signature id if the body didnt have quotes
solrubado Mar 31, 2026
7c68a7a
refactor: Clean code a bit and add documentation
LunarX Mar 31, 2026
4cea075
fix: Compare snapshots without EDITOR_LOCAL_SIGNATURE_ID
LunarX Mar 31, 2026
3c1669b
fix: Split and save body, signature and quotes and fix snapshot
solrubado Mar 31, 2026
e3a5c81
refactor: Code refactor and copyrights update
solrubado Mar 31, 2026
f14f847
refactor: Remove unnecessary function getSanitizedHtml when the conte…
solrubado Apr 1, 2026
87e2611
refactor: Save snapshot outside common body processing
LunarX Apr 1, 2026
47fd588
refactor: Put back savesnapshot inside common body processing and ren…
LunarX Apr 1, 2026
0512ae7
refactor: Change javascript bridge name
solrubado Apr 1, 2026
563cb86
fix: Change mutable map for map
solrubado Apr 1, 2026
8adaed2
refactor: Rename toggle button to show button
solrubado Apr 1, 2026
461d6b4
refactor: Remove unnecessary comment
solrubado Apr 1, 2026
f1db35f
refactor: Remove unnecessary style
solrubado Apr 1, 2026
5a61c06
refactor: Rename showQuotesScript to includeQuotesScript
solrubado Apr 1, 2026
ad4ddca
refactor: Refactor functions names, remove unnecessary is empty check…
solrubado Apr 1, 2026
61f9181
docs: Explain why we need normalizeCarriageReturns
LunarX Apr 2, 2026
7aeffe7
fix: Unnecessary new message check
solrubado Apr 2, 2026
15b7660
fix: Fix editor when the email is generated by Euria
solrubado Apr 14, 2026
e1bab45
fix: Fix sonar recommendations in js script
solrubado Apr 15, 2026
97f958b
feat: Add Matomo for when the user clicks on showQuote button
solrubado Apr 23, 2026
d5b2d2a
fix: JS string literal shouldnt be wrapped in quotes
solrubado Apr 24, 2026
d84a35f
refactor: Move BodyData class to MessageBodyUtils
solrubado Apr 24, 2026
8f75fc3
fix: Don't duplicate shouldLoadResources in MessageDisplayWebViewClient
solrubado Apr 24, 2026
0608720
chore: Update richHtmlEditor library
solrubado Apr 27, 2026
3b5b9eb
fix: Add signature local id after split
solrubado Apr 27, 2026
efbcc8a
fix: Send saved elements as parameters in ai script
solrubado Apr 27, 2026
2f82790
fix: Return all html after removing unwanted html
solrubado Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/java/com/infomaniak/mail/MatomoMail.kt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ object MatomoMail : Matomo {
DeleteDraft("deleteDraft"),
DeleteExecuted("deleteExecuted"),
DeleteFromHistory("deleteFromHistory"),
ShowQuote("showQuote"),
DeleteQuote("deleteQuote"),
DeleteRecipient("deleteRecipient"),
DeleteSearch("deleteSearch"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,27 @@ class ReplyForwardFooterManager @Inject constructor(private val appContext: Cont
addAndEscapeTextLine("")

addAlreadyEscapedBody(previousFullBody)
// We insert an unstyled empty line immediately after the quoted block. This is necessary because deleting styled
// content often leaves behind empty containers with residual formatting (borders, backgrounds). This plain line acts
// as a "clean exit", when the user deletes the quotes up to this point, the editor drops the quotes' inline styles
// completely, allowing the entire block to be removed in one go.
addAndEscapeTextLine("")
}.outerHtml()
}

private fun assembleReplyHtmlFooter(messageReplyHeader: String, previousFullBody: String): String {
val replyRoot = """<div class="${MessageBodyUtils.INFOMANIAK_REPLY_QUOTE_HTML_CLASS_NAME}" />"""
val replyRoot = """<div class="${MessageBodyUtils.INFOMANIAK_REPLY_QUOTE_HTML_CLASS_NAME}"/>"""
return parseAndWrapElementInNewDocument(replyRoot).apply {
addAndEscapeTextLine("")
addAndEscapeTextLine(messageReplyHeader, endWithBr = false)
addReplyBlockQuote {
addAlreadyEscapedBody(previousFullBody)
}
// We insert an unstyled empty line immediately after the quoted block. This is necessary because deleting styled
// content often leaves behind empty containers with residual formatting (borders, backgrounds). This plain line acts
// as a "clean exit", when the user deletes the quotes up to this point, the editor drops the quotes' inline styles
// completely, allowing the entire block to be removed in one go.
addAndEscapeTextLine("")
}.outerHtml()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.mail.data.models.javascriptBridge

import android.webkit.JavascriptInterface
import com.infomaniak.core.sentry.SentryLog
import org.json.JSONArray

class EditorJavascriptBridge(
private val onInlineImagesDeleted: (List<String>) -> Unit,
) {
@JavascriptInterface
fun onInlineImagesDeleted(cidJson: String) {
val jsonArray = runCatching {
JSONArray(cidJson)
}.onFailure {
SentryLog.e(TAG, "Failed to parse CIDs", it)
}.getOrNull() ?: return

val cids = (0 until jsonArray.length()).map { jsonArray.getString(it) }
onInlineImagesDeleted(cids)
}

companion object {
private const val TAG = "EditorJavascriptBridge"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.mail.data.models.javascriptBridge

import android.webkit.JavascriptInterface
import com.infomaniak.mail.utils.SentryDebug

class MessageDisplayJavascriptBridge(
private val onWebViewFinishedLoading: () -> Unit,
) {

@JavascriptInterface
fun reportOverScroll(clientWidth: Int, scrollWidth: Int, messageUid: String) {
SentryDebug.sendOverScrolledMessage(clientWidth, scrollWidth, messageUid)
}

@JavascriptInterface
fun reportError(
errorName: String,
errorMessage: String,
errorStack: String,
scriptFirstLine: String,
messageUid: String,
) {
val correctErrorStack = fixStackTraceLineNumber(errorStack, scriptFirstLine)
SentryDebug.sendJavaScriptError(errorName, errorMessage, correctErrorStack, messageUid)
}

@JavascriptInterface
fun webviewFinishedLoading() {
onWebViewFinishedLoading()
}

private fun fixStackTraceLineNumber(errorStack: String, scriptFirstLine: String): String {
var correctErrorStack = errorStack
val matches = "about:blank:([0-9]+):".toRegex().findAll(correctErrorStack)
matches.forEach { match ->
val lineNumber = match.groupValues[1]
val newLineNumber = lineNumber.toInt() - scriptFirstLine.toInt() + 1
correctErrorStack = correctErrorStack.replace(match.groupValues[0], "about:blank:$newLineNumber:")
}
return correctErrorStack
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import com.infomaniak.mail.databinding.ItemSuperCollapsedBlockBinding
import com.infomaniak.mail.ui.main.thread.ThreadAdapter.ThreadAdapterViewHolder
import com.infomaniak.mail.ui.main.thread.models.MessageUi
import com.infomaniak.mail.ui.main.thread.models.MessageUi.UnsubscribeState
import com.infomaniak.mail.ui.main.thread.webViewClient.MessageDisplayWebViewClient
import com.infomaniak.mail.utils.AccountUtils
import com.infomaniak.mail.utils.HtmlFormatter
import com.infomaniak.mail.utils.MessageBodyUtils
Expand All @@ -86,7 +87,7 @@ import com.infomaniak.mail.utils.extensions.enableAlgorithmicDarkening
import com.infomaniak.mail.utils.extensions.formatSubject
import com.infomaniak.mail.utils.extensions.getAttributeColor
import com.infomaniak.mail.utils.extensions.indexOfFirstOrNull
import com.infomaniak.mail.utils.extensions.initWebViewClientAndBridge
import com.infomaniak.mail.utils.extensions.initDisplayWebViewClientAndBridge
import com.infomaniak.mail.utils.extensions.toDate
import com.infomaniak.mail.utils.extensions.toggleChevron
import io.sentry.Sentry
Expand Down Expand Up @@ -797,8 +798,8 @@ class ThreadAdapter(
quoteButtonFrameLayout.isVisible = hasQuote

initWebViewClientIfNeeded(
message,
threadAdapterCallbacks?.navigateToNewMessageActivity,
message = message,
navigateToNewMessageActivity = threadAdapterCallbacks?.navigateToNewMessageActivity,
onPageFinished = { onExpandedMessageLoaded(message.uid) },
onWebViewFinishedLoading = { threadAdapterCallbacks?.onBodyWebViewFinishedLoading?.invoke() },
)
Expand Down Expand Up @@ -1122,8 +1123,8 @@ class ThreadAdapter(
onAttachmentOptionsClicked = { onAttachmentOptionsClicked?.invoke(it) },
)

private var _bodyWebViewClient: MessageWebViewClient? = null
private var _fullMessageWebViewClient: MessageWebViewClient? = null
private var _bodyWebViewClient: MessageDisplayWebViewClient? = null
private var _fullMessageWebViewClient: MessageDisplayWebViewClient? = null
val bodyWebViewClient get() = _bodyWebViewClient!!
val fullMessageWebViewClient get() = _fullMessageWebViewClient!!

Expand All @@ -1149,7 +1150,7 @@ class ThreadAdapter(
}

if (_bodyWebViewClient == null) {
_bodyWebViewClient = binding.bodyWebView.initWebViewClientAndBridge(
_bodyWebViewClient = binding.bodyWebView.initDisplayWebViewClientAndBridge(
attachments = message.attachments,
messageUid = message.uid,
shouldLoadDistantResources = shouldLoadDistantResources,
Expand All @@ -1158,7 +1159,7 @@ class ThreadAdapter(
onPageFinished = onPageFinished,
onWebViewFinishedLoading = onWebViewFinishedLoading,
)
_fullMessageWebViewClient = binding.fullMessageWebView.initWebViewClientAndBridge(
_fullMessageWebViewClient = binding.fullMessageWebView.initDisplayWebViewClientAndBridge(
attachments = message.attachments,
messageUid = message.uid,
shouldLoadDistantResources = shouldLoadDistantResources,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.mail.ui.main.thread.webViewClient

import android.content.Context
import android.webkit.WebView
import com.infomaniak.mail.data.models.Attachment

class EditorWebViewClient(
context: Context,
cidDictionary: Map<String, Attachment>,
shouldLoadDistantResources: Boolean,
private val onPageFinished: () -> Unit,
) : MessageWebViewClient(
context,
cidDictionary,
shouldLoadDistantResources,
) {
override fun onPageFinished(webView: WebView, url: String?) {
onPageFinished()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.mail.ui.main.thread.webViewClient

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.webkit.WebResourceRequest
import android.webkit.WebView
import com.infomaniak.core.ui.showToast
import com.infomaniak.core.ui.view.toDp
import com.infomaniak.lib.richhtmleditor.looselyEscapeAsStringLiteralForJs
import com.infomaniak.mail.R
import com.infomaniak.mail.data.models.Attachment
import com.infomaniak.mail.utils.Utils.runCatchingRealm
import com.infomaniak.mail.utils.WebViewVersionUtils
import io.sentry.Sentry
import io.sentry.SentryLevel

class MessageDisplayWebViewClient(
private val context: Context,
cidDictionary: Map<String, Attachment>,
private val messageUid: String,
shouldLoadDistantResources: Boolean,
onBlockedResourcesDetected: () -> Unit,
private val navigateToNewMessageActivity: ((Uri) -> Unit)?,
private val onPageFinished: (() -> Unit)?,
) : MessageWebViewClient(
context,
cidDictionary,
shouldLoadDistantResources,
onBlockedResourcesDetected,
) {
Comment thread
solrubado marked this conversation as resolved.
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
request.url?.let { uri ->

if (uri.scheme == "mailto") {
navigateToNewMessageActivity?.invoke(uri)
return true
}

runCatching {
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}.onFailure {
context.showToast(R.string.startActivityCantHandleAction)
}
}
return true
}

override fun onPageFinished(webView: WebView, url: String?) {
runCatchingRealm {
val widthInDp = webView.width.toDp(webView)
if (widthInDp <= 0) {
val versionData = WebViewVersionUtils.getWebViewVersionData(context)

Sentry.captureMessage("Zero width webview detected onPageFinished which prevents message width's normalization") { scope ->
scope.level = SentryLevel.WARNING
scope.setExtra("width", webView.width.toString())
scope.setExtra("measuredWidth", webView.measuredWidth.toString())
scope.setExtra("height", webView.height.toString())
scope.setExtra("measuredHeight", webView.measuredHeight.toString())
scope.setTag(
"webview version",
"${versionData?.webViewPackageName}: ${versionData?.versionName} - ${versionData?.majorVersion}"
)
scope.setTag("visibility", webView.visibility.toString())
scope.setTag("messageUid", messageUid)
scope.setTag("shouldLoadDistantResources", shouldLoadDistantResources.toString())
}
}
val escapedMessageUid = looselyEscapeAsStringLiteralForJs(messageUid)
webView.loadUrl("javascript:removeAllProperties(); normalizeMessageWidth($widthInDp, $escapedMessageUid)")
onPageFinished?.invoke()
}
}
}
Loading
Loading