Loading app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/EmailCache.kt +49 −35 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2023 * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package com.fsck.k9.ui.messagelist import android.content.Context Loading @@ -6,22 +22,21 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.fsck.k9.Account import com.fsck.k9.controller.MessageReference import com.fsck.k9.search.getAccounts import com.google.gson.Gson import com.google.gson.TypeAdapter import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import com.squareup.moshi.Moshi import java.io.IOException import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking import timber.log.Timber class EmailCache constructor(private val context: Context, private val moshi: Moshi, private val gson: Gson) { class EmailCache constructor(private val context: Context, private val gson: Gson) { companion object { private const val MAX_CACHE_SIZE = 20 private const val preferenceDataStoreName = "emailCache" private val Context.emailCacheDataStore by preferencesDataStore(preferenceDataStoreName) } Loading @@ -29,27 +44,19 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo private val MAIL_LIST_KEY = stringPreferencesKey("mail_list") private var isCacheShown = false suspend fun getCachedMails(accountList: List<Account>? = null): List<MessageListItem>? { suspend fun getCachedMails(): List<MessageListItem>? { if (isCacheShown) { return null } return runBlocking { fetchCachedMail() } } private suspend fun fetchCachedMail(): List<MessageListItem>? { val listType = object : TypeToken<List<MessageListItem>>() {}.type val mailListJson = context.emailCacheDataStore.data.map { it[MAIL_LIST_KEY] }.firstOrNull() Timber.d("email data: $mailListJson") return gson.fromJson(mailListJson, listType) } suspend fun saveLatestMails(mailList: List<MessageListItem>) { // val type = Types.newParameterizedType(List::class.java, MessageListItem::class.java) // val adapter = moshi.adapter<List<MessageListItem>>(type) // val mailListJson = adapter.toJson(mailList) if (isCacheShown) return isCacheShown = true val cachedMailsWithLatest = getLatestMails(mailList) Loading @@ -58,18 +65,7 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo context.emailCacheDataStore.edit { it[MAIL_LIST_KEY] = mailListJson } } private suspend fun getLatestMails(mailList: List<MessageListItem>): MutableList<MessageListItem> { var cachedMailsWithLatest = mutableListOf<MessageListItem>() cachedMailsWithLatest.addAll(mailList) fetchCachedMail()?.let { cachedMailsWithLatest.addAll(it) } cachedMailsWithLatest.sortedByDescending { it.messageDate } val lastIndex = if (cachedMailsWithLatest.size < 40) cachedMailsWithLatest.size else 39 cachedMailsWithLatest = cachedMailsWithLatest.subList(0, lastIndex) return cachedMailsWithLatest Timber.d("Saved latest mails in the cache") } suspend fun deleteMail(messages: List<MessageReference>) { Loading @@ -82,27 +78,45 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo } } } private suspend fun fetchCachedMail(): List<MessageListItem>? { val listType = object : TypeToken<List<MessageListItem>>() {}.type val mailListJson = context.emailCacheDataStore.data.map { it[MAIL_LIST_KEY] }.firstOrNull() Timber.d("Cached email data: $mailListJson") return gson.fromJson(mailListJson, listType) } private suspend fun getLatestMails(mailList: List<MessageListItem>): MutableList<MessageListItem> { var cachedMailsWithLatest = mutableListOf<MessageListItem>() cachedMailsWithLatest.addAll(mailList) fetchCachedMail()?.let { cachedMailsWithLatest.addAll(it) } cachedMailsWithLatest.sortedByDescending { it.messageDate } val lastIndex = if (cachedMailsWithLatest.size < MAX_CACHE_SIZE) cachedMailsWithLatest.size else MAX_CACHE_SIZE cachedMailsWithLatest = cachedMailsWithLatest.subList(0, lastIndex) return cachedMailsWithLatest } } internal class CharSequenceTypeAdapter : TypeAdapter<CharSequence?>() { @Throws(IOException::class) override fun write(out: JsonWriter, value: CharSequence?) { override fun write(writer: JsonWriter, value: CharSequence?) { if (value == null) { out.nullValue() writer.nullValue() } else { // Assumes that value complies with CharSequence.toString() contract out.value(value.toString()) writer.value(value.toString()) } } @Throws(IOException::class) override fun read(`in`: JsonReader): CharSequence? { return if (`in`.peek() === JsonToken.NULL) { // Skip the JSON null `in`.skipValue() override fun read(reader: JsonReader): CharSequence? { return if (reader.peek() === JsonToken.NULL) { reader.skipValue() null } else { `in`.nextString() reader.nextString() } } } app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/KoinModule.kt +2 −3 Original line number Diff line number Diff line package com.fsck.k9.ui.messagelist import com.google.gson.Gson import com.google.gson.GsonBuilder import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory Loading @@ -21,7 +20,7 @@ val messageListUiModule = module { } single { Moshi.Builder().add(KotlinJsonAdapterFactory()) .build() } single { GsonBuilder().registerTypeAdapter(CharSequence::class.java, CharSequenceTypeAdapter()).create() } single<EmailCache> { EmailCache(androidContext(), get(), get()) } single<EmailCache> { EmailCache(androidContext(), get()) } factory { MessageListLiveDataFactory(messageListLoader = get(), preferences = get(), messageListRepository = get(), emailCache = get()) } Loading app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListLiveData.kt +2 −1 Original line number Diff line number Diff line Loading @@ -26,9 +26,10 @@ class MessageListLiveData( private fun loadMessageListAsync() { coroutineScope.launch(Dispatchers.Main) { emailCache.getCachedMails(config.search.getAccounts(preferences))?.let { emailCache.getCachedMails()?.let { value = MessageListInfo(it, true) } val messageList = withContext(Dispatchers.IO) { messageListLoader.getMessageList(config) } Loading Loading
app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/EmailCache.kt +49 −35 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2023 * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package com.fsck.k9.ui.messagelist import android.content.Context Loading @@ -6,22 +22,21 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.fsck.k9.Account import com.fsck.k9.controller.MessageReference import com.fsck.k9.search.getAccounts import com.google.gson.Gson import com.google.gson.TypeAdapter import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import com.squareup.moshi.Moshi import java.io.IOException import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking import timber.log.Timber class EmailCache constructor(private val context: Context, private val moshi: Moshi, private val gson: Gson) { class EmailCache constructor(private val context: Context, private val gson: Gson) { companion object { private const val MAX_CACHE_SIZE = 20 private const val preferenceDataStoreName = "emailCache" private val Context.emailCacheDataStore by preferencesDataStore(preferenceDataStoreName) } Loading @@ -29,27 +44,19 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo private val MAIL_LIST_KEY = stringPreferencesKey("mail_list") private var isCacheShown = false suspend fun getCachedMails(accountList: List<Account>? = null): List<MessageListItem>? { suspend fun getCachedMails(): List<MessageListItem>? { if (isCacheShown) { return null } return runBlocking { fetchCachedMail() } } private suspend fun fetchCachedMail(): List<MessageListItem>? { val listType = object : TypeToken<List<MessageListItem>>() {}.type val mailListJson = context.emailCacheDataStore.data.map { it[MAIL_LIST_KEY] }.firstOrNull() Timber.d("email data: $mailListJson") return gson.fromJson(mailListJson, listType) } suspend fun saveLatestMails(mailList: List<MessageListItem>) { // val type = Types.newParameterizedType(List::class.java, MessageListItem::class.java) // val adapter = moshi.adapter<List<MessageListItem>>(type) // val mailListJson = adapter.toJson(mailList) if (isCacheShown) return isCacheShown = true val cachedMailsWithLatest = getLatestMails(mailList) Loading @@ -58,18 +65,7 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo context.emailCacheDataStore.edit { it[MAIL_LIST_KEY] = mailListJson } } private suspend fun getLatestMails(mailList: List<MessageListItem>): MutableList<MessageListItem> { var cachedMailsWithLatest = mutableListOf<MessageListItem>() cachedMailsWithLatest.addAll(mailList) fetchCachedMail()?.let { cachedMailsWithLatest.addAll(it) } cachedMailsWithLatest.sortedByDescending { it.messageDate } val lastIndex = if (cachedMailsWithLatest.size < 40) cachedMailsWithLatest.size else 39 cachedMailsWithLatest = cachedMailsWithLatest.subList(0, lastIndex) return cachedMailsWithLatest Timber.d("Saved latest mails in the cache") } suspend fun deleteMail(messages: List<MessageReference>) { Loading @@ -82,27 +78,45 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo } } } private suspend fun fetchCachedMail(): List<MessageListItem>? { val listType = object : TypeToken<List<MessageListItem>>() {}.type val mailListJson = context.emailCacheDataStore.data.map { it[MAIL_LIST_KEY] }.firstOrNull() Timber.d("Cached email data: $mailListJson") return gson.fromJson(mailListJson, listType) } private suspend fun getLatestMails(mailList: List<MessageListItem>): MutableList<MessageListItem> { var cachedMailsWithLatest = mutableListOf<MessageListItem>() cachedMailsWithLatest.addAll(mailList) fetchCachedMail()?.let { cachedMailsWithLatest.addAll(it) } cachedMailsWithLatest.sortedByDescending { it.messageDate } val lastIndex = if (cachedMailsWithLatest.size < MAX_CACHE_SIZE) cachedMailsWithLatest.size else MAX_CACHE_SIZE cachedMailsWithLatest = cachedMailsWithLatest.subList(0, lastIndex) return cachedMailsWithLatest } } internal class CharSequenceTypeAdapter : TypeAdapter<CharSequence?>() { @Throws(IOException::class) override fun write(out: JsonWriter, value: CharSequence?) { override fun write(writer: JsonWriter, value: CharSequence?) { if (value == null) { out.nullValue() writer.nullValue() } else { // Assumes that value complies with CharSequence.toString() contract out.value(value.toString()) writer.value(value.toString()) } } @Throws(IOException::class) override fun read(`in`: JsonReader): CharSequence? { return if (`in`.peek() === JsonToken.NULL) { // Skip the JSON null `in`.skipValue() override fun read(reader: JsonReader): CharSequence? { return if (reader.peek() === JsonToken.NULL) { reader.skipValue() null } else { `in`.nextString() reader.nextString() } } }
app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/KoinModule.kt +2 −3 Original line number Diff line number Diff line package com.fsck.k9.ui.messagelist import com.google.gson.Gson import com.google.gson.GsonBuilder import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory Loading @@ -21,7 +20,7 @@ val messageListUiModule = module { } single { Moshi.Builder().add(KotlinJsonAdapterFactory()) .build() } single { GsonBuilder().registerTypeAdapter(CharSequence::class.java, CharSequenceTypeAdapter()).create() } single<EmailCache> { EmailCache(androidContext(), get(), get()) } single<EmailCache> { EmailCache(androidContext(), get()) } factory { MessageListLiveDataFactory(messageListLoader = get(), preferences = get(), messageListRepository = get(), emailCache = get()) } Loading
app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListLiveData.kt +2 −1 Original line number Diff line number Diff line Loading @@ -26,9 +26,10 @@ class MessageListLiveData( private fun loadMessageListAsync() { coroutineScope.launch(Dispatchers.Main) { emailCache.getCachedMails(config.search.getAccounts(preferences))?.let { emailCache.getCachedMails()?.let { value = MessageListInfo(it, true) } val messageList = withContext(Dispatchers.IO) { messageListLoader.getMessageList(config) } Loading