From 4371972654d53226bc84519bf972b93592e12327 Mon Sep 17 00:00:00 2001
From: cketti
Date: Mon, 18 Jul 2022 19:39:53 +0200
Subject: [PATCH 01/47] Prepare for version 6.300
---
app/k9mail/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/k9mail/build.gradle b/app/k9mail/build.gradle
index 85780b1332..f893dc7a1b 100644
--- a/app/k9mail/build.gradle
+++ b/app/k9mail/build.gradle
@@ -47,8 +47,8 @@ android {
applicationId "com.fsck.k9"
testApplicationId "com.fsck.k9.tests"
- versionCode 32001
- versionName '6.201'
+ versionCode 33000
+ versionName '6.300-SNAPSHOT'
// Keep in sync with the resource string array 'supported_languages'
resConfigs "in", "br", "ca", "cs", "cy", "da", "de", "et", "en", "en_GB", "es", "eo", "eu", "fr", "gd", "gl",
--
GitLab
From 52b22ba2ca7486a98be92aa73bf92cd6044984b3 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 19 Jul 2022 12:54:44 +0200
Subject: [PATCH 02/47] Call `invalidateMenu()` instead of directly updating
the current `Menu`
---
.../src/main/java/com/fsck/k9/activity/MessageList.kt | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index 0a533dc741..0aa635a108 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -1309,7 +1309,7 @@ open class MessageList :
showMessageViewPlaceHolder()
}
- configureMenu(menu)
+ invalidateMenu()
}
private fun addMessageListFragment(fragment: MessageListFragment) {
@@ -1402,7 +1402,7 @@ open class MessageList :
override fun remoteSearchStarted() {
// Remove action button for remote search
- configureMenu(menu)
+ invalidateMenu()
}
override fun goBack() {
@@ -1477,7 +1477,7 @@ open class MessageList :
setDrawerLockState()
showDefaultTitleView()
- configureMenu(menu)
+ invalidateMenu()
onMessageListDisplayed()
}
@@ -1506,7 +1506,7 @@ open class MessageList :
}
showMessageTitleView()
- configureMenu(menu)
+ invalidateMenu()
}
override fun updateMenu() {
--
GitLab
From 185a08de0b5a46d7eca55bc3a8c9ce4299646f1e Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 19 Jul 2022 13:00:23 +0200
Subject: [PATCH 03/47] Remove custom `updateMenu()` mechanism
---
.../src/main/java/com/fsck/k9/activity/MessageList.kt | 4 ----
.../java/com/fsck/k9/fragment/MessageListFragment.kt | 7 +++++--
.../fsck/k9/ui/messageview/MessageViewFragment.java | 11 +++++++----
3 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index 0aa635a108..fa430b8724 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -1509,10 +1509,6 @@ open class MessageList :
invalidateMenu()
}
- override fun updateMenu() {
- invalidateOptionsMenu()
- }
-
override fun disableDeleteAction() {
menu!!.findItem(R.id.delete).isEnabled = false
}
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
index f0e8625e7e..157c6c0d9c 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
@@ -1493,7 +1493,7 @@ class MessageListFragment :
savedListState = null
}
- fragmentListener.updateMenu()
+ invalidateMenu()
currentFolder?.let { currentFolder ->
currentFolder.moreMessages = messageListInfo.hasMoreMessages
@@ -1591,6 +1591,10 @@ class MessageListFragment :
resetActionMode()
}
+ private fun invalidateMenu() {
+ requireActivity().invalidateMenu()
+ }
+
val isCheckMailSupported: Boolean
get() = allAccounts || !isSingleAccountMode || !isSingleFolderMode || isRemoteFolder
@@ -1949,7 +1953,6 @@ class MessageListFragment :
fun startSearch(query: String, account: Account?, folderId: Long?): Boolean
fun remoteSearchStarted()
fun goBack()
- fun updateMenu()
fun onFolderNotFoundError()
companion object {
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java
index e1ce84d49d..81b1fd019e 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java
@@ -240,7 +240,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
mAccount = Preferences.getPreferences(getApplicationContext()).getAccount(mMessageReference.getAccountUuid());
messageLoaderHelper.asyncStartOrResumeLoadingMessage(messageReference, null);
- mFragmentListener.updateMenu();
+ invalidateMenu();
}
private void hideKeyboard() {
@@ -281,7 +281,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
mMessageView.getMessageHeaderView().setCryptoStatusLoading();
}
displaySubject(message.getSubject());
- mFragmentListener.updateMenu();
+ invalidateMenu();
}
private void displaySubject(String subject) {
@@ -517,7 +517,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
Collections.singletonList(mMessage), Flag.SEEN, !mMessage.isSet(Flag.SEEN));
mMessageView.setHeaders(mMessage, mAccount, true);
- mFragmentListener.updateMenu();
+ invalidateMenu();
}
}
@@ -773,7 +773,6 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
void onReply(MessageReference messageReference, @Nullable Parcelable decryptionResultForReply);
void setProgress(boolean b);
void showNextMessageOrReturn();
- void updateMenu();
}
public boolean isInitialized() {
@@ -884,6 +883,10 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
return new AttachmentController(mController, this, attachment);
}
+ private void invalidateMenu() {
+ requireActivity().invalidateMenu();
+ }
+
private enum FolderOperation {
COPY, MOVE
}
--
GitLab
From 1de5fa8cf25f224d8c7a7c54a81558f3a1e3503d Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 19 Jul 2022 14:54:13 +0200
Subject: [PATCH 04/47] Mark message list as visible when listing messages in a
thread
---
app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index fa430b8724..04fbdf60fd 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -1326,6 +1326,7 @@ open class MessageList :
}
messageListFragment = fragment
+ fragment.onListVisible()
if (isDrawerEnabled) {
lockDrawer()
--
GitLab
From 4486782166076d9184ed2e53f4ce682bedbe6e56 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 19 Jul 2022 13:56:23 +0200
Subject: [PATCH 05/47] Move code to prepare the message list menu to
`MessageListFragment`
---
.../java/com/fsck/k9/activity/MessageList.kt | 45 ---------------
.../fsck/k9/fragment/MessageListFragment.kt | 57 ++++++++++++++++---
2 files changed, 49 insertions(+), 53 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index 04fbdf60fd..dde63fa0d2 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -1185,51 +1185,6 @@ open class MessageList :
menu.findItem(R.id.unsubscribe).isVisible = messageViewFragment!!.canMessageBeUnsubscribed()
}
-
- // Set visibility of menu items related to the message list
-
- // Hide search menu items by default and enable one when appropriate
- menu.findItem(R.id.search).isVisible = false
- menu.findItem(R.id.search_remote).isVisible = false
- menu.findItem(R.id.search_everywhere).isVisible = false
-
- if (displayMode == DisplayMode.MESSAGE_VIEW || messageListFragment == null ||
- !messageListFragment!!.isInitialized
- ) {
- menu.findItem(R.id.set_sort).isVisible = false
- menu.findItem(R.id.select_all).isVisible = false
- menu.findItem(R.id.send_messages).isVisible = false
- menu.findItem(R.id.expunge).isVisible = false
- menu.findItem(R.id.empty_trash).isVisible = false
- menu.findItem(R.id.mark_all_as_read).isVisible = false
- } else {
- menu.findItem(R.id.set_sort).isVisible = true
- menu.findItem(R.id.select_all).isVisible = true
- menu.findItem(R.id.compose).isVisible = true
- menu.findItem(R.id.mark_all_as_read).isVisible = messageListFragment!!.isMarkAllAsReadSupported
-
- if (!messageListFragment!!.isSingleAccountMode) {
- menu.findItem(R.id.expunge).isVisible = false
- menu.findItem(R.id.send_messages).isVisible = false
- } else {
- menu.findItem(R.id.send_messages).isVisible = messageListFragment!!.isOutbox
- menu.findItem(R.id.expunge).isVisible = messageListFragment!!.isRemoteFolder &&
- messageListFragment!!.shouldShowExpungeAction()
- }
- menu.findItem(R.id.empty_trash).isVisible = messageListFragment!!.isShowingTrashFolder
-
- // If this is an explicit local search, show the option to search on the server
- if (!messageListFragment!!.isRemoteSearch && messageListFragment!!.isRemoteSearchAllowed) {
- menu.findItem(R.id.search_remote).isVisible = true
- } else if (!messageListFragment!!.isManualSearch) {
- menu.findItem(R.id.search).isVisible = true
- }
-
- val messageListFragment = messageListFragment!!
- if (messageListFragment.isManualSearch && !messageListFragment.localSearch.searchAllAccounts()) {
- menu.findItem(R.id.search_everywhere).isVisible = true
- }
- }
}
fun setActionBarTitle(title: String, subtitle: String? = null) {
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
index 157c6c0d9c..b23bb8426a 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
@@ -147,6 +147,7 @@ class MessageListFragment :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
restoreInstanceState(savedInstanceState)
decodeArguments() ?: return
@@ -644,7 +645,7 @@ class MessageListFragment :
}
}
- val isShowingTrashFolder: Boolean
+ private val isShowingTrashFolder: Boolean
get() {
if (!isSingleFolderMode) return false
return currentFolder!!.databaseId == account!!.trashFolderId
@@ -703,6 +704,46 @@ class MessageListFragment :
return "dialog-$dialogId"
}
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ if (isListVisible) {
+ prepareMenu(menu)
+ } else {
+ hideMenu(menu)
+ }
+ }
+
+ private fun prepareMenu(menu: Menu) {
+ menu.findItem(R.id.set_sort).isVisible = true
+ menu.findItem(R.id.select_all).isVisible = true
+ menu.findItem(R.id.compose).isVisible = true
+ menu.findItem(R.id.mark_all_as_read).isVisible = isMarkAllAsReadSupported
+ menu.findItem(R.id.empty_trash).isVisible = isShowingTrashFolder
+
+ if (isSingleAccountMode) {
+ menu.findItem(R.id.send_messages).isVisible = isOutbox
+ menu.findItem(R.id.expunge).isVisible = isRemoteFolder && shouldShowExpungeAction()
+ } else {
+ menu.findItem(R.id.send_messages).isVisible = false
+ menu.findItem(R.id.expunge).isVisible = false
+ }
+
+ menu.findItem(R.id.search).isVisible = !isManualSearch
+ menu.findItem(R.id.search_remote).isVisible = !isRemoteSearch && isRemoteSearchAllowed
+ menu.findItem(R.id.search_everywhere).isVisible = isManualSearch && !localSearch.searchAllAccounts()
+ }
+
+ private fun hideMenu(menu: Menu) {
+ menu.findItem(R.id.search).isVisible = false
+ menu.findItem(R.id.search_remote).isVisible = false
+ menu.findItem(R.id.set_sort).isVisible = false
+ menu.findItem(R.id.select_all).isVisible = false
+ menu.findItem(R.id.mark_all_as_read).isVisible = false
+ menu.findItem(R.id.send_messages).isVisible = false
+ menu.findItem(R.id.empty_trash).isVisible = false
+ menu.findItem(R.id.expunge).isVisible = false
+ menu.findItem(R.id.search_everywhere).isVisible = false
+ }
+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.set_sort_date) {
@@ -1416,7 +1457,7 @@ class MessageListFragment :
return currentFolder.databaseId == folderId
}
- val isRemoteFolder: Boolean
+ private val isRemoteFolder: Boolean
get() {
if (localSearch.isManualSearch || isOutbox) return false
@@ -1428,10 +1469,10 @@ class MessageListFragment :
}
}
- val isManualSearch: Boolean
+ private val isManualSearch: Boolean
get() = localSearch.isManualSearch
- fun shouldShowExpungeAction(): Boolean {
+ private fun shouldShowExpungeAction(): Boolean {
val account = this.account ?: return false
return account.expungePolicy == Expunge.EXPUNGE_MANUALLY && messagingController.supportsExpunge(account)
}
@@ -1445,7 +1486,7 @@ class MessageListFragment :
}
}
- val isRemoteSearchAllowed: Boolean
+ private val isRemoteSearchAllowed: Boolean
get() = isManualSearch && !isRemoteSearch && isSingleFolderMode && messagingController.isPushCapable(account)
fun onSearchRequested(query: String): Boolean {
@@ -1453,7 +1494,7 @@ class MessageListFragment :
return fragmentListener.startSearch(query, account, folderId)
}
- fun setMessageList(messageListInfo: MessageListInfo) {
+ private fun setMessageList(messageListInfo: MessageListInfo) {
val messageListItems = messageListInfo.messageListItems
if (isThreadDisplay && messageListItems.isEmpty()) {
handler.goBack()
@@ -1564,7 +1605,7 @@ class MessageListFragment :
}
}
- val isMarkAllAsReadSupported: Boolean
+ private val isMarkAllAsReadSupported: Boolean
get() = isSingleAccountMode && isSingleFolderMode && !isOutbox
fun confirmMarkAllAsRead() {
@@ -1595,7 +1636,7 @@ class MessageListFragment :
requireActivity().invalidateMenu()
}
- val isCheckMailSupported: Boolean
+ private val isCheckMailSupported: Boolean
get() = allAccounts || !isSingleAccountMode || !isSingleFolderMode || isRemoteFolder
private val isCheckMailAllowed: Boolean
--
GitLab
From 9f45fe1d460c9a5c7a01a9f41f8087b38bf5c15d Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 19 Jul 2022 15:13:29 +0200
Subject: [PATCH 06/47] Refactor `MessageListFragment.onOptionsItemSelected()`
---
.../fsck/k9/fragment/MessageListFragment.kt | 55 +++++--------------
1 file changed, 13 insertions(+), 42 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
index b23bb8426a..16a204640b 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
@@ -745,50 +745,21 @@ class MessageListFragment :
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
- val id = item.itemId
- if (id == R.id.set_sort_date) {
- changeSort(SortType.SORT_DATE)
- return true
- } else if (id == R.id.set_sort_arrival) {
- changeSort(SortType.SORT_ARRIVAL)
- return true
- } else if (id == R.id.set_sort_subject) {
- changeSort(SortType.SORT_SUBJECT)
- return true
- } else if (id == R.id.set_sort_sender) {
- changeSort(SortType.SORT_SENDER)
- return true
- } else if (id == R.id.set_sort_flag) {
- changeSort(SortType.SORT_FLAGGED)
- return true
- } else if (id == R.id.set_sort_unread) {
- changeSort(SortType.SORT_UNREAD)
- return true
- } else if (id == R.id.set_sort_attach) {
- changeSort(SortType.SORT_ATTACHMENT)
- return true
- } else if (id == R.id.select_all) {
- selectAll()
- return true
- }
-
- if (!isSingleAccountMode) {
- // None of the options after this point are "safe" for search results
- // TODO: This is not true for "unread" and "starred" searches in regular folders
- return false
+ when (item.itemId) {
+ R.id.set_sort_date -> changeSort(SortType.SORT_DATE)
+ R.id.set_sort_arrival -> changeSort(SortType.SORT_ARRIVAL)
+ R.id.set_sort_subject -> changeSort(SortType.SORT_SUBJECT)
+ R.id.set_sort_sender -> changeSort(SortType.SORT_SENDER)
+ R.id.set_sort_flag -> changeSort(SortType.SORT_FLAGGED)
+ R.id.set_sort_unread -> changeSort(SortType.SORT_UNREAD)
+ R.id.set_sort_attach -> changeSort(SortType.SORT_ATTACHMENT)
+ R.id.select_all -> selectAll()
+ R.id.send_messages -> onSendPendingMessages()
+ R.id.expunge -> onExpunge()
+ else -> return super.onOptionsItemSelected(item)
}
- if (id == R.id.send_messages) {
- onSendPendingMessages()
- return true
- } else if (id == R.id.expunge) {
- currentFolder?.let { folderInfoHolder ->
- onExpunge(account, folderInfoHolder.databaseId)
- }
- return true
- } else {
- return super.onOptionsItemSelected(item)
- }
+ return true
}
fun onSendPendingMessages() {
--
GitLab
From 42daa056fd0a9f1a62a67fe5c594bbd57e0072e5 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 19 Jul 2022 15:32:27 +0200
Subject: [PATCH 07/47] Move message list menu item handling code to
`MessageListFragment`
---
.../java/com/fsck/k9/activity/MessageList.kt | 58 +------------------
.../fsck/k9/fragment/MessageListFragment.kt | 31 +++++-----
2 files changed, 15 insertions(+), 74 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index dde63fa0d2..b56b7ecc98 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -26,7 +26,6 @@ import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit
import androidx.lifecycle.Observer
import com.fsck.k9.Account
-import com.fsck.k9.Account.SortType
import com.fsck.k9.K9
import com.fsck.k9.K9.SplitViewMode
import com.fsck.k9.Preferences
@@ -917,45 +916,12 @@ open class MessageList :
goBack()
}
return true
- } else if (id == R.id.compose) {
- messageListFragment!!.onCompose()
- return true
} else if (id == R.id.toggle_message_view_theme) {
onToggleTheme()
return true
- } else if (id == R.id.set_sort_date) { // MessageList
- messageListFragment!!.changeSort(SortType.SORT_DATE)
- return true
- } else if (id == R.id.set_sort_arrival) {
- messageListFragment!!.changeSort(SortType.SORT_ARRIVAL)
- return true
- } else if (id == R.id.set_sort_subject) {
- messageListFragment!!.changeSort(SortType.SORT_SUBJECT)
- return true
- } else if (id == R.id.set_sort_sender) {
- messageListFragment!!.changeSort(SortType.SORT_SENDER)
- return true
- } else if (id == R.id.set_sort_flag) {
- messageListFragment!!.changeSort(SortType.SORT_FLAGGED)
- return true
- } else if (id == R.id.set_sort_unread) {
- messageListFragment!!.changeSort(SortType.SORT_UNREAD)
- return true
- } else if (id == R.id.set_sort_attach) {
- messageListFragment!!.changeSort(SortType.SORT_ATTACHMENT)
- return true
- } else if (id == R.id.select_all) {
- messageListFragment!!.selectAll()
- return true
- } else if (id == R.id.search_remote) {
- messageListFragment!!.onRemoteSearch()
- return true
} else if (id == R.id.search_everywhere) {
searchEverywhere()
return true
- } else if (id == R.id.mark_all_as_read) {
- messageListFragment!!.confirmMarkAllAsRead()
- return true
} else if (id == R.id.next_message) { // MessageView
showNextMessage()
return true
@@ -1009,29 +975,7 @@ open class MessageList :
return true
}
- if (!singleFolderMode) {
- // None of the options after this point are "safe" for search results
- // TODO: This is not true for "unread" and "starred" searches in regular folders
- return false
- }
-
- return when (id) {
- R.id.send_messages -> {
- messageListFragment!!.onSendPendingMessages()
- true
- }
- R.id.expunge -> {
- messageListFragment!!.onExpunge()
- true
- }
- R.id.empty_trash -> {
- messageListFragment!!.onEmptyTrash()
- true
- }
- else -> {
- super.onOptionsItemSelected(item)
- }
- }
+ return super.onOptionsItemSelected(item)
}
private fun searchEverywhere() {
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
index 16a204640b..ed72925398 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
@@ -115,10 +115,8 @@ class MessageListFragment :
private set
var isSingleAccountMode = false
private set
- var isSingleFolderMode = false
- private set
- var isRemoteSearch = false
- private set
+ private var isSingleFolderMode = false
+ private var isRemoteSearch = false
private val isUnifiedInbox: Boolean
get() = localSearch.id == SearchAccount.UNIFIED_INBOX
@@ -130,8 +128,7 @@ class MessageListFragment :
* `true` after [.onCreate] was executed. Used in [.updateTitle] to
* make sure we don't access member variables before initialization is complete.
*/
- var isInitialized = false
- private set
+ private var isInitialized = false
private var isListVisible = false
@@ -507,7 +504,7 @@ class MessageListFragment :
}
}
- fun changeSort(sortType: SortType) {
+ private fun changeSort(sortType: SortType) {
val sortAscending = if (this.sortType == sortType) !sortAscending else null
changeSort(sortType, sortAscending)
}
@@ -629,17 +626,13 @@ class MessageListFragment :
}
}
- fun onExpunge() {
+ private fun onExpunge() {
currentFolder?.let { folderInfoHolder ->
- onExpunge(account, folderInfoHolder.databaseId)
+ messagingController.expunge(account, folderInfoHolder.databaseId)
}
}
- private fun onExpunge(account: Account?, folderId: Long) {
- messagingController.expunge(account, folderId)
- }
-
- fun onEmptyTrash() {
+ private fun onEmptyTrash() {
if (isShowingTrashFolder) {
showDialog(R.id.dialog_confirm_empty_trash)
}
@@ -746,6 +739,8 @@ class MessageListFragment :
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
+ R.id.search_remote -> onRemoteSearch()
+ R.id.compose -> onCompose()
R.id.set_sort_date -> changeSort(SortType.SORT_DATE)
R.id.set_sort_arrival -> changeSort(SortType.SORT_ARRIVAL)
R.id.set_sort_subject -> changeSort(SortType.SORT_SUBJECT)
@@ -754,7 +749,9 @@ class MessageListFragment :
R.id.set_sort_unread -> changeSort(SortType.SORT_UNREAD)
R.id.set_sort_attach -> changeSort(SortType.SORT_ATTACHMENT)
R.id.select_all -> selectAll()
+ R.id.mark_all_as_read -> confirmMarkAllAsRead()
R.id.send_messages -> onSendPendingMessages()
+ R.id.empty_trash -> onEmptyTrash()
R.id.expunge -> onExpunge()
else -> return super.onOptionsItemSelected(item)
}
@@ -762,7 +759,7 @@ class MessageListFragment :
return true
}
- fun onSendPendingMessages() {
+ private fun onSendPendingMessages() {
messagingController.sendPendingMessages(account, null)
}
@@ -1448,7 +1445,7 @@ class MessageListFragment :
return account.expungePolicy == Expunge.EXPUNGE_MANUALLY && messagingController.supportsExpunge(account)
}
- fun onRemoteSearch() {
+ private fun onRemoteSearch() {
// Remote search is useless without the network.
if (hasConnectivity == true) {
onRemoteSearchRequested()
@@ -1579,7 +1576,7 @@ class MessageListFragment :
private val isMarkAllAsReadSupported: Boolean
get() = isSingleAccountMode && isSingleFolderMode && !isOutbox
- fun confirmMarkAllAsRead() {
+ private fun confirmMarkAllAsRead() {
if (K9.isConfirmMarkAllRead) {
showDialog(R.id.dialog_confirm_mark_all_as_read)
} else {
--
GitLab
From 48847e17eaecc519e31ca84ed91e270b368cf17f Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 19 Jul 2022 17:48:42 +0200
Subject: [PATCH 08/47] Rename .java to .kt
---
.../{MessageViewFragment.java => MessageViewFragment.kt} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/{MessageViewFragment.java => MessageViewFragment.kt} (100%)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
similarity index 100%
rename from app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java
rename to app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
--
GitLab
From 87dbccdb51bd7421cae96b57f7647e0dade7d9be Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 19 Jul 2022 17:48:42 +0200
Subject: [PATCH 09/47] Convert `MessageViewFragment` to Kotlin
---
.../ui/messageview/AttachmentController.java | 4 +-
.../k9/ui/messageview/MessageViewFragment.kt | 1233 ++++++++---------
2 files changed, 576 insertions(+), 661 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java
index fab62cbab3..7eaf416782 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentController.java
@@ -40,9 +40,9 @@ public class AttachmentController {
private final AttachmentViewInfo attachment;
- AttachmentController(MessagingController controller, MessageViewFragment messageViewFragment,
+ AttachmentController(Context context, MessagingController controller, MessageViewFragment messageViewFragment,
AttachmentViewInfo attachment) {
- this.context = messageViewFragment.getApplicationContext();
+ this.context = context;
this.controller = controller;
this.messageViewFragment = messageViewFragment;
this.attachment = attachment;
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
index 81b1fd019e..b88fee3d4b 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
@@ -1,893 +1,808 @@
-package com.fsck.k9.ui.messageview;
-
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.IntentSender.SendIntentException;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Parcelable;
-import android.os.SystemClock;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener;
-import android.text.TextUtils;
-import android.view.ContextThemeWrapper;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Toast;
-
-import com.fsck.k9.Account;
-import com.fsck.k9.DI;
-import com.fsck.k9.K9;
-import com.fsck.k9.Preferences;
-import com.fsck.k9.activity.MessageCompose;
-import com.fsck.k9.helper.MailtoUnsubscribeUri;
-import com.fsck.k9.helper.UnsubscribeUri;
-import com.fsck.k9.ui.choosefolder.ChooseFolderActivity;
-import com.fsck.k9.activity.MessageLoaderHelper;
-import com.fsck.k9.activity.MessageLoaderHelper.MessageLoaderCallbacks;
-import com.fsck.k9.activity.MessageLoaderHelperFactory;
-import com.fsck.k9.controller.MessageReference;
-import com.fsck.k9.controller.MessagingController;
-import com.fsck.k9.fragment.AttachmentDownloadDialogFragment;
-import com.fsck.k9.fragment.ConfirmationDialogFragment;
-import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
-import com.fsck.k9.mail.Flag;
-import com.fsck.k9.mailstore.AttachmentViewInfo;
-import com.fsck.k9.mailstore.LocalMessage;
-import com.fsck.k9.mailstore.MessageViewInfo;
-import com.fsck.k9.ui.R;
-import com.fsck.k9.ui.base.ThemeManager;
-import com.fsck.k9.ui.messageview.CryptoInfoDialog.OnClickShowCryptoKeyListener;
-import com.fsck.k9.ui.messageview.MessageCryptoPresenter.MessageCryptoMvpView;
-import com.fsck.k9.ui.settings.account.AccountSettingsActivity;
-import com.fsck.k9.ui.share.ShareIntentBuilder;
-import com.fsck.k9.view.MessageCryptoDisplayStatus;
-import timber.log.Timber;
-
-
-public class MessageViewFragment extends Fragment implements ConfirmationDialogFragmentListener,
- AttachmentViewCallback, OnClickShowCryptoKeyListener {
-
- private static final String ARG_REFERENCE = "reference";
-
- private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
- private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
- private static final int REQUEST_CODE_CREATE_DOCUMENT = 3;
-
- public static final int REQUEST_MASK_LOADER_HELPER = (1 << 8);
- public static final int REQUEST_MASK_CRYPTO_PRESENTER = (1 << 9);
-
- public static final int PROGRESS_THRESHOLD_MILLIS = 500 * 1000;
-
- public static MessageViewFragment newInstance(MessageReference reference) {
- MessageViewFragment fragment = new MessageViewFragment();
-
- Bundle args = new Bundle();
- args.putString(ARG_REFERENCE, reference.toIdentityString());
- fragment.setArguments(args);
-
- return fragment;
- }
-
- private final ThemeManager themeManager = DI.get(ThemeManager.class);
- private final MessageLoaderHelperFactory messageLoaderHelperFactory = DI.get(MessageLoaderHelperFactory.class);
-
- private MessageTopView mMessageView;
-
- private Account mAccount;
- private MessageReference mMessageReference;
- private LocalMessage mMessage;
- private MessagingController mController;
- private Handler handler = new Handler();
- private MessageLoaderHelper messageLoaderHelper;
- private MessageCryptoPresenter messageCryptoPresenter;
- private Long showProgressThreshold;
- private UnsubscribeUri preferredUnsubscribeUri;
+package com.fsck.k9.ui.messageview
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.content.IntentSender
+import android.content.IntentSender.SendIntentException
+import android.os.Bundle
+import android.os.Parcelable
+import android.os.SystemClock
+import android.view.ContextThemeWrapper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.Fragment
+import com.fsck.k9.Account
+import com.fsck.k9.K9
+import com.fsck.k9.activity.MessageCompose
+import com.fsck.k9.activity.MessageLoaderHelper
+import com.fsck.k9.activity.MessageLoaderHelper.MessageLoaderCallbacks
+import com.fsck.k9.activity.MessageLoaderHelperFactory
+import com.fsck.k9.controller.MessageReference
+import com.fsck.k9.controller.MessagingController
+import com.fsck.k9.fragment.AttachmentDownloadDialogFragment
+import com.fsck.k9.fragment.ConfirmationDialogFragment
+import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener
+import com.fsck.k9.helper.HttpsUnsubscribeUri
+import com.fsck.k9.helper.MailtoUnsubscribeUri
+import com.fsck.k9.helper.UnsubscribeUri
+import com.fsck.k9.mail.Flag
+import com.fsck.k9.mailstore.AttachmentViewInfo
+import com.fsck.k9.mailstore.LocalMessage
+import com.fsck.k9.mailstore.MessageViewInfo
+import com.fsck.k9.preferences.AccountManager
+import com.fsck.k9.ui.R
+import com.fsck.k9.ui.base.ThemeManager
+import com.fsck.k9.ui.choosefolder.ChooseFolderActivity
+import com.fsck.k9.ui.messageview.CryptoInfoDialog.OnClickShowCryptoKeyListener
+import com.fsck.k9.ui.messageview.MessageCryptoPresenter.MessageCryptoMvpView
+import com.fsck.k9.ui.settings.account.AccountSettingsActivity
+import com.fsck.k9.ui.share.ShareIntentBuilder
+import com.fsck.k9.ui.withArguments
+import com.fsck.k9.view.MessageCryptoDisplayStatus
+import java.util.Locale
+import org.koin.android.ext.android.inject
+import timber.log.Timber
+
+class MessageViewFragment :
+ Fragment(),
+ ConfirmationDialogFragmentListener,
+ AttachmentViewCallback,
+ OnClickShowCryptoKeyListener {
+
+ private val themeManager: ThemeManager by inject()
+ private val messageLoaderHelperFactory: MessageLoaderHelperFactory by inject()
+ private val accountManager: AccountManager by inject()
+ private val messagingController: MessagingController by inject()
+ private val shareIntentBuilder: ShareIntentBuilder by inject()
+
+ private lateinit var messageTopView: MessageTopView
+
+ private var message: LocalMessage? = null
+ private lateinit var messageLoaderHelper: MessageLoaderHelper
+ private lateinit var messageCryptoPresenter: MessageCryptoPresenter
+ private var showProgressThreshold: Long? = null
+ private var preferredUnsubscribeUri: UnsubscribeUri? = null
/**
* Used to temporarily store the destination folder for refile operations if a confirmation
* dialog is shown.
*/
- private Long destinationFolderId;
+ private var destinationFolderId: Long? = null
+ private lateinit var fragmentListener: MessageViewFragmentListener
- private MessageViewFragmentListener mFragmentListener;
+ private lateinit var account: Account
+ lateinit var messageReference: MessageReference
/**
- * {@code true} after {@link #onCreate(Bundle)} has been executed. This is used by
- * {@code MessageList.configureMenu()} to make sure the fragment has been initialized before
- * it is used.
+ * `true` after [.onCreate] has been executed. This is used by `MessageList.configureMenu()` to make sure the
+ * fragment has been initialized before it is used.
*/
- private boolean mInitialized = false;
+ var isInitialized = false
+ private set
- private Context mContext;
+ private var currentAttachmentViewInfo: AttachmentViewInfo? = null
- private AttachmentViewInfo currentAttachmentViewInfo;
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
-
- mContext = context.getApplicationContext();
-
- try {
- mFragmentListener = (MessageViewFragmentListener) getActivity();
- } catch (ClassCastException e) {
- throw new ClassCastException("This fragment must be attached to a MessageViewFragmentListener");
+ fragmentListener = try {
+ activity as MessageViewFragmentListener
+ } catch (e: ClassCastException) {
+ throw ClassCastException("This fragment must be attached to a MessageViewFragmentListener")
}
}
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
- // This fragments adds options to the action bar
- setHasOptionsMenu(true);
+ setHasOptionsMenu(true)
- Context context = getActivity().getApplicationContext();
- mController = MessagingController.getInstance(context);
- messageCryptoPresenter = new MessageCryptoPresenter(messageCryptoMvpView);
+ messageCryptoPresenter = MessageCryptoPresenter(messageCryptoMvpView)
messageLoaderHelper = messageLoaderHelperFactory.createForMessageView(
- context, getLoaderManager(), getParentFragmentManager(), messageLoaderCallbacks);
- mInitialized = true;
- }
+ context = requireContext().applicationContext,
+ loaderManager = loaderManager,
+ fragmentManager = parentFragmentManager,
+ callback = messageLoaderCallbacks
+ )
- @Override
- public void onResume() {
- super.onResume();
-
- messageCryptoPresenter.onResume();
+ isInitialized = true
}
- @Override
- public void onDestroy() {
- super.onDestroy();
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ val messageViewThemeResourceId = themeManager.messageViewThemeResourceId
+ val themedContext = ContextThemeWrapper(inflater.context, messageViewThemeResourceId)
+ val layoutInflater = LayoutInflater.from(themedContext)
- Activity activity = getActivity();
- boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
- if (isChangingConfigurations) {
- messageLoaderHelper.onDestroyChangingConfigurations();
- return;
- }
+ val view = layoutInflater.inflate(R.layout.message, container, false)
+ messageTopView = view.findViewById(R.id.message_view)
- messageLoaderHelper.onDestroy();
- }
+ initializeMessageTopView(messageTopView)
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- int messageViewThemeResourceId = themeManager.getMessageViewThemeResourceId();
- Context context = new ContextThemeWrapper(inflater.getContext(), messageViewThemeResourceId);
- LayoutInflater layoutInflater = LayoutInflater.from(context);
- View view = layoutInflater.inflate(R.layout.message, container, false);
+ return view
+ }
- mMessageView = view.findViewById(R.id.message_view);
- mMessageView.setAttachmentCallback(this);
- mMessageView.setMessageCryptoPresenter(messageCryptoPresenter);
+ private fun initializeMessageTopView(messageTopView: MessageTopView) {
+ messageTopView.setAttachmentCallback(this)
+ messageTopView.setMessageCryptoPresenter(messageCryptoPresenter)
- mMessageView.setOnToggleFlagClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onToggleFlagged();
- }
- });
-
- mMessageView.setOnMenuItemClickListener(new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- int id = item.getItemId();
- if (id == R.id.reply) {
- onReply();
- return true;
- } else if (id == R.id.reply_all) {
- onReplyAll();
- return true;
- } else if (id == R.id.forward) {
- onForward();
- return true;
- } else if (id == R.id.forward_as_attachment) {
- onForwardAsAttachment();
- return true;
- } else if (id == R.id.share) {
- onSendAlternate();
- return true;
- }
- return false;
- }
- });
+ messageTopView.setOnToggleFlagClickListener {
+ onToggleFlagged()
+ }
- mMessageView.setOnDownloadButtonClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mMessageView.disableDownloadButton();
- messageLoaderHelper.downloadCompleteMessage();
- }
- });
+ messageTopView.setOnMenuItemClickListener { item ->
+ onReplyMenuItemClicked(item.itemId)
+ }
- return view;
+ messageTopView.setOnDownloadButtonClickListener {
+ onDownloadButtonClicked()
+ }
}
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
- Bundle arguments = getArguments();
- String messageReferenceString = arguments.getString(ARG_REFERENCE);
- MessageReference messageReference = MessageReference.parse(messageReferenceString);
+ val messageReference = MessageReference.parse(arguments?.getString(ARG_REFERENCE))
+ ?: error("Invalid argument '$ARG_REFERENCE'")
- displayMessage(messageReference);
+ displayMessage(messageReference)
}
- private void displayMessage(MessageReference messageReference) {
- mMessageReference = messageReference;
- Timber.d("MessageView displaying message %s", mMessageReference);
+ private fun displayMessage(messageReference: MessageReference) {
+ Timber.d("MessageViewFragment displaying message %s", messageReference)
+
+ this.messageReference = messageReference
+ account = accountManager.getAccount(messageReference.accountUuid)
+ ?: error("Account ${messageReference.accountUuid} not found")
- mAccount = Preferences.getPreferences(getApplicationContext()).getAccount(mMessageReference.getAccountUuid());
- messageLoaderHelper.asyncStartOrResumeLoadingMessage(messageReference, null);
+ messageLoaderHelper.asyncStartOrResumeLoadingMessage(messageReference, null)
- invalidateMenu();
+ invalidateMenu()
}
- private void hideKeyboard() {
- Activity activity = getActivity();
- if (activity == null) {
- return;
- }
- InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
- View decorView = activity.getWindow().getDecorView();
- if (decorView != null) {
- imm.hideSoftInputFromWindow(decorView.getApplicationWindowToken(), 0);
+ override fun onResume() {
+ super.onResume()
+ messageCryptoPresenter.onResume()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ if (requireActivity().isChangingConfigurations) {
+ messageLoaderHelper.onDestroyChangingConfigurations()
+ } else {
+ messageLoaderHelper.onDestroy()
}
}
- private void showMessage(MessageViewInfo messageViewInfo) {
- hideKeyboard();
+ private fun showMessage(messageViewInfo: MessageViewInfo) {
+ hideKeyboard()
+
+ val handledByCryptoPresenter = messageCryptoPresenter.maybeHandleShowMessage(
+ messageTopView, account, messageViewInfo
+ )
- boolean handledByCryptoPresenter = messageCryptoPresenter.maybeHandleShowMessage(
- mMessageView, mAccount, messageViewInfo);
if (!handledByCryptoPresenter) {
- mMessageView.showMessage(mAccount, messageViewInfo);
- if (mAccount.isOpenPgpProviderConfigured()) {
- mMessageView.getMessageHeaderView().setCryptoStatusDisabled();
+ messageTopView.showMessage(account, messageViewInfo)
+
+ if (account.isOpenPgpProviderConfigured) {
+ messageTopView.messageHeaderView.setCryptoStatusDisabled()
} else {
- mMessageView.getMessageHeaderView().hideCryptoStatus();
+ messageTopView.messageHeaderView.hideCryptoStatus()
}
}
if (messageViewInfo.subject != null) {
- displaySubject(messageViewInfo.subject);
+ displaySubject(messageViewInfo.subject)
}
}
- private void displayHeaderForLoadingMessage(LocalMessage message) {
- boolean showStar = !isOutbox();
- mMessageView.setHeaders(message, mAccount, showStar);
- if (mAccount.isOpenPgpProviderConfigured()) {
- mMessageView.getMessageHeaderView().setCryptoStatusLoading();
+ private fun hideKeyboard() {
+ val activity = activity ?: return
+
+ val inputMethodManager = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ val decorView = activity.window.decorView
+ inputMethodManager.hideSoftInputFromWindow(decorView.applicationWindowToken, 0)
+ }
+
+ private fun displayHeaderForLoadingMessage(message: LocalMessage) {
+ val showStar = !isOutbox
+ messageTopView.setHeaders(message, account, showStar)
+
+ if (account.isOpenPgpProviderConfigured) {
+ messageTopView.messageHeaderView.setCryptoStatusLoading()
}
- displaySubject(message.getSubject());
- invalidateMenu();
+
+ displaySubject(message.subject)
+ invalidateMenu()
}
- private void displaySubject(String subject) {
- if (TextUtils.isEmpty(subject)) {
- subject = mContext.getString(R.string.general_no_subject);
+ private fun displaySubject(subject: String) {
+ val displaySubject = subject.ifEmpty { getString(R.string.general_no_subject) }
+ messageTopView.setSubject(displaySubject)
+ }
+
+ private fun onReplyMenuItemClicked(itemId: Int): Boolean {
+ when (itemId) {
+ R.id.reply -> onReply()
+ R.id.reply_all -> onReplyAll()
+ R.id.forward -> onForward()
+ R.id.forward_as_attachment -> onForwardAsAttachment()
+ R.id.share -> onSendAlternate()
+ else -> error("Missing handler for reply menu item $itemId")
}
- mMessageView.setSubject(subject);
+ return true
+ }
+
+ private fun onDownloadButtonClicked() {
+ messageTopView.disableDownloadButton()
+ messageLoaderHelper.downloadCompleteMessage()
}
/**
* Called from UI thread when user select Delete
*/
- public void onDelete() {
- if (K9.isConfirmDelete() || (K9.isConfirmDeleteStarred() && mMessage.isSet(Flag.FLAGGED))) {
- showDialog(R.id.dialog_confirm_delete);
+ fun onDelete() {
+ val message = checkNotNull(message)
+
+ if (K9.isConfirmDelete || K9.isConfirmDeleteStarred && message.isSet(Flag.FLAGGED)) {
+ showDialog(R.id.dialog_confirm_delete)
} else {
- delete();
+ delete()
}
}
- private void delete() {
- if (mMessage != null) {
- // Disable the delete button after it's tapped (to try to prevent
- // accidental clicks)
- mFragmentListener.disableDeleteAction();
- LocalMessage messageToDelete = mMessage;
- mFragmentListener.showNextMessageOrReturn();
- mController.deleteMessage(mMessageReference);
- }
+ private fun delete() {
+ // Disable the delete button after it has been tapped (to try to prevent accidental clicks)
+ fragmentListener.disableDeleteAction()
+
+ fragmentListener.showNextMessageOrReturn()
+
+ messagingController.deleteMessage(messageReference)
}
- public void onRefile(Long dstFolderId) {
- if (dstFolderId == null || !mController.isMoveCapable(mAccount)) {
- return;
+ private fun onRefile(destinationFolderId: Long?) {
+ if (destinationFolderId == null || !messagingController.isMoveCapable(account)) {
+ return
}
- if (!mController.isMoveCapable(mMessageReference)) {
- Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
- toast.show();
- return;
+
+ if (!messagingController.isMoveCapable(messageReference)) {
+ Toast.makeText(activity, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG).show()
+ return
}
- if (dstFolderId.equals(mAccount.getSpamFolderId()) && K9.isConfirmSpam()) {
- destinationFolderId = dstFolderId;
- showDialog(R.id.dialog_confirm_spam);
+ if (destinationFolderId == account.spamFolderId && K9.isConfirmSpam) {
+ this.destinationFolderId = destinationFolderId
+ showDialog(R.id.dialog_confirm_spam)
} else {
- refileMessage(dstFolderId);
+ refileMessage(destinationFolderId)
}
}
- private void refileMessage(long dstFolderId) {
- long srcFolderId = mMessageReference.getFolderId();
- MessageReference messageToMove = mMessageReference;
- mFragmentListener.showNextMessageOrReturn();
- mController.moveMessage(mAccount, srcFolderId, messageToMove, dstFolderId);
- }
+ private fun refileMessage(destinationFolderId: Long) {
+ fragmentListener.showNextMessageOrReturn()
- public void onReply() {
- if (mMessage != null) {
- mFragmentListener.onReply(mMessage.makeMessageReference(), messageCryptoPresenter.getDecryptionResultForReply());
- }
+ val sourceFolderId = messageReference.folderId
+ messagingController.moveMessage(account, sourceFolderId, messageReference, destinationFolderId)
}
- public void onReplyAll() {
- if (mMessage != null) {
- mFragmentListener.onReplyAll(mMessage.makeMessageReference(), messageCryptoPresenter.getDecryptionResultForReply());
- }
+ fun onReply() {
+ val message = this.message ?: return
+
+ fragmentListener.onReply(
+ messageReference = message.makeMessageReference(),
+ decryptionResultForReply = messageCryptoPresenter.decryptionResultForReply
+ )
}
- public void onForward() {
- if (mMessage != null) {
- mFragmentListener.onForward(mMessage.makeMessageReference(), messageCryptoPresenter.getDecryptionResultForReply());
- }
+ fun onReplyAll() {
+ val message = checkNotNull(this.message)
+
+ fragmentListener.onReplyAll(
+ messageReference = message.makeMessageReference(),
+ decryptionResultForReply = messageCryptoPresenter.decryptionResultForReply
+ )
}
- public void onForwardAsAttachment() {
- if (mMessage != null) {
- mFragmentListener.onForwardAsAttachment(mMessage.makeMessageReference(), messageCryptoPresenter.getDecryptionResultForReply());
- }
+ fun onForward() {
+ val message = checkNotNull(this.message)
+
+ fragmentListener.onForward(
+ messageReference = message.makeMessageReference(),
+ decryptionResultForReply = messageCryptoPresenter.decryptionResultForReply
+ )
}
- public void onEditAsNewMessage() {
- if (mMessage != null) {
- mFragmentListener.onEditAsNewMessage(mMessage.makeMessageReference());
- }
+ fun onForwardAsAttachment() {
+ val message = checkNotNull(this.message)
+
+ fragmentListener.onForwardAsAttachment(
+ messageReference = message.makeMessageReference(),
+ decryptionResultForReply = messageCryptoPresenter.decryptionResultForReply
+ )
}
- public void onToggleFlagged() {
- if (mMessage != null && !isOutbox()) {
- boolean newState = !mMessage.isSet(Flag.FLAGGED);
- mController.setFlag(mAccount, mMessage.getFolder().getDatabaseId(),
- Collections.singletonList(mMessage), Flag.FLAGGED, newState);
- mMessageView.setHeaders(mMessage, mAccount, true);
- }
+ fun onEditAsNewMessage() {
+ val message = checkNotNull(this.message)
+
+ fragmentListener.onEditAsNewMessage(message.makeMessageReference())
}
- public void onMove() {
- if ((!mController.isMoveCapable(mAccount))
- || (mMessage == null)) {
- return;
- }
- if (!mController.isMoveCapable(mMessageReference)) {
- Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
- toast.show();
- return;
- }
+ fun onMove() {
+ check(messagingController.isMoveCapable(account))
+ checkNotNull(message)
- startRefileActivity(FolderOperation.MOVE, ACTIVITY_CHOOSE_FOLDER_MOVE);
+ if (!messagingController.isMoveCapable(messageReference)) {
+ Toast.makeText(activity, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG).show()
+ return
+ }
+ startRefileActivity(FolderOperation.MOVE, ACTIVITY_CHOOSE_FOLDER_MOVE)
}
- public void onCopy() {
- if ((!mController.isCopyCapable(mAccount))
- || (mMessage == null)) {
- return;
- }
- if (!mController.isCopyCapable(mMessageReference)) {
- Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
- toast.show();
- return;
+ fun onCopy() {
+ check(messagingController.isCopyCapable(account))
+ checkNotNull(message)
+
+ if (!messagingController.isCopyCapable(messageReference)) {
+ Toast.makeText(activity, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG).show()
+ return
}
- startRefileActivity(FolderOperation.COPY, ACTIVITY_CHOOSE_FOLDER_COPY);
+ startRefileActivity(FolderOperation.COPY, ACTIVITY_CHOOSE_FOLDER_COPY)
}
- public void onMoveToDrafts() {
- Account account = mAccount;
- long folderId = mMessageReference.getFolderId();
- List messages = Collections.singletonList(mMessageReference);
+ fun onMoveToDrafts() {
+ fragmentListener.showNextMessageOrReturn()
- mFragmentListener.showNextMessageOrReturn();
-
- mController.moveToDraftsFolder(account, folderId, messages);
+ val account = account
+ val folderId = messageReference.folderId
+ val messages = listOf(messageReference)
+ messagingController.moveToDraftsFolder(account, folderId, messages)
}
- public void onArchive() {
- onRefile(mAccount.getArchiveFolderId());
+ fun onArchive() {
+ onRefile(account.archiveFolderId)
}
- public void onSpam() {
- onRefile(mAccount.getSpamFolderId());
+ fun onSpam() {
+ onRefile(account.spamFolderId)
}
- private void startRefileActivity(FolderOperation operation, int requestCode) {
- String accountUuid = mAccount.getUuid();
- long currentFolderId = mMessageReference.getFolderId();
- Long scrollToFolderId = mAccount.getLastSelectedFolderId();
- final ChooseFolderActivity.Action action;
- if (operation == FolderOperation.MOVE) {
- action = ChooseFolderActivity.Action.MOVE;
+ private fun startRefileActivity(operation: FolderOperation, requestCode: Int) {
+ val action = if (operation == FolderOperation.MOVE) {
+ ChooseFolderActivity.Action.MOVE
} else {
- action = ChooseFolderActivity.Action.COPY;
+ ChooseFolderActivity.Action.COPY
}
- Intent intent = ChooseFolderActivity.buildLaunchIntent(requireActivity(), action, accountUuid, currentFolderId,
- scrollToFolderId, false, mMessageReference);
+ val intent = ChooseFolderActivity.buildLaunchIntent(
+ context = requireActivity(),
+ action = action,
+ accountUuid = account.uuid,
+ currentFolderId = messageReference.folderId,
+ scrollToFolderId = account.lastSelectedFolderId,
+ showDisplayableOnly = false,
+ messageReference = messageReference
+ )
- startActivityForResult(intent, requestCode);
+ startActivityForResult(intent, requestCode)
}
- public void onPendingIntentResult(int requestCode, int resultCode, Intent data) {
- if ((requestCode & REQUEST_MASK_LOADER_HELPER) == REQUEST_MASK_LOADER_HELPER) {
- requestCode ^= REQUEST_MASK_LOADER_HELPER;
- messageLoaderHelper.onActivityResult(requestCode, resultCode, data);
- return;
+ fun onPendingIntentResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode and REQUEST_MASK_LOADER_HELPER == REQUEST_MASK_LOADER_HELPER) {
+ val maskedRequestCode = requestCode xor REQUEST_MASK_LOADER_HELPER
+ messageLoaderHelper.onActivityResult(maskedRequestCode, resultCode, data)
+ } else if (requestCode and REQUEST_MASK_CRYPTO_PRESENTER == REQUEST_MASK_CRYPTO_PRESENTER) {
+ val maskedRequestCode = requestCode xor REQUEST_MASK_CRYPTO_PRESENTER
+ messageCryptoPresenter.onActivityResult(maskedRequestCode, resultCode, data)
}
+ }
- if ((requestCode & REQUEST_MASK_CRYPTO_PRESENTER) == REQUEST_MASK_CRYPTO_PRESENTER) {
- requestCode ^= REQUEST_MASK_CRYPTO_PRESENTER;
- messageCryptoPresenter.onActivityResult(requestCode, resultCode, data);
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (resultCode != Activity.RESULT_OK) return
+
+ when (requestCode) {
+ REQUEST_CODE_CREATE_DOCUMENT -> onCreateDocumentResult(data)
+ ACTIVITY_CHOOSE_FOLDER_MOVE -> onChooseFolderMoveResult(data)
+ ACTIVITY_CHOOSE_FOLDER_COPY -> onChooseFolderCopyResult(data)
}
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode != Activity.RESULT_OK) {
- return;
+ private fun onCreateDocumentResult(data: Intent?) {
+ if (data != null && data.data != null) {
+ createAttachmentController(currentAttachmentViewInfo).saveAttachmentTo(data.data)
}
+ }
- // Note: because fragments do not have a startIntentSenderForResult method, pending intent activities are
- // launched through the MessageList activity, and delivered back via onPendingIntentResult()
+ private fun onChooseFolderMoveResult(data: Intent?) {
+ if (data == null) return
- switch (requestCode) {
- case REQUEST_CODE_CREATE_DOCUMENT: {
- if (data != null && data.getData() != null) {
- getAttachmentController(currentAttachmentViewInfo).saveAttachmentTo(data.getData());
- }
- break;
- }
- case ACTIVITY_CHOOSE_FOLDER_MOVE:
- case ACTIVITY_CHOOSE_FOLDER_COPY: {
- if (data == null) {
- return;
- }
+ val destinationFolderId = data.getLongExtra(ChooseFolderActivity.RESULT_SELECTED_FOLDER_ID, -1L)
+ val messageReferenceString = data.getStringExtra(ChooseFolderActivity.RESULT_MESSAGE_REFERENCE)
+ val messageReference = MessageReference.parse(messageReferenceString)
+ if (this.messageReference != messageReference) return
- long destFolderId = data.getLongExtra(ChooseFolderActivity.RESULT_SELECTED_FOLDER_ID, -1L);
- String messageReferenceString = data.getStringExtra(ChooseFolderActivity.RESULT_MESSAGE_REFERENCE);
- MessageReference ref = MessageReference.parse(messageReferenceString);
- if (mMessageReference.equals(ref)) {
- mAccount.setLastSelectedFolderId(destFolderId);
- switch (requestCode) {
- case ACTIVITY_CHOOSE_FOLDER_MOVE: {
- mFragmentListener.showNextMessageOrReturn();
- moveMessage(ref, destFolderId);
- break;
- }
- case ACTIVITY_CHOOSE_FOLDER_COPY: {
- copyMessage(ref, destFolderId);
- break;
- }
- }
- }
- break;
- }
- }
- }
-
- public void onSendAlternate() {
- if (mMessage != null) {
- ShareIntentBuilder shareIntentBuilder = DI.get(ShareIntentBuilder.class);
- Intent shareIntent = shareIntentBuilder.createShareIntent(mMessage);
+ account.setLastSelectedFolderId(destinationFolderId)
- String shareTitle = getString(R.string.send_alternate_chooser_title);
- Intent chooserIntent = Intent.createChooser(shareIntent, shareTitle);
+ fragmentListener.showNextMessageOrReturn()
- startActivity(chooserIntent);
- }
+ moveMessage(messageReference, destinationFolderId)
}
- public void onToggleRead() {
- if (mMessage != null && !isOutbox()) {
- mController.setFlag(mAccount, mMessage.getFolder().getDatabaseId(),
- Collections.singletonList(mMessage), Flag.SEEN, !mMessage.isSet(Flag.SEEN));
+ private fun onChooseFolderCopyResult(data: Intent?) {
+ if (data == null) return
- mMessageView.setHeaders(mMessage, mAccount, true);
- invalidateMenu();
- }
- }
+ val destinationFolderId = data.getLongExtra(ChooseFolderActivity.RESULT_SELECTED_FOLDER_ID, -1L)
+ val messageReferenceString = data.getStringExtra(ChooseFolderActivity.RESULT_MESSAGE_REFERENCE)
+ val messageReference = MessageReference.parse(messageReferenceString)
+ if (this.messageReference != messageReference) return
- private void setProgress(boolean enable) {
- if (mFragmentListener != null) {
- mFragmentListener.setProgress(enable);
- }
- }
+ account.setLastSelectedFolderId(destinationFolderId)
- public void moveMessage(MessageReference reference, long folderId) {
- mController.moveMessage(mAccount, mMessageReference.getFolderId(), reference, folderId);
+ copyMessage(messageReference, destinationFolderId)
}
- public void copyMessage(MessageReference reference, long folderId) {
- mController.copyMessage(mAccount, mMessageReference.getFolderId(), reference, folderId);
- }
+ fun onSendAlternate() {
+ val message = checkNotNull(message)
- private void showDialog(int dialogId) {
- DialogFragment fragment;
- if (dialogId == R.id.dialog_confirm_delete) {
- String title = getString(R.string.dialog_confirm_delete_title);
- String message = getString(R.string.dialog_confirm_delete_message);
- String confirmText = getString(R.string.dialog_confirm_delete_confirm_button);
- String cancelText = getString(R.string.dialog_confirm_delete_cancel_button);
+ val shareIntent = shareIntentBuilder.createShareIntent(message)
+ val shareTitle = getString(R.string.send_alternate_chooser_title)
+ val chooserIntent = Intent.createChooser(shareIntent, shareTitle)
- fragment = ConfirmationDialogFragment.newInstance(dialogId, title, message,
- confirmText, cancelText);
- } else if (dialogId == R.id.dialog_confirm_spam) {
- String title = getString(R.string.dialog_confirm_spam_title);
- String message = getResources().getQuantityString(R.plurals.dialog_confirm_spam_message, 1);
- String confirmText = getString(R.string.dialog_confirm_spam_confirm_button);
- String cancelText = getString(R.string.dialog_confirm_spam_cancel_button);
-
- fragment = ConfirmationDialogFragment.newInstance(dialogId, title, message,
- confirmText, cancelText);
- } else if (dialogId == R.id.dialog_attachment_progress) {
- String message = getString(R.string.dialog_attachment_progress_title);
- long size = currentAttachmentViewInfo.size;
- fragment = AttachmentDownloadDialogFragment.newInstance(size, message);
- } else {
- throw new RuntimeException("Called showDialog(int) with unknown dialog id.");
- }
+ startActivity(chooserIntent)
+ }
- fragment.setTargetFragment(this, dialogId);
- fragment.show(getParentFragmentManager(), getDialogTag(dialogId));
+ fun onToggleRead() {
+ toggleFlag(Flag.SEEN)
}
- private void removeDialog(int dialogId) {
- if (!isAdded()) {
- return;
- }
+ fun onToggleFlagged() {
+ toggleFlag(Flag.FLAGGED)
+ }
- FragmentManager fm = getParentFragmentManager();
+ private fun toggleFlag(flag: Flag) {
+ check(!isOutbox)
+ val message = checkNotNull(this.message)
- // Make sure the "show dialog" transaction has been processed when we call
- // findFragmentByTag() below. Otherwise the fragment won't be found and the dialog will
- // never be dismissed.
- fm.executePendingTransactions();
+ val newState = !message.isSet(flag)
+ messagingController.setFlag(account, message.folder.databaseId, listOf(message), flag, newState)
- DialogFragment fragment = (DialogFragment) fm.findFragmentByTag(getDialogTag(dialogId));
+ messageTopView.setHeaders(message, account, true)
- if (fragment != null) {
- fragment.dismissAllowingStateLoss();
- }
+ invalidateMenu()
}
- private String getDialogTag(int dialogId) {
- return String.format(Locale.US, "dialog-%d", dialogId);
+ private fun moveMessage(reference: MessageReference?, folderId: Long) {
+ messagingController.moveMessage(account, messageReference.folderId, reference, folderId)
}
- public void zoom(KeyEvent event) {
- // mMessageView.zoom(event);
+ private fun copyMessage(reference: MessageReference?, folderId: Long) {
+ messagingController.copyMessage(account, messageReference.folderId, reference, folderId)
}
- @Override
- public void doPositiveClick(int dialogId) {
- if (dialogId == R.id.dialog_confirm_delete) {
- delete();
- } else if (dialogId == R.id.dialog_confirm_spam) {
- refileMessage(destinationFolderId);
- destinationFolderId = null;
+ private fun showDialog(dialogId: Int) {
+ val fragment = when (dialogId) {
+ R.id.dialog_confirm_delete -> {
+ val title = getString(R.string.dialog_confirm_delete_title)
+ val message = getString(R.string.dialog_confirm_delete_message)
+ val confirmText = getString(R.string.dialog_confirm_delete_confirm_button)
+ val cancelText = getString(R.string.dialog_confirm_delete_cancel_button)
+ ConfirmationDialogFragment.newInstance(
+ dialogId, title, message,
+ confirmText, cancelText
+ )
+ }
+ R.id.dialog_confirm_spam -> {
+ val title = getString(R.string.dialog_confirm_spam_title)
+ val message = resources.getQuantityString(R.plurals.dialog_confirm_spam_message, 1)
+ val confirmText = getString(R.string.dialog_confirm_spam_confirm_button)
+ val cancelText = getString(R.string.dialog_confirm_spam_cancel_button)
+ ConfirmationDialogFragment.newInstance(
+ dialogId, title, message,
+ confirmText, cancelText
+ )
+ }
+ R.id.dialog_attachment_progress -> {
+ val currentAttachmentViewInfo = checkNotNull(this.currentAttachmentViewInfo)
+
+ val message = getString(R.string.dialog_attachment_progress_title)
+ val size = currentAttachmentViewInfo.size
+ AttachmentDownloadDialogFragment.newInstance(size, message)
+ }
+ else -> {
+ throw RuntimeException("Called showDialog(int) with unknown dialog id.")
+ }
}
- }
- @Override
- public void doNegativeClick(int dialogId) {
- /* do nothing */
+ fragment.setTargetFragment(this, dialogId)
+ fragment.show(parentFragmentManager, getDialogTag(dialogId))
}
- @Override
- public void dialogCancelled(int dialogId) {
- /* do nothing */
- }
+ private fun removeDialog(dialogId: Int) {
+ if (!isAdded) return
- /**
- * Get the {@link MessageReference} of the currently displayed message.
- */
- public MessageReference getMessageReference() {
- return mMessageReference;
- }
+ val fragmentManager = parentFragmentManager
- public boolean isOutbox() {
- if (mMessage == null || mAccount == null) {
- return false;
- }
+ // Make sure the "show dialog" transaction has been processed when we call findFragmentByTag() below.
+ // Otherwise the fragment won't be found and the dialog will never be dismissed.
+ fragmentManager.executePendingTransactions()
- long folderId = mMessage.getFolder().getDatabaseId();
- Long outboxFolderId = mAccount.getOutboxFolderId();
- return outboxFolderId != null && outboxFolderId == folderId;
+ val fragment = fragmentManager.findFragmentByTag(getDialogTag(dialogId)) as DialogFragment?
+ fragment?.dismissAllowingStateLoss()
}
- public boolean isMessageRead() {
- return (mMessage != null) && mMessage.isSet(Flag.SEEN);
+ private fun getDialogTag(dialogId: Int): String {
+ return String.format(Locale.US, "dialog-%d", dialogId)
}
- public boolean isCopyCapable() {
- return !isOutbox() && mController.isCopyCapable(mAccount);
- }
+ override fun doPositiveClick(dialogId: Int) {
+ if (dialogId == R.id.dialog_confirm_delete) {
+ delete()
+ } else if (dialogId == R.id.dialog_confirm_spam) {
+ val destinationFolderId = checkNotNull(this.destinationFolderId)
- public boolean isMoveCapable() {
- return !isOutbox() && mController.isMoveCapable(mAccount);
+ refileMessage(destinationFolderId)
+ this.destinationFolderId = null
+ }
}
- public boolean canMessageBeArchived() {
- Long archiveFolderId = mAccount.getArchiveFolderId();
- if (archiveFolderId == null) {
- return false;
- }
+ override fun doNegativeClick(dialogId: Int) = Unit
- return mMessageReference.getFolderId() != archiveFolderId;
- }
+ override fun dialogCancelled(dialogId: Int) = Unit
- public boolean canMessageBeMovedToSpam() {
- Long spamFolderId = mAccount.getSpamFolderId();
- if (spamFolderId == null) {
- return false;
- }
+ val isOutbox: Boolean
+ get() = messageReference.folderId == account.outboxFolderId
- return mMessageReference.getFolderId() != spamFolderId;
- }
+ val isMessageRead: Boolean
+ get() = message?.isSet(Flag.SEEN) == true
- public boolean canMessageBeUnsubscribed() {
- return preferredUnsubscribeUri != null;
- }
+ val isCopyCapable: Boolean
+ get() = !isOutbox && messagingController.isCopyCapable(account)
- public void onUnsubscribe() {
- if (preferredUnsubscribeUri instanceof MailtoUnsubscribeUri) {
- Intent intent = new Intent(mContext, MessageCompose.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.setData(preferredUnsubscribeUri.getUri());
- intent.putExtra(MessageCompose.EXTRA_ACCOUNT, mMessageReference.getAccountUuid());
- startActivity(intent);
- } else {
- Intent intent = new Intent(Intent.ACTION_VIEW, preferredUnsubscribeUri.getUri());
- startActivity(intent);
- }
+ val isMoveCapable: Boolean
+ get() = !isOutbox && messagingController.isMoveCapable(account)
+
+ fun canMessageBeArchived(): Boolean {
+ val archiveFolderId = account.archiveFolderId ?: return false
+ return messageReference.folderId != archiveFolderId
}
- public Context getApplicationContext() {
- return mContext;
+ fun canMessageBeMovedToSpam(): Boolean {
+ val spamFolderId = account.spamFolderId ?: return false
+ return messageReference.folderId != spamFolderId
}
- public void disableAttachmentButtons(AttachmentViewInfo attachment) {
- // mMessageView.disableAttachmentButtons(attachment);
+ fun canMessageBeUnsubscribed(): Boolean {
+ return preferredUnsubscribeUri != null
}
- public void enableAttachmentButtons(AttachmentViewInfo attachment) {
- // mMessageView.enableAttachmentButtons(attachment);
+ fun onUnsubscribe() {
+ val intent = when (val unsubscribeUri = preferredUnsubscribeUri) {
+ is MailtoUnsubscribeUri -> {
+ Intent(requireContext(), MessageCompose::class.java).apply {
+ action = Intent.ACTION_VIEW
+ data = unsubscribeUri.uri
+ putExtra(MessageCompose.EXTRA_ACCOUNT, messageReference.accountUuid)
+ }
+ }
+ is HttpsUnsubscribeUri -> {
+ Intent(Intent.ACTION_VIEW, unsubscribeUri.uri)
+ }
+ else -> error("Unknown UnsubscribeUri - $unsubscribeUri")
+ }
+
+ startActivity(intent)
}
- public void runOnMainThread(Runnable runnable) {
- handler.post(runnable);
+ fun disableAttachmentButtons(attachment: AttachmentViewInfo?) = Unit
+
+ fun enableAttachmentButtons(attachment: AttachmentViewInfo?) = Unit
+
+ fun runOnMainThread(runnable: Runnable) {
+ requireActivity().runOnUiThread(runnable)
}
- public void showAttachmentLoadingDialog() {
- // mMessageView.disableAttachmentButtons();
- showDialog(R.id.dialog_attachment_progress);
+ fun showAttachmentLoadingDialog() {
+ showDialog(R.id.dialog_attachment_progress)
}
- public void hideAttachmentLoadingDialogOnMainThread() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- removeDialog(R.id.dialog_attachment_progress);
- // mMessageView.enableAttachmentButtons();
- }
- });
+ fun hideAttachmentLoadingDialogOnMainThread() {
+ runOnMainThread {
+ removeDialog(R.id.dialog_attachment_progress)
+ }
}
- public void refreshAttachmentThumbnail(AttachmentViewInfo attachment) {
- mMessageView.refreshAttachmentThumbnail(attachment);
+ fun refreshAttachmentThumbnail(attachment: AttachmentViewInfo?) {
+ messageTopView.refreshAttachmentThumbnail(attachment)
}
- private MessageCryptoMvpView messageCryptoMvpView = new MessageCryptoMvpView() {
- @Override
- public void redisplayMessage() {
- messageLoaderHelper.asyncReloadMessage();
+ private val messageCryptoMvpView: MessageCryptoMvpView = object : MessageCryptoMvpView {
+ override fun redisplayMessage() {
+ messageLoaderHelper.asyncReloadMessage()
}
- @Override
- public void startPendingIntentForCryptoPresenter(IntentSender si, Integer requestCode, Intent fillIntent,
- int flagsMask, int flagValues, int extraFlags) throws SendIntentException {
+ @Throws(SendIntentException::class)
+ override fun startPendingIntentForCryptoPresenter(
+ intentSender: IntentSender,
+ requestCode: Int?,
+ fillIntent: Intent?,
+ flagsMask: Int,
+ flagValues: Int,
+ extraFlags: Int
+ ) {
if (requestCode == null) {
- getActivity().startIntentSender(si, fillIntent, flagsMask, flagValues, extraFlags);
- return;
+ requireActivity().startIntentSender(intentSender, fillIntent, flagsMask, flagValues, extraFlags)
+ return
}
- requestCode |= REQUEST_MASK_CRYPTO_PRESENTER;
- getActivity().startIntentSenderForResult(
- si, requestCode, fillIntent, flagsMask, flagValues, extraFlags);
+ val maskedRequestCode = requestCode or REQUEST_MASK_CRYPTO_PRESENTER
+ requireActivity().startIntentSenderForResult(
+ intentSender, maskedRequestCode, fillIntent, flagsMask, flagValues, extraFlags
+ )
}
- @Override
- public void showCryptoInfoDialog(MessageCryptoDisplayStatus displayStatus, boolean hasSecurityWarning) {
- CryptoInfoDialog dialog = CryptoInfoDialog.newInstance(displayStatus, hasSecurityWarning);
- dialog.setTargetFragment(MessageViewFragment.this, 0);
- dialog.show(getParentFragmentManager(), "crypto_info_dialog");
+ override fun showCryptoInfoDialog(displayStatus: MessageCryptoDisplayStatus, hasSecurityWarning: Boolean) {
+ val dialog = CryptoInfoDialog.newInstance(displayStatus, hasSecurityWarning)
+ dialog.setTargetFragment(this@MessageViewFragment, 0)
+ dialog.show(parentFragmentManager, "crypto_info_dialog")
}
- @Override
- public void restartMessageCryptoProcessing() {
- mMessageView.setToLoadingState();
- messageLoaderHelper.asyncRestartMessageCryptoProcessing();
+ override fun restartMessageCryptoProcessing() {
+ messageTopView.setToLoadingState()
+ messageLoaderHelper.asyncRestartMessageCryptoProcessing()
}
- @Override
- public void showCryptoConfigDialog() {
- AccountSettingsActivity.startCryptoSettings(getActivity(), mAccount.getUuid());
+ override fun showCryptoConfigDialog() {
+ AccountSettingsActivity.startCryptoSettings(requireActivity(), account.uuid)
}
- };
-
- @Override
- public void onClickShowSecurityWarning() {
- messageCryptoPresenter.onClickShowCryptoWarningDetails();
}
- @Override
- public void onClickSearchKey() {
- messageCryptoPresenter.onClickSearchKey();
+ override fun onClickShowSecurityWarning() {
+ messageCryptoPresenter.onClickShowCryptoWarningDetails()
}
- @Override
- public void onClickShowCryptoKey() {
- messageCryptoPresenter.onClickShowCryptoKey();
+ override fun onClickSearchKey() {
+ messageCryptoPresenter.onClickSearchKey()
}
- public interface MessageViewFragmentListener {
- void onForward(MessageReference messageReference, @Nullable Parcelable decryptionResultForReply);
- void onForwardAsAttachment(MessageReference messageReference, @Nullable Parcelable decryptionResultForReply);
- void onEditAsNewMessage(MessageReference messageReference);
- void disableDeleteAction();
- void onReplyAll(MessageReference messageReference, @Nullable Parcelable decryptionResultForReply);
- void onReply(MessageReference messageReference, @Nullable Parcelable decryptionResultForReply);
- void setProgress(boolean b);
- void showNextMessageOrReturn();
+ override fun onClickShowCryptoKey() {
+ messageCryptoPresenter.onClickShowCryptoKey()
}
- public boolean isInitialized() {
- return mInitialized ;
+ interface MessageViewFragmentListener {
+ fun onForward(messageReference: MessageReference, decryptionResultForReply: Parcelable?)
+ fun onForwardAsAttachment(messageReference: MessageReference, decryptionResultForReply: Parcelable?)
+ fun onEditAsNewMessage(messageReference: MessageReference)
+ fun disableDeleteAction()
+ fun onReplyAll(messageReference: MessageReference, decryptionResultForReply: Parcelable?)
+ fun onReply(messageReference: MessageReference, decryptionResultForReply: Parcelable?)
+ fun setProgress(enable: Boolean)
+ fun showNextMessageOrReturn()
}
+ private val messageLoaderCallbacks: MessageLoaderCallbacks = object : MessageLoaderCallbacks {
+ override fun onMessageDataLoadFinished(message: LocalMessage) {
+ this@MessageViewFragment.message = message
- private MessageLoaderCallbacks messageLoaderCallbacks = new MessageLoaderCallbacks() {
- @Override
- public void onMessageDataLoadFinished(LocalMessage message) {
- mMessage = message;
-
- displayHeaderForLoadingMessage(message);
- mMessageView.setToLoadingState();
- showProgressThreshold = null;
+ displayHeaderForLoadingMessage(message)
+ messageTopView.setToLoadingState()
+ showProgressThreshold = null
}
- @Override
- public void onMessageDataLoadFailed() {
- Toast.makeText(getActivity(), R.string.status_loading_error, Toast.LENGTH_LONG).show();
- showProgressThreshold = null;
+ override fun onMessageDataLoadFailed() {
+ Toast.makeText(activity, R.string.status_loading_error, Toast.LENGTH_LONG).show()
+ showProgressThreshold = null
}
- @Override
- public void onMessageViewInfoLoadFinished(MessageViewInfo messageViewInfo) {
- showMessage(messageViewInfo);
- preferredUnsubscribeUri = messageViewInfo.preferredUnsubscribeUri;
- showProgressThreshold = null;
+ override fun onMessageViewInfoLoadFinished(messageViewInfo: MessageViewInfo) {
+ showMessage(messageViewInfo)
+ preferredUnsubscribeUri = messageViewInfo.preferredUnsubscribeUri
+ showProgressThreshold = null
}
- @Override
- public void onMessageViewInfoLoadFailed(MessageViewInfo messageViewInfo) {
- showMessage(messageViewInfo);
- preferredUnsubscribeUri = null;
- showProgressThreshold = null;
+ override fun onMessageViewInfoLoadFailed(messageViewInfo: MessageViewInfo) {
+ showMessage(messageViewInfo)
+ preferredUnsubscribeUri = null
+ showProgressThreshold = null
}
- @Override
- public void setLoadingProgress(int current, int max) {
- if (showProgressThreshold == null) {
- showProgressThreshold = SystemClock.elapsedRealtime() + PROGRESS_THRESHOLD_MILLIS;
- } else if (showProgressThreshold == 0L || SystemClock.elapsedRealtime() > showProgressThreshold) {
- showProgressThreshold = 0L;
- mMessageView.setLoadingProgress(current, max);
+ override fun setLoadingProgress(current: Int, max: Int) {
+ val oldShowProgressThreshold = showProgressThreshold
+
+ if (oldShowProgressThreshold == null) {
+ showProgressThreshold = SystemClock.elapsedRealtime() + PROGRESS_THRESHOLD_MILLIS
+ } else if (oldShowProgressThreshold == 0L || SystemClock.elapsedRealtime() > oldShowProgressThreshold) {
+ showProgressThreshold = 0L
+ messageTopView.setLoadingProgress(current, max)
}
}
- @Override
- public void onDownloadErrorMessageNotFound() {
- mMessageView.enableDownloadButton();
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(getActivity(), R.string.status_invalid_id_error, Toast.LENGTH_LONG).show();
- }
- });
+ override fun onDownloadErrorMessageNotFound() {
+ messageTopView.enableDownloadButton()
+ Toast.makeText(requireContext(), R.string.status_invalid_id_error, Toast.LENGTH_LONG).show()
}
- @Override
- public void onDownloadErrorNetworkError() {
- mMessageView.enableDownloadButton();
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(getActivity(), R.string.status_network_error, Toast.LENGTH_LONG).show();
- }
- });
+ override fun onDownloadErrorNetworkError() {
+ messageTopView.enableDownloadButton()
+ Toast.makeText(requireContext(), R.string.status_network_error, Toast.LENGTH_LONG).show()
}
- @Override
- public void startIntentSenderForMessageLoaderHelper(IntentSender si, int requestCode, Intent fillIntent,
- int flagsMask, int flagValues, int extraFlags) {
- showProgressThreshold = null;
+ override fun startIntentSenderForMessageLoaderHelper(
+ intentSender: IntentSender,
+ requestCode: Int,
+ fillIntent: Intent?,
+ flagsMask: Int,
+ flagValues: Int,
+ extraFlags: Int
+ ) {
+ showProgressThreshold = null
try {
- requestCode |= REQUEST_MASK_LOADER_HELPER;
- getActivity().startIntentSenderForResult(
- si, requestCode, fillIntent, flagsMask, flagValues, extraFlags);
- } catch (SendIntentException e) {
- Timber.e(e, "Irrecoverable error calling PendingIntent!");
+ val maskedRequestCode = requestCode or REQUEST_MASK_LOADER_HELPER
+ requireActivity().startIntentSenderForResult(
+ intentSender, maskedRequestCode, fillIntent, flagsMask, flagValues, extraFlags
+ )
+ } catch (e: SendIntentException) {
+ Timber.e(e, "Irrecoverable error calling PendingIntent!")
}
}
- };
+ }
+ override fun onViewAttachment(attachment: AttachmentViewInfo) {
+ currentAttachmentViewInfo = attachment
- @Override
- public void onViewAttachment(AttachmentViewInfo attachment) {
- currentAttachmentViewInfo = attachment;
- getAttachmentController(attachment).viewAttachment();
+ createAttachmentController(attachment).viewAttachment()
}
- @Override
- public void onSaveAttachment(final AttachmentViewInfo attachment) {
- currentAttachmentViewInfo = attachment;
+ override fun onSaveAttachment(attachment: AttachmentViewInfo) {
+ currentAttachmentViewInfo = attachment
- Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
- intent.setType(attachment.mimeType);
- intent.putExtra(Intent.EXTRA_TITLE, attachment.displayName);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
+ val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ type = attachment.mimeType
+ putExtra(Intent.EXTRA_TITLE, attachment.displayName)
+ addCategory(Intent.CATEGORY_OPENABLE)
+ }
try {
- startActivityForResult(intent, REQUEST_CODE_CREATE_DOCUMENT);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(requireContext(), R.string.error_activity_not_found, Toast.LENGTH_LONG).show();
+ startActivityForResult(intent, REQUEST_CODE_CREATE_DOCUMENT)
+ } catch (e: ActivityNotFoundException) {
+ Toast.makeText(requireContext(), R.string.error_activity_not_found, Toast.LENGTH_LONG).show()
}
}
- private AttachmentController getAttachmentController(AttachmentViewInfo attachment) {
- return new AttachmentController(mController, this, attachment);
+ private fun createAttachmentController(attachment: AttachmentViewInfo?): AttachmentController {
+ return AttachmentController(requireContext(), messagingController, this, attachment)
}
- private void invalidateMenu() {
- requireActivity().invalidateMenu();
+ private fun invalidateMenu() {
+ requireActivity().invalidateMenu()
}
- private enum FolderOperation {
+ private enum class FolderOperation {
COPY, MOVE
}
+
+ companion object {
+ const val REQUEST_MASK_LOADER_HELPER = 1 shl 8
+ const val REQUEST_MASK_CRYPTO_PRESENTER = 1 shl 9
+ const val PROGRESS_THRESHOLD_MILLIS = 500 * 1000
+
+ private const val ARG_REFERENCE = "reference"
+
+ private const val ACTIVITY_CHOOSE_FOLDER_MOVE = 1
+ private const val ACTIVITY_CHOOSE_FOLDER_COPY = 2
+ private const val REQUEST_CODE_CREATE_DOCUMENT = 3
+
+ fun newInstance(reference: MessageReference): MessageViewFragment {
+ return MessageViewFragment().withArguments(
+ ARG_REFERENCE to reference.toIdentityString()
+ )
+ }
+ }
}
--
GitLab
From 03de64e84cb318a1716e125c66c114f2563c7572 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 20 Jul 2022 16:43:40 +0200
Subject: [PATCH 10/47] Move code to prepare the message view menu to
`MessageViewFragment`
---
.../java/com/fsck/k9/activity/MessageList.kt | 120 ------------------
.../k9/ui/messageview/MessageViewFragment.kt | 84 ++++++++++++
.../src/main/res/menu/message_list_option.xml | 13 ++
3 files changed, 97 insertions(+), 120 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index b56b7ecc98..e86e9bd822 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -1011,126 +1011,6 @@ open class MessageList :
return true
}
- override fun onPrepareOptionsMenu(menu: Menu): Boolean {
- super.onPrepareOptionsMenu(menu)
- configureMenu(menu)
- return true
- }
-
- /**
- * Hide menu items not appropriate for the current context.
- *
- * **Note:**
- * Please adjust the comments in `res/menu/message_list_option.xml` if you change the visibility of a menu item
- * in this method.
- */
- private fun configureMenu(menu: Menu?) {
- if (menu == null) return
-
- // Set visibility of menu items related to the message view
- if (displayMode == DisplayMode.MESSAGE_LIST || messageViewFragment == null ||
- !messageViewFragment!!.isInitialized
- ) {
- menu.findItem(R.id.next_message).isVisible = false
- menu.findItem(R.id.previous_message).isVisible = false
- menu.findItem(R.id.single_message_options).isVisible = false
- menu.findItem(R.id.delete).isVisible = false
- menu.findItem(R.id.compose).isVisible = false
- menu.findItem(R.id.archive).isVisible = false
- menu.findItem(R.id.move).isVisible = false
- menu.findItem(R.id.copy).isVisible = false
- menu.findItem(R.id.spam).isVisible = false
- menu.findItem(R.id.refile).isVisible = false
- menu.findItem(R.id.toggle_unread).isVisible = false
- menu.findItem(R.id.toggle_message_view_theme).isVisible = false
- menu.findItem(R.id.unsubscribe).isVisible = false
- menu.findItem(R.id.show_headers).isVisible = false
- } else {
- // hide prev/next buttons in split mode
- if (displayMode != DisplayMode.MESSAGE_VIEW) {
- menu.findItem(R.id.next_message).isVisible = false
- menu.findItem(R.id.previous_message).isVisible = false
- } else {
- val ref = messageViewFragment!!.messageReference
- val initialized = messageListFragment != null &&
- messageListFragment!!.isLoadFinished
- val canDoPrev = initialized && !messageListFragment!!.isFirst(ref)
- val canDoNext = initialized && !messageListFragment!!.isLast(ref)
- val prev = menu.findItem(R.id.previous_message)
- prev.isEnabled = canDoPrev
- prev.icon.alpha = if (canDoPrev) 255 else 127
- val next = menu.findItem(R.id.next_message)
- next.isEnabled = canDoNext
- next.icon.alpha = if (canDoNext) 255 else 127
- }
-
- val toggleTheme = menu.findItem(R.id.toggle_message_view_theme)
- if (generalSettingsManager.getSettings().fixedMessageViewTheme) {
- toggleTheme.isVisible = false
- } else {
- // Set title of menu item to switch to dark/light theme
- if (themeManager.messageViewTheme === Theme.DARK) {
- toggleTheme.setTitle(R.string.message_view_theme_action_light)
- } else {
- toggleTheme.setTitle(R.string.message_view_theme_action_dark)
- }
- toggleTheme.isVisible = true
- }
-
- if (messageViewFragment!!.isOutbox) {
- menu.findItem(R.id.toggle_unread).isVisible = false
- } else {
- // Set title of menu item to toggle the read state of the currently displayed message
- val drawableAttr = if (messageViewFragment!!.isMessageRead) {
- menu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_unread_action)
- intArrayOf(R.attr.iconActionMarkAsUnread)
- } else {
- menu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_read_action)
- intArrayOf(R.attr.iconActionMarkAsRead)
- }
- val typedArray = obtainStyledAttributes(drawableAttr)
- menu.findItem(R.id.toggle_unread).icon = typedArray.getDrawable(0)
- typedArray.recycle()
- }
-
- menu.findItem(R.id.delete).isVisible = K9.isMessageViewDeleteActionVisible
-
- // Set visibility of copy, move, archive, spam in action bar and refile submenu
- if (messageViewFragment!!.isCopyCapable) {
- menu.findItem(R.id.copy).isVisible = K9.isMessageViewCopyActionVisible
- menu.findItem(R.id.refile_copy).isVisible = true
- } else {
- menu.findItem(R.id.copy).isVisible = false
- menu.findItem(R.id.refile_copy).isVisible = false
- }
-
- if (messageViewFragment!!.isMoveCapable) {
- val canMessageBeArchived = messageViewFragment!!.canMessageBeArchived()
- val canMessageBeMovedToSpam = messageViewFragment!!.canMessageBeMovedToSpam()
-
- menu.findItem(R.id.move).isVisible = K9.isMessageViewMoveActionVisible
- menu.findItem(R.id.archive).isVisible = canMessageBeArchived && K9.isMessageViewArchiveActionVisible
- menu.findItem(R.id.spam).isVisible = canMessageBeMovedToSpam && K9.isMessageViewSpamActionVisible
-
- menu.findItem(R.id.refile_move).isVisible = true
- menu.findItem(R.id.refile_archive).isVisible = canMessageBeArchived
- menu.findItem(R.id.refile_spam).isVisible = canMessageBeMovedToSpam
- } else {
- menu.findItem(R.id.move).isVisible = false
- menu.findItem(R.id.archive).isVisible = false
- menu.findItem(R.id.spam).isVisible = false
-
- menu.findItem(R.id.refile).isVisible = false
- }
-
- if (messageViewFragment!!.isOutbox) {
- menu.findItem(R.id.move_to_drafts).isVisible = true
- }
-
- menu.findItem(R.id.unsubscribe).isVisible = messageViewFragment!!.canMessageBeUnsubscribed()
- }
- }
-
fun setActionBarTitle(title: String, subtitle: String? = null) {
actionBar.title = title
actionBar.subtitle = subtitle
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
index b88fee3d4b..74c8c27572 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
@@ -11,15 +11,19 @@ import android.os.Parcelable
import android.os.SystemClock
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
+import androidx.core.content.withStyledAttributes
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.activity.MessageCompose
+import com.fsck.k9.activity.MessageList
import com.fsck.k9.activity.MessageLoaderHelper
import com.fsck.k9.activity.MessageLoaderHelper.MessageLoaderCallbacks
import com.fsck.k9.activity.MessageLoaderHelperFactory
@@ -36,7 +40,9 @@ import com.fsck.k9.mailstore.AttachmentViewInfo
import com.fsck.k9.mailstore.LocalMessage
import com.fsck.k9.mailstore.MessageViewInfo
import com.fsck.k9.preferences.AccountManager
+import com.fsck.k9.preferences.GeneralSettingsManager
import com.fsck.k9.ui.R
+import com.fsck.k9.ui.base.Theme
import com.fsck.k9.ui.base.ThemeManager
import com.fsck.k9.ui.choosefolder.ChooseFolderActivity
import com.fsck.k9.ui.messageview.CryptoInfoDialog.OnClickShowCryptoKeyListener
@@ -60,6 +66,7 @@ class MessageViewFragment :
private val accountManager: AccountManager by inject()
private val messagingController: MessagingController by inject()
private val shareIntentBuilder: ShareIntentBuilder by inject()
+ private val generalSettingsManager: GeneralSettingsManager by inject()
private lateinit var messageTopView: MessageTopView
@@ -180,6 +187,83 @@ class MessageViewFragment :
}
}
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ menu.findItem(R.id.delete).isVisible = K9.isMessageViewDeleteActionVisible
+
+ val showToggleUnread = !isOutbox
+ menu.findItem(R.id.toggle_unread).isVisible = showToggleUnread
+
+ if (showToggleUnread) {
+ // Set title of menu item to toggle the read state of the currently displayed message
+ if (isMessageRead) {
+ menu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_unread_action)
+ } else {
+ menu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_read_action)
+ }
+
+ val drawableAttr = if (isMessageRead) {
+ intArrayOf(R.attr.iconActionMarkAsUnread)
+ } else {
+ intArrayOf(R.attr.iconActionMarkAsRead)
+ }
+
+ requireContext().withStyledAttributes(attrs = drawableAttr) {
+ menu.findItem(R.id.toggle_unread).icon = getDrawable(0)
+ }
+ }
+
+ // FIXME: Remove previous/next actions
+ menu.findItem(R.id.next_message).isVisible = false
+ menu.findItem(R.id.previous_message).isVisible = false
+
+ if (isMoveCapable) {
+ val canMessageBeArchived = canMessageBeArchived()
+ val canMessageBeMovedToSpam = canMessageBeMovedToSpam()
+
+ menu.findItem(R.id.move).isVisible = K9.isMessageViewMoveActionVisible
+ menu.findItem(R.id.archive).isVisible = canMessageBeArchived && K9.isMessageViewArchiveActionVisible
+ menu.findItem(R.id.spam).isVisible = canMessageBeMovedToSpam && K9.isMessageViewSpamActionVisible
+
+ menu.findItem(R.id.refile_move).isVisible = true
+ menu.findItem(R.id.refile_archive).isVisible = canMessageBeArchived
+ menu.findItem(R.id.refile_spam).isVisible = canMessageBeMovedToSpam
+
+ menu.findItem(R.id.refile).isVisible = true
+ } else {
+ menu.findItem(R.id.move).isVisible = false
+ menu.findItem(R.id.archive).isVisible = false
+ menu.findItem(R.id.spam).isVisible = false
+
+ menu.findItem(R.id.refile).isVisible = false
+ }
+
+ if (isCopyCapable) {
+ menu.findItem(R.id.copy).isVisible = K9.isMessageViewCopyActionVisible
+ menu.findItem(R.id.refile_copy).isVisible = true
+ } else {
+ menu.findItem(R.id.copy).isVisible = false
+ menu.findItem(R.id.refile_copy).isVisible = false
+ }
+
+ menu.findItem(R.id.move_to_drafts).isVisible = isOutbox
+ menu.findItem(R.id.single_message_options).isVisible = true
+ menu.findItem(R.id.unsubscribe).isVisible = canMessageBeUnsubscribed()
+ menu.findItem(R.id.show_headers).isVisible = true
+
+ val toggleTheme = menu.findItem(R.id.toggle_message_view_theme)
+ if (generalSettingsManager.getSettings().fixedMessageViewTheme) {
+ toggleTheme.isVisible = false
+ } else {
+ // Set title of menu item to switch to dark/light theme
+ if (themeManager.messageViewTheme === Theme.DARK) {
+ toggleTheme.setTitle(R.string.message_view_theme_action_light)
+ } else {
+ toggleTheme.setTitle(R.string.message_view_theme_action_dark)
+ }
+ toggleTheme.isVisible = true
+ }
+ }
+
private fun showMessage(messageViewInfo: MessageViewInfo) {
hideKeyboard()
diff --git a/app/ui/legacy/src/main/res/menu/message_list_option.xml b/app/ui/legacy/src/main/res/menu/message_list_option.xml
index db167ddc63..da41a81ff1 100644
--- a/app/ui/legacy/src/main/res/menu/message_list_option.xml
+++ b/app/ui/legacy/src/main/res/menu/message_list_option.xml
@@ -30,6 +30,7 @@
@@ -37,6 +38,7 @@
@@ -44,6 +46,7 @@
@@ -51,6 +54,7 @@
@@ -58,6 +62,7 @@
-
@@ -73,6 +79,7 @@
@@ -80,6 +87,7 @@
@@ -94,6 +102,7 @@
-
@@ -122,6 +131,7 @@
-
@@ -148,10 +158,12 @@
@@ -227,6 +239,7 @@
--
GitLab
From 4d7a40b52068f7e69284ec500bc269ce747dc545 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 20 Jul 2022 16:46:19 +0200
Subject: [PATCH 11/47] Reformat message_list_option.xml
---
.../src/main/res/menu/message_list_option.xml | 165 +++++++++---------
1 file changed, 81 insertions(+), 84 deletions(-)
diff --git a/app/ui/legacy/src/main/res/menu/message_list_option.xml b/app/ui/legacy/src/main/res/menu/message_list_option.xml
index da41a81ff1..db6cbfbc9f 100644
--- a/app/ui/legacy/src/main/res/menu/message_list_option.xml
+++ b/app/ui/legacy/src/main/res/menu/message_list_option.xml
@@ -1,207 +1,204 @@
-
De ferbettere mooglikheden bestean ûnder oare út:
+
+
+- Pushmail middels IMAP IDLE
+- Bettere prestaasjes
+- Berjocht werklassifikaasje
+- E-mailhantekeningen
+- Bcc nei josels
+- Mapabonneminten
+- Syngronisaasje fan alle mappen
+- Antwurdadres ynstelle
+- Toetseboerd fluchkeppelingen
+- Bettere stipe IMAP
+- Bylage bewarje nei SD
+- Jiskefet leegje
+- Berjochten sortearje
+- en mear…
+
+
+Hâld der rekkening mei dat K-9 de measte fergeze Hotmail-accounts net stipet, en krekt as in protte e-mailclients, problemen hawwe kin om te ferbinen mei Microsoft Exchange.
+
+Graach flaterrapporten stjoere, bydragen foar nije funksjes en fragen stelle op
+https://github.com/k9mail/k-9/.
+
+]]>
+
+ -- \nFerstjoerd fan myn Android-apparaat ôf mei K-9 Mail.
+
+ De account ‘%s’ wurdt út K-9 Mail fuortsmiten.
+
+ Auteurs
+ Oer K-9 mail
+ Accounts
+ Avansearre
+ Nij berjocht
+ Beäntwurdzje
+ Alle beäntwurdzje
+ Trochstjoere
+ Trochstjoere as bylage
+ Kies in account
+ Kies in map
+ Ferpleatse nei…
+ Kopiearje nei…
+ %d selektearre
+ Folgjende
+ Foarige
+
+ OK
+ Annulearje
+ Ferstjoere
+ Der is gjin ûnderwerp ynfierd. Tik nochris om dochs te ferstjoeren.
+ Antwurdzje
+ Allen beäntwurdzje
+ Fuortsmite
+ Argyf
+ Net-winske
+ Trochstjoere
+ Trochstjoere as bylage
+ As nij berjocht bewurkje
+ Ferpleatse
+ Nei konsepten ferpleatse
+ Ferstjoere…
+ Opnij bewarje…
+ Dien
+ Ferjitte
+ As konsept bewarje
+ Op e-mailberjochten kontrolearje
+ Berjochten ferstjoere
+ Maplist ferfarskje
+ Map sykje
+ Account tafoegje
+ Nij berjocht
+ Sykje
+ Oeral sykje
+ Sykresultaten
+ Nije berjochten
+ Ynstellingen
+ Mappen beheare
+ Accountynstellingen
+ Account fuortsmite
+ As lêzen markearje
+ Diele
+ Ofstjoerder kieze
+ Stjer tafoegje
+ Stjer fuortsmite
+ Kopiearje
+ Utskriuwe
+ Berjochtkoppen toane
+
+ - Adres nei klamboerd kopiearre
+ - Adressen nei klamboerd kopiearre
+
+ It ûnderwerp is nei it klamboerd kopiearre
+ Nei donker tema wikselje
+ Nei ljocht tema wikselje
+ As net-lêzen markearje
+ Untfangstbefêstiging
+ Untfangstbefêstiging freegje
+ Gjin ûntfangstbefêstiging freegje
+ Bylage tafoegje
+ Jiskefet leegje
+ Wiskje
+ Oer
+ Ynstellingen
+
+ (Gjin ûnderwerp)
+ Gjin ôfstjoerder
+ Berjochten oan it laden\u2026
+ Netwurkflater
+ Berjocht net fûn
+ Berjocht laden is mislearre
+ Folgjende %d berjochten
+ %.1f GB
+ %.1f MB
+ %.1f kB
+ %d B
+ Nij berjocht
+
+ - %d nije berjocht
+ - %d nije berjochten
+
+ %d Net lêzen (%s)
+ + %1$d mear by %2$s
+ Beäntwurdzje
+ As lêzen markearje
+ Alles as lêzen markearje
+ Fuortsmite
+ Alle fuortsmite
+ Argivearje
+ Alles argivearje
+ Net-winske
+ Sertifikaatflater
+ Sertifikaatflater foar %s
+ Kontrolearje de serverynstellingen
+ Autentikaasje mislearre
+ Autentikaasje foar %s mislearre. Wurkje de serverynstellingen by.
+
+ Meldingsflater
+
+ Der is in flater bard wylst it meitsje fan in systeemmelding foar in nij berjocht. De reden is wierskynlik in ûntbrekken fan in meldingslûd.\n\nTik om meldingsynstellingen te iepenjen.
+ Berjochten kontrolearje: %s:%s
+ Berjochten kontrolearje
+ Berjochten ferstjoere: %s
+ Berjochten ferstjoere
+ :
+ Syngronisearje (Push)
+ Wurdt yn ôfwachting fan nije berjochten werjûn
+ Berjochten
+ Meldingen relatearre oan berjochten
+ Diversken
+ Oare meldingen, lykas flaters en soksawat.
+ Postfek YN
+ Postfek ÚT
+ Konsepten
+ Jiskefet
+ Ferstjoerd
+ Flater by ferstjoeren fan berjochten
+ Ferzje
+ Debuglog tastean
+ Ekstra diagnostyske ynformaasje logge
+ Gefoelige ynformaasje logge
+ Kin wachtwurden yn logs toane.
+ Logs eksportearje
+ Eksportearjen is slagge. Der sit mooglik persoanlike ynformaasje yn de logs. Kies ferstannich nei wa’t jo jo logs stjoere.
+ Eksportearjen is mislearre
+ Mear berjochten lade
+ Oan:%s
+ Underwerp
+ Berjochttekst
+ Hantekening
+ -------- Orizjineel berjocht --------
+ Underwerp:
+ Ferstjoerd:
+ Fan:
+ Oan:
+ Cc:
+ %s skriuw:
+ %2$s skreau op %1$s:
+ Foegje minimaal 1 ûntfanger ta.
+ De ûntfanger is net (folslein) ynfolle!
+ Gjin e-mailadres fûn.
+ Guon bylagen kinne net trochstjoerd wurde, omdat se net download binne.
+ Dit berjocht kin net trochstjoerd wurde, omdat bylagen net download binne.
+ Sitearre berjocht tafoegje
+ Sitaattekst fuortsmite
+ Sitaattekst bewurkje
+ Bylage fuortsmite
+ Fan: %s <%s>
+ Oan:
+ Cc:
+ Bcc:
+ Kin bylage net bewarje.
+ Ofbyldingen toane
+ Gjin viewer te finen foar %s.
+ Folslein berjocht downloade
+ fia %1$s
+ Mear fan dizze ôfstjoerder
+ Fan %s
+ Berjocht fuortsmiten
+ Berjocht as konsept bewarre
+ Stjerren toane
+ Stjerren jouwe markearre berjochten oan
+ Rigels yn it foar besjen
+ Namme by berjocht toane
+ Toan by foarkar namme fan ôfstjoerder/adressearre y.s.f. e-mailadres
+ Korrespondint boppe ûnderwerp
+ Nammen korrespondinten boppe de ûnderwerprigel toane, net derûnder
+ Namme út adreslist toane
+ Brûk namme út it adresboek
+ Kontakten kleur jaan
+ Nammen yn jo kontaktlist kleur jaan
+ Kleur foar nammen fan kontaktpersoanen
+ Fêste breedte lettertypen
+ Brûk in lettertype mei fêste breedte by it werjaan fan platte-tekstberjochten
+ Berjochten auto-passe
+ Berjochten passend meitsje op it skerm
+ Werom nei list nei fuortsmiten
+ Werom nei berjochtelist nei fuortsmiten berjocht
+ Folgjend berjocht toane nei fuortsmiten
+ Standert folgjend berjocht toane nei fuortsmiten
+ Aksjes befêstigje
+ Altyd in dialoochfinster toane wannear’t jo de selektearre aksjes útfiere
+ Fuortsmite
+ Berjochten mei in stjer fuortsmite (yn berjochtwerjefte)
+ Net-winske
+ Berjocht ôfbrekke
+ Alles as lêzen markearje
+ Fuortsmite (fan meldingen)
+ E-mailclient ferstopje
+ K-9-brûkersagent fan e-mailkopteksten fuortsmite
+ Tiidsône ferstopje
+ UTC brûke yn stee fan de lokale tiidsône yn de e-mailkopteksten en by it beäntwurdzjen fan e-mailberjochten
+ Knop ‘Fuortsmite’ toane
+ Nea
+ Melding foar los berjocht
+ Altyd
+ Startskermmelding
+ Gjin startskermmelding
+ Applikaasjenamme
+ Oantal nije berjochten
+ Berjochteteller (ek ferstjoerd)
+ Itselde. Ek nei skermûntskoatteling
+ Stilteperioade
+ Beltoan, trille en leds yn de nacht útskeakelje
+ Notifikaasjes útsette
+ Meldingen wylst stilteperioade folslein útskeakelje
+ Stilteperioade start
+ Stilteperioade ein
+ In nij account ynstelle
+ E-mailadres
+ Wachtwurd
+ Om mei K-9 dit e-mailaccount te brûken moatte jo jo oanmelde om K-9 tagong te jaan ta jo e-mailberjochten.
+
+ Oanmelde
+
+ Oanmelde mei Google
+
+ Skeakelje op dit apparaat in skermbeskoatteling yn om hjir jo wachtwurd sjen te kinnen.
+ Jo identiteit ferifiearje
+ Untskoattelje om jo wachtwurd te sjen
+ Hânmjittich ynstelle
+
+ Accountynformaasje ophelje\u2026
+ Kontrôle fan ynstellingen ynkommende server\u2026
+ Kontrôle fan ynstellingen útgeande server\u2026
+ Autentikaasje\u2026
+ Accountynstellingen ophelje\u2026
+ Annulearje\u2026
+ Hast klear!
+ Jou dit account in namme (opsjoneel):
+ Typ jo namme (sichtber by útgeande berjochten):
+ Accounttype
+ Hokker type account is dit?
+ POP3
+ IMAP
+ Normaal wachtwurd
+ Wachtwurd, net feilich ferstjoerd
+ Fersifere wachtwurd
+ Clientsertifikaat
+ OAuth 2.0
+ Ynkommende serverynstellingen
+ Brûkersnamme
+ Wachtwurd
+ Clientsertifikaat
+ POP3-server
+ IMAP-server
+ WebDAV (Exchange)-server
+ Poarte
+ Befeiligingstype
+ Autentikaasjetype
+ Gjin
+ SSL/TLS
+ STARTTLS
+ ‘%1$s = %2$s’ is net jildich mei ‘%3$s = %4$s’
+ Wannear ik in berjocht fuortsmyt
+ Net fan server fuortsmite
+ Fan server fuortsmite
+ Op server as lêzen markearje
+ Kompresje brûke
+ Op server fuortsmiten berjochten wiskje
+ Daliks nei it fuortsmiten of ferpleatsen
+ By elke berjochtkontrôle
+ Hânmjittich
+ IMAP-namespace automatysk detektearje
+ IMAP-paadfoarfoegsel
+ Konseptmap
+ Ferstjoerdmap
+ Jiskefetmap
+ Argyfmap
+ Net-winskemap
+ Allinnich abonnearre mappen toane
+ Map automatysk útklappe
+ WebDAV (Exchange) path
+ Autentikaasjepaad
+ Postbusalias
+ Utgeande serverynstellingen
+ SMTP-server
+ Poarte
+ Befeiligingstype
+ Oanmelden fereaske
+ Brûkersnamme
+ Wachtwurd
+ Autentikaasjetype
+ ‘%1$s = %2$s’ is net jildich mei ‘%3$s = %4$s’
+ Unjildige ynstellingen: %s
+ Accountopsjes
+ Frekwinsje mapkontrôle
+ Nea
+ Elke 15 minuten
+ Elke 30 minuten
+ Elk oere
+ Elke 2 oeren
+ Elke 3 oeren
+ Elke 6 oeren
+ Elke 12 oeren
+ Elke 24 oeren
+ Ynaktive ferbining fernije
+ Elke 2 minuten
+ Elke 3 minuten
+ Elke 6 minuten
+ Elke 12 minuten
+ Elke 24 minuten
+ Elke 36 minuten
+ Elke 48 minuten
+ Elke 60 minuten
+ Warskôgje my wannear nije e-mailberjochten ynkomme
+ Oantal berjochten om te toanen
+ 10 berjochten
+ 25 berjochten
+ 50 berjochten
+ 100 berjochten
+ 250 berjochten
+ 500 berjochten
+ 1000 berjochten
+ 2500 berjochten
+ 5000 berjochten
+ 10000 berjochten
+ alle berjochten
+ Kin berjocht net kopiearje of ferpleatse, omdat dizze net syngronisearre is mei de server
+ Ynstellen koe net foltôgje
+ Brûkersnamme of wachtwurd ûnjildich.\n(%s)
+ De server presintearre in ûnjildich SSL-sertifikaat. Dit kin barre troch in ferkeard konfigurearre server. Dit kin ek barre trochdat ien jo e-mailserver probearret oan te fallen. As jo net wis binne wat der oan de hân is, klik dan op Reject en nim kontakt op mei de behearder fan de e-mailserver.\n\n(%s)
+ Kin gjin ferbining mei server meitsje.\n(%s)
+ Autorisaasje is annulearre
+ Autorisaasje is mislearre mei de folgjende flater: %s
+ OAuth 2.0 wurdt op dit stuit net stipe troch dizze tsjinstferliener.
+ Dizze app kin gjin browser fine. In browser is nedich om tagong te jaan ta jo account.
+ Details oanpasse
+ Trochgean
+ Avansearre
+ Accountynstellingen
+ Nije-e-mailmelding
+ Meldingsmappen
+ Alles
+ Allinnich 1e-klassemappen
+ 1e- en 2e-klassemappen
+ Alle útsein 2e-klassemappen
+ Gjin
+ Syngronisaasjemeldingen
+ Jo e-mailadres
+ Melding yn steatbalke by in nij e-mailberjocht
+ Yn steatbalke melding werjaan wannear op nije e-mail kontrolearre wurdt
+ By eigen berjochten
+ Melding ek foar e-mail ferstjoerd fan in identiteit ôf
+ Allinnich kontakten
+ Meldingen allinnich werjaan foar bekende kontakten
+ Petearberjochten negearje
+ Gjin meldingen toane foar berjochten dy’t ûnderdiel binne fan in e-mailpetear
+ As lêzen markearje as iepene
+ As lêzen markearje as besjoen
+ As lêzen markearje by wiskjen
+ Markearje in berjocht as lêzen wannear’t dy wiske wurdt
+ Meldingskategoryen
+ Meldingen foar nije berjochten ynstelle
+ Flater- en steatmeldingen ynstelle
+ Ofbyldingen automatysk toane
+ Nea
+ Allinnich fan kontakten
+ Altyd
+ Berjochten ferstjoere
+ Berjocht by beäntwurdzjen sitearje
+ Orizjinele berjocht yn it antwurd meinimme.
+ Beäntwurdzje nei sitaat
+ Wannear’t jo antwurdzje op in berjocht, sil it orizjinele berjocht boppe jo antwurd stean.
+ Hantekening by reaksje fuortsmite
+ Hantekeningen wurde by sitearre berjochten fuortsmiten
+ Berjochtopmaak
+ Platte tekst (ôfbyldingen en opmaak wurde fuortsmiten)
+ HTML (ôfbyldingen en opmaak bliuwe behâlden)
+ Automatysk
+ Cc/Bcc altyd toane
+ Untfangstbefêstiging
+ Altyd in lêsbefêstiging freegje
+ Sitaatstyl by beäntwurdzjen
+ Prefix (lykas Gmail, Pine)
+ Kop (lykas Outlook)
+ Ferstjoerde berjochten oplade
+ Berjochten nei ferstjoeren nei ‘Ferstjoerd’-map oplade
+ Algemiene ynstellingen
+ Berjochten lêze
+ Berjochten ophelje
+ Mappen
+ Sitaatfoarfoegsel
+ Ein-ta-einfersifering
+ OpenPGP-stipe ynskeakelje
+ OpenPGP-app selektearje
+ Ein-ta-einkaai ynstelle
+ Gjin OpenPGP-app ynsteld
+ Ferbûn mei %s
+ Konfiguraasje dwaande…
+ Alle konsepten fersifere bewarje.
+ Alle konsepten sille fersifere bewarre wurde
+ Konsepten allinnich fersiferje as fersifering ynskeakele is
+ Frekwinsje mapkontrôle
+ Accountkleur
+ De accountkleur brûkt by mappen en accountlist
+ Grutte fan lokale map
+ Automatysk berjochten downloade oant
+ 1 KiB
+ 2 KiB
+ 4 KiB
+ 8 KiB
+ 16 KiB
+ 32 KiB
+ 64 KiB
+ 128 KiB
+ 256 KiB
+ 512 KiB
+ 1 MiB
+ 2 MiB
+ 5 MiB
+ 10 MiB
+ elke grutte (gjin limyt)
+ Berjochten syngronisearje fan
+ alles (gjin limyt)
+ hjoed
+ lêste 2 dagen
+ lêtste 3 dagen
+ ôfrûne wike
+ ôfrûne 2 wiken
+ ôfrûne 3 wiken
+ ôfrûne moanne
+ ôfrûne 2 moannen
+ ôfrûne 3 moannen
+ ôfrûne 6 moannen
+ ôfrûne jier
+ Mappen om te toanen
+ Alles
+ Allinnich 1e-klassemappen
+ 1e- en 2e-klassemappen
+ Alle útsein 2e-klassemappen
+ Peiling mappen
+ Alles
+ Allinnich 1e-klassemappen
+ 1e- en 2e-klassemappen
+ Alle útsein 2e-klassemappen
+ Gjin
+ Push-mappen
+ Alles
+ Allinnich 1e-klassemappen
+ 1e- en 2e-klassemappen
+ Alle útsein 2e-klassemappen
+ Gjin
+ Ferpleats/kopiearje doelmappen
+ Alles
+ Allinnich 1e-klassemappen
+ 1e- en 2e-klassemappen
+ Alle útsein 2e-klassemappen
+ Ferwideringen op server syngronisearje
+ Berjochten fuortsmite as fuortsmiten fan server
+ OpenPGP-app net oanwêzich – is de app fuortsmiten?
+ Mapynstellingen
+ Yn topgroep toane
+ By top fan de maplist toane
+ Mapwerjefteklasse
+ Gjin klasse
+ 1e klasse
+ 2e klasse
+ Mappeilingklasse
+ Gjin
+ 1e klasse
+ 2e klasse
+ Selde as werjefteklasse
+ Pushklassemap
+ Gjin klasse
+ 1e klasse
+ 2e klasse
+ Selde as peilingklasse
+ Mapmeldingsklasse
+ Gjin klasse
+ 1e klasse
+ 2e klasse
+ Selde as pushklasse
+ Lokale berjochten wiskje
+ Ynkommende server
+ Ynstellen fan de ynkommende mailserver
+ Utgeande server
+ Ynstellen fan de útgeande (SMTP) server
+ Accountnamme
+ Jo namme
+ Meldingen
+ Trille
+ Trille
+ Trilpatroan
+ Standert
+ Patroan 1
+ Patroan 2
+ Patroan 3
+ Patroan 4
+ Patroan 5
+ Oantal trillingen
+ Utskeakele
+ Beltoan nij e-mailberjocht
+ Meldingsljocht
+ Utskeakele
+ Accountkleur
+ Systeemstandertkleur
+ Wyt
+ Read
+ Grien
+ Blau
+ Giel
+ Syaan
+ Maginta
+ Opsjes berjochtgearstalling
+ Standert gearstalling
+ Stel standert yn foar: Fan, Bcc en hântekening
+ Identiteiten beheare
+ Ynstellen alternative ‘Fan’-adressen en hantekeningen
+ Identiteiten beheare
+ Identiteit beheare
+ Identiteit oanpasse
+ Bewarje
+ Nije identiteit
+ Alle berjochten Bcc nei
+ Bewurkje
+ Omheech ferpleatse
+ Omleech ferpleatse
+ Nei boppe ferpleatse / standert meitsje
+ Fuortsmite
+ Identiteitsbeskriuwing
+ (Opsjoneel)
+ Jo namme
+ (Opsjoneel)
+ E-mailadres
+ (Fereaske)
+ Antwurdadres
+ (Opsjoneel)
+ Hantekening
+ (Opsjoneel)
+ Hantekening brûke
+ Hantekening
+ Inisjele identiteit
+ Identiteit kieze
+ Ferstjoere as
+ Jo kinne jo eigen identiteit net fuosrtsmite
+ Jo kinne in identiteit net brûke sûnder e-mailadres
+ Aldste berjochten earst
+ Nijste berjochten earst
+ Underwerp alfabetysk
+ Underwerp omkeard alfabetysk
+ Ofstjoerder alfabetysk
+ Ofstjoerder omkeard alfabetysk
+ Berjochten mei stjer earst
+ Berjochten sûnder stjer earst
+ Net-lêzen berjochten earst
+ Lêzen berjochten earst
+ Berjochten mei bylagen earst
+ Berjochten sûnder bylagen earst
+ Sortearje op…
+ Datum
+ Oankomst
+ Underwerp
+ Ofstjoerder
+ Stjer
+ Lêzen/net-lêzen
+ Bylagen
+ Account fuortsmite
+ Unbekend sertifikaat
+ Kaai akseptearje
+ Kaai wegerje
+ Del (of D) - Fuortsmite\nR - Beäntwurdzje\nA - Elkenien beäntwurdzje\nC - Opstelle\nF - Trochstjoere\nM - Ferpleatse\nV - Argivearje\nY - Kopiearje\nZ - (net)lêzen markearje\nG - Stjer\nO - Sorteartype\nI - Sortearfolchoarder\nQ - Tebek nei Mappen\nS - Selektearje/deselektearje\nJ of P - Foarich berjocht\nK of N - Folgjende berjocht
+ Del (of D) - Fuortsmite\nC - Opstelle\nM - Ferpleatse\nV - Argivearje\nY - Kopiearje\nZ - (net)lêzen markearje\nG - Stjer\nO - Sorteartype\nI - Sortearfolchoarder\nQ - Tebek nei Mappen\nS - Selektearje/deselektearje
+ Mapnamme befettet
+ Mappen
+ Alle mappen
+ 1e-klassemappen
+ 1e- & 2e-klassemappen
+ 2e-klassemappen ferstopje
+ Posysje hantekening
+ Foar sitearre berjocht
+ Nei sitearre berjocht
+ App-tema brûke
+ Donker
+ Ljocht
+ Systeemstandert brûke
+ Algemiene ynstellingen
+ Algemien
+ Debugge
+ Privacy
+ Netwurk
+ Ynteraksje
+ Accountlist
+ Berjochtlisten
+ Berjochten
+ Tema
+ Tema om berjochten te sjen
+ Tema om berjochten te skriuwen
+ Taal
+ Gjin ynstellingen fûn
+ Fêst berjochtetema
+ Kies it tema wylst it besjen fan it berjocht
+ Fêst tema brûke om it berjocht te besjen
+ Systeemstandert
+ Eftergrûnsyngronisaasje
+ Nea
+ Altyd
+ As ‘Auto-sync’ selektearre is
+ Alle selektearje
+ Maks. mappen om te kontrolearjen mei push
+ 5 mappen
+ 10 mappen
+ 25 mappen
+ 50 mappen
+ 100 mappen
+ 250 mappen
+ 500 mappen
+ 1000 mappen
+ Animaasje
+ Opsichtige fisuele effekten brûke
+ Folumetoetsnavigaasje
+ Berjochtbyld
+ Fariabele listwerjefte
+ Kombinearre Postfek Yn werjaan
+ It oantal berjochten mei in stjer werjaan
+ Kombinearre Postfek Yn
+ Alle berjochten yn kombinearre mappen
+ Kombinearje
+ Alle berjochten wurde yn it kombinearre Postfek Yn werjûn
+ Sykmappen
+ Alles
+ Sichtbere
+ Gjin
+ Gjin
+ Automatysk (%s)
+ Lettergrutte
+ Lettergrutte ynstelle
+ Accountlist
+ Accountnamme
+ Accountbeskriuwing
+ Maplisten
+ Mapnamme
+ Mapsteat
+ Berjochtlisten
+ Underwerp
+ Ofstjoerder
+ Datum
+ Foarbyld
+ Berjochten
+ Ofstjoerder
+ Oan
+ Cc
+ Bcc
+ Ekstra koppen
+ Underwerp
+ Tiid en datum
+ Berichtynhâld
+ Berjocht opstelle
+ Tekst ynfierfjilden
+ Standert
+ Meast lytse
+ Hiel lyts
+ Lytser
+ Lyts
+ Gemiddeld
+ Grut
+ Grutter
+ Gjin geskikte applikaasje fûn foar dizze aksje.
+ Ferstjoeren mislearre: %s
+ Konsept bewarje?
+ Dit berjocht bewarje of ferwerpe?
+ Wizigingen bewarje of ferwerpe?
+ Berjocht ôfbrekke?
+ Binne jo wis dat jo dit berjocht fuortsmite wolle
+ Selektearje tekst om te kopiearjen.
+ Lokale berjochten wiskje?
+ Dit sil alle lokale berjochten út dizze map wiskje. Der wurde gjin berjochten fan de server wiske.
+ Berjochten wiskje
+ Fuortsmiten befêstigje
+ Wolle jo dit berjocht fuortsmite?
+
+ - Wolle jo dit berjocht echt fuortsmite?
+ - Wolle jo echt %1$d berjochten fuortsmite?
+
+ Ja
+ Nea
+ Alles as lêzen markearje
+ Wolle jo alle berjochten as lêzen markearje?
+ Jiskefet leegje befêstigje
+ Wolle jo it jiskefet leech smite?
+ Ja
+ Nea
+ Ferpleatsing nei net-winskemap befêstigje
+
+ - Wolle jo dit berjocht echt ferpleatse nei de map Net-winske?
+ - Wolle jo echt %1$dferpleatse nei de map Net-winske?
+
+ Ja
+ Nea
+ Bylage wurdt ophelle
+ »
+ ›
+ Reservekopy
+ Diversken
+ Eksportynstellingen
+ Eksportearje
+ Diele
+ Eksportynstellingen…
+ Ynstellingen mei sukses eksportearre
+ Ynstellingen eksportearjen mislearre
+ Ymportynstellingen
+ Bestân selektearje
+ Ymportearje
+ Ynstellingen mei sukses ymportearre
+
+ Fier de wachtwurden yn
+
+ Oanmelde
+
+ Meld jo oan en fier it wachtwurd yn
+ Ynstellingen ymportearjen mislearre
+ Lêzen fan ynstellingenbestân mislearre
+ Ymportearjen fan guon ynstellingen mislearre
+ Mei sukses ymportearre
+ Wachtwurd fereaske
+
+ Oanmelden fereaske
+ Net ymportearre
+ Ymportearjen mislearre
+ Letter
+ Ymportynstellingen
+ Ymportynstellingen…
+
+ - Om de account ‘%s’ te brûken moatte jo it wachtwurd fan de server opjaan.
+ - Om de account ‘%s’ te brûken moatte jo de wachtwurden fan de server opjaan.
+
+ Wachtwurd ynkommende server
+ Wachtwurd útgeande server
+ Brûk itselde wachtwurd foar de útgeande server
+ Servernamme: %s
+ Oantal net-lêzen werjaan foar…
+ Account
+ De account wêrfan it oantal net-lêzen berjochten toand wurdt
+ Kombinearre Postfek Yn
+ Mapoantal
+ Toan it oantal net-lêzen berjochten fan in inkelde map
+ Map
+ De map wêrby oantal net-lêzen berjochten toand wurdt
+ Dien
+ %1$s - %2$s
+ Gjin account keazen
+ Gjin map keazen
+ Gjin tekst
+ Keppeling iepenje
+ Keppeling diele
+ Keppeling nei klamboerd kopiearje
+ Keppeling
+ Keppelingtekst nei klamboerd kopiearje
+ Keppelingstekst
+ Ofbylding
+ Ofbylding toane
+ Ofbylding bewarje
+ Ofbylding downloade
+ Ofbylding-URL nei klamboerd kopiearje
+ Ofbylding-URL
+ Nûmer belje
+ Yn kontakten bewarje
+ Nûmer nei klamboerd kopiearje
+ Telefoannûmer
+ E-mailberjocht stjoere
+ Yn kontakten bewarje
+ Adres nei klamboerd kopiearje
+ E-mailadres
+ Alles
+ 10
+ 25
+ 50
+ 100
+ 250
+ 500
+ 1000
+ Server-sykklimyt
+ Sykopdracht nei server stjoere
+
+ - %d resultaat ophelje
+ - %d resultaten ophelje
+
+
+ - %1$d fan %2$d berjochten ophelje
+ - %1$d fan %2$d berjochten ophelje
+
+ Sykopdracht mislearre
+ Sykje
+ Berjochten op server sykje
+ In netwurkferbining is foar sykjen op server nedich.
+ Kleur wizigje nei lêzen
+ In oare eftergrûn toane neidat it e-mailberjocht lêzen is
+ Petearoersjoch
+ Berjochten per petear groepearje
+ Databases bywurkje
+ Databases bywurkje dwaande…
+ Databases fan account ‘%s’ bywurkje
+ Splitst skerm toane
+ Altyd
+ Nea
+ Yn lizzende oriïntaasje
+ Selektearje in berjocht oan de linker kant
+ Kontaktôfbyldingen toane
+ Kontactôfbyldingen yn de berjochtelist toane
+ Alles as lêzen markearje
+ Kontaktôfbyldingen kleurje
+ Ofwêzige kontaktôfbyldingen in kleur jaan
+ Sichtbere berjochtaksjes
+ Selektearre aksjes yn it Berjochte-menu
+ Bylage oan it laden…
+ Berjocht wurdt ferstjoerd
+ Konsept wurdt bewarre
+ Bylage oan it opheljen…
+ Kin net autentisearje. De server stipet gjin SASL EXTERNAL. Dit kin komme troch in probleem mei it clientsertifikaat (ferrûn of ûnbekende CA) of in oar konfiguraasjeprobleem.
+
+ Clientsertifikaat brûke
+ Gjin clientsertifikaat
+ Clientsertifikaatseleksje fuortsmite
+ Clientsertifikaat net ûntfongen foar alias \‘%s\’
+ Avansearre opsjes
+ Clientsertifikaat \‘%1$s\’ is ferrûn of net jildich (%2$s)
+
+ *Fersifere*
+ Fan kontakten út tafoegje
+ Bericht ontvanger (CC)
+ Bericht ontvanger (BCC)
+ Oan
+ Fan
+ Antwurdzje nei
+ <Unbekende ûntfanger>
+ <Unbekende ôfstjoerder>
+ Privee
+ Wurk
+ Oars
+ Mobyl
+ Gjin konseptemap ynsteld foar dit account!
+ Gjin kaai ynsteld foar dit account! Kontrolearje jo ynstellingen.
+ Cryptoprovider brûkt ynkompatibele ferzje. Kontrolearje jo ynstellingen!
+ Kin gjin ferbining meitsje mei de cryptoprovider. Kontrolearje de ynstellingen of klik op it cryptopiktogram om nochris te probearjen.
+ Inisjalisaasje fan ein-ta-einfersifering is mislearre, kontrolearje de ynstellingen
+ PGP/MIME-ynstelling stipet gjin bylagen!
+ PGP/INLINE tastean
+ PGP/INLINE útskeakelje
+ PGP-hantekening tastean
+ PGP-hantekening útskeakelje
+ PGP/INLINE-ynstellingen
+ It e-mailberjocht is ferstjoerd yn PGP/INLINE-formaat.\nDit wurdt allinnich brûkt foar kompatibiliteit:
+ Guon clients stypje allinnich dit formaat
+ Hantekeningen kinne ûnderweis brekke
+ Bylagen wurde net stipe
+ Begrepen!
+ Utskeakelje
+ Ynskeakele hâlde
+ Begrepen!
+ Utskeakelje
+ Ynskeakele hâlde
+ allinnich PGP-hantekeningmodus
+ Yn dizze modus wurdt dyn PGP-kaai brûkt foar in kryptografyske hantekening of in net-kodearre e-mailberjocht.
+ Dit fersiferet net it e-mailberjocht, mar kontrolearret dat jo eigen kaai brûkt is.
+ Hantekeningen kinne by ferstjoeren nei mailinglist brekke
+ Hantekeningen kinne by guon programma\'s as ‘signature.asc’-bylage werjûn wurde
+ Fersifere berjochten befetsje altyd in hantekening.
+ Platte tekst
+ ein-ta-ein-hantekening befettet in flater
+ moat berjocht folslein downloade om hantekening te ferwurkjen
+ befettet net-stipe ein-ta-ein-hantekening
+ Berjocht is fersifere, mar yn in net-stipe formaat.
+ Berjocht is fersifere, mar ûntsiferjen is annulearre.
+ Ein-ta-ein tekene platte tekst
+ fan ferifiearre ûndertekener
+ Platte tekst tekene
+ mar ein-ta-ein-kaai komt net oerien mei ôfstjoerder
+ mar ein-ta-ein-kaai is ferrûn
+ mar ein-ta-ein-kaai is ynlutsen
+ mar ein-ta-ein-kaai is net feilich
+ fan in ûnbekende ein-ta-ein-kaai
+ Fersifere
+ mar der is in ûntsiferflater bard
+ moat berjocht folslein downloade foar ûntsifering
+ mar der is gjin crypto-app konfigurearre
+ Fersifere
+ mar net ein-ta-ein
+ Ein-ta-ein fersifere
+ fan ferifiearre ôfstjoerder
+ Fersifere
+ fan in ûnbekende ein-ta-ein-kaai
+ mar ein-ta-ein-kaai komt net oerien mei ôfstjoerder
+ mar ein-ta-ein-kaai is ferrûn
+ mar ein-ta-ein-kaai is ynlutsen
+ mar ein-ta-ein-kaai is net feilich
+ mar ein-ta-ein-gegevens befetsje flaters
+ mar fersifering is net feilich
+ OK
+ Kaai sykje
+ Undertekener besjen
+ Ofstjoerder besjen
+ Details
+ Deblokkearje
+ Dit ûnderdiel is net fersifere en is miskien net feilich.
+ Unbefeilige bylage
+ Lade…
+ Untsifering is stoppe
+ Opnij
+ Fersifere berjocht moat download wêze foar ûntsifering.
+ Flater wylst ûntsiferjen e-mailberjocht
+ Spesjale lêstekens wurde noch net stipe!
+ Flater by ferwurkjen fan adres!
+ Net-fersifere hantekeningen ferstopje
+ Allinnich fersifere hantekeningen wurde werjûn
+ Alle hantekeningen wurde werjûn
+ Fersifering net mooglik yn sign-only-modus!
+ net ûndertekene tekst
+ Dit e-mailberjocht is fersifere
+ Dit e-mailberjocht is OpenPGP-fersifere.\nYnstallearje en stel in OpenPGP-app yn om it e-mailberjocht te lêzen.
+ Gean nei Ynstellingen
+ K-9-berjochtelist
+ Berjochten oan it laden…
+ Fersifering net mooglik
+ Guon fan de ûntfangers stypje dizze funksje net!
+ Fersifering ynskeakelje
+ Fersifering útskeakelje
+ Fersifering makket dat berjochten allinnich troch de ûntfanger lêzen wurde kin en nearne oars.
+ Fersifering is allinnich beskikber as alle ûntfangers dit stypje en dyjinge moatte jo al earder in e-mailberjocht stjoerd hawwe.
+ Skeakelje fersifering yn of út mei dit piktogram.
+ Ik begryp it
+ Tebek
+ Fersifering útskeakelje
+ OpenPGP-fersifering
+ Autocrypt fan wjerskantenmodus
+ Autocrypt fan wjerskantenmodus
+ Berjochten wurde op fersyk fersifere, of by beäntwurdzjen fan in fersifere berjocht.
+ As ôfstjoerder en ûntfanger de fan-wjerskantenmodus ynskeakelje, dan wurdt standert fersifering ynsteld.
+ Klik hjir foar mear ynformaasje.
+ Algemiene ynstellingen
+ Gjin OpenPGP-app ynstallearre
+ Ynstallearje
+ K-9 Mail fereasket OpenKeychain foar ein-ta-ein-fersifering.
+ Fersifere berjocht
+ Underwerp fan berjocht fersiferje
+ Mooglik net stipe troch alle ûntfangers
+ Ynterne flater: Unjildich account!
+ Flater by it ferbinen mei %s!
+ Autocrypt ynstelberjocht ferstjoere
+ Feilich dielen fan ein-ta-ein-ynstellingen mei oare apparaten
+ Autocrypt ynstelberjocht
+ In Autocrypt ynstelberjocht dielt jo ein-ta-ein-ynstellingen op in befeilige manier mei oare apparaten.
+ Ynstelberjocht ferstjoere
+ It berjocht wurdt ferstjoerd nei jo adres:
+ Ynstelberjocht oan it generearjen…
+ Berjocht stjoere nei:
+ Om te foltôgjen, iepenje it berjocht op jo oare apparaat en fier de ynstelkoade yn.
+ Ynstelkoade werjaan
+ Autocrypt ynstelberjocht
+ Dit berjocht befettet alle ynformaasje om jo Autocrypt-ynstellingen mei geheime kaai befeilige oer te bringen fan jo oarspronklike apparaat ôf.
+
+Folgje de ynstruksjes op jo nije apparaat om dêrop Autocrypt yn te stellen.
+
+Jo kinne dit berjocht bewarje as reservekopy foar jo geheime kaai. As jo dit dwaan wolle, skriuw dan it wachtwurd op en bewarje it op in feilich plak.
+
+ Der is in flater bard wylst it ferstjoeren fan it berjocht. Kontrolearje de netwurkferbining en útgeande serverkonfiguraasje.
+ Oan
+ Ut
+
+ Tagong ta kontakten tastean
+ Om kontaktsuggestjes biede te kinnen en om nammen en/of foto\'s fan kontakten wer te jaan, hat de app tagong nedich ta jo kontaktelist.
+ Der is in flater bard by it laden fan de gegevens
+ Oan it inisjalisearjen…
+ Yn ôfwachting fan nije e-mailberjochten
+ Docht neat oant eftergrûnsyngronisaasje tastien wurdt
+ Docht neat oant der in netwurk beskikber is
+ Tik hjir om der mear oer te lêzen.
+ Push-ynformaasje
+ As jo push brûke, dan hâld K-9 Mail in ferbining iepen mei de e-mailserver. Android fereasket dat in app dy op de eftergrûn aktyf bliuwt in melding pleatst. %s
+ Android makket it echter ek mooglik om meldingen te ferstopjen.
+ Mear ynfo
+ Melding ynstelle
+ As jo it net nedich fine om daliks meldingen te ûntfangen fan nije berjochten, dan kinne jo better push útskeakelje en yn stee dêrfan kieze foar peilen. Mei peilen wurdt der om de sa folle tiid kontrolearre oft der nije e-mailberjochten ynkommen binne. Dêrfoar is it werjaan fan in melding net kontinu nedich.
+ Push útskeakelje
+
diff --git a/app/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml b/app/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml
index d2e38b39c2..328f4cce3b 100644
--- a/app/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml
+++ b/app/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml
@@ -23,6 +23,7 @@
- Ɛʋɛ
- Français
- Français (Canada)
+ - Frysk
- Fulfulde, Pulaar, Pular
- Gaeilge
- Gàidhlig
@@ -47,7 +48,6 @@
- Polski
- Português
- Português (Brasil)
- - Pyccĸий
- Română
- Shqip
- Slovenčina
@@ -66,6 +66,7 @@
- Кыргыз
- Қазақ
- Македонски
+ - Русский
- Српски
- Українська
- Հայերեն
--
GitLab
From 7dbae49c8e73a63eabf8458483a095434c64b098 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 26 Jul 2022 18:03:01 +0200
Subject: [PATCH 27/47] Fix removing multiple notifications at once
---
.../k9/notification/NotificationDataStore.kt | 49 ++++++++++++-------
.../notification/NotificationDataStoreTest.kt | 32 ++++++++++++
2 files changed, 62 insertions(+), 19 deletions(-)
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 db99f40340..de9f8c94be 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
@@ -108,32 +108,43 @@ internal class NotificationDataStore {
account: Account,
selector: (List) -> List
): RemoveNotificationsResult? {
- val notificationData = getNotificationData(account)
+ var notificationData = getNotificationData(account)
if (notificationData.isEmpty()) return null
val removeMessageReferences = selector.invoke(notificationData.messageReferences)
+ if (removeMessageReferences.isEmpty()) return null
val operations = mutableListOf()
val newNotificationHolders = mutableListOf()
val cancelNotificationIds = mutableListOf()
- for (messageReference in removeMessageReferences) {
- val notificationHolder = notificationData.activeNotifications.firstOrNull {
- it.content.messageReference == messageReference
+ val activeMessageReferences = notificationData.activeNotifications.map { it.content.messageReference }.toSet()
+ val (removeActiveMessageReferences, removeInactiveMessageReferences) = removeMessageReferences
+ .partition { it in activeMessageReferences }
+
+ if (removeInactiveMessageReferences.isNotEmpty()) {
+ val inactiveMessageReferences = notificationData.inactiveNotifications
+ .map { it.content.messageReference }.toSet()
+
+ for (messageReference in removeInactiveMessageReferences) {
+ if (messageReference in inactiveMessageReferences) {
+ operations.add(NotificationStoreOperation.Remove(messageReference))
+ }
}
- if (notificationHolder == null) {
- val inactiveNotificationHolder = notificationData.inactiveNotifications.firstOrNull {
- it.content.messageReference == messageReference
- } ?: continue
+ val removeMessageReferenceSet = removeInactiveMessageReferences.toSet()
+ notificationData = notificationData.copy(
+ inactiveNotifications = notificationData.inactiveNotifications
+ .filter { it.content.messageReference !in removeMessageReferenceSet }
+ )
+ }
- operations.add(NotificationStoreOperation.Remove(messageReference))
+ for (messageReference in removeActiveMessageReferences) {
+ val notificationHolder = notificationData.activeNotifications.first {
+ it.content.messageReference == messageReference
+ }
- val newNotificationData = notificationData.copy(
- inactiveNotifications = notificationData.inactiveNotifications - inactiveNotificationHolder
- )
- notificationDataMap[account.uuid] = newNotificationData
- } else if (notificationData.inactiveNotifications.isNotEmpty()) {
+ if (notificationData.inactiveNotifications.isNotEmpty()) {
val newNotificationHolder = notificationData.inactiveNotifications.first()
.toNotificationHolder(notificationHolder.notificationId)
@@ -148,29 +159,29 @@ internal class NotificationDataStore {
)
)
- val newNotificationData = notificationData.copy(
+ notificationData = notificationData.copy(
activeNotifications = notificationData.activeNotifications - notificationHolder +
newNotificationHolder,
inactiveNotifications = notificationData.inactiveNotifications.drop(1)
)
- notificationDataMap[account.uuid] = newNotificationData
} else {
cancelNotificationIds.add(notificationHolder.notificationId)
operations.add(NotificationStoreOperation.Remove(messageReference))
- val newNotificationData = notificationData.copy(
+ notificationData = notificationData.copy(
activeNotifications = notificationData.activeNotifications - notificationHolder
)
- notificationDataMap[account.uuid] = newNotificationData
}
}
+ notificationDataMap[account.uuid] = notificationData
+
return if (operations.isEmpty()) {
null
} else {
RemoveNotificationsResult(
- notificationData = getNotificationData(account),
+ notificationData = notificationData,
notificationStoreOperations = operations,
notificationHolders = newNotificationHolders,
cancelNotificationIds = cancelNotificationIds
diff --git a/app/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt b/app/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt
index 57ffc8aba4..c3000f7711 100644
--- a/app/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt
+++ b/app/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt
@@ -92,6 +92,38 @@ class NotificationDataStoreTest : RobolectricTest() {
}
}
+ @Test
+ fun `remove multiple notifications`() {
+ repeat(MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS + 1) { index ->
+ notificationDataStore.addNotification(account, createNotificationContent(index.toString()), TIMESTAMP)
+ }
+
+ val result = notificationDataStore.removeNotifications(account) { it.dropLast(1) }
+
+ assertNotNull(result) { removeResult ->
+ assertThat(removeResult.notificationData.newMessagesCount).isEqualTo(1)
+ assertThat(removeResult.cancelNotificationIds).hasSize(MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS)
+ }
+ }
+
+ @Test
+ fun `remove all notifications`() {
+ repeat(MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS + 1) { index ->
+ notificationDataStore.addNotification(account, createNotificationContent(index.toString()), TIMESTAMP)
+ }
+
+ val result = notificationDataStore.removeNotifications(account) { it }
+
+ assertNotNull(result) { removeResult ->
+ assertThat(removeResult.notificationData.newMessagesCount).isEqualTo(0)
+ assertThat(removeResult.notificationHolders).hasSize(0)
+ assertThat(removeResult.notificationStoreOperations).hasSize(MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS + 1)
+ for (notificationStoreOperation in removeResult.notificationStoreOperations) {
+ assertThat(notificationStoreOperation).isInstanceOf(NotificationStoreOperation.Remove::class.java)
+ }
+ }
+ }
+
@Test
fun testRemoveDoesNotLeakNotificationIds() {
for (i in 1..MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS + 1) {
--
GitLab
From 147945f7578593ade7611497e5eddc90c74c7b54 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 26 Jul 2022 22:01:39 +0200
Subject: [PATCH 28/47] Make sure `Intents` used by
`K9NotificationActionCreator` are unique
---
.../K9NotificationActionCreator.kt | 117 ++++++++++++------
1 file changed, 77 insertions(+), 40 deletions(-)
diff --git a/app/k9mail/src/main/java/com/fsck/k9/notification/K9NotificationActionCreator.kt b/app/k9mail/src/main/java/com/fsck/k9/notification/K9NotificationActionCreator.kt
index 77a997ced2..0da55f29a6 100644
--- a/app/k9mail/src/main/java/com/fsck/k9/notification/K9NotificationActionCreator.kt
+++ b/app/k9mail/src/main/java/com/fsck/k9/notification/K9NotificationActionCreator.kt
@@ -5,6 +5,7 @@ import android.app.PendingIntent.FLAG_CANCEL_CURRENT
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
+import android.net.Uri
import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.activity.MessageList
@@ -25,8 +26,10 @@ import com.fsck.k9.ui.notification.DeleteConfirmationActivity
* We need to take special care to ensure the `PendingIntent`s are unique as defined in the documentation of
* [PendingIntent]. Otherwise selecting a notification action might perform the action on the wrong message.
*
- * We use the notification ID as `requestCode` argument to ensure each notification/action pair gets a unique
- * `PendingIntent`.
+ * We add unique values to `Intent.data` so we end up with unique `PendingIntent`s.
+ *
+ * In the past we've used the notification ID as `requestCode` argument when creating a `PendingIntent`. But since we're
+ * reusing notification IDs, it's safer to make sure the `Intent` itself is unique.
*/
internal class K9NotificationActionCreator(
private val context: Context,
@@ -41,12 +44,12 @@ internal class K9NotificationActionCreator(
val openInUnifiedInbox = K9.isShowUnifiedInbox && isIncludedInUnifiedInbox(messageReference)
val intent = createMessageViewIntent(messageReference, openInUnifiedInbox)
- return PendingIntent.getActivity(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createViewFolderPendingIntent(account: Account, folderId: Long, notificationId: Int): PendingIntent {
val intent = createMessageListIntent(account, folderId)
- return PendingIntent.getActivity(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createViewMessagesPendingIntent(
@@ -64,38 +67,46 @@ internal class K9NotificationActionCreator(
createNewMessagesIntent(account)
}
- return PendingIntent.getActivity(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createViewFolderListPendingIntent(account: Account, notificationId: Int): PendingIntent {
val intent = createMessageListIntent(account)
- return PendingIntent.getActivity(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createDismissAllMessagesPendingIntent(account: Account, notificationId: Int): PendingIntent {
- val intent = NotificationActionService.createDismissAllMessagesIntent(context, account)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createDismissAllMessagesIntent(context, account).apply {
+ data = Uri.parse("data:,dismissAll/${account.uuid}/${System.currentTimeMillis()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createDismissMessagePendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
- val intent = NotificationActionService.createDismissMessageIntent(context, messageReference)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createDismissMessageIntent(context, messageReference).apply {
+ data = Uri.parse("data:,dismiss/${messageReference.toIdentityString()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createReplyPendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent {
- val intent = MessageActions.getActionReplyIntent(context, messageReference)
- return PendingIntent.getActivity(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = MessageActions.getActionReplyIntent(context, messageReference).apply {
+ data = Uri.parse("data:,reply/${messageReference.toIdentityString()}")
+ }
+ return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createMarkMessageAsReadPendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
- val intent = NotificationActionService.createMarkMessageAsReadIntent(context, messageReference)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createMarkMessageAsReadIntent(context, messageReference).apply {
+ data = Uri.parse("data:,markAsRead/${messageReference.toIdentityString()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createMarkAllAsReadPendingIntent(
@@ -104,8 +115,10 @@ internal class K9NotificationActionCreator(
notificationId: Int
): PendingIntent {
val accountUuid = account.uuid
- val intent = NotificationActionService.createMarkAllAsReadIntent(context, accountUuid, messageReferences)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createMarkAllAsReadIntent(context, accountUuid, messageReferences).apply {
+ data = Uri.parse("data:,markAllAsRead/$accountUuid/${System.currentTimeMillis()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun getEditIncomingServerSettingsIntent(account: Account): PendingIntent {
@@ -123,9 +136,9 @@ internal class K9NotificationActionCreator(
notificationId: Int
): PendingIntent {
return if (K9.isConfirmDeleteFromNotification) {
- createDeleteConfirmationPendingIntent(messageReference, notificationId)
+ createDeleteConfirmationPendingIntent(messageReference, 0)
} else {
- createDeleteServicePendingIntent(messageReference, notificationId)
+ createDeleteServicePendingIntent(messageReference, 0)
}
}
@@ -133,16 +146,20 @@ internal class K9NotificationActionCreator(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
- val intent = NotificationActionService.createDeleteMessageIntent(context, messageReference)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createDeleteMessageIntent(context, messageReference).apply {
+ data = Uri.parse("data:,delete/${messageReference.toIdentityString()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
private fun createDeleteConfirmationPendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
- val intent = DeleteConfirmationActivity.getIntent(context, messageReference)
- return PendingIntent.getActivity(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = DeleteConfirmationActivity.getIntent(context, messageReference).apply {
+ data = Uri.parse("data:,deleteConfirmation/${messageReference.toIdentityString()}")
+ }
+ return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createDeleteAllPendingIntent(
@@ -151,9 +168,9 @@ internal class K9NotificationActionCreator(
notificationId: Int
): PendingIntent {
return if (K9.isConfirmDeleteFromNotification) {
- getDeleteAllConfirmationPendingIntent(messageReferences, notificationId)
+ getDeleteAllConfirmationPendingIntent(messageReferences, 0)
} else {
- getDeleteAllServicePendingIntent(account, messageReferences, notificationId)
+ getDeleteAllServicePendingIntent(account, messageReferences, 0)
}
}
@@ -161,8 +178,10 @@ internal class K9NotificationActionCreator(
messageReferences: List,
notificationId: Int
): PendingIntent {
- val intent = DeleteConfirmationActivity.getIntent(context, messageReferences)
- return PendingIntent.getActivity(context, notificationId, intent, FLAG_CANCEL_CURRENT or FLAG_IMMUTABLE)
+ val intent = DeleteConfirmationActivity.getIntent(context, messageReferences).apply {
+ data = Uri.parse("data:,deleteAllConfirmation/${System.currentTimeMillis()}")
+ }
+ return PendingIntent.getActivity(context, 0, intent, FLAG_CANCEL_CURRENT or FLAG_IMMUTABLE)
}
private fun getDeleteAllServicePendingIntent(
@@ -171,16 +190,20 @@ internal class K9NotificationActionCreator(
notificationId: Int
): PendingIntent {
val accountUuid = account.uuid
- val intent = NotificationActionService.createDeleteAllMessagesIntent(context, accountUuid, messageReferences)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createDeleteAllMessagesIntent(context, accountUuid, messageReferences).apply {
+ data = Uri.parse("data:,deleteAll/$accountUuid/${System.currentTimeMillis()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createArchiveMessagePendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
- val intent = NotificationActionService.createArchiveMessageIntent(context, messageReference)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createArchiveMessageIntent(context, messageReference).apply {
+ data = Uri.parse("data:,archive/${messageReference.toIdentityString()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createArchiveAllPendingIntent(
@@ -188,16 +211,20 @@ internal class K9NotificationActionCreator(
messageReferences: List,
notificationId: Int
): PendingIntent {
- val intent = NotificationActionService.createArchiveAllIntent(context, account, messageReferences)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createArchiveAllIntent(context, account, messageReferences).apply {
+ data = Uri.parse("data:,archiveAll/${account.uuid}/${System.currentTimeMillis()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createMarkMessageAsSpamPendingIntent(
messageReference: MessageReference,
notificationId: Int
): PendingIntent {
- val intent = NotificationActionService.createMarkMessageAsSpamIntent(context, messageReference)
- return PendingIntent.getService(context, notificationId, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
+ val intent = NotificationActionService.createMarkMessageAsSpamIntent(context, messageReference).apply {
+ data = Uri.parse("data:,spam/${messageReference.toIdentityString()}")
+ }
+ return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
private fun createMessageListIntent(account: Account): Intent {
@@ -213,7 +240,9 @@ internal class K9NotificationActionCreator(
noThreading = false,
newTask = true,
clearTop = true
- )
+ ).apply {
+ data = Uri.parse("data:,messageList/${account.uuid}/$folderId")
+ }
}
private fun createMessageListIntent(account: Account, folderId: Long): Intent {
@@ -228,19 +257,27 @@ internal class K9NotificationActionCreator(
noThreading = false,
newTask = true,
clearTop = true
- )
+ ).apply {
+ data = Uri.parse("data:,messageList/${account.uuid}/$folderId")
+ }
}
- private fun createMessageViewIntent(message: MessageReference, openInUnifiedInbox: Boolean): Intent {
- return MessageList.actionDisplayMessageIntent(context, message, openInUnifiedInbox)
+ private fun createMessageViewIntent(messageReference: MessageReference, openInUnifiedInbox: Boolean): Intent {
+ return MessageList.actionDisplayMessageIntent(context, messageReference, openInUnifiedInbox).apply {
+ data = Uri.parse("data:,messageView/${messageReference.toIdentityString()}")
+ }
}
private fun createUnifiedInboxIntent(account: Account): Intent {
- return MessageList.createUnifiedInboxIntent(context, account)
+ return MessageList.createUnifiedInboxIntent(context, account).apply {
+ data = Uri.parse("data:,unifiedInbox/${account.uuid}")
+ }
}
private fun createNewMessagesIntent(account: Account): Intent {
- return MessageList.createNewMessagesIntent(context, account)
+ return MessageList.createNewMessagesIntent(context, account).apply {
+ data = Uri.parse("data:,newMessages/${account.uuid}")
+ }
}
private fun extractFolderIds(messageReferences: List): Set {
--
GitLab
From bc7758b9cfbfc54b3c56cd34a74b5848a3150290 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 26 Jul 2022 23:19:27 +0200
Subject: [PATCH 29/47] Remove `notificationId` parameters from methods in
`NotificationActionCreator`
---
.../notification/NotificationActionCreator.kt | 47 ++++--------
.../SendFailedNotificationController.kt | 8 +-
.../SingleMessageNotificationCreator.kt | 37 +++------
.../SummaryNotificationCreator.kt | 29 ++-----
.../SyncNotificationController.kt | 8 +-
.../SendFailedNotificationControllerTest.kt | 5 +-
.../SyncNotificationControllerTest.kt | 3 +-
.../K9NotificationActionCreator.kt | 76 ++++++-------------
8 files changed, 60 insertions(+), 153 deletions(-)
diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationActionCreator.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationActionCreator.kt
index d7e403b8c3..84297e06b4 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/NotificationActionCreator.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationActionCreator.kt
@@ -5,54 +5,35 @@ import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference
interface NotificationActionCreator {
- fun createViewMessagePendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent
+ fun createViewMessagePendingIntent(messageReference: MessageReference): PendingIntent
- fun createViewFolderPendingIntent(account: Account, folderId: Long, notificationId: Int): PendingIntent
+ fun createViewFolderPendingIntent(account: Account, folderId: Long): PendingIntent
- fun createViewMessagesPendingIntent(
- account: Account,
- messageReferences: List,
- notificationId: Int
- ): PendingIntent
+ fun createViewMessagesPendingIntent(account: Account, messageReferences: List): PendingIntent
- fun createViewFolderListPendingIntent(account: Account, notificationId: Int): PendingIntent
+ fun createViewFolderListPendingIntent(account: Account): PendingIntent
- fun createDismissAllMessagesPendingIntent(account: Account, notificationId: Int): PendingIntent
+ fun createDismissAllMessagesPendingIntent(account: Account): PendingIntent
- fun createDismissMessagePendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent
+ fun createDismissMessagePendingIntent(messageReference: MessageReference): PendingIntent
- fun createReplyPendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent
+ fun createReplyPendingIntent(messageReference: MessageReference): PendingIntent
- fun createMarkMessageAsReadPendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent
+ fun createMarkMessageAsReadPendingIntent(messageReference: MessageReference): PendingIntent
- fun createMarkAllAsReadPendingIntent(
- account: Account,
- messageReferences: List,
- notificationId: Int
- ): PendingIntent
+ fun createMarkAllAsReadPendingIntent(account: Account, messageReferences: List): PendingIntent
fun getEditIncomingServerSettingsIntent(account: Account): PendingIntent
fun getEditOutgoingServerSettingsIntent(account: Account): PendingIntent
- fun createDeleteMessagePendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent
+ fun createDeleteMessagePendingIntent(messageReference: MessageReference): PendingIntent
- fun createDeleteAllPendingIntent(
- account: Account,
- messageReferences: List,
- notificationId: Int
- ): PendingIntent
+ fun createDeleteAllPendingIntent(account: Account, messageReferences: List): PendingIntent
- fun createArchiveMessagePendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent
+ fun createArchiveMessagePendingIntent(messageReference: MessageReference): PendingIntent
- fun createArchiveAllPendingIntent(
- account: Account,
- messageReferences: List,
- notificationId: Int
- ): PendingIntent
+ fun createArchiveAllPendingIntent(account: Account, messageReferences: List): PendingIntent
- fun createMarkMessageAsSpamPendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent
+ fun createMarkMessageAsSpamPendingIntent(messageReference: MessageReference): PendingIntent
}
diff --git a/app/core/src/main/java/com/fsck/k9/notification/SendFailedNotificationController.kt b/app/core/src/main/java/com/fsck/k9/notification/SendFailedNotificationController.kt
index 3dd80b75ed..2f82eabe11 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/SendFailedNotificationController.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/SendFailedNotificationController.kt
@@ -19,13 +19,9 @@ internal class SendFailedNotificationController(
val pendingIntent = account.outboxFolderId.let { outboxFolderId ->
if (outboxFolderId != null) {
- actionBuilder.createViewFolderPendingIntent(
- account, outboxFolderId, notificationId
- )
+ actionBuilder.createViewFolderPendingIntent(account, outboxFolderId)
} else {
- actionBuilder.createViewFolderListPendingIntent(
- account, notificationId
- )
+ actionBuilder.createViewFolderListPendingIntent(account)
}
}
diff --git a/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt b/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt
index 21553997b7..93e9456b67 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt
@@ -1,6 +1,5 @@
package com.fsck.k9.notification
-import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.WearableExtender
import com.fsck.k9.notification.NotificationChannelManager.ChannelType
@@ -35,8 +34,8 @@ internal class SingleMessageNotificationCreator(
.setContentText(content.subject)
.setSubText(baseNotificationData.accountName)
.setBigText(content.preview)
- .setContentIntent(createViewIntent(content, notificationId))
- .setDeleteIntent(createDismissIntent(content, notificationId))
+ .setContentIntent(actionCreator.createViewMessagePendingIntent(content.messageReference))
+ .setDeleteIntent(actionCreator.createDismissMessagePendingIntent(content.messageReference))
.setDeviceActions(singleNotificationData)
.setWearActions(singleNotificationData)
.setAppearance(singleNotificationData.isSilent, baseNotificationData.appearance)
@@ -57,14 +56,6 @@ internal class SingleMessageNotificationCreator(
setStyle(NotificationCompat.BigTextStyle().bigText(text))
}
- private fun createViewIntent(content: NotificationContent, notificationId: Int): PendingIntent {
- return actionCreator.createViewMessagePendingIntent(content.messageReference, notificationId)
- }
-
- private fun createDismissIntent(content: NotificationContent, notificationId: Int): PendingIntent {
- return actionCreator.createDismissMessagePendingIntent(content.messageReference, notificationId)
- }
-
private fun NotificationBuilder.setDeviceActions(notificationData: SingleNotificationData) = apply {
val actions = notificationData.actions
for (action in actions) {
@@ -81,8 +72,7 @@ internal class SingleMessageNotificationCreator(
val title = resourceProvider.actionReply()
val content = notificationData.content
val messageReference = content.messageReference
- val replyToMessagePendingIntent =
- actionCreator.createReplyPendingIntent(messageReference, notificationData.notificationId)
+ val replyToMessagePendingIntent = actionCreator.createReplyPendingIntent(messageReference)
addAction(icon, title, replyToMessagePendingIntent)
}
@@ -91,9 +81,8 @@ internal class SingleMessageNotificationCreator(
val icon = resourceProvider.iconMarkAsRead
val title = resourceProvider.actionMarkAsRead()
val content = notificationData.content
- val notificationId = notificationData.notificationId
val messageReference = content.messageReference
- val action = actionCreator.createMarkMessageAsReadPendingIntent(messageReference, notificationId)
+ val action = actionCreator.createMarkMessageAsReadPendingIntent(messageReference)
addAction(icon, title, action)
}
@@ -102,9 +91,8 @@ internal class SingleMessageNotificationCreator(
val icon = resourceProvider.iconDelete
val title = resourceProvider.actionDelete()
val content = notificationData.content
- val notificationId = notificationData.notificationId
val messageReference = content.messageReference
- val action = actionCreator.createDeleteMessagePendingIntent(messageReference, notificationId)
+ val action = actionCreator.createDeleteMessagePendingIntent(messageReference)
addAction(icon, title, action)
}
@@ -129,8 +117,7 @@ internal class SingleMessageNotificationCreator(
val icon = resourceProvider.wearIconReplyAll
val title = resourceProvider.actionReply()
val messageReference = notificationData.content.messageReference
- val notificationId = notificationData.notificationId
- val action = actionCreator.createReplyPendingIntent(messageReference, notificationId)
+ val action = actionCreator.createReplyPendingIntent(messageReference)
val replyAction = NotificationCompat.Action.Builder(icon, title, action).build()
addAction(replyAction)
@@ -140,8 +127,7 @@ internal class SingleMessageNotificationCreator(
val icon = resourceProvider.wearIconMarkAsRead
val title = resourceProvider.actionMarkAsRead()
val messageReference = notificationData.content.messageReference
- val notificationId = notificationData.notificationId
- val action = actionCreator.createMarkMessageAsReadPendingIntent(messageReference, notificationId)
+ val action = actionCreator.createMarkMessageAsReadPendingIntent(messageReference)
val markAsReadAction = NotificationCompat.Action.Builder(icon, title, action).build()
addAction(markAsReadAction)
@@ -151,8 +137,7 @@ internal class SingleMessageNotificationCreator(
val icon = resourceProvider.wearIconDelete
val title = resourceProvider.actionDelete()
val messageReference = notificationData.content.messageReference
- val notificationId = notificationData.notificationId
- val action = actionCreator.createDeleteMessagePendingIntent(messageReference, notificationId)
+ val action = actionCreator.createDeleteMessagePendingIntent(messageReference)
val deleteAction = NotificationCompat.Action.Builder(icon, title, action).build()
addAction(deleteAction)
@@ -162,8 +147,7 @@ internal class SingleMessageNotificationCreator(
val icon = resourceProvider.wearIconArchive
val title = resourceProvider.actionArchive()
val messageReference = notificationData.content.messageReference
- val notificationId = notificationData.notificationId
- val action = actionCreator.createArchiveMessagePendingIntent(messageReference, notificationId)
+ val action = actionCreator.createArchiveMessagePendingIntent(messageReference)
val archiveAction = NotificationCompat.Action.Builder(icon, title, action).build()
addAction(archiveAction)
@@ -173,8 +157,7 @@ internal class SingleMessageNotificationCreator(
val icon = resourceProvider.wearIconMarkAsSpam
val title = resourceProvider.actionMarkAsSpam()
val messageReference = notificationData.content.messageReference
- val notificationId = notificationData.notificationId
- val action = actionCreator.createMarkMessageAsSpamPendingIntent(messageReference, notificationId)
+ val action = actionCreator.createMarkMessageAsSpamPendingIntent(messageReference)
val spamAction = NotificationCompat.Action.Builder(icon, title, action).build()
addAction(spamAction)
diff --git a/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationCreator.kt b/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationCreator.kt
index eb55bc9eda..a1f06df7bc 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationCreator.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationCreator.kt
@@ -5,7 +5,6 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.WearableExtender
import com.fsck.k9.Account
import com.fsck.k9.notification.NotificationChannelManager.ChannelType
-import com.fsck.k9.notification.NotificationIds.getNewMailSummaryNotificationId
import timber.log.Timber
import androidx.core.app.NotificationCompat.Builder as NotificationBuilder
@@ -65,7 +64,7 @@ internal class SummaryNotificationCreator(
.setSubText(accountName)
.setInboxStyle(title, summary, notificationData.content)
.setContentIntent(createViewIntent(account, notificationData))
- .setDeleteIntent(createDismissIntent(account, notificationData.notificationId))
+ .setDeleteIntent(actionCreator.createDismissAllMessagesPendingIntent(account))
.setDeviceActions(account, notificationData)
.setWearActions(account, notificationData)
.setAppearance(notificationData.isSilent, baseNotificationData.appearance)
@@ -101,15 +100,7 @@ internal class SummaryNotificationCreator(
}
private fun createViewIntent(account: Account, notificationData: SummaryInboxNotificationData): PendingIntent {
- return actionCreator.createViewMessagesPendingIntent(
- account = account,
- messageReferences = notificationData.messageReferences,
- notificationId = notificationData.notificationId
- )
- }
-
- private fun createDismissIntent(account: Account, notificationId: Int): PendingIntent {
- return actionCreator.createDismissAllMessagesPendingIntent(account, notificationId)
+ return actionCreator.createViewMessagesPendingIntent(account, notificationData.messageReferences)
}
private fun NotificationBuilder.setDeviceActions(
@@ -131,9 +122,7 @@ internal class SummaryNotificationCreator(
val icon = resourceProvider.iconMarkAsRead
val title = resourceProvider.actionMarkAsRead()
val messageReferences = notificationData.messageReferences
- val notificationId = notificationData.notificationId
- val markAllAsReadPendingIntent =
- actionCreator.createMarkAllAsReadPendingIntent(account, messageReferences, notificationId)
+ val markAllAsReadPendingIntent = actionCreator.createMarkAllAsReadPendingIntent(account, messageReferences)
addAction(icon, title, markAllAsReadPendingIntent)
}
@@ -144,9 +133,8 @@ internal class SummaryNotificationCreator(
) {
val icon = resourceProvider.iconDelete
val title = resourceProvider.actionDelete()
- val notificationId = getNewMailSummaryNotificationId(account)
val messageReferences = notificationData.messageReferences
- val action = actionCreator.createDeleteAllPendingIntent(account, messageReferences, notificationId)
+ val action = actionCreator.createDeleteAllPendingIntent(account, messageReferences)
addAction(icon, title, action)
}
@@ -175,8 +163,7 @@ internal class SummaryNotificationCreator(
val icon = resourceProvider.wearIconMarkAsRead
val title = resourceProvider.actionMarkAllAsRead()
val messageReferences = notificationData.messageReferences
- val notificationId = getNewMailSummaryNotificationId(account)
- val action = actionCreator.createMarkAllAsReadPendingIntent(account, messageReferences, notificationId)
+ val action = actionCreator.createMarkAllAsReadPendingIntent(account, messageReferences)
val markAsReadAction = NotificationCompat.Action.Builder(icon, title, action).build()
addAction(markAsReadAction)
@@ -186,8 +173,7 @@ internal class SummaryNotificationCreator(
val icon = resourceProvider.wearIconDelete
val title = resourceProvider.actionDeleteAll()
val messageReferences = notificationData.messageReferences
- val notificationId = getNewMailSummaryNotificationId(account)
- val action = actionCreator.createDeleteAllPendingIntent(account, messageReferences, notificationId)
+ val action = actionCreator.createDeleteAllPendingIntent(account, messageReferences)
val deleteAction = NotificationCompat.Action.Builder(icon, title, action).build()
addAction(deleteAction)
@@ -197,8 +183,7 @@ internal class SummaryNotificationCreator(
val icon = resourceProvider.wearIconArchive
val title = resourceProvider.actionArchiveAll()
val messageReferences = notificationData.messageReferences
- val notificationId = getNewMailSummaryNotificationId(account)
- val action = actionCreator.createArchiveAllPendingIntent(account, messageReferences, notificationId)
+ val action = actionCreator.createArchiveAllPendingIntent(account, messageReferences)
val archiveAction = NotificationCompat.Action.Builder(icon, title, action).build()
addAction(archiveAction)
diff --git a/app/core/src/main/java/com/fsck/k9/notification/SyncNotificationController.kt b/app/core/src/main/java/com/fsck/k9/notification/SyncNotificationController.kt
index 3140fb813a..441d569833 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/SyncNotificationController.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/SyncNotificationController.kt
@@ -18,9 +18,7 @@ internal class SyncNotificationController(
val notificationId = NotificationIds.getFetchingMailNotificationId(account)
val outboxFolderId = account.outboxFolderId ?: error("Outbox folder not configured")
- val showMessageListPendingIntent = actionBuilder.createViewFolderPendingIntent(
- account, outboxFolderId, notificationId
- )
+ val showMessageListPendingIntent = actionBuilder.createViewFolderPendingIntent(account, outboxFolderId)
val notificationBuilder = notificationHelper
.createNotificationBuilder(account, NotificationChannelManager.ChannelType.MISCELLANEOUS)
@@ -53,9 +51,7 @@ internal class SyncNotificationController(
val text = accountName + resourceProvider.checkingMailSeparator() + folderName
val notificationId = NotificationIds.getFetchingMailNotificationId(account)
- val showMessageListPendingIntent = actionBuilder.createViewFolderPendingIntent(
- account, folderId, notificationId
- )
+ val showMessageListPendingIntent = actionBuilder.createViewFolderPendingIntent(account, folderId)
val notificationBuilder = notificationHelper
.createNotificationBuilder(account, NotificationChannelManager.ChannelType.MISCELLANEOUS)
diff --git a/app/core/src/test/java/com/fsck/k9/notification/SendFailedNotificationControllerTest.kt b/app/core/src/test/java/com/fsck/k9/notification/SendFailedNotificationControllerTest.kt
index 591fe1a176..e92089fc68 100644
--- a/app/core/src/test/java/com/fsck/k9/notification/SendFailedNotificationControllerTest.kt
+++ b/app/core/src/test/java/com/fsck/k9/notification/SendFailedNotificationControllerTest.kt
@@ -9,7 +9,6 @@ import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest
import com.fsck.k9.testing.MockHelper.mockBuilder
import org.junit.Test
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
@@ -88,8 +87,8 @@ class SendFailedNotificationControllerTest : RobolectricTest() {
private fun createActionBuilder(contentIntent: PendingIntent): NotificationActionCreator {
return mock {
- on { createViewFolderListPendingIntent(any(), anyInt()) } doReturn contentIntent
- on { createViewFolderPendingIntent(any(), anyLong(), anyInt()) } doReturn contentIntent
+ on { createViewFolderListPendingIntent(any()) } doReturn contentIntent
+ on { createViewFolderPendingIntent(any(), anyLong()) } doReturn contentIntent
}
}
}
diff --git a/app/core/src/test/java/com/fsck/k9/notification/SyncNotificationControllerTest.kt b/app/core/src/test/java/com/fsck/k9/notification/SyncNotificationControllerTest.kt
index cde4972259..18b5aa11cd 100644
--- a/app/core/src/test/java/com/fsck/k9/notification/SyncNotificationControllerTest.kt
+++ b/app/core/src/test/java/com/fsck/k9/notification/SyncNotificationControllerTest.kt
@@ -11,7 +11,6 @@ import com.fsck.k9.mailstore.LocalFolder
import com.fsck.k9.notification.NotificationIds.getFetchingMailNotificationId
import com.fsck.k9.testing.MockHelper.mockBuilder
import org.junit.Test
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
@@ -140,7 +139,7 @@ class SyncNotificationControllerTest : RobolectricTest() {
private fun createActionBuilder(contentIntent: PendingIntent): NotificationActionCreator {
return mock {
- on { createViewFolderPendingIntent(eq(account), anyLong(), anyInt()) } doReturn contentIntent
+ on { createViewFolderPendingIntent(eq(account), anyLong()) } doReturn contentIntent
}
}
diff --git a/app/k9mail/src/main/java/com/fsck/k9/notification/K9NotificationActionCreator.kt b/app/k9mail/src/main/java/com/fsck/k9/notification/K9NotificationActionCreator.kt
index 0da55f29a6..b08374a516 100644
--- a/app/k9mail/src/main/java/com/fsck/k9/notification/K9NotificationActionCreator.kt
+++ b/app/k9mail/src/main/java/com/fsck/k9/notification/K9NotificationActionCreator.kt
@@ -37,25 +37,21 @@ internal class K9NotificationActionCreator(
private val messageStoreManager: MessageStoreManager
) : NotificationActionCreator {
- override fun createViewMessagePendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent {
+ override fun createViewMessagePendingIntent(messageReference: MessageReference): PendingIntent {
val openInUnifiedInbox = K9.isShowUnifiedInbox && isIncludedInUnifiedInbox(messageReference)
val intent = createMessageViewIntent(messageReference, openInUnifiedInbox)
return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createViewFolderPendingIntent(account: Account, folderId: Long, notificationId: Int): PendingIntent {
+ override fun createViewFolderPendingIntent(account: Account, folderId: Long): PendingIntent {
val intent = createMessageListIntent(account, folderId)
return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
override fun createViewMessagesPendingIntent(
account: Account,
- messageReferences: List,
- notificationId: Int
+ messageReferences: List
): PendingIntent {
val folderIds = extractFolderIds(messageReferences)
@@ -70,39 +66,33 @@ internal class K9NotificationActionCreator(
return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createViewFolderListPendingIntent(account: Account, notificationId: Int): PendingIntent {
+ override fun createViewFolderListPendingIntent(account: Account): PendingIntent {
val intent = createMessageListIntent(account)
return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createDismissAllMessagesPendingIntent(account: Account, notificationId: Int): PendingIntent {
+ override fun createDismissAllMessagesPendingIntent(account: Account): PendingIntent {
val intent = NotificationActionService.createDismissAllMessagesIntent(context, account).apply {
data = Uri.parse("data:,dismissAll/${account.uuid}/${System.currentTimeMillis()}")
}
return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createDismissMessagePendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent {
+ override fun createDismissMessagePendingIntent(messageReference: MessageReference): PendingIntent {
val intent = NotificationActionService.createDismissMessageIntent(context, messageReference).apply {
data = Uri.parse("data:,dismiss/${messageReference.toIdentityString()}")
}
return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createReplyPendingIntent(messageReference: MessageReference, notificationId: Int): PendingIntent {
+ override fun createReplyPendingIntent(messageReference: MessageReference): PendingIntent {
val intent = MessageActions.getActionReplyIntent(context, messageReference).apply {
data = Uri.parse("data:,reply/${messageReference.toIdentityString()}")
}
return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createMarkMessageAsReadPendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent {
+ override fun createMarkMessageAsReadPendingIntent(messageReference: MessageReference): PendingIntent {
val intent = NotificationActionService.createMarkMessageAsReadIntent(context, messageReference).apply {
data = Uri.parse("data:,markAsRead/${messageReference.toIdentityString()}")
}
@@ -111,8 +101,7 @@ internal class K9NotificationActionCreator(
override fun createMarkAllAsReadPendingIntent(
account: Account,
- messageReferences: List,
- notificationId: Int
+ messageReferences: List
): PendingIntent {
val accountUuid = account.uuid
val intent = NotificationActionService.createMarkAllAsReadIntent(context, accountUuid, messageReferences).apply {
@@ -131,31 +120,22 @@ internal class K9NotificationActionCreator(
return PendingIntent.getActivity(context, account.accountNumber, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createDeleteMessagePendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent {
+ override fun createDeleteMessagePendingIntent(messageReference: MessageReference): PendingIntent {
return if (K9.isConfirmDeleteFromNotification) {
- createDeleteConfirmationPendingIntent(messageReference, 0)
+ createDeleteConfirmationPendingIntent(messageReference)
} else {
- createDeleteServicePendingIntent(messageReference, 0)
+ createDeleteServicePendingIntent(messageReference)
}
}
- private fun createDeleteServicePendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent {
+ private fun createDeleteServicePendingIntent(messageReference: MessageReference): PendingIntent {
val intent = NotificationActionService.createDeleteMessageIntent(context, messageReference).apply {
data = Uri.parse("data:,delete/${messageReference.toIdentityString()}")
}
return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- private fun createDeleteConfirmationPendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent {
+ private fun createDeleteConfirmationPendingIntent(messageReference: MessageReference): PendingIntent {
val intent = DeleteConfirmationActivity.getIntent(context, messageReference).apply {
data = Uri.parse("data:,deleteConfirmation/${messageReference.toIdentityString()}")
}
@@ -164,20 +144,16 @@ internal class K9NotificationActionCreator(
override fun createDeleteAllPendingIntent(
account: Account,
- messageReferences: List,
- notificationId: Int
+ messageReferences: List
): PendingIntent {
return if (K9.isConfirmDeleteFromNotification) {
- getDeleteAllConfirmationPendingIntent(messageReferences, 0)
+ getDeleteAllConfirmationPendingIntent(messageReferences)
} else {
- getDeleteAllServicePendingIntent(account, messageReferences, 0)
+ getDeleteAllServicePendingIntent(account, messageReferences)
}
}
- private fun getDeleteAllConfirmationPendingIntent(
- messageReferences: List,
- notificationId: Int
- ): PendingIntent {
+ private fun getDeleteAllConfirmationPendingIntent(messageReferences: List): PendingIntent {
val intent = DeleteConfirmationActivity.getIntent(context, messageReferences).apply {
data = Uri.parse("data:,deleteAllConfirmation/${System.currentTimeMillis()}")
}
@@ -186,8 +162,7 @@ internal class K9NotificationActionCreator(
private fun getDeleteAllServicePendingIntent(
account: Account,
- messageReferences: List,
- notificationId: Int
+ messageReferences: List
): PendingIntent {
val accountUuid = account.uuid
val intent = NotificationActionService.createDeleteAllMessagesIntent(context, accountUuid, messageReferences).apply {
@@ -196,10 +171,7 @@ internal class K9NotificationActionCreator(
return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createArchiveMessagePendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent {
+ override fun createArchiveMessagePendingIntent(messageReference: MessageReference): PendingIntent {
val intent = NotificationActionService.createArchiveMessageIntent(context, messageReference).apply {
data = Uri.parse("data:,archive/${messageReference.toIdentityString()}")
}
@@ -208,8 +180,7 @@ internal class K9NotificationActionCreator(
override fun createArchiveAllPendingIntent(
account: Account,
- messageReferences: List,
- notificationId: Int
+ messageReferences: List
): PendingIntent {
val intent = NotificationActionService.createArchiveAllIntent(context, account, messageReferences).apply {
data = Uri.parse("data:,archiveAll/${account.uuid}/${System.currentTimeMillis()}")
@@ -217,10 +188,7 @@ internal class K9NotificationActionCreator(
return PendingIntent.getService(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
- override fun createMarkMessageAsSpamPendingIntent(
- messageReference: MessageReference,
- notificationId: Int
- ): PendingIntent {
+ override fun createMarkMessageAsSpamPendingIntent(messageReference: MessageReference): PendingIntent {
val intent = NotificationActionService.createMarkMessageAsSpamIntent(context, messageReference).apply {
data = Uri.parse("data:,spam/${messageReference.toIdentityString()}")
}
--
GitLab
From f60f573f0779aa576092f1db21e3281a406d36e7 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 27 Jul 2022 00:21:07 +0200
Subject: [PATCH 30/47] Don't auto-cancel new message notifications
On one of my test devices (Android 12), tapping a single message notification opens the message view, which leads to the notification being removed. If there's an inactive notification it will be promoted to an active notification and use the notification ID of the notification that was just removed. Due to auto-cancel being used, the delete intent of the first notification is then triggered. However, the system seems to use the notification ID to retrieve the delete intent. Because it will fetch the delete intent from the new notification, not the old one. (I made sure to check that it's not a PendingIntent reuse issue)
Since we remove the notification ourselves, we can simply stop using the (apparently buggy) auto-cancel mechanism.
---
.../com/fsck/k9/notification/SingleMessageNotificationCreator.kt | 1 -
.../java/com/fsck/k9/notification/SummaryNotificationCreator.kt | 1 -
2 files changed, 2 deletions(-)
diff --git a/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt b/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt
index 93e9456b67..d6d5e3626a 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt
@@ -23,7 +23,6 @@ internal class SingleMessageNotificationCreator(
val notification = notificationHelper.createNotificationBuilder(account, ChannelType.MESSAGES)
.setCategory(NotificationCompat.CATEGORY_EMAIL)
- .setAutoCancel(true)
.setGroup(baseNotificationData.groupKey)
.setGroupSummary(isGroupSummary)
.setSmallIcon(resourceProvider.iconNewMail)
diff --git a/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationCreator.kt b/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationCreator.kt
index a1f06df7bc..c780415f11 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationCreator.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationCreator.kt
@@ -52,7 +52,6 @@ internal class SummaryNotificationCreator(
val notification = notificationHelper.createNotificationBuilder(account, ChannelType.MESSAGES)
.setCategory(NotificationCompat.CATEGORY_EMAIL)
- .setAutoCancel(true)
.setGroup(baseNotificationData.groupKey)
.setGroupSummary(true)
.setSmallIcon(resourceProvider.iconNewMail)
--
GitLab
From f16522cdc43e1dab7822ef53e455abcde996a847 Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 28 Jul 2022 15:46:39 +0200
Subject: [PATCH 31/47] Remove unused code
---
.../ui/messageview/MessageContainerView.java | 13 -------------
.../java/com/fsck/k9/view/MessageWebView.java | 19 -------------------
app/ui/legacy/src/main/res/values/strings.xml | 2 --
3 files changed, 34 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
index 3efcff567c..1b38cfe181 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
@@ -16,7 +16,6 @@ import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -500,18 +499,6 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
}
}
- public void zoom(KeyEvent event) {
- if (event.isShiftPressed()) {
- mMessageContentView.zoomIn();
- } else {
- mMessageContentView.zoomOut();
- }
- }
-
- public void beginSelectingText() {
- mMessageContentView.emulateShiftHeld();
- }
-
public void resetView() {
setLoadPictures(false);
mAttachments.removeAllViews();
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.java b/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.java
index 206785237f..023bfb1236 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.java
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.java
@@ -7,14 +7,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import timber.log.Timber;
-import android.view.KeyEvent;
import android.webkit.WebSettings;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebSettings.RenderPriority;
import android.webkit.WebView;
-import android.widget.Toast;
-import com.fsck.k9.ui.R;
import com.fsck.k9.mailstore.AttachmentResolver;
@@ -134,22 +131,6 @@ public class MessageWebView extends WebView {
resumeTimers();
}
- /*
- * Emulate the shift key being pressed to trigger the text selection mode
- * of a WebView.
- */
- public void emulateShiftHeld() {
- try {
-
- KeyEvent shiftPressEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
- KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0);
- shiftPressEvent.dispatch(this, null, null);
- Toast.makeText(getContext() , R.string.select_text_now, Toast.LENGTH_SHORT).show();
- } catch (Exception e) {
- Timber.e(e, "Exception in emulateShiftHeld()");
- }
- }
-
public interface OnPageFinishedListener {
void onPageFinished();
}
diff --git a/app/ui/legacy/src/main/res/values/strings.xml b/app/ui/legacy/src/main/res/values/strings.xml
index 8e2285d265..d6fb93cff4 100644
--- a/app/ui/legacy/src/main/res/values/strings.xml
+++ b/app/ui/legacy/src/main/res/values/strings.xml
@@ -887,8 +887,6 @@ Please submit bug reports, contribute new features and ask questions at
Discard message?
Are you sure you want to discard this message?
- Select text to copy.
-
Clear local messages?
This will remove all local messages from the folder. No messages will be deleted from the server.
Clear messages
--
GitLab
From b35e6ac269755c3d056786fa23fab2317da551dc Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 28 Jul 2022 16:05:32 +0200
Subject: [PATCH 32/47] Rename .java to .kt
---
.../com/fsck/k9/view/{MessageWebView.java => MessageWebView.kt} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename app/ui/legacy/src/main/java/com/fsck/k9/view/{MessageWebView.java => MessageWebView.kt} (100%)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.java b/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
similarity index 100%
rename from app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.java
rename to app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
--
GitLab
From b05309f316a5fa6a5dd212147e0e1e5dcec277f7 Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 28 Jul 2022 16:05:32 +0200
Subject: [PATCH 33/47] Convert `MessageWebView` to Kotlin
---
.../java/com/fsck/k9/view/MessageWebView.kt | 178 +++++++-----------
1 file changed, 70 insertions(+), 108 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt b/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
index 023bfb1236..946cd704cf 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
@@ -1,137 +1,99 @@
-package com.fsck.k9.view;
-
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import android.util.AttributeSet;
-import timber.log.Timber;
-import android.webkit.WebSettings;
-import android.webkit.WebSettings.LayoutAlgorithm;
-import android.webkit.WebSettings.RenderPriority;
-import android.webkit.WebView;
-
-import com.fsck.k9.mailstore.AttachmentResolver;
-
-
-public class MessageWebView extends WebView {
-
- public MessageWebView(Context context) {
- super(context);
- }
-
- public MessageWebView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public MessageWebView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- /**
- * Configure a web view to load or not load network data. A true setting here means that
- * network data will be blocked.
- * @param shouldBlockNetworkData True if network data should be blocked, false to allow network data.
- */
- public void blockNetworkData(final boolean shouldBlockNetworkData) {
- /*
- * Block network loads.
- *
- * Images with content: URIs will not be blocked, nor
- * will network images that are already in the WebView cache.
- *
- */
+package com.fsck.k9.view
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.util.AttributeSet
+import android.webkit.WebSettings.LayoutAlgorithm
+import android.webkit.WebSettings.RenderPriority
+import android.webkit.WebView
+import com.fsck.k9.mailstore.AttachmentResolver
+import timber.log.Timber
+
+class MessageWebView : WebView {
+ constructor(context: Context) : super(context)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+ constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun blockNetworkData(shouldBlockNetworkData: Boolean) {
+ // Images with content: URIs will not be blocked, nor will network images that are already in the WebView cache.
try {
- getSettings().setBlockNetworkLoads(shouldBlockNetworkData);
- } catch (SecurityException e) {
- Timber.e(e, "Failed to unblock network loads. Missing INTERNET permission?");
+ settings.blockNetworkLoads = shouldBlockNetworkData
+ } catch (e: SecurityException) {
+ Timber.e(e, "Failed to unblock network loads. Missing INTERNET permission?")
}
}
+ fun configure(config: WebViewConfig) {
+ isVerticalScrollBarEnabled = true
+ setVerticalScrollbarOverlay(true)
+ scrollBarStyle = SCROLLBARS_INSIDE_OVERLAY
+ isLongClickable = true
- /**
- * Configure a {@link WebView} to display a Message. This method takes into account a user's
- * preferences when configuring the view. This message is used to view a message and to display a message being
- * replied to.
- */
- public void configure(WebViewConfig config) {
- this.setVerticalScrollBarEnabled(true);
- this.setVerticalScrollbarOverlay(true);
- this.setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY);
- this.setLongClickable(true);
-
- if (config.getUseDarkMode()) {
- // Black theme should get a black webview background
- // we'll set the background of the messages on load
- this.setBackgroundColor(0xff000000);
+ if (config.useDarkMode) {
+ setBackgroundColor(0xff000000L.toInt())
}
- final WebSettings webSettings = this.getSettings();
+ with(settings) {
+ setSupportZoom(true)
+ builtInZoomControls = true
+ useWideViewPort = true
- /* TODO this might improve rendering smoothness when webview is animated into view
- if (VERSION.SDK_INT >= VERSION_CODES.M) {
- webSettings.setOffscreenPreRaster(true);
- }
- */
-
- webSettings.setSupportZoom(true);
- webSettings.setBuiltInZoomControls(true);
- webSettings.setUseWideViewPort(true);
- if (config.getAutoFitWidth()) {
- webSettings.setLoadWithOverviewMode(true);
- }
+ if (config.autoFitWidth) {
+ loadWithOverviewMode = true
+ }
- disableDisplayZoomControls();
+ disableDisplayZoomControls()
- webSettings.setJavaScriptEnabled(false);
- webSettings.setLoadsImagesAutomatically(true);
- webSettings.setRenderPriority(RenderPriority.HIGH);
+ javaScriptEnabled = false
+ loadsImagesAutomatically = true
+ setRenderPriority(RenderPriority.HIGH)
- // TODO: Review alternatives. NARROW_COLUMNS is deprecated on KITKAT
- webSettings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);
+ // TODO: Review alternatives. NARROW_COLUMNS is deprecated on KITKAT
+ layoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS
- setOverScrollMode(OVER_SCROLL_NEVER);
+ overScrollMode = OVER_SCROLL_NEVER
- webSettings.setTextZoom(config.getTextZoom());
+ textZoom = config.textZoom
+ }
- // Disable network images by default. This is overridden by preferences.
- blockNetworkData(true);
+ // Disable network images by default. This is overridden by preferences.
+ blockNetworkData(true)
}
- /**
- * Disable on-screen zoom controls on devices that support zooming via pinch-to-zoom.
- */
- private void disableDisplayZoomControls() {
- PackageManager pm = getContext().getPackageManager();
- boolean supportsMultiTouch =
- pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) ||
- pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT);
+ private fun disableDisplayZoomControls() {
+ val packageManager = context.packageManager
+ val supportsMultiTouch = packageManager.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) ||
+ packageManager.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT)
- getSettings().setDisplayZoomControls(!supportsMultiTouch);
+ settings.displayZoomControls = !supportsMultiTouch
}
- public void displayHtmlContentWithInlineAttachments(@NonNull String htmlText,
- @Nullable AttachmentResolver attachmentResolver, @Nullable OnPageFinishedListener onPageFinishedListener) {
- setWebViewClient(attachmentResolver, onPageFinishedListener);
- setHtmlContent(htmlText);
+ fun displayHtmlContentWithInlineAttachments(
+ htmlText: String,
+ attachmentResolver: AttachmentResolver?,
+ onPageFinishedListener: OnPageFinishedListener?
+ ) {
+ setWebViewClient(attachmentResolver, onPageFinishedListener)
+ setHtmlContent(htmlText)
}
- private void setWebViewClient(@Nullable AttachmentResolver attachmentResolver,
- @Nullable OnPageFinishedListener onPageFinishedListener) {
- K9WebViewClient webViewClient = K9WebViewClient.newInstance(attachmentResolver);
+ private fun setWebViewClient(
+ attachmentResolver: AttachmentResolver?,
+ onPageFinishedListener: OnPageFinishedListener?
+ ) {
+ val webViewClient = K9WebViewClient.newInstance(attachmentResolver)
if (onPageFinishedListener != null) {
- webViewClient.setOnPageFinishedListener(onPageFinishedListener);
+ webViewClient.setOnPageFinishedListener(onPageFinishedListener)
}
- setWebViewClient(webViewClient);
+ setWebViewClient(webViewClient)
}
- private void setHtmlContent(@NonNull String htmlText) {
- loadDataWithBaseURL("about:blank", htmlText, "text/html", "utf-8", null);
- resumeTimers();
+ private fun setHtmlContent(htmlText: String) {
+ loadDataWithBaseURL("about:blank", htmlText, "text/html", "utf-8", null)
+ resumeTimers()
}
- public interface OnPageFinishedListener {
- void onPageFinished();
+ interface OnPageFinishedListener {
+ fun onPageFinished()
}
}
--
GitLab
From 66ebddbd1a44548bceefef063820d40074c1a60f Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 28 Jul 2022 17:41:16 +0200
Subject: [PATCH 34/47] Remove unused code
---
.../ui/messageview/MessageContainerView.java | 22 -------------------
1 file changed, 22 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
index 1b38cfe181..83d4129c4d 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
@@ -74,7 +74,6 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
private View unsignedTextContainer;
private View unsignedTextDivider;
private TextView unsignedText;
- private View mAttachmentsContainer;
private boolean showingPictures;
private LayoutInflater mInflater;
@@ -98,7 +97,6 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
mMessageContentView.setOnCreateContextMenuListener(this);
mMessageContentView.setVisibility(View.VISIBLE);
- mAttachmentsContainer = findViewById(R.id.attachments_container);
mAttachments = findViewById(R.id.attachments);
unsignedTextContainer = findViewById(R.id.message_unsigned_container);
@@ -387,18 +385,6 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
refreshDisplayedContent();
}
- public void enableAttachmentButtons() {
- for (AttachmentView attachmentView : attachmentViewMap.values()) {
- attachmentView.enableButtons();
- }
- }
-
- public void disableAttachmentButtons() {
- for (AttachmentView attachmentView : attachmentViewMap.values()) {
- attachmentView.disableButtons();
- }
- }
-
public void displayMessageViewContainer(MessageViewInfo messageViewInfo,
final OnRenderingFinishedListener onRenderingFinishedListener, boolean loadPictures,
boolean hideUnsignedTextDivider, AttachmentViewCallback attachmentCallback) {
@@ -516,14 +502,6 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
clearDisplayedContent();
}
- public void enableAttachmentButtons(AttachmentViewInfo attachment) {
- getAttachmentView(attachment).enableButtons();
- }
-
- public void disableAttachmentButtons(AttachmentViewInfo attachment) {
- getAttachmentView(attachment).disableButtons();
- }
-
public void refreshAttachmentThumbnail(AttachmentViewInfo attachment) {
getAttachmentView(attachment).refreshThumbnail();
}
--
GitLab
From ec80646b6c386605437b54a7abc1fcd42b3567c0 Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 28 Jul 2022 18:02:22 +0200
Subject: [PATCH 35/47] Simplify `message_container` layout
---
.../k9/ui/messageview/MessageContainerView.java | 15 ++++++++-------
.../src/main/res/layout/message_container.xml | 12 ++----------
2 files changed, 10 insertions(+), 17 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
index 83d4129c4d..222fc57e61 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
@@ -22,6 +22,7 @@ import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebView.HitTestResult;
import android.widget.LinearLayout;
@@ -70,7 +71,7 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
private final ClipboardManager clipboardManager = DI.get(ClipboardManager.class);
private MessageWebView mMessageContentView;
- private LinearLayout mAttachments;
+ private ViewGroup attachmentsContainer;
private View unsignedTextContainer;
private View unsignedTextDivider;
private TextView unsignedText;
@@ -97,7 +98,7 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
mMessageContentView.setOnCreateContextMenuListener(this);
mMessageContentView.setVisibility(View.VISIBLE);
- mAttachments = findViewById(R.id.attachments);
+ attachmentsContainer = findViewById(R.id.attachments_container);
unsignedTextContainer = findViewById(R.id.message_unsigned_container);
unsignedTextDivider = findViewById(R.id.message_unsigned_divider);
@@ -458,12 +459,12 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
}
AttachmentView view =
- (AttachmentView) mInflater.inflate(R.layout.message_view_attachment, mAttachments, false);
+ (AttachmentView) mInflater.inflate(R.layout.message_view_attachment, attachmentsContainer, false);
view.setCallback(attachmentCallback);
view.setAttachment(attachment);
attachmentViewMap.put(attachment, view);
- mAttachments.addView(view);
+ attachmentsContainer.addView(view);
}
}
@@ -475,19 +476,19 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
}
LockedAttachmentView view = (LockedAttachmentView) mInflater
- .inflate(R.layout.message_view_attachment_locked, mAttachments, false);
+ .inflate(R.layout.message_view_attachment_locked, attachmentsContainer, false);
view.setCallback(attachmentCallback);
view.setAttachment(attachment);
// attachments.put(attachment, view);
- mAttachments.addView(view);
+ attachmentsContainer.addView(view);
}
}
}
public void resetView() {
setLoadPictures(false);
- mAttachments.removeAllViews();
+ attachmentsContainer.removeAllViews();
currentHtmlText = null;
currentAttachmentResolver = null;
diff --git a/app/ui/legacy/src/main/res/layout/message_container.xml b/app/ui/legacy/src/main/res/layout/message_container.xml
index fb5c072b1c..3898c4a3d3 100644
--- a/app/ui/legacy/src/main/res/layout/message_container.xml
+++ b/app/ui/legacy/src/main/res/layout/message_container.xml
@@ -68,16 +68,8 @@
-
-
-
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
--
GitLab
From 87dbcfecc56306d21a9271156c2fc8211176c3de Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 28 Jul 2022 18:44:34 +0200
Subject: [PATCH 36/47] Rename .java to .kt
---
.../{MessageContainerView.java => MessageContainerView.kt} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/{MessageContainerView.java => MessageContainerView.kt} (100%)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt
similarity index 100%
rename from app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.java
rename to app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt
--
GitLab
From bfdc0eb04c8e9296e502c7b23ee7913da5a1cf6f Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 28 Jul 2022 18:44:34 +0200
Subject: [PATCH 37/47] Convert `MessageContainerView` to Kotlin
---
.../messageview/AttachmentViewCallback.java | 2 +-
.../k9/ui/messageview/MessageContainerView.kt | 853 +++++++++---------
.../java/com/fsck/k9/view/MessageWebView.kt | 2 +-
3 files changed, 439 insertions(+), 418 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentViewCallback.java b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentViewCallback.java
index 43c2e40e6d..e78fe3a09d 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentViewCallback.java
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/AttachmentViewCallback.java
@@ -4,7 +4,7 @@ package com.fsck.k9.ui.messageview;
import com.fsck.k9.mailstore.AttachmentViewInfo;
-interface AttachmentViewCallback {
+public interface AttachmentViewCallback {
void onViewAttachment(AttachmentViewInfo attachment);
void onSaveAttachment(AttachmentViewInfo attachment);
}
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt
index 222fc57e61..887c7b559b 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt
@@ -1,497 +1,502 @@
-package com.fsck.k9.ui.messageview;
-
-
-import java.util.HashMap;
-import java.util.Map;
-
-import android.app.DownloadManager;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Message;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.view.View;
-import android.view.View.OnCreateContextMenuListener;
-import android.view.ViewGroup;
-import android.webkit.WebView;
-import android.webkit.WebView.HitTestResult;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.core.app.ShareCompat;
-import com.fsck.k9.DI;
-import com.fsck.k9.message.html.DisplayHtml;
-import com.fsck.k9.ui.R;
-import com.fsck.k9.helper.ClipboardManager;
-import com.fsck.k9.helper.Contacts;
-import com.fsck.k9.helper.Utility;
-import com.fsck.k9.mail.Address;
-import com.fsck.k9.mailstore.AttachmentResolver;
-import com.fsck.k9.mailstore.AttachmentViewInfo;
-import com.fsck.k9.mailstore.MessageViewInfo;
-import com.fsck.k9.ui.helper.DisplayHtmlUiFactory;
-import com.fsck.k9.view.MessageWebView;
-import com.fsck.k9.view.MessageWebView.OnPageFinishedListener;
-import com.fsck.k9.view.WebViewConfigProvider;
-
-import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
-
-
-public class MessageContainerView extends LinearLayout implements OnCreateContextMenuListener {
- private static final int MENU_ITEM_LINK_VIEW = Menu.FIRST;
- private static final int MENU_ITEM_LINK_SHARE = Menu.FIRST + 1;
- private static final int MENU_ITEM_LINK_COPY = Menu.FIRST + 2;
- private static final int MENU_ITEM_LINK_TEXT_COPY = Menu.FIRST + 3;
-
- private static final int MENU_ITEM_IMAGE_VIEW = Menu.FIRST;
- private static final int MENU_ITEM_IMAGE_SAVE = Menu.FIRST + 1;
- private static final int MENU_ITEM_IMAGE_COPY = Menu.FIRST + 2;
-
- private static final int MENU_ITEM_PHONE_CALL = Menu.FIRST;
- private static final int MENU_ITEM_PHONE_SAVE = Menu.FIRST + 1;
- private static final int MENU_ITEM_PHONE_COPY = Menu.FIRST + 2;
-
- private static final int MENU_ITEM_EMAIL_SEND = Menu.FIRST;
- private static final int MENU_ITEM_EMAIL_SAVE = Menu.FIRST + 1;
- private static final int MENU_ITEM_EMAIL_COPY = Menu.FIRST + 2;
-
- private final DisplayHtml displayHtml = DI.get(DisplayHtmlUiFactory.class).createForMessageView();
- private final WebViewConfigProvider webViewConfigProvider = DI.get(WebViewConfigProvider.class);
- private final ClipboardManager clipboardManager = DI.get(ClipboardManager.class);
-
- private MessageWebView mMessageContentView;
- private ViewGroup attachmentsContainer;
- private View unsignedTextContainer;
- private View unsignedTextDivider;
- private TextView unsignedText;
-
- private boolean showingPictures;
- private LayoutInflater mInflater;
- private AttachmentViewCallback attachmentCallback;
- private Map attachmentViewMap = new HashMap<>();
- private Map attachments = new HashMap<>();
- private boolean hasHiddenExternalImages;
-
- private String currentHtmlText;
- private AttachmentResolver currentAttachmentResolver;
-
-
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
-
- mMessageContentView = findViewById(R.id.message_content);
- if (!isInEditMode()) {
- mMessageContentView.configure(webViewConfigProvider.createForMessageView());
- }
- mMessageContentView.setOnCreateContextMenuListener(this);
- mMessageContentView.setVisibility(View.VISIBLE);
-
- attachmentsContainer = findViewById(R.id.attachments_container);
-
- unsignedTextContainer = findViewById(R.id.message_unsigned_container);
- unsignedTextDivider = findViewById(R.id.message_unsigned_divider);
- unsignedText = findViewById(R.id.message_unsigned_text);
-
- showingPictures = false;
-
- Context context = getContext();
- mInflater = LayoutInflater.from(context);
+package com.fsck.k9.ui.messageview
+
+import android.app.DownloadManager
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.util.AttributeSet
+import android.view.ContextMenu
+import android.view.ContextMenu.ContextMenuInfo
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.View.OnCreateContextMenuListener
+import android.view.ViewGroup
+import android.webkit.WebView
+import android.webkit.WebView.HitTestResult
+import android.widget.LinearLayout
+import android.widget.TextView
+import android.widget.Toast
+import androidx.core.app.ShareCompat.IntentBuilder
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import com.fsck.k9.helper.ClipboardManager
+import com.fsck.k9.helper.Contacts
+import com.fsck.k9.helper.Utility
+import com.fsck.k9.mail.Address
+import com.fsck.k9.mailstore.AttachmentResolver
+import com.fsck.k9.mailstore.AttachmentViewInfo
+import com.fsck.k9.mailstore.MessageViewInfo
+import com.fsck.k9.ui.R
+import com.fsck.k9.ui.helper.DisplayHtmlUiFactory
+import com.fsck.k9.view.MessageWebView
+import com.fsck.k9.view.MessageWebView.OnPageFinishedListener
+import com.fsck.k9.view.WebViewConfigProvider
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.get
+import org.koin.core.component.inject
+
+class MessageContainerView(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs),
+ OnCreateContextMenuListener,
+ KoinComponent {
+
+ private val displayHtml by lazy(mode = LazyThreadSafetyMode.NONE) {
+ get().createForMessageView()
}
+ private val webViewConfigProvider: WebViewConfigProvider by inject()
+ private val clipboardManager: ClipboardManager by inject()
+ private val linkTextHandler: LinkTextHandler by inject()
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- super.onCreateContextMenu(menu);
+ private lateinit var layoutInflater: LayoutInflater
- WebView webview = (WebView) v;
- WebView.HitTestResult result = webview.getHitTestResult();
+ private lateinit var messageContentView: MessageWebView
+ private lateinit var attachmentsContainer: ViewGroup
+ private lateinit var unsignedTextContainer: View
+ private lateinit var unsignedTextDivider: View
+ private lateinit var unsignedText: TextView
- if (result == null) {
- return;
- }
-
- int type = result.getType();
- Context context = getContext();
-
- switch (type) {
- case HitTestResult.SRC_ANCHOR_TYPE: {
- final String url = result.getExtra();
- OnMenuItemClickListener listener = new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_ITEM_LINK_VIEW: {
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
- startActivityIfAvailable(getContext(), intent);
- break;
- }
- case MENU_ITEM_LINK_SHARE: {
- new ShareCompat.IntentBuilder(getContext())
- .setType("text/plain")
- .setText(url)
- .startChooser();
- break;
- }
- case MENU_ITEM_LINK_COPY: {
- String label = getContext().getString(
- R.string.webview_contextmenu_link_clipboard_label);
- clipboardManager.setText(label, url);
- break;
- }
- case MENU_ITEM_LINK_TEXT_COPY: {
- LinkTextHandler linkTextHandler = DI.get(LinkTextHandler.class);
- Message message = linkTextHandler.obtainMessage();
- webview.requestFocusNodeHref(message);
- break;
- }
- }
- return true;
- }
- };
+ private var isShowingPictures = false
+ private var currentHtmlText: String? = null
+ private val attachmentViewMap = mutableMapOf()
+ private val attachments = mutableMapOf()
+ private var attachmentCallback: AttachmentViewCallback? = null
+ private var currentAttachmentResolver: AttachmentResolver? = null
- menu.setHeaderTitle(url);
+ @get:JvmName("hasHiddenExternalImages")
+ var hasHiddenExternalImages = false
+ private set
- menu.add(Menu.NONE, MENU_ITEM_LINK_VIEW, 0,
- context.getString(R.string.webview_contextmenu_link_view_action))
- .setOnMenuItemClickListener(listener);
+ public override fun onFinishInflate() {
+ super.onFinishInflate()
- menu.add(Menu.NONE, MENU_ITEM_LINK_SHARE, 1,
- context.getString(R.string.webview_contextmenu_link_share_action))
- .setOnMenuItemClickListener(listener);
+ layoutInflater = LayoutInflater.from(context)
- menu.add(Menu.NONE, MENU_ITEM_LINK_COPY, 2,
- context.getString(R.string.webview_contextmenu_link_copy_action))
- .setOnMenuItemClickListener(listener);
+ messageContentView = findViewById(R.id.message_content).apply {
+ if (!isInEditMode) {
+ configure(webViewConfigProvider.createForMessageView())
+ }
- menu.add(Menu.NONE, MENU_ITEM_LINK_TEXT_COPY, 3,
- context.getString(R.string.webview_contextmenu_link_text_copy_action))
- .setOnMenuItemClickListener(listener);
+ setOnCreateContextMenuListener(this@MessageContainerView)
+ visibility = VISIBLE
+ }
- break;
- }
- case HitTestResult.IMAGE_TYPE:
- case HitTestResult.SRC_IMAGE_ANCHOR_TYPE: {
- final Uri uri = Uri.parse(result.getExtra());
- if (uri == null) {
- return;
- }
-
- final AttachmentViewInfo attachmentViewInfo = getAttachmentViewInfoIfCidUri(uri);
- final boolean inlineImage = attachmentViewInfo != null;
-
- OnMenuItemClickListener listener = new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_ITEM_IMAGE_VIEW: {
- if (inlineImage) {
- attachmentCallback.onViewAttachment(attachmentViewInfo);
- } else {
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- startActivityIfAvailable(getContext(), intent);
- }
- break;
- }
- case MENU_ITEM_IMAGE_SAVE: {
- if (inlineImage) {
- attachmentCallback.onSaveAttachment(attachmentViewInfo);
- } else {
- downloadImage(uri);
- }
- break;
- }
- case MENU_ITEM_IMAGE_COPY: {
- String label = getContext().getString(
- R.string.webview_contextmenu_image_clipboard_label);
- clipboardManager.setText(label, uri.toString());
- break;
- }
- }
- return true;
- }
- };
+ attachmentsContainer = findViewById(R.id.attachments_container)
+ unsignedTextContainer = findViewById(R.id.message_unsigned_container)
+ unsignedTextDivider = findViewById(R.id.message_unsigned_divider)
+ unsignedText = findViewById(R.id.message_unsigned_text)
+ }
- menu.setHeaderTitle(inlineImage ?
- context.getString(R.string.webview_contextmenu_image_title) : uri.toString());
+ override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo) {
+ super.onCreateContextMenu(menu)
- menu.add(Menu.NONE, MENU_ITEM_IMAGE_VIEW, 0,
- context.getString(R.string.webview_contextmenu_image_view_action))
- .setOnMenuItemClickListener(listener);
+ val webView = view as WebView
+ val hitTestResult = webView.hitTestResult
- menu.add(Menu.NONE, MENU_ITEM_IMAGE_SAVE, 1,
- inlineImage ?
- context.getString(R.string.webview_contextmenu_image_save_action) :
- context.getString(R.string.webview_contextmenu_image_download_action))
- .setOnMenuItemClickListener(listener);
+ when (hitTestResult.type) {
+ HitTestResult.SRC_ANCHOR_TYPE -> {
+ createLinkMenu(menu, webView, linkUrl = hitTestResult.extra)
+ }
+ HitTestResult.IMAGE_TYPE, HitTestResult.SRC_IMAGE_ANCHOR_TYPE -> {
+ createImageMenu(menu, imageUrl = hitTestResult.extra)
+ }
+ HitTestResult.PHONE_TYPE -> {
+ createPhoneNumberMenu(menu, phoneNumber = hitTestResult.extra)
+ }
+ HitTestResult.EMAIL_TYPE -> {
+ createEmailMenu(menu, email = hitTestResult.extra)
+ }
+ }
+ }
- if (!inlineImage) {
- menu.add(Menu.NONE, MENU_ITEM_IMAGE_COPY, 2,
- context.getString(R.string.webview_contextmenu_image_copy_action))
- .setOnMenuItemClickListener(listener);
+ private fun createLinkMenu(
+ menu: ContextMenu,
+ webView: WebView,
+ linkUrl: String?
+ ) {
+ if (linkUrl == null) return
+
+ val listener = MenuItem.OnMenuItemClickListener { item ->
+ when (item.itemId) {
+ MENU_ITEM_LINK_VIEW -> {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(linkUrl))
+ startActivityIfAvailable(context, intent)
+ }
+ MENU_ITEM_LINK_SHARE -> {
+ IntentBuilder(context)
+ .setType("text/plain")
+ .setText(linkUrl)
+ .startChooser()
+ }
+ MENU_ITEM_LINK_COPY -> {
+ val label = context.getString(R.string.webview_contextmenu_link_clipboard_label)
+ clipboardManager.setText(label, linkUrl)
+ }
+ MENU_ITEM_LINK_TEXT_COPY -> {
+ val message = linkTextHandler.obtainMessage()
+ webView.requestFocusNodeHref(message)
}
-
- break;
}
- case HitTestResult.PHONE_TYPE: {
- final String phoneNumber = result.getExtra();
- OnMenuItemClickListener listener = new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_ITEM_PHONE_CALL: {
- Uri uri = Uri.parse(WebView.SCHEME_TEL + phoneNumber);
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- startActivityIfAvailable(getContext(), intent);
- break;
- }
- case MENU_ITEM_PHONE_SAVE: {
- Contacts contacts = Contacts.getInstance(getContext());
- contacts.addPhoneContact(phoneNumber);
- break;
- }
- case MENU_ITEM_PHONE_COPY: {
- String label = getContext().getString(
- R.string.webview_contextmenu_phone_clipboard_label);
- clipboardManager.setText(label, phoneNumber);
- break;
- }
- }
-
- return true;
- }
- };
+ true
+ }
- menu.setHeaderTitle(phoneNumber);
+ menu.setHeaderTitle(linkUrl)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_LINK_VIEW,
+ 0,
+ context.getString(R.string.webview_contextmenu_link_view_action)
+ ).setOnMenuItemClickListener(listener)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_LINK_SHARE,
+ 1,
+ context.getString(R.string.webview_contextmenu_link_share_action)
+ ).setOnMenuItemClickListener(listener)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_LINK_COPY,
+ 2,
+ context.getString(R.string.webview_contextmenu_link_copy_action)
+ ).setOnMenuItemClickListener(listener)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_LINK_TEXT_COPY,
+ 3,
+ context.getString(R.string.webview_contextmenu_link_text_copy_action)
+ ).setOnMenuItemClickListener(listener)
+ }
- menu.add(Menu.NONE, MENU_ITEM_PHONE_CALL, 0,
- context.getString(R.string.webview_contextmenu_phone_call_action))
- .setOnMenuItemClickListener(listener);
+ private fun createImageMenu(menu: ContextMenu, imageUrl: String?) {
+ if (imageUrl == null) return
- menu.add(Menu.NONE, MENU_ITEM_PHONE_SAVE, 1,
- context.getString(R.string.webview_contextmenu_phone_save_action))
- .setOnMenuItemClickListener(listener);
+ val imageUri = Uri.parse(imageUrl)
+ val attachmentViewInfo = getAttachmentViewInfoIfCidUri(imageUri)
+ val inlineImage = attachmentViewInfo != null
- menu.add(Menu.NONE, MENU_ITEM_PHONE_COPY, 2,
- context.getString(R.string.webview_contextmenu_phone_copy_action))
- .setOnMenuItemClickListener(listener);
+ val listener = MenuItem.OnMenuItemClickListener { item ->
+ val attachmentCallback = checkNotNull(attachmentCallback)
- break;
- }
- case WebView.HitTestResult.EMAIL_TYPE: {
- final String email = result.getExtra();
- OnMenuItemClickListener listener = new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_ITEM_EMAIL_SEND: {
- Uri uri = Uri.parse(WebView.SCHEME_MAILTO + email);
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- startActivityIfAvailable(getContext(), intent);
- break;
- }
- case MENU_ITEM_EMAIL_SAVE: {
- Contacts contacts = Contacts.getInstance(getContext());
- contacts.createContact(new Address(email));
- break;
- }
- case MENU_ITEM_EMAIL_COPY: {
- String label = getContext().getString(
- R.string.webview_contextmenu_email_clipboard_label);
- clipboardManager.setText(label, email);
- break;
- }
- }
-
- return true;
+ when (item.itemId) {
+ MENU_ITEM_IMAGE_VIEW -> {
+ if (inlineImage) {
+ attachmentCallback.onViewAttachment(attachmentViewInfo)
+ } else {
+ val intent = Intent(Intent.ACTION_VIEW, imageUri)
+ startActivityIfAvailable(context, intent)
}
- };
-
- menu.setHeaderTitle(email);
-
- menu.add(Menu.NONE, MENU_ITEM_EMAIL_SEND, 0,
- context.getString(R.string.webview_contextmenu_email_send_action))
- .setOnMenuItemClickListener(listener);
-
- menu.add(Menu.NONE, MENU_ITEM_EMAIL_SAVE, 1,
- context.getString(R.string.webview_contextmenu_email_save_action))
- .setOnMenuItemClickListener(listener);
+ }
+ MENU_ITEM_IMAGE_SAVE -> {
+ if (inlineImage) {
+ attachmentCallback.onSaveAttachment(attachmentViewInfo)
+ } else {
+ downloadImage(imageUri)
+ }
+ }
+ MENU_ITEM_IMAGE_COPY -> {
+ val label = context.getString(R.string.webview_contextmenu_image_clipboard_label)
+ clipboardManager.setText(label, imageUri.toString())
+ }
+ }
+ true
+ }
- menu.add(Menu.NONE, MENU_ITEM_EMAIL_COPY, 2,
- context.getString(R.string.webview_contextmenu_email_copy_action))
- .setOnMenuItemClickListener(listener);
+ if (inlineImage) {
+ menu.setHeaderTitle(R.string.webview_contextmenu_image_title)
+ } else {
+ menu.setHeaderTitle(imageUrl)
+ }
- break;
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_IMAGE_VIEW,
+ 0,
+ context.getString(R.string.webview_contextmenu_image_view_action)
+ ).setOnMenuItemClickListener(listener)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_IMAGE_SAVE,
+ 1,
+ if (inlineImage) {
+ context.getString(R.string.webview_contextmenu_image_save_action)
+ } else {
+ context.getString(R.string.webview_contextmenu_image_download_action)
}
+ ).setOnMenuItemClickListener(listener)
+
+ if (!inlineImage) {
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_IMAGE_COPY,
+ 2,
+ context.getString(R.string.webview_contextmenu_image_copy_action)
+ ).setOnMenuItemClickListener(listener)
}
}
- private void downloadImage(Uri uri) {
- DownloadManager.Request request = new DownloadManager.Request(uri);
- if (Build.VERSION.SDK_INT >= 29) {
- String filename = uri.getLastPathSegment();
- request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
+ private fun createPhoneNumberMenu(menu: ContextMenu, phoneNumber: String?) {
+ if (phoneNumber == null) return
+
+ val listener = MenuItem.OnMenuItemClickListener { item ->
+ when (item.itemId) {
+ MENU_ITEM_PHONE_CALL -> {
+ val uri = Uri.parse(WebView.SCHEME_TEL + phoneNumber)
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ startActivityIfAvailable(context, intent)
+ }
+ MENU_ITEM_PHONE_SAVE -> {
+ val contacts = Contacts.getInstance(context)
+ contacts.addPhoneContact(phoneNumber)
+ }
+ MENU_ITEM_PHONE_COPY -> {
+ val label = context.getString(R.string.webview_contextmenu_phone_clipboard_label)
+ clipboardManager.setText(label, phoneNumber)
+ }
+ }
+ true
}
- request.setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- DownloadManager downloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
- downloadManager.enqueue(request);
+ menu.setHeaderTitle(phoneNumber)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_PHONE_CALL,
+ 0,
+ context.getString(R.string.webview_contextmenu_phone_call_action)
+ ).setOnMenuItemClickListener(listener)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_PHONE_SAVE,
+ 1,
+ context.getString(R.string.webview_contextmenu_phone_save_action)
+ ).setOnMenuItemClickListener(listener)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_PHONE_COPY,
+ 2,
+ context.getString(R.string.webview_contextmenu_phone_copy_action)
+ ).setOnMenuItemClickListener(listener)
}
- private AttachmentViewInfo getAttachmentViewInfoIfCidUri(Uri uri) {
- if (!"cid".equals(uri.getScheme())) {
- return null;
- }
+ private fun createEmailMenu(menu: ContextMenu, email: String?) {
+ if (email == null) return
- String cid = uri.getSchemeSpecificPart();
- Uri internalUri = currentAttachmentResolver.getAttachmentUriForContentId(cid);
+ val listener = MenuItem.OnMenuItemClickListener { item ->
+ when (item.itemId) {
+ MENU_ITEM_EMAIL_SEND -> {
+ val uri = Uri.parse(WebView.SCHEME_MAILTO + email)
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ startActivityIfAvailable(context, intent)
+ }
+ MENU_ITEM_EMAIL_SAVE -> {
+ val contacts = Contacts.getInstance(context)
+ contacts.createContact(Address(email))
+ }
+ MENU_ITEM_EMAIL_COPY -> {
+ val label = context.getString(R.string.webview_contextmenu_email_clipboard_label)
+ clipboardManager.setText(label, email)
+ }
+ }
+ true
+ }
- return attachments.get(internalUri);
+ menu.setHeaderTitle(email)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_EMAIL_SEND,
+ 0,
+ context.getString(R.string.webview_contextmenu_email_send_action)
+ ).setOnMenuItemClickListener(listener)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_EMAIL_SAVE,
+ 1,
+ context.getString(R.string.webview_contextmenu_email_save_action)
+ ).setOnMenuItemClickListener(listener)
+
+ menu.add(
+ Menu.NONE,
+ MENU_ITEM_EMAIL_COPY,
+ 2,
+ context.getString(R.string.webview_contextmenu_email_copy_action)
+ ).setOnMenuItemClickListener(listener)
}
- private void startActivityIfAvailable(Context context, Intent intent) {
- try {
- context.startActivity(intent);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(context, R.string.error_activity_not_found, Toast.LENGTH_LONG).show();
+ private fun downloadImage(uri: Uri) {
+ val request = DownloadManager.Request(uri).apply {
+ if (Build.VERSION.SDK_INT >= 29) {
+ val filename = uri.lastPathSegment
+ setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
+ }
+
+ setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
}
- }
- public MessageContainerView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
+ downloadManager.enqueue(request)
}
+ private fun getAttachmentViewInfoIfCidUri(uri: Uri): AttachmentViewInfo? {
+ if (uri.scheme != "cid") return null
- private boolean isShowingPictures() {
- return showingPictures;
- }
+ val attachmentResolver = checkNotNull(currentAttachmentResolver)
- private void setLoadPictures(boolean enable) {
- mMessageContentView.blockNetworkData(!enable);
- showingPictures = enable;
- }
+ val cid = uri.schemeSpecificPart
+ val internalUri = attachmentResolver.getAttachmentUriForContentId(cid)
- public void showPictures() {
- setLoadPictures(true);
- refreshDisplayedContent();
+ return attachments[internalUri]
}
- public void displayMessageViewContainer(MessageViewInfo messageViewInfo,
- final OnRenderingFinishedListener onRenderingFinishedListener, boolean loadPictures,
- boolean hideUnsignedTextDivider, AttachmentViewCallback attachmentCallback) {
-
- this.attachmentCallback = attachmentCallback;
+ private fun startActivityIfAvailable(context: Context, intent: Intent) {
+ try {
+ context.startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ Toast.makeText(context, R.string.error_activity_not_found, Toast.LENGTH_LONG).show()
+ }
+ }
- resetView();
+ private fun setLoadPictures(enable: Boolean) {
+ messageContentView.blockNetworkData(!enable)
+ isShowingPictures = enable
+ }
- renderAttachments(messageViewInfo);
+ fun showPictures() {
+ setLoadPictures(true)
+ refreshDisplayedContent()
+ }
- String textToDisplay = messageViewInfo.text;
- if (textToDisplay != null && !isShowingPictures()) {
- if (Utility.hasExternalImages(textToDisplay)) {
+ fun displayMessageViewContainer(
+ messageViewInfo: MessageViewInfo,
+ onRenderingFinishedListener: OnRenderingFinishedListener,
+ loadPictures: Boolean,
+ hideUnsignedTextDivider: Boolean,
+ attachmentCallback: AttachmentViewCallback?
+ ) {
+ this.attachmentCallback = attachmentCallback
+
+ resetView()
+ renderAttachments(messageViewInfo)
+
+ val messageText = messageViewInfo.text
+ if (messageText != null && !isShowingPictures) {
+ if (Utility.hasExternalImages(messageText)) {
if (loadPictures) {
- setLoadPictures(true);
+ setLoadPictures(true)
} else {
- hasHiddenExternalImages = true;
+ hasHiddenExternalImages = true
}
}
}
- if (textToDisplay == null) {
- String noTextMessage = getContext().getString(R.string.webview_empty_message);
- textToDisplay = displayHtml.wrapStatusMessage(noTextMessage);
- }
-
- OnPageFinishedListener onPageFinishedListener = new OnPageFinishedListener() {
- @Override
- public void onPageFinished() {
- onRenderingFinishedListener.onLoadFinished();
- }
- };
+ val textToDisplay = messageText
+ ?: displayHtml.wrapStatusMessage(context.getString(R.string.webview_empty_message))
displayHtmlContentWithInlineAttachments(
- textToDisplay, messageViewInfo.attachmentResolver, onPageFinishedListener);
-
- if (!TextUtils.isEmpty(messageViewInfo.extraText)) {
- unsignedTextContainer.setVisibility(View.VISIBLE);
- unsignedTextDivider.setVisibility(hideUnsignedTextDivider ? View.GONE : View.VISIBLE);
- unsignedText.setText(messageViewInfo.extraText);
+ htmlText = textToDisplay,
+ attachmentResolver = messageViewInfo.attachmentResolver,
+ onPageFinishedListener = onRenderingFinishedListener::onLoadFinished
+ )
+
+ if (!messageViewInfo.extraText.isNullOrEmpty()) {
+ unsignedTextContainer.isVisible = true
+ unsignedTextDivider.isGone = hideUnsignedTextDivider
+ unsignedText.text = messageViewInfo.extraText
}
}
- public boolean hasHiddenExternalImages() {
- return hasHiddenExternalImages;
+ private fun displayHtmlContentWithInlineAttachments(
+ htmlText: String,
+ attachmentResolver: AttachmentResolver,
+ onPageFinishedListener: OnPageFinishedListener
+ ) {
+ currentHtmlText = htmlText
+ currentAttachmentResolver = attachmentResolver
+ messageContentView.displayHtmlContentWithInlineAttachments(htmlText, attachmentResolver, onPageFinishedListener)
}
- private void displayHtmlContentWithInlineAttachments(String htmlText, AttachmentResolver attachmentResolver,
- OnPageFinishedListener onPageFinishedListener) {
- currentHtmlText = htmlText;
- currentAttachmentResolver = attachmentResolver;
- mMessageContentView.displayHtmlContentWithInlineAttachments(htmlText, attachmentResolver, onPageFinishedListener);
- }
+ private fun refreshDisplayedContent() {
+ val htmlText = checkNotNull(currentHtmlText)
- private void refreshDisplayedContent() {
- mMessageContentView.displayHtmlContentWithInlineAttachments(currentHtmlText, currentAttachmentResolver, null);
+ messageContentView.displayHtmlContentWithInlineAttachments(
+ htmlText = htmlText,
+ attachmentResolver = currentAttachmentResolver,
+ onPageFinishedListener = null
+ )
}
- private void clearDisplayedContent() {
- mMessageContentView.displayHtmlContentWithInlineAttachments("", null, null);
- unsignedTextContainer.setVisibility(View.GONE);
- unsignedText.setText("");
+ private fun clearDisplayedContent() {
+ messageContentView.displayHtmlContentWithInlineAttachments(
+ htmlText = "",
+ attachmentResolver = null,
+ onPageFinishedListener = null
+ )
+
+ unsignedTextContainer.isVisible = false
+ unsignedText.text = ""
}
- public void renderAttachments(MessageViewInfo messageViewInfo) {
+ private fun renderAttachments(messageViewInfo: MessageViewInfo) {
if (messageViewInfo.attachments != null) {
- for (AttachmentViewInfo attachment : messageViewInfo.attachments) {
- attachments.put(attachment.internalUri, attachment);
+ for (attachment in messageViewInfo.attachments) {
+ attachments[attachment.internalUri] = attachment
if (attachment.inlineAttachment) {
- continue;
+ continue
}
- AttachmentView view =
- (AttachmentView) mInflater.inflate(R.layout.message_view_attachment, attachmentsContainer, false);
- view.setCallback(attachmentCallback);
- view.setAttachment(attachment);
+ val attachmentView = layoutInflater.inflate(
+ R.layout.message_view_attachment,
+ attachmentsContainer,
+ false
+ ) as AttachmentView
- attachmentViewMap.put(attachment, view);
- attachmentsContainer.addView(view);
+ attachmentView.setCallback(attachmentCallback)
+ attachmentView.setAttachment(attachment)
+
+ attachmentViewMap[attachment] = attachmentView
+ attachmentsContainer.addView(attachmentView)
}
}
if (messageViewInfo.extraAttachments != null) {
- for (AttachmentViewInfo attachment : messageViewInfo.extraAttachments) {
- attachments.put(attachment.internalUri, attachment);
+ for (attachment in messageViewInfo.extraAttachments) {
+ attachments[attachment.internalUri] = attachment
if (attachment.inlineAttachment) {
- continue;
+ continue
}
- LockedAttachmentView view = (LockedAttachmentView) mInflater
- .inflate(R.layout.message_view_attachment_locked, attachmentsContainer, false);
- view.setCallback(attachmentCallback);
- view.setAttachment(attachment);
+ val lockedAttachmentView = layoutInflater.inflate(
+ R.layout.message_view_attachment_locked,
+ attachmentsContainer,
+ false
+ ) as LockedAttachmentView
+
+ lockedAttachmentView.setCallback(attachmentCallback)
+ lockedAttachmentView.setAttachment(attachment)
- // attachments.put(attachment, view);
- attachmentsContainer.addView(view);
+ attachmentsContainer.addView(lockedAttachmentView)
}
}
}
- public void resetView() {
- setLoadPictures(false);
- attachmentsContainer.removeAllViews();
+ private fun resetView() {
+ setLoadPictures(false)
+ attachmentsContainer.removeAllViews()
- currentHtmlText = null;
- currentAttachmentResolver = null;
+ currentHtmlText = null
+ currentAttachmentResolver = null
/*
* Clear the WebView content
@@ -500,18 +505,34 @@ public class MessageContainerView extends LinearLayout implements OnCreateContex
* its size because the button to download the complete message was previously shown and
* is now hidden.
*/
- clearDisplayedContent();
+ clearDisplayedContent()
}
- public void refreshAttachmentThumbnail(AttachmentViewInfo attachment) {
- getAttachmentView(attachment).refreshThumbnail();
+ fun refreshAttachmentThumbnail(attachment: AttachmentViewInfo) {
+ getAttachmentView(attachment)?.refreshThumbnail()
}
- private AttachmentView getAttachmentView(AttachmentViewInfo attachment) {
- return attachmentViewMap.get(attachment);
+ private fun getAttachmentView(attachment: AttachmentViewInfo): AttachmentView? {
+ return attachmentViewMap[attachment]
}
interface OnRenderingFinishedListener {
- void onLoadFinished();
+ fun onLoadFinished()
+ }
+
+ companion object {
+ private const val MENU_ITEM_LINK_VIEW = Menu.FIRST
+ private const val MENU_ITEM_LINK_SHARE = Menu.FIRST + 1
+ private const val MENU_ITEM_LINK_COPY = Menu.FIRST + 2
+ private const val MENU_ITEM_LINK_TEXT_COPY = Menu.FIRST + 3
+ private const val MENU_ITEM_IMAGE_VIEW = Menu.FIRST
+ private const val MENU_ITEM_IMAGE_SAVE = Menu.FIRST + 1
+ private const val MENU_ITEM_IMAGE_COPY = Menu.FIRST + 2
+ private const val MENU_ITEM_PHONE_CALL = Menu.FIRST
+ private const val MENU_ITEM_PHONE_SAVE = Menu.FIRST + 1
+ private const val MENU_ITEM_PHONE_COPY = Menu.FIRST + 2
+ private const val MENU_ITEM_EMAIL_SEND = Menu.FIRST
+ private const val MENU_ITEM_EMAIL_SAVE = Menu.FIRST + 1
+ private const val MENU_ITEM_EMAIL_COPY = Menu.FIRST + 2
}
}
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt b/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
index 946cd704cf..fde796689f 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/view/MessageWebView.kt
@@ -93,7 +93,7 @@ class MessageWebView : WebView {
resumeTimers()
}
- interface OnPageFinishedListener {
+ fun interface OnPageFinishedListener {
fun onPageFinished()
}
}
--
GitLab
From d014c2f7f3840971e019beed2effe0d0aa8b7c5d Mon Sep 17 00:00:00 2001
From: cketti
Date: Fri, 29 Jul 2022 15:29:32 +0200
Subject: [PATCH 38/47] Fix nullability of parameter
---
.../java/com/fsck/k9/ui/messageview/MessageContainerView.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt
index 887c7b559b..0d3534f269 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageContainerView.kt
@@ -91,7 +91,7 @@ class MessageContainerView(context: Context, attrs: AttributeSet?) :
unsignedText = findViewById(R.id.message_unsigned_text)
}
- override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo) {
+ override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) {
super.onCreateContextMenu(menu)
val webView = view as WebView
--
GitLab
From 7bed9b05b618dea679efb4f7b3dcf926da31c4b6 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 2 Aug 2022 16:42:58 +0200
Subject: [PATCH 39/47] Rename .java to .kt
---
.../message/html/{HtmlConverterTest.java => HtmlConverterTest.kt} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename app/core/src/test/java/com/fsck/k9/message/html/{HtmlConverterTest.java => HtmlConverterTest.kt} (100%)
diff --git a/app/core/src/test/java/com/fsck/k9/message/html/HtmlConverterTest.java b/app/core/src/test/java/com/fsck/k9/message/html/HtmlConverterTest.kt
similarity index 100%
rename from app/core/src/test/java/com/fsck/k9/message/html/HtmlConverterTest.java
rename to app/core/src/test/java/com/fsck/k9/message/html/HtmlConverterTest.kt
--
GitLab
From 114af53c8491f54396d7982235897bd65c1b837d Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 2 Aug 2022 16:42:58 +0200
Subject: [PATCH 40/47] Convert `HtmlConverterTest` to Kotlin
---
.../fsck/k9/message/html/HtmlConverterTest.kt | 664 +++++++++++-------
.../java/com/fsck/k9/mail/StringHelper.kt | 2 +
2 files changed, 422 insertions(+), 244 deletions(-)
diff --git a/app/core/src/test/java/com/fsck/k9/message/html/HtmlConverterTest.kt b/app/core/src/test/java/com/fsck/k9/message/html/HtmlConverterTest.kt
index d7c34f9fb7..178110da15 100644
--- a/app/core/src/test/java/com/fsck/k9/message/html/HtmlConverterTest.kt
+++ b/app/core/src/test/java/com/fsck/k9/message/html/HtmlConverterTest.kt
@@ -1,329 +1,505 @@
-package com.fsck.k9.message.html;
+package com.fsck.k9.message.html
+import com.fsck.k9.mail.crlf
+import com.fsck.k9.mail.removeLineBreaks
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
-import org.junit.Test;
-
-import static junit.framework.Assert.assertEquals;
-
-
-public class HtmlConverterTest {
+class HtmlConverterTest {
@Test
- public void testTextQuoteToHtmlBlockquote() {
- String message = "Panama!\r\n" +
- "\r\n" +
- "Bob Barker wrote:\r\n" +
- "> a canal\r\n" +
- ">\r\n" +
- "> Dorothy Jo Gideon espoused:\r\n" +
- "> >A man, a plan...\r\n" +
- "> Too easy!\r\n" +
- "\r\n" +
- "Nice job :)\r\n" +
- ">> Guess!";
-
- String result = HtmlConverter.textToHtml(message);
-
- assertEquals(""
- + "Panama!
"
- + "
"
- + "Bob Barker <bob@aol.com> wrote:
"
- +
- ""
- + " a canal
"
- + "
"
- + " Dorothy Jo Gideon <dorothy@aol.com> espoused:
"
- +
- ""
- + "A man, a plan...
"
- + "
"
- + "Too easy!
"
- + "
"
- + "
"
- + "Nice job :)
"
- +
- ""
- +
- ""
- + "Guess!"
- + "
"
- + "
"
- + "
", result);
+ fun `textToHtml() should convert quoted text using blockquote tags`() {
+ val message =
+ """
+ Panama!
+
+ Bob Barker wrote:
+ > a canal
+ >
+ > Dorothy Jo Gideon espoused:
+ > >A man, a plan...
+ > Too easy!
+
+ Nice job :)
+ >> Guess!
+ """.trimIndent().crlf()
+
+ val result = HtmlConverter.textToHtml(message)
+
+ assertThat(result).isEqualTo(
+ """
+ |
+ |Panama!
+ |
+ |Bob Barker <bob@aol.com> wrote:
+ |
+ | a canal
+ |
+ | Dorothy Jo Gideon <dorothy@aol.com> espoused:
+ |
+ |A man, a plan...
+ |
+ |Too easy!
+ |
+ |
+ |Nice job :)
+ |
+ |
+ |Guess!
+ |
+ |
+ |
+ """.trimMargin().removeLineBreaks()
+ )
}
@Test
- public void testTextQuoteToHtmlBlockquoteIndented() {
- String message = "*facepalm*\r\n" +
- "\r\n" +
- "Bob Barker wrote:\r\n" +
- "> A wise man once said...\r\n" +
- ">\r\n" +
- "> LOL F1RST!!!!!\r\n" +
- ">\r\n" +
- "> :)";
-
- String result = HtmlConverter.textToHtml(message);
-
- assertEquals(""
- + "*facepalm*
"
- + "
"
- + "Bob Barker <bob@aol.com> wrote:
"
- + ""
- + " A wise man once said...
"
- + "
"
- + " LOL F1RST!!!!!
"
- + "
"
- + " :)"
- + "
", result);
-
+ fun `textToHtml() should retain indentation inside quoted text`() {
+ val message =
+ """
+ *facepalm*
+
+ Bob Barker wrote:
+ > A wise man once said...
+ >
+ > LOL F1RST!!!!!
+ >
+ > :)
+ """.trimIndent().crlf()
+
+ val result = HtmlConverter.textToHtml(message)
+
+ assertThat(result).isEqualTo(
+ """
+ |
+ |*facepalm*
+ |
+ |Bob Barker <bob@aol.com> wrote:
+ |
+ | A wise man once said...
+ |
+ | LOL F1RST!!!!!
+ |
+ | :)
+ |
+ |
+ """.trimMargin().removeLineBreaks()
+ )
}
@Test
- public void testQuoteDepthColor() {
- String message = "zero\r\n" +
- "> one\r\n" +
- ">> two\r\n" +
- ">>> three\r\n" +
- ">>>> four\r\n" +
- ">>>>> five\r\n" +
- ">>>>>> six";
-
- String result = HtmlConverter.textToHtml(message);
-
- assertEquals(""
- + "zero
"
- + ""
- + "one
"
- + ""
- + "two
"
- + ""
- + "three
"
- + ""
- + "four
"
- + ""
- + "five
"
- + ""
- + "six"
- + "
"
- + "
"
- + "
"
- + "
"
- + "
"
- + "
"
- + "
", result);
+ fun `textToHtml() with various quotation depths`() {
+ val message =
+ """
+ zero
+ > one
+ >> two
+ >>> three
+ >>>> four
+ >>>>> five
+ >>>>>> six
+ """.trimIndent().crlf()
+
+ val result = HtmlConverter.textToHtml(message)
+
+ assertThat(result).isEqualTo(
+ """
+ |
+ |zero
+ |
+ |one
+ |
+ |two
+ |
+ |three
+ |
+ |four
+ |
+ |five
+ |
+ |six
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ """.trimMargin().removeLineBreaks()
+ )
}
@Test
- public void testPreserveSpacesAtFirst() {
- String message = "foo\r\n"
- + " bar\r\n"
- + " baz\r\n";
-
- String result = HtmlConverter.textToHtml(message);
-
- assertEquals(""
- + "foo
"
- + " bar
"
- + " baz
"
- + "
", result);
+ fun `textToHtml() should preserve spaces at the start of a line`() {
+ val message =
+ """
+ |foo
+ | bar
+ | baz
+ |
+ """.trimMargin().crlf()
+
+ val result = HtmlConverter.textToHtml(message)
+
+ assertThat(result).isEqualTo(
+ """
+ |
+ |foo
+ | bar
+ | baz
+ |
+ """.trimMargin().removeLineBreaks()
+ )
}
@Test
- public void testPreserveSpacesAtFirstForSpecialCharacters() {
- String message =
- " \r\n"
- + " &\r\n"
- + " \n"
- + " <\r\n"
- + " > \r\n";
-
- String result = HtmlConverter.textToHtml(message);
-
- assertEquals(""
- + "
"
- + " &
"
- + "
"
- + " <
"
- + ""
- + "
"
- + "
"
- + "
", result);
+ fun `textToHtml() should preserve spaces at the start of a line followed by special characters`() {
+ val message =
+ """
+ |
+ | &
+ | ${" "}
+ | <
+ | >${" "}
+ |
+ """.trimMargin().crlf()
+
+ val result = HtmlConverter.textToHtml(message)
+
+ assertThat(result).isEqualTo(
+ """
+ |
+ |
+ | &
+ |
+ | <
+ |
+ |
+ |
+ |
+ """.trimMargin().removeLineBreaks()
+ )
}
@Test
- public void issue2259Spec() {
- String text = "text\n" +
- "---------------------------\n" +
- "some other text\n" +
- "===========================\n" +
- "more text\n" +
- "-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" +
- "scissors below\n" +
- "-- >8 --\n" +
- "other direction\n" +
- "-- 8< --\n" +
- "end";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("text
" +
- "some other text
" +
- "more text
" +
- "scissors below
" +
- "other direction
" +
- "end
",
- result);
+ fun `textToHtml() should replace common horizontal divider ASCII patterns with HR tags`() {
+ val text =
+ """
+ text
+ ---------------------------
+ some other text
+ ===========================
+ more text
+ -=-=-=-=-=-=-=-=-=-=-=-=-=-
+ scissors below
+ -- >8 --
+ other direction
+ -- 8< --
+ end
+ """.trimIndent()
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo(
+ """
+
+ text
+
+ some other text
+
+ more text
+
+ scissors below
+
+ other direction
+
+ end
+
+ """.trimIndent().removeLineBreaks()
+ )
}
@Test
- public void dashesContainingSpacesIgnoredAsHR() {
- String text = "hello\n--- --- --- --- ---\nfoo bar";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("hello
--- --- --- --- ---
foo bar
",
- result);
+ fun `textToHtml() should not convert dashes mixed with spaces to an HR tag`() {
+ val text =
+ """
+ hello
+ --- --- --- --- ---
+ foo bar
+ """.trimIndent()
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo(
+ """
+
+ hello
+ --- --- --- --- ---
+ foo bar
+
+ """.trimIndent().removeLineBreaks()
+ )
}
@Test
- public void mergeConsecutiveBreaksIntoOne() {
- String text = "hello\n------------\n---------------\nfoo bar";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("hello
foo bar
", result);
+ fun `textToHtml() should merge consecutive horizontal dividers into a single HR tag`() {
+ val text =
+ """
+ hello
+ ------------
+ ---------------
+ foo bar
+ """.trimIndent()
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo(
+ """
+
+ hello
+
+ foo bar
+
+ """.trimIndent().removeLineBreaks()
+ )
}
@Test
- public void dashedHorizontalRulePrefixedWithTextIgnoredAsHR() {
- String text = "hello----\n\n";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("hello----
", result);
+ fun `textToHtml() should not replace dashed horizontal divider prefixed with text`() {
+ val text = "hello----\n\n"
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo(
+ """
+
+ hello----
+
+
+ """.trimIndent().removeLineBreaks()
+ )
}
@Test
- public void doubleMinusIgnoredAsHR() {
- String text = "--\n";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("--
", result);
- }
+ fun `textToHtml() should not replace double dash with an HR tag`() {
+ val text = "--\n"
- @Test
- public void doubleEqualsIgnoredAsHR() {
- String text = "==\n";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("==
", result);
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo("""--
""")
}
@Test
- public void doubleUnderscoreIgnoredAsHR() {
- String text = "__\n";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("__
", result);
+ fun `textToHtml() should not replace double equal sign with an HR tag`() {
+ val text = "==\n"
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo("""==
""")
}
@Test
- public void anyTripletIsHRuledOut() {
- String text = "--=\n-=-\n===\n___\n\n";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("
", result);
+ fun `textToHtml() should not replace double underscore with an HR tag`() {
+ val text = "__\n"
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo("""__
""")
}
@Test
- public void replaceSpaceSeparatedDashesWithHR() {
- String text = "hello\n---------------------------\nfoo bar";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("hello
foo bar
", result);
+ fun `textToHtml() should replace any combination of three consecutive divider characters with an HR tag`() {
+ val text =
+ """
+ --=
+ -=-
+ ===
+ ___
+
+ """.trimIndent()
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo("""
""")
}
@Test
- public void replacementWithHRAtBeginning() {
- String text = "---------------------------\nfoo bar";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("
foo bar
", result);
+ fun `textToHtml() should replace dashes at the start of the input`() {
+ val text = "---------------------------\nfoo bar"
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo(
+ """
+
+
+ foo bar
+
+ """.trimIndent().removeLineBreaks()
+ )
}
@Test
- public void replacementWithHRAtEnd() {
- String text = "hello\n__________________________________";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("hello
", result);
+ fun `textToHtml() should replace dashes at the end of the input`() {
+ val text = "hello\n__________________________________"
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo(
+ """
+
+ hello
+
+
+ """.trimIndent().removeLineBreaks()
+ )
}
@Test
- public void replacementOfScissorsByHR() {
- String text = "hello\n-- %< -------------- >8 --\nworld\n";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("hello
world
", result);
+ fun `textToHtml() should replace horizontal divider using ASCII scissors with an HR tag`() {
+ val text =
+ """
+ hello
+ -- %< -------------- >8 --
+ world
+ """.trimIndent()
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo(
+ """
+
+ hello
+
+ world
+
+ """.trimIndent().removeLineBreaks()
+ )
}
@Test
- public void signatureEndingWithUrl() {
- String text = "text\n-- \nsignature with url: https://domain.example/";
- String result = HtmlConverter.textToHtml(text);
- assertEquals("" +
- "text
" +
- "
", result);
+ fun `textToHtml() should wrap email signature in a DIV`() {
+ val text =
+ """
+ text
+ --${" "}
+ signature with url: https://domain.example/
+ """.trimIndent()
+
+ val result = HtmlConverter.textToHtml(text)
+
+ assertThat(result).isEqualTo(
+ """
+
+ text
+
+
+ """.trimIndent().removeLineBreaks()
+ )
}
@Test
- public void htmlToText_withLineBreaks() {
- String input = "One
Two
Three";
-
- String result = HtmlConverter.htmlToText(input);
-
- assertEquals("One\nTwo\n\nThree", result);
+ fun `htmlToText() should convert BR tags to line breaks`() {
+ val input =
+ """
+ One
+ Two
+
+ Three
+ """.trimIndent().removeLineBreaks()
+
+ val result = HtmlConverter.htmlToText(input)
+
+ assertThat(result).isEqualTo(
+ """
+ One
+ Two
+
+ Three
+ """.trimIndent()
+ )
}
@Test
- public void htmlToText_withBlockElements() {
- String input = "One
Two
Three
Four
";
-
- String result = HtmlConverter.htmlToText(input);
-
- assertEquals("One\n\nTwo\nThree\n\nFour", result);
+ fun `htmlToText() should insert line breaks after block elements`() {
+ val input =
+ """
+ One
+
+ Two
+ Three
+
+ Four
+ """.trimIndent().removeLineBreaks()
+
+ val result = HtmlConverter.htmlToText(input)
+
+ assertThat(result).isEqualTo(
+ """
+ One
+
+ Two
+ Three
+
+ Four
+ """.trimIndent()
+ )
}
@Test
- public void htmlToText_withLink() {
- String input = "Link text";
+ fun `htmlToText() should include link URIs`() {
+ val input = "Link text"
- String result = HtmlConverter.htmlToText(input);
+ val result = HtmlConverter.htmlToText(input)
- assertEquals("Link text ", result);
+ assertThat(result).isEqualTo("Link text ")
}
@Test
- public void htmlToText_withLinkifiedUrl() {
- String input = "Text https://domain.example/path/ more text";
+ fun `htmlToText() should not duplicate URI when link URI and text are the same`() {
+ val input = "Text https://domain.example/path/ more text"
- String result = HtmlConverter.htmlToText(input);
+ val result = HtmlConverter.htmlToText(input)
- assertEquals("Text https://domain.example/path/ more text", result);
+ assertThat(result).isEqualTo("Text https://domain.example/path/ more text")
}
@Test
- public void htmlToText_withLinkifiedUrlContainingFormatting() {
- String input = "https://domain.example/path/";
+ fun `htmlToText() should not duplicate URI when the link text is just the link URI with some formatting`() {
+ val input = "https://domain.example/path/"
- String result = HtmlConverter.htmlToText(input);
+ val result = HtmlConverter.htmlToText(input)
- assertEquals("https://domain.example/path/", result);
+ assertThat(result).isEqualTo("https://domain.example/path/")
}
@Test
- public void htmlToText_withLineBreaksInHtml() {
- String input = "One\nTwo\r\nThree";
+ fun `htmlToText() should strip line breaks`() {
+ val input =
+ """
+ One
+ Two
+ Three
+ """.trimIndent()
- String result = HtmlConverter.htmlToText(input);
+ val result = HtmlConverter.htmlToText(input)
- assertEquals("One Two Three", result);
+ assertThat(result).isEqualTo("One Two Three")
}
@Test
- public void htmlToText_withLongTextLine_shouldNotAddLineBreaksToOutput() {
- String input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam sit amet finibus felis, " +
- "viverra ullamcorper justo. Suspendisse potenti. Etiam erat sem, interdum a condimentum quis, " +
- "fringilla quis orci.";
+ fun `htmlToText() with long text line should not add line breaks to output`() {
+ val input =
+ """
+ |Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam sit amet finibus felis,
+ | viverra ullamcorper justo. Suspendisse potenti. Etiam erat sem, interdum a condimentum quis,
+ | fringilla quis orci.
+ """.trimMargin().removeLineBreaks()
- String result = HtmlConverter.htmlToText(input);
+ val result = HtmlConverter.htmlToText(input)
- assertEquals(input, result);
+ assertThat(result).isEqualTo(input)
}
}
diff --git a/mail/testing/src/main/java/com/fsck/k9/mail/StringHelper.kt b/mail/testing/src/main/java/com/fsck/k9/mail/StringHelper.kt
index 73766b23ee..cada3ad040 100644
--- a/mail/testing/src/main/java/com/fsck/k9/mail/StringHelper.kt
+++ b/mail/testing/src/main/java/com/fsck/k9/mail/StringHelper.kt
@@ -1,3 +1,5 @@
package com.fsck.k9.mail
fun String.crlf() = replace("\n", "\r\n")
+
+fun String.removeLineBreaks() = replace(Regex("""\r|\n"""), "")
--
GitLab
From d82b4625654e9bfb3588ba38d921c86b192f7b20 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 2 Aug 2022 19:36:30 +0200
Subject: [PATCH 41/47] IMAP: Fix creating header values from a BODYSTRUCTURE
item
---
.../k9/mail/internet/MimeParameterEncoder.kt | 4 +-
.../fsck/k9/mail/store/imap/RealImapFolder.kt | 8 +-
.../k9/mail/store/imap/RealImapFolderTest.kt | 94 ++++++++++++++++++-
3 files changed, 101 insertions(+), 5 deletions(-)
diff --git a/mail/common/src/main/java/com/fsck/k9/mail/internet/MimeParameterEncoder.kt b/mail/common/src/main/java/com/fsck/k9/mail/internet/MimeParameterEncoder.kt
index b4023de0f4..51e0505ac7 100644
--- a/mail/common/src/main/java/com/fsck/k9/mail/internet/MimeParameterEncoder.kt
+++ b/mail/common/src/main/java/com/fsck/k9/mail/internet/MimeParameterEncoder.kt
@@ -138,14 +138,14 @@ object MimeParameterEncoder {
return length
}
- private fun String.isToken() = when {
+ fun String.isToken() = when {
isEmpty() -> false
else -> all { it.isTokenChar() }
}
private fun String.isQuotable() = all { it.isQuotable() }
- private fun String.quoted(): String {
+ fun String.quoted(): String {
// quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]
// qcontent = qtext / quoted-pair
// quoted-pair = ("\" (VCHAR / WSP))
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt
index 9508716b77..c67605f645 100644
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt
@@ -15,6 +15,8 @@ import com.fsck.k9.mail.internet.MimeBodyPart
import com.fsck.k9.mail.internet.MimeHeader
import com.fsck.k9.mail.internet.MimeMessageHelper
import com.fsck.k9.mail.internet.MimeMultipart
+import com.fsck.k9.mail.internet.MimeParameterEncoder.isToken
+import com.fsck.k9.mail.internet.MimeParameterEncoder.quoted
import com.fsck.k9.mail.internet.MimeUtility
import java.io.IOException
import java.io.InputStream
@@ -889,7 +891,8 @@ internal class RealImapFolder(
for (i in bodyParams.indices step 2) {
val paramName = bodyParams.getString(i)
val paramValue = bodyParams.getString(i + 1)
- contentType.append(String.format(";\r\n %s=\"%s\"", paramName, paramValue))
+ val encodedValue = if (paramValue.isToken()) paramValue else paramValue.quoted()
+ contentType.append(String.format(";\r\n %s=%s", paramName, encodedValue))
}
}
@@ -915,7 +918,8 @@ internal class RealImapFolder(
for (i in bodyDispositionParams.indices step 2) {
val paramName = bodyDispositionParams.getString(i).lowercase()
val paramValue = bodyDispositionParams.getString(i + 1)
- contentDisposition.append(String.format(";\r\n %s=\"%s\"", paramName, paramValue))
+ val encodedValue = if (paramValue.isToken()) paramValue else paramValue.quoted()
+ contentDisposition.append(String.format(";\r\n %s=%s", paramName, encodedValue))
}
}
}
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt
index 1fea9ae2ca..5b2251fc57 100644
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt
@@ -731,7 +731,83 @@ class RealImapFolderTest {
folder.fetch(messages, fetchProfile, null, MAX_DOWNLOAD_SIZE)
- verify(messages[0]).setHeader(MimeHeader.HEADER_CONTENT_TYPE, "text/plain;\r\n CHARSET=\"US-ASCII\"")
+ verify(messages[0]).setHeader(MimeHeader.HEADER_CONTENT_TYPE, "text/plain;\r\n CHARSET=US-ASCII")
+ }
+
+ @Test
+ fun `fetch() with simple content type parameter`() {
+ testHeaderFromBodyStructure(
+ bodyStructure = """("text" "plain" ("name" "token") NIL NIL "7bit" 42 23)""",
+ headerName = MimeHeader.HEADER_CONTENT_TYPE,
+ expectedHeaderValue = "text/plain;\r\n name=token"
+ )
+ }
+
+ @Test
+ fun `fetch() with content type parameter that needs to be a quoted string`() {
+ testHeaderFromBodyStructure(
+ bodyStructure = """("text" "plain" ("name" "one two three") NIL NIL "7bit" 42 23)""",
+ headerName = MimeHeader.HEADER_CONTENT_TYPE,
+ expectedHeaderValue = "text/plain;\r\n name=\"one two three\""
+ )
+ }
+
+ @Test
+ fun `fetch() with content type parameter that needs to be a quoted string with escaped characters`() {
+ testHeaderFromBodyStructure(
+ bodyStructure = """("text" "plain" ("name" "one \"two\" three") NIL NIL "7bit" 42 23)""",
+ headerName = MimeHeader.HEADER_CONTENT_TYPE,
+ expectedHeaderValue = "text/plain;\r\n name=\"one \\\"two\\\" three\""
+ )
+ }
+
+ @Test
+ fun `fetch() with RFC 2231 encoded content type parameter`() {
+ testHeaderFromBodyStructure(
+ bodyStructure = """("text" "plain" ("name*" "utf-8''filen%C3%A4me.ext") NIL NIL "7bit" 42 23)""",
+ headerName = MimeHeader.HEADER_CONTENT_TYPE,
+ expectedHeaderValue = "text/plain;\r\n name*=utf-8''filen%C3%A4me.ext"
+ )
+ }
+
+ @Test
+ fun `fetch() with simple content disposition parameter`() {
+ testHeaderFromBodyStructure(
+ bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
+ """("attachment" ("filename" "token")) NIL NIL)""",
+ headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
+ expectedHeaderValue = "attachment;\r\n filename=token;\r\n size=23"
+ )
+ }
+
+ @Test
+ fun `fetch() with content disposition parameter that needs to be a quoted string`() {
+ testHeaderFromBodyStructure(
+ bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
+ """("attachment" ("filename" "one two three")) NIL NIL)""",
+ headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
+ expectedHeaderValue = "attachment;\r\n filename=\"one two three\";\r\n size=23"
+ )
+ }
+
+ @Test
+ fun `fetch() with content disposition parameter that needs to be a quoted string with escaped characters`() {
+ testHeaderFromBodyStructure(
+ bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
+ """("attachment" ("filename" "one \"two\" three")) NIL NIL)""",
+ headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
+ expectedHeaderValue = "attachment;\r\n filename=\"one \\\"two\\\" three\";\r\n size=23"
+ )
+ }
+
+ @Test
+ fun `fetch() with RFC 2231 encoded content disposition parameter`() {
+ testHeaderFromBodyStructure(
+ bodyStructure = """("application" "octet-stream" NIL NIL NIL "8bit" 23 NIL """ +
+ """("attachment" ("filename*" "utf-8''filen%C3%A4me.ext")) NIL NIL)""",
+ headerName = MimeHeader.HEADER_CONTENT_DISPOSITION,
+ expectedHeaderValue = "attachment;\r\n filename*=utf-8''filen%C3%A4me.ext;\r\n size=23"
+ )
}
@Test
@@ -1118,6 +1194,22 @@ class RealImapFolderTest {
.thenReturn(imapResponses)
}
+ private fun testHeaderFromBodyStructure(bodyStructure: String, headerName: String, expectedHeaderValue: String) {
+ val folder = createFolder("Folder")
+ prepareImapFolderForOpen(OpenMode.READ_ONLY)
+ folder.open(OpenMode.READ_ONLY)
+ whenever(imapConnection.readResponse(anyOrNull()))
+ .thenReturn(createImapResponse("* 1 FETCH (BODYSTRUCTURE $bodyStructure UID 1)"))
+ .thenReturn(createImapResponse("x OK"))
+ val imapMessage = ImapMessage("1")
+ val messages = listOf(imapMessage)
+ val fetchProfile = createFetchProfile(FetchProfile.Item.STRUCTURE)
+
+ folder.fetch(messages, fetchProfile, null, MAX_DOWNLOAD_SIZE)
+
+ assertThat(imapMessage.getHeader(headerName)).asList().containsExactly(expectedHeaderValue)
+ }
+
companion object {
private const val MAX_DOWNLOAD_SIZE = -1
}
--
GitLab
From 16ae99ac09f3ede34f9834ee292093e2f151a8bb Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 3 Aug 2022 17:30:07 +0200
Subject: [PATCH 42/47] Fix initialization problems with MessageListFragment
and MessageViewFragment
---
.../legacy/src/main/java/com/fsck/k9/activity/MessageList.kt | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index 6985863dd3..fbbe2ffe22 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -24,6 +24,7 @@ import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit
+import androidx.fragment.app.commitNow
import androidx.lifecycle.Observer
import com.fsck.k9.Account
import com.fsck.k9.K9
@@ -270,7 +271,7 @@ open class MessageList :
search!!, false, K9.isThreadedViewEnabled && !noThreading
)
fragmentTransaction.add(R.id.message_list_container, messageListFragment)
- fragmentTransaction.commit()
+ fragmentTransaction.commitNow()
this.messageListFragment = messageListFragment
}
@@ -979,7 +980,7 @@ open class MessageList :
}
val fragment = MessageViewFragment.newInstance(messageReference)
- supportFragmentManager.commit {
+ supportFragmentManager.commitNow {
replace(R.id.message_view_container, fragment, FRAGMENT_TAG_MESSAGE_VIEW)
}
--
GitLab
From de6d4197f244a75064f4182504f7fa5c4528d1bd Mon Sep 17 00:00:00 2001
From: cketti
Date: Fri, 22 Jul 2022 14:56:25 +0200
Subject: [PATCH 43/47] Add basic support for swiping between messages
---
.../java/com/fsck/k9/activity/MessageList.kt | 179 +++++-----
.../fsck/k9/fragment/MessageListFragment.kt | 34 +-
.../com/fsck/k9/ui/messageview/Direction.kt | 6 +
.../MessageViewContainerFragment.kt | 311 ++++++++++++++++++
.../k9/ui/messageview/MessageViewFragment.kt | 13 -
.../res/layout/message_view_container.xml | 5 +
6 files changed, 415 insertions(+), 133 deletions(-)
create mode 100644 app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/Direction.kt
create mode 100644 app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewContainerFragment.kt
create mode 100644 app/ui/legacy/src/main/res/layout/message_view_container.xml
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index fbbe2ffe22..b9a66cd2cf 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -54,7 +54,9 @@ import com.fsck.k9.ui.changelog.RecentChangesActivity
import com.fsck.k9.ui.changelog.RecentChangesViewModel
import com.fsck.k9.ui.managefolders.ManageFoldersActivity
import com.fsck.k9.ui.messagelist.DefaultFolderProvider
-import com.fsck.k9.ui.messageview.MessageViewFragment
+import com.fsck.k9.ui.messageview.Direction
+import com.fsck.k9.ui.messageview.MessageViewContainerFragment
+import com.fsck.k9.ui.messageview.MessageViewContainerFragment.MessageViewContainerListener
import com.fsck.k9.ui.messageview.MessageViewFragment.MessageViewFragmentListener
import com.fsck.k9.ui.messageview.PlaceholderFragment
import com.fsck.k9.ui.onboarding.OnboardingActivity
@@ -80,6 +82,7 @@ open class MessageList :
K9Activity(),
MessageListFragmentListener,
MessageViewFragmentListener,
+ MessageViewContainerListener,
FragmentManager.OnBackStackChangedListener,
OnSwitchCompleteListener,
PermissionUiHelper {
@@ -102,11 +105,16 @@ open class MessageList :
private var progressBar: ProgressBar? = null
private var messageViewPlaceHolder: PlaceholderFragment? = null
private var messageListFragment: MessageListFragment? = null
- private var messageViewFragment: MessageViewFragment? = null
+ private var messageViewContainerFragment: MessageViewContainerFragment? = null
private var account: Account? = null
private var search: LocalSearch? = null
private var singleFolderMode = false
- private var lastDirection = if (K9.isMessageViewShowNext) NEXT else PREVIOUS
+
+ private val lastDirection: Direction
+ get() {
+ return messageViewContainerFragment?.lastDirection
+ ?: if (K9.isMessageViewShowNext) Direction.NEXT else Direction.PREVIOUS
+ }
private var messageListActivityAppearance: MessageListActivityAppearance? = null
@@ -225,7 +233,7 @@ open class MessageList :
supportFragmentManager.popBackStackImmediate(FIRST_FRAGMENT_TRANSACTION, FragmentManager.POP_BACK_STACK_INCLUSIVE)
removeMessageListFragment()
- removeMessageViewFragment()
+ removeMessageViewContainerFragment()
messageReference = null
search = null
@@ -253,9 +261,11 @@ open class MessageList :
private fun findFragments() {
val fragmentManager = supportFragmentManager
messageListFragment = fragmentManager.findFragmentById(R.id.message_list_container) as MessageListFragment?
- messageViewFragment = fragmentManager.findFragmentByTag(FRAGMENT_TAG_MESSAGE_VIEW) as MessageViewFragment?
+ messageViewContainerFragment =
+ fragmentManager.findFragmentByTag(FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER) as MessageViewContainerFragment?
messageListFragment?.let { messageListFragment ->
+ messageViewContainerFragment?.setViewModel(messageListFragment.viewModel)
initializeFromLocalSearch(messageListFragment.localSearch)
}
}
@@ -278,7 +288,7 @@ open class MessageList :
// Check if the fragment wasn't restarted and has a MessageReference in the arguments.
// If so, open the referenced message.
- if (!hasMessageListFragment && messageViewFragment == null && messageReference != null) {
+ if (!hasMessageListFragment && messageViewContainerFragment == null && messageReference != null) {
openMessage(messageReference!!)
}
}
@@ -304,7 +314,7 @@ open class MessageList :
}
}
- displayMode = if (messageViewFragment != null || messageReference != null) {
+ displayMode = if (messageViewContainerFragment != null || messageReference != null) {
DisplayMode.MESSAGE_VIEW
} else {
DisplayMode.MESSAGE_LIST
@@ -337,12 +347,12 @@ open class MessageList :
messageListWasDisplayed = true
messageListFragment.isActive = true
- messageViewFragment.let { messageViewFragment ->
- if (messageViewFragment == null) {
+ messageViewContainerFragment.let { messageViewContainerFragment ->
+ if (messageViewContainerFragment == null) {
showMessageViewPlaceHolder()
} else {
- messageViewFragment.isActive = true
- val activeMessage = messageViewFragment.messageReference
+ messageViewContainerFragment.isActive = true
+ val activeMessage = messageViewContainerFragment.messageReference
messageListFragment.setActiveMessage(activeMessage)
}
}
@@ -607,7 +617,7 @@ open class MessageList :
fun openFolder(folderId: Long) {
if (displayMode == DisplayMode.SPLIT_VIEW) {
- removeMessageViewFragment()
+ removeMessageViewContainerFragment()
showMessageViewPlaceHolder()
}
@@ -736,7 +746,7 @@ open class MessageList :
when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
- if (messageViewFragment != null && displayMode != DisplayMode.MESSAGE_LIST &&
+ if (messageViewContainerFragment != null && displayMode != DisplayMode.MESSAGE_LIST &&
K9.isUseVolumeKeysForNavigation
) {
showPreviousMessage()
@@ -747,7 +757,7 @@ open class MessageList :
}
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
- if (messageViewFragment != null && displayMode != DisplayMode.MESSAGE_LIST &&
+ if (messageViewContainerFragment != null && displayMode != DisplayMode.MESSAGE_LIST &&
K9.isUseVolumeKeysForNavigation
) {
showNextMessage()
@@ -762,14 +772,14 @@ open class MessageList :
return true
}
KeyEvent.KEYCODE_DPAD_LEFT -> {
- return if (messageViewFragment != null && displayMode == DisplayMode.MESSAGE_VIEW) {
+ return if (messageViewContainerFragment != null && displayMode == DisplayMode.MESSAGE_VIEW) {
showPreviousMessage()
} else {
false
}
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
- return if (messageViewFragment != null && displayMode == DisplayMode.MESSAGE_VIEW) {
+ return if (messageViewContainerFragment != null && displayMode == DisplayMode.MESSAGE_VIEW) {
showNextMessage()
} else {
false
@@ -801,69 +811,69 @@ open class MessageList :
'g' -> {
if (displayMode == DisplayMode.MESSAGE_LIST) {
messageListFragment!!.onToggleFlagged()
- } else if (messageViewFragment != null) {
- messageViewFragment!!.onToggleFlagged()
+ } else if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onToggleFlagged()
}
return true
}
'm' -> {
if (displayMode == DisplayMode.MESSAGE_LIST) {
messageListFragment!!.onMove()
- } else if (messageViewFragment != null) {
- messageViewFragment!!.onMove()
+ } else if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onMove()
}
return true
}
'v' -> {
if (displayMode == DisplayMode.MESSAGE_LIST) {
messageListFragment!!.onArchive()
- } else if (messageViewFragment != null) {
- messageViewFragment!!.onArchive()
+ } else if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onArchive()
}
return true
}
'y' -> {
if (displayMode == DisplayMode.MESSAGE_LIST) {
messageListFragment!!.onCopy()
- } else if (messageViewFragment != null) {
- messageViewFragment!!.onCopy()
+ } else if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onCopy()
}
return true
}
'z' -> {
if (displayMode == DisplayMode.MESSAGE_LIST) {
messageListFragment!!.onToggleRead()
- } else if (messageViewFragment != null) {
- messageViewFragment!!.onToggleRead()
+ } else if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onToggleRead()
}
return true
}
'f' -> {
- if (messageViewFragment != null) {
- messageViewFragment!!.onForward()
+ if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onForward()
}
return true
}
'a' -> {
- if (messageViewFragment != null) {
- messageViewFragment!!.onReplyAll()
+ if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onReplyAll()
}
return true
}
'r' -> {
- if (messageViewFragment != null) {
- messageViewFragment!!.onReply()
+ if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onReply()
}
return true
}
'j', 'p' -> {
- if (messageViewFragment != null) {
+ if (messageViewContainerFragment != null) {
showPreviousMessage()
}
return true
}
'n', 'k' -> {
- if (messageViewFragment != null) {
+ if (messageViewContainerFragment != null) {
showNextMessage()
}
return true
@@ -885,8 +895,8 @@ open class MessageList :
private fun onDeleteHotKey() {
if (displayMode == DisplayMode.MESSAGE_LIST) {
messageListFragment!!.onDelete()
- } else if (messageViewFragment != null) {
- messageViewFragment!!.onDelete()
+ } else if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onDelete()
}
}
@@ -979,12 +989,16 @@ open class MessageList :
messageListFragment!!.setActiveMessage(messageReference)
}
- val fragment = MessageViewFragment.newInstance(messageReference)
+ val fragment = MessageViewContainerFragment.newInstance(messageReference)
supportFragmentManager.commitNow {
- replace(R.id.message_view_container, fragment, FRAGMENT_TAG_MESSAGE_VIEW)
+ replace(R.id.message_view_container, fragment, FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER)
}
- messageViewFragment = fragment
+ messageViewContainerFragment = fragment
+
+ messageListFragment?.let { messageListFragment ->
+ fragment.setViewModel(messageListFragment.viewModel)
+ }
if (displayMode == DisplayMode.SPLIT_VIEW) {
fragment.isActive = true
@@ -1089,7 +1103,7 @@ open class MessageList :
}
private fun showMessageViewPlaceHolder() {
- removeMessageViewFragment()
+ removeMessageViewContainerFragment()
// Add placeholder fragment if necessary
val fragmentManager = supportFragmentManager
@@ -1102,11 +1116,11 @@ open class MessageList :
messageListFragment!!.setActiveMessage(null)
}
- private fun removeMessageViewFragment() {
- if (messageViewFragment != null) {
+ private fun removeMessageViewContainerFragment() {
+ if (messageViewContainerFragment != null) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
- fragmentTransaction.remove(messageViewFragment!!)
- messageViewFragment = null
+ fragmentTransaction.remove(messageViewContainerFragment!!)
+ messageViewContainerFragment = null
fragmentTransaction.commit()
showDefaultTitleView()
@@ -1129,29 +1143,35 @@ open class MessageList :
}
}
+ override fun closeMessageView() {
+ returnToMessageList()
+ }
+
+ override fun setActiveMessage(messageReference: MessageReference) {
+ val messageListFragment = checkNotNull(messageListFragment)
+
+ messageListFragment.setActiveMessage(messageReference)
+ }
+
override fun showNextMessageOrReturn() {
if (K9.isMessageViewReturnToList || !showLogicalNextMessage()) {
- if (displayMode == DisplayMode.SPLIT_VIEW) {
- showMessageViewPlaceHolder()
- } else {
- showMessageList()
- }
+ returnToMessageList()
}
}
- private fun showLogicalNextMessage(): Boolean {
- var result = false
- if (lastDirection == NEXT) {
- result = showNextMessage()
- } else if (lastDirection == PREVIOUS) {
- result = showPreviousMessage()
+ private fun returnToMessageList() {
+ if (displayMode == DisplayMode.SPLIT_VIEW) {
+ showMessageViewPlaceHolder()
+ } else {
+ showMessageList()
}
+ }
- if (!result) {
- result = showNextMessage() || showPreviousMessage()
+ private fun showLogicalNextMessage(): Boolean {
+ return when (lastDirection) {
+ Direction.NEXT -> showNextMessage()
+ Direction.PREVIOUS -> showPreviousMessage()
}
-
- return result
}
override fun setProgress(enable: Boolean) {
@@ -1159,25 +1179,15 @@ open class MessageList :
}
private fun showNextMessage(): Boolean {
- val ref = messageViewFragment!!.messageReference
- if (ref != null) {
- if (messageListFragment!!.openNext(ref)) {
- lastDirection = NEXT
- return true
- }
- }
- return false
+ val messageViewContainerFragment = checkNotNull(messageViewContainerFragment)
+
+ return messageViewContainerFragment.showNextMessage()
}
private fun showPreviousMessage(): Boolean {
- val ref = messageViewFragment!!.messageReference
- if (ref != null) {
- if (messageListFragment!!.openPrevious(ref)) {
- lastDirection = PREVIOUS
- return true
- }
- }
- return false
+ val messageViewContainerFragment = checkNotNull(messageViewContainerFragment)
+
+ return messageViewContainerFragment.showPreviousMessage()
}
private fun showMessageList() {
@@ -1186,7 +1196,7 @@ open class MessageList :
displayMode = DisplayMode.MESSAGE_LIST
viewSwitcher!!.showFirstView()
- messageViewFragment?.isActive = false
+ messageViewContainerFragment?.isActive = false
messageListFragment!!.isActive = true
messageListFragment!!.setActiveMessage(null)
@@ -1208,11 +1218,11 @@ open class MessageList :
}
private fun showMessageView() {
- val messageViewFragment = checkNotNull(this.messageViewFragment)
+ val messageViewContainerFragment = checkNotNull(this.messageViewContainerFragment)
displayMode = DisplayMode.MESSAGE_VIEW
messageListFragment?.isActive = false
- messageViewFragment.isActive = true
+ messageViewContainerFragment.isActive = true
if (!messageListWasDisplayed) {
viewSwitcher!!.animateFirstView = false
@@ -1238,7 +1248,7 @@ open class MessageList :
override fun onSwitchComplete(displayedChild: Int) {
if (displayedChild == 0) {
- removeMessageViewFragment()
+ removeMessageViewContainerFragment()
}
}
@@ -1276,8 +1286,8 @@ open class MessageList :
if (requestCode and REQUEST_FLAG_PENDING_INTENT != 0) {
val originalRequestCode = requestCode xor REQUEST_FLAG_PENDING_INTENT
- if (messageViewFragment != null) {
- messageViewFragment!!.onPendingIntentResult(originalRequestCode, resultCode, data)
+ if (messageViewContainerFragment != null) {
+ messageViewContainerFragment!!.onPendingIntentResult(originalRequestCode, resultCode, data)
}
}
}
@@ -1387,16 +1397,11 @@ open class MessageList :
private const val STATE_DISPLAY_MODE = "displayMode"
private const val STATE_MESSAGE_VIEW_ONLY = "messageViewOnly"
private const val STATE_MESSAGE_LIST_WAS_DISPLAYED = "messageListWasDisplayed"
- private const val STATE_FIRST_BACK_STACK_ID = "firstBackstackId"
private const val FIRST_FRAGMENT_TRANSACTION = "first"
- private const val FRAGMENT_TAG_MESSAGE_VIEW = "MessageViewFragment"
+ private const val FRAGMENT_TAG_MESSAGE_VIEW_CONTAINER = "MessageViewContainerFragment"
private const val FRAGMENT_TAG_PLACEHOLDER = "MessageViewPlaceholder"
- // Used for navigating to next/previous message
- private const val PREVIOUS = 1
- private const val NEXT = 2
-
private const val REQUEST_CODE_MASK = 0xFFFF0000.toInt()
private const val REQUEST_FLAG_PENDING_INTENT = 1 shl 15
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
index 0d2701333b..5e61517c89 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
@@ -66,7 +66,7 @@ class MessageListFragment :
ConfirmationDialogFragmentListener,
MessageListItemActionListener {
- private val viewModel: MessageListViewModel by viewModel()
+ val viewModel: MessageListViewModel by viewModel()
private val sortTypeToastProvider: SortTypeToastProvider by inject()
private val folderNameFormatterFactory: FolderNameFormatterFactory by inject()
private val folderNameFormatter: FolderNameFormatter by lazy { folderNameFormatterFactory.create(requireContext()) }
@@ -1299,30 +1299,6 @@ class MessageListFragment :
}
}
- fun openPrevious(messageReference: MessageReference): Boolean {
- val position = getPosition(messageReference)
- if (position <= 0) return false
-
- openMessageAtPosition(position - 1)
- return true
- }
-
- fun openNext(messageReference: MessageReference): Boolean {
- val position = getPosition(messageReference)
- if (position < 0 || position == adapter.count - 1) return false
-
- openMessageAtPosition(position + 1)
- return true
- }
-
- fun isFirst(messageReference: MessageReference): Boolean {
- return adapter.isEmpty || messageReference == getReferenceForPosition(0)
- }
-
- fun isLast(messageReference: MessageReference): Boolean {
- return adapter.isEmpty || messageReference == getReferenceForPosition(adapter.count - 1)
- }
-
private fun getReferenceForPosition(position: Int): MessageReference {
val item = adapter.getItem(position)
return MessageReference(item.account.uuid, item.folderId, item.messageUid)
@@ -1349,14 +1325,6 @@ class MessageListFragment :
fragmentListener.openMessage(messageReference)
}
- private fun getPosition(messageReference: MessageReference): Int {
- return adapter.messages.indexOfFirst { messageListItem ->
- messageListItem.account.uuid == messageReference.accountUuid &&
- messageListItem.folderId == messageReference.folderId &&
- messageListItem.messageUid == messageReference.uid
- }
- }
-
fun onReverseSort() {
changeSort(sortType)
}
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/Direction.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/Direction.kt
new file mode 100644
index 0000000000..62431d3871
--- /dev/null
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/Direction.kt
@@ -0,0 +1,6 @@
+package com.fsck.k9.ui.messageview
+
+enum class Direction {
+ PREVIOUS,
+ NEXT
+}
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewContainerFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewContainerFragment.kt
new file mode 100644
index 0000000000..6c4e79a388
--- /dev/null
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewContainerFragment.kt
@@ -0,0 +1,311 @@
+package com.fsck.k9.ui.messageview
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import androidx.viewpager2.widget.ViewPager2
+import com.fsck.k9.controller.MessageReference
+import com.fsck.k9.ui.R
+import com.fsck.k9.ui.messagelist.MessageListItem
+import com.fsck.k9.ui.messagelist.MessageListViewModel
+import com.fsck.k9.ui.withArguments
+
+/**
+ * A fragment that uses [ViewPager2] to allow the user to swipe between messages.
+ *
+ * Individual messages are displayed using a [MessageViewFragment].
+ */
+class MessageViewContainerFragment : Fragment() {
+ var isActive: Boolean = false
+ set(value) {
+ field = value
+ setMenuVisibility(value)
+ }
+
+ lateinit var messageReference: MessageReference
+ private set
+
+ var lastDirection: Direction? = null
+ private set
+
+ private lateinit var fragmentListener: MessageViewContainerListener
+ private lateinit var viewPager: ViewPager2
+ private lateinit var adapter: MessageViewContainerAdapter
+
+ private var currentPosition: Int? = null
+
+ private val messageViewFragment: MessageViewFragment
+ get() {
+ check(isResumed)
+ val itemId = adapter.getItemId(messageReference)
+
+ // ViewPager2/FragmentStateAdapter don't provide an easy way to get hold of the Fragment for the active
+ // page. So we're using an implementation detail (the fragment tag) to find the fragment.
+ return childFragmentManager.findFragmentByTag("f$itemId") as MessageViewFragment
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setHasOptionsMenu(true)
+
+ if (savedInstanceState == null) {
+ messageReference = MessageReference.parse(arguments?.getString(ARG_REFERENCE))
+ ?: error("Missing argument $ARG_REFERENCE")
+ } else {
+ messageReference = MessageReference.parse(savedInstanceState.getString(STATE_MESSAGE_REFERENCE))
+ ?: error("Missing state $STATE_MESSAGE_REFERENCE")
+
+ lastDirection = savedInstanceState.getSerializable(STATE_LAST_DIRECTION) as Direction?
+ }
+
+ adapter = MessageViewContainerAdapter(this)
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+
+ fragmentListener = try {
+ context as MessageViewContainerListener
+ } catch (e: ClassCastException) {
+ throw ClassCastException("This fragment must be attached to a MessageViewContainerListener")
+ }
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ val view = inflater.inflate(R.layout.message_view_container, container, false)
+
+ viewPager = view.findViewById(R.id.message_viewpager)
+ viewPager.isUserInputEnabled = true
+ viewPager.offscreenPageLimit = ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT
+ viewPager.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.HORIZONTAL))
+ viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
+ // The message list is updated each time the active message is changed. To avoid message list updates
+ // during the animation, we only set the active message after the animation has finished.
+ override fun onPageScrollStateChanged(state: Int) {
+ if (state == ViewPager2.SCROLL_STATE_IDLE) {
+ setActiveMessage(viewPager.currentItem)
+ }
+ }
+
+ override fun onPageSelected(position: Int) {
+ if (viewPager.scrollState == ViewPager2.SCROLL_STATE_IDLE) {
+ setActiveMessage(position)
+ }
+ }
+ })
+
+ return view
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putString(STATE_MESSAGE_REFERENCE, messageReference.toIdentityString())
+ outState.putSerializable(STATE_LAST_DIRECTION, lastDirection)
+ }
+
+ fun setViewModel(viewModel: MessageListViewModel) {
+ viewModel.getMessageListLiveData().observe(this) { messageListInfo ->
+ updateMessageList(messageListInfo.messageListItems)
+ }
+ }
+
+ private fun updateMessageList(messageListItems: List) {
+ if (messageListItems.isEmpty() || messageListItems.none { it.messageReference == messageReference }) {
+ fragmentListener.closeMessageView()
+ return
+ }
+
+ adapter.messageList = messageListItems
+
+ // We only set the adapter on ViewPager2 after the message list has been loaded. This way ViewPager2 can
+ // restore its saved state after a configuration change.
+ if (viewPager.adapter == null) {
+ viewPager.adapter = adapter
+ }
+
+ val position = adapter.getPosition(messageReference)
+ viewPager.setCurrentItem(position, false)
+ }
+
+ private fun setActiveMessage(position: Int) {
+ rememberNavigationDirection(position, messageReference)
+
+ messageReference = adapter.getMessageReference(position)
+
+ fragmentListener.setActiveMessage(messageReference)
+ }
+
+ private fun rememberNavigationDirection(newPosition: Int, currentMessageReference: MessageReference) {
+ // When messages are added or removed from the list, the current position will change even though we're still
+ // displaying the same message. In those cases we don't want to update `lastDirection`.
+ val newMessageReference = adapter.getMessageReference(newPosition)
+ if (newMessageReference == currentMessageReference) {
+ currentPosition = newPosition
+ return
+ }
+
+ currentPosition?.let { currentPosition ->
+ lastDirection = if (newPosition < currentPosition) Direction.PREVIOUS else Direction.NEXT
+ }
+ currentPosition = newPosition
+ }
+
+ fun showPreviousMessage(): Boolean {
+ val newPosition = viewPager.currentItem - 1
+ return if (newPosition >= 0) {
+ setActiveMessage(newPosition)
+
+ val smoothScroll = true
+ viewPager.setCurrentItem(newPosition, smoothScroll)
+ true
+ } else {
+ false
+ }
+ }
+
+ fun showNextMessage(): Boolean {
+ val newPosition = viewPager.currentItem + 1
+ return if (newPosition < adapter.itemCount) {
+ setActiveMessage(newPosition)
+
+ val smoothScroll = true
+ viewPager.setCurrentItem(newPosition, smoothScroll)
+ true
+ } else {
+ false
+ }
+ }
+
+ fun onToggleFlagged() {
+ messageViewFragment.onToggleFlagged()
+ }
+
+ fun onMove() {
+ messageViewFragment.onMove()
+ }
+
+ fun onArchive() {
+ messageViewFragment.onArchive()
+ }
+
+ fun onCopy() {
+ messageViewFragment.onCopy()
+ }
+
+ fun onToggleRead() {
+ messageViewFragment.onToggleRead()
+ }
+
+ fun onForward() {
+ messageViewFragment.onForward()
+ }
+
+ fun onReplyAll() {
+ messageViewFragment.onReplyAll()
+ }
+
+ fun onReply() {
+ messageViewFragment.onReply()
+ }
+
+ fun onDelete() {
+ messageViewFragment.onDelete()
+ }
+
+ fun onPendingIntentResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ messageViewFragment.onPendingIntentResult(requestCode, resultCode, data)
+ }
+
+ private class MessageViewContainerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
+ var messageList: List = emptyList()
+ set(value) {
+ val diffResult = DiffUtil.calculateDiff(
+ MessageListDiffCallback(oldMessageList = messageList, newMessageList = value)
+ )
+
+ field = value
+
+ diffResult.dispatchUpdatesTo(this)
+ }
+
+ override fun getItemCount(): Int {
+ return messageList.size
+ }
+
+ override fun getItemId(position: Int): Long {
+ return messageList[position].uniqueId
+ }
+
+ override fun containsItem(itemId: Long): Boolean {
+ return messageList.any { it.uniqueId == itemId }
+ }
+
+ override fun createFragment(position: Int): Fragment {
+ check(position in messageList.indices)
+
+ val messageReference = messageList[position].messageReference
+ return MessageViewFragment.newInstance(messageReference)
+ }
+
+ fun getMessageReference(position: Int): MessageReference {
+ check(position in messageList.indices)
+
+ return messageList[position].messageReference
+ }
+
+ fun getPosition(messageReference: MessageReference): Int {
+ return messageList.indexOfFirst { it.messageReference == messageReference }
+ }
+
+ fun getItemId(messageReference: MessageReference): Long {
+ return messageList.first { it.messageReference == messageReference }.uniqueId
+ }
+ }
+
+ private class MessageListDiffCallback(
+ private val oldMessageList: List,
+ private val newMessageList: List
+ ) : DiffUtil.Callback() {
+ override fun getOldListSize(): Int = oldMessageList.size
+
+ override fun getNewListSize(): Int = newMessageList.size
+
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ return oldMessageList[oldItemPosition].uniqueId == newMessageList[newItemPosition].uniqueId
+ }
+
+ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ // Let MessageViewFragment deal with content changes
+ return areItemsTheSame(oldItemPosition, newItemPosition)
+ }
+ }
+
+ interface MessageViewContainerListener {
+ fun closeMessageView()
+ fun setActiveMessage(messageReference: MessageReference)
+ }
+
+ companion object {
+ private const val ARG_REFERENCE = "reference"
+
+ private const val STATE_MESSAGE_REFERENCE = "messageReference"
+ private const val STATE_LAST_DIRECTION = "lastDirection"
+
+ fun newInstance(reference: MessageReference): MessageViewContainerFragment {
+ return MessageViewContainerFragment().withArguments(
+ ARG_REFERENCE to reference.toIdentityString()
+ )
+ }
+ }
+}
+
+private val MessageListItem.messageReference: MessageReference
+ get() = MessageReference(account.uuid, folderId, messageUid)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
index d3045ab25b..5fd7f706b6 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
@@ -90,17 +90,6 @@ class MessageViewFragment :
private var currentAttachmentViewInfo: AttachmentViewInfo? = null
private var isDeleteMenuItemDisabled: Boolean = false
- /**
- * Set this to `true` when the fragment should be considered active. When active, the fragment adds its actions to
- * the toolbar. When inactive, the fragment won't add its actions to the toolbar, even it is still visible, e.g. as
- * part of an animation.
- */
- var isActive: Boolean = false
- set(value) {
- field = value
- invalidateMenu()
- }
-
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -191,8 +180,6 @@ class MessageViewFragment :
}
override fun onPrepareOptionsMenu(menu: Menu) {
- if (!isActive) return
-
menu.findItem(R.id.delete).apply {
isVisible = K9.isMessageViewDeleteActionVisible
isEnabled = !isDeleteMenuItemDisabled
diff --git a/app/ui/legacy/src/main/res/layout/message_view_container.xml b/app/ui/legacy/src/main/res/layout/message_view_container.xml
new file mode 100644
index 0000000000..e8b24d6cb9
--- /dev/null
+++ b/app/ui/legacy/src/main/res/layout/message_view_container.xml
@@ -0,0 +1,5 @@
+
+
--
GitLab
From 3e90e92b2e1a2362f7a7a667e5ac2bf1b94da6d5 Mon Sep 17 00:00:00 2001
From: cketti
Date: Mon, 25 Jul 2022 18:40:18 +0200
Subject: [PATCH 44/47] Only mark a message as read when it is the active
message
Previously we marked a message as read when loading it for viewing. But with swiping between messages we could now load a message before it is considered the active message.
---
.../k9/controller/MessagingController.java | 25 +++++++++----
.../k9/ui/messageview/MessageViewFragment.kt | 37 +++++++++++++++++++
2 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java
index 5165f2b52c..409dedc6dd 100644
--- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java
+++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java
@@ -1309,9 +1309,6 @@ public class MessagingController {
fp.add(FetchProfile.Item.BODY);
localFolder.fetch(Collections.singletonList(message), fp, null);
- notificationController.removeNewMailNotification(account, message.makeMessageReference());
- markMessageAsOpened(account, message);
-
return message;
}
@@ -1333,7 +1330,15 @@ public class MessagingController {
return message;
}
- private void markMessageAsOpened(Account account, LocalMessage message) throws MessagingException {
+ public void markMessageAsOpened(Account account, LocalMessage message) {
+ put("markMessageAsOpened", null, () -> {
+ markMessageAsOpenedBlocking(account, message);
+ });
+ }
+
+ private void markMessageAsOpenedBlocking(Account account, LocalMessage message) {
+ notificationController.removeNewMailNotification(account,message.makeMessageReference());
+
if (!message.isSet(Flag.SEEN)) {
if (account.isMarkMessageAsReadOnView()) {
markMessageAsReadOnView(account, message);
@@ -1345,11 +1350,15 @@ public class MessagingController {
}
}
- private void markMessageAsReadOnView(Account account, LocalMessage message) throws MessagingException {
- List messageIds = Collections.singletonList(message.getDatabaseId());
- setFlag(account, messageIds, Flag.SEEN, true);
+ private void markMessageAsReadOnView(Account account, LocalMessage message) {
+ try {
+ List messageIds = Collections.singletonList(message.getDatabaseId());
+ setFlag(account, messageIds, Flag.SEEN, true);
- message.setFlagInternal(Flag.SEEN, true);
+ message.setFlagInternal(Flag.SEEN, true);
+ } catch (MessagingException e) {
+ Timber.e(e, "Error while marking message as read");
+ }
}
private void markMessageAsNotNew(Account account, LocalMessage message) {
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
index 5fd7f706b6..9eec05e6f5 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.kt
@@ -89,6 +89,7 @@ class MessageViewFragment :
private var currentAttachmentViewInfo: AttachmentViewInfo? = null
private var isDeleteMenuItemDisabled: Boolean = false
+ private var wasMessageMarkedAsOpened: Boolean = false
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -108,6 +109,10 @@ class MessageViewFragment :
messageReference = MessageReference.parse(arguments?.getString(ARG_REFERENCE))
?: error("Invalid argument '$ARG_REFERENCE'")
+ if (savedInstanceState != null) {
+ wasMessageMarkedAsOpened = savedInstanceState.getBoolean(STATE_WAS_MESSAGE_MARKED_AS_OPENED)
+ }
+
messageCryptoPresenter = MessageCryptoPresenter(messageCryptoMvpView)
messageLoaderHelper = messageLoaderHelperFactory.createForMessageView(
context = requireContext().applicationContext,
@@ -164,8 +169,24 @@ class MessageViewFragment :
invalidateMenu()
}
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putBoolean(STATE_WAS_MESSAGE_MARKED_AS_OPENED, wasMessageMarkedAsOpened)
+ }
+
+ override fun setMenuVisibility(menuVisible: Boolean) {
+ super.setMenuVisibility(menuVisible)
+
+ // When the menu is hidden, the message associated with this fragment is no longer active. If the user returns
+ // to it, we want to mark the message as opened again.
+ if (!menuVisible) {
+ wasMessageMarkedAsOpened = false
+ }
+ }
+
override fun onResume() {
super.onResume()
+ markMessageAsOpened()
messageCryptoPresenter.onResume()
}
@@ -737,6 +758,15 @@ class MessageViewFragment :
messageTopView.refreshAttachmentThumbnail(attachment)
}
+ private fun markMessageAsOpened() {
+ val message = message ?: return
+
+ if (!wasMessageMarkedAsOpened) {
+ messagingController.markMessageAsOpened(account, message)
+ wasMessageMarkedAsOpened = true
+ }
+ }
+
private val messageCryptoMvpView: MessageCryptoMvpView = object : MessageCryptoMvpView {
override fun redisplayMessage() {
messageLoaderHelper.asyncReloadMessage()
@@ -807,6 +837,11 @@ class MessageViewFragment :
displayHeaderForLoadingMessage(message)
messageTopView.setToLoadingState()
showProgressThreshold = null
+
+ // Only mark the message as opened when the fragment is resumed, i.e. when this is the active message.
+ if (isResumed) {
+ markMessageAsOpened()
+ }
}
override fun onMessageDataLoadFailed() {
@@ -908,6 +943,8 @@ class MessageViewFragment :
private const val ARG_REFERENCE = "reference"
+ private const val STATE_WAS_MESSAGE_MARKED_AS_OPENED = "wasMessageMarkedAsOpened"
+
private const val ACTIVITY_CHOOSE_FOLDER_MOVE = 1
private const val ACTIVITY_CHOOSE_FOLDER_COPY = 2
private const val REQUEST_CODE_CREATE_DOCUMENT = 3
--
GitLab
From 6992cc732f1550c1fa5573964f02c231a80e4b5d Mon Sep 17 00:00:00 2001
From: cketti
Date: Mon, 1 Aug 2022 17:22:11 +0200
Subject: [PATCH 45/47] Handle view pager swipe vs. WebView horizontal
scrolling
---
.../k9/ui/messageview/TouchInterceptView.kt | 69 +++++++++++++++++++
app/ui/legacy/src/main/res/layout/message.xml | 1 +
.../res/layout/message_view_container.xml | 12 +++-
3 files changed, 79 insertions(+), 3 deletions(-)
create mode 100644 app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/TouchInterceptView.kt
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/TouchInterceptView.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/TouchInterceptView.kt
new file mode 100644
index 0000000000..df6fe2c015
--- /dev/null
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/messageview/TouchInterceptView.kt
@@ -0,0 +1,69 @@
+package com.fsck.k9.ui.messageview
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import android.webkit.WebView
+import android.widget.FrameLayout
+import android.widget.ScrollView
+import com.fsck.k9.ui.R
+import kotlin.math.absoluteValue
+
+/**
+ * A view that listens to touch events to make sure [R.id.message_viewpager] doesn't get to see events that should be
+ * handled by [R.id.message_scrollview] or [R.id.message_content].
+ *
+ * We allow the view pager to listen to touch events until we know it's a gesture that will be handled by the scroll
+ * view or the web view.
+ * If we instead hid events from the view pager until we knew the scroll view or the web view don't want to handle the
+ * gesture, we'd risk minimal fling gestures ([MotionEvent.ACTION_DOWN], [MotionEvent.ACTION_MOVE],
+ * [MotionEvent.ACTION_UP]) not working because the single [MotionEvent.ACTION_MOVE] event would be invisible to the
+ * view pager.
+ */
+class TouchInterceptView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
+ private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
+
+ private var initialX: Float = 0f
+ private var initialY: Float = 0f
+
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ handleOnInterceptTouchEvent(event)
+ return super.onInterceptTouchEvent(event)
+ }
+
+ private fun handleOnInterceptTouchEvent(event: MotionEvent) {
+ val webView = findViewById(R.id.message_content) ?: return
+ val scrollView = findViewById(R.id.message_scrollview) ?: return
+ val scrollViewParent = scrollView.parent ?: return
+
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ initialX = event.x
+ initialY = event.y
+ scrollViewParent.requestDisallowInterceptTouchEvent(false)
+ }
+ MotionEvent.ACTION_POINTER_DOWN -> {
+ // If a second finger/pointer is involved, never allow parents of the ScrollView to intercept
+ scrollViewParent.requestDisallowInterceptTouchEvent(true)
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val deltaX = initialX - event.x
+ val deltaY = initialY - event.y
+
+ val absoluteDeltaX = deltaX.absoluteValue
+ val absoluteDeltaY = deltaY.absoluteValue
+
+ if (absoluteDeltaY > touchSlop && absoluteDeltaY > absoluteDeltaX &&
+ (scrollView.canScrollVertically(deltaY.toInt()) || webView.canScrollVertically(deltaY.toInt()))
+ ) {
+ scrollViewParent.requestDisallowInterceptTouchEvent(true)
+ } else if (absoluteDeltaX > touchSlop && absoluteDeltaX > absoluteDeltaY &&
+ webView.canScrollHorizontally(deltaX.toInt())
+ ) {
+ scrollViewParent.requestDisallowInterceptTouchEvent(true)
+ }
+ }
+ }
+ }
+}
diff --git a/app/ui/legacy/src/main/res/layout/message.xml b/app/ui/legacy/src/main/res/layout/message.xml
index 2494b31a1b..4d8f1f43d9 100644
--- a/app/ui/legacy/src/main/res/layout/message.xml
+++ b/app/ui/legacy/src/main/res/layout/message.xml
@@ -12,6 +12,7 @@
tools:context=".messageview.MessageViewFragment">
diff --git a/app/ui/legacy/src/main/res/layout/message_view_container.xml b/app/ui/legacy/src/main/res/layout/message_view_container.xml
index e8b24d6cb9..99ce488fa2 100644
--- a/app/ui/legacy/src/main/res/layout/message_view_container.xml
+++ b/app/ui/legacy/src/main/res/layout/message_view_container.xml
@@ -1,5 +1,11 @@
-
+ android:layout_height="match_parent">
+
+
+
+
\ No newline at end of file
--
GitLab
From 8b546cadd6e23d64d88814f3fdf37b4c1fd6331a Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 3 Aug 2022 21:44:35 +0200
Subject: [PATCH 46/47] Scroll message list to active message
---
.../fsck/k9/fragment/MessageListFragment.kt | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
index 5e61517c89..b998ae2059 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt
@@ -1563,6 +1563,28 @@ class MessageListFragment :
if (::adapter.isInitialized) {
adapter.activeMessage = activeMessage
adapter.notifyDataSetChanged()
+
+ if (messageReference != null) {
+ scrollToMessage(messageReference)
+ }
+ }
+ }
+
+ private fun scrollToMessage(messageReference: MessageReference) {
+ val position = getPosition(messageReference)
+ val viewPosition = adapterToListViewPosition(position)
+ if (viewPosition != AdapterView.INVALID_POSITION &&
+ (viewPosition <= listView.firstVisiblePosition || viewPosition >= listView.lastVisiblePosition)
+ ) {
+ listView.smoothScrollToPosition(viewPosition)
+ }
+ }
+
+ private fun getPosition(messageReference: MessageReference): Int {
+ return adapter.messages.indexOfFirst { messageListItem ->
+ messageListItem.account.uuid == messageReference.accountUuid &&
+ messageListItem.folderId == messageReference.folderId &&
+ messageListItem.messageUid == messageReference.uid
}
}
--
GitLab
From ee7dcd07c64bd550f32276503d44386d04586be6 Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 4 Aug 2022 17:27:43 +0200
Subject: [PATCH 47/47] Version 6.300
---
app/k9mail/build.gradle | 2 +-
app/ui/legacy/src/main/res/raw/changelog_master.xml | 6 ++++++
fastlane/metadata/android/en-US/changelogs/33000.txt | 4 ++++
3 files changed, 11 insertions(+), 1 deletion(-)
create mode 100644 fastlane/metadata/android/en-US/changelogs/33000.txt
diff --git a/app/k9mail/build.gradle b/app/k9mail/build.gradle
index 58f27128ff..ca0ee46377 100644
--- a/app/k9mail/build.gradle
+++ b/app/k9mail/build.gradle
@@ -48,7 +48,7 @@ android {
testApplicationId "com.fsck.k9.tests"
versionCode 33000
- versionName '6.300-SNAPSHOT'
+ versionName '6.300'
// Keep in sync with the resource string array 'supported_languages'
resConfigs "in", "br", "ca", "cs", "cy", "da", "de", "et", "en", "en_GB", "es", "eo", "eu", "fr", "gd", "gl",
diff --git a/app/ui/legacy/src/main/res/raw/changelog_master.xml b/app/ui/legacy/src/main/res/raw/changelog_master.xml
index a3e5eed962..4b7fdf4b09 100644
--- a/app/ui/legacy/src/main/res/raw/changelog_master.xml
+++ b/app/ui/legacy/src/main/res/raw/changelog_master.xml
@@ -5,6 +5,12 @@
Locale-specific versions are kept in res/raw-/changelog.xml.
-->
+
+ Added support for swiping between messages
+ Fixed multiple bugs when there are notifications for more than 8 messages
+ Fixed bug that could lead to broken attachment names when large messages were only partially downloaded (IMAP)
+ Added Western Frisian translation
+
Increased timeout when sending messages because some users have reported problems with sending large attachments
Allow all URI schemes in HTML links
diff --git a/fastlane/metadata/android/en-US/changelogs/33000.txt b/fastlane/metadata/android/en-US/changelogs/33000.txt
new file mode 100644
index 0000000000..8b5c3a67c0
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/33000.txt
@@ -0,0 +1,4 @@
+- Added support for swiping between messages
+- Fixed multiple bugs when there are notifications for more than 8 messages
+- Fixed bug that could lead to broken attachment names when large messages were only partially downloaded (IMAP)
+- Added Western Frisian translation
--
GitLab