Repo created
This commit is contained in:
parent
a629de6271
commit
3cef7c5092
2161 changed files with 246605 additions and 2 deletions
27
app/ui/message-list-widget/build.gradle.kts
Normal file
27
app/ui/message-list-widget/build.gradle.kts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.android)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.app.ui.legacy)
|
||||
implementation(projects.app.core)
|
||||
|
||||
implementation(libs.timber)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.k9mail.ui.widget.list"
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
manifestPlaceholders["appAuthRedirectScheme"] = "FIXME: override this in your app project"
|
||||
}
|
||||
release {
|
||||
manifestPlaceholders["appAuthRedirectScheme"] = "FIXME: override this in your app project"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/ui/message-list-widget/src/main/AndroidManifest.xml
Normal file
10
app/ui/message-list-widget/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
|
||||
<service
|
||||
android:name=".MessageListWidgetService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import org.koin.dsl.module
|
||||
|
||||
val messageListWidgetModule = module {
|
||||
single { MessageListWidgetManager(context = get(), messageListRepository = get(), config = get()) }
|
||||
factory { MessageListLoader(preferences = get(), messageListRepository = get(), messageHelper = get()) }
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import com.fsck.k9.Account.SortType
|
||||
import com.fsck.k9.search.LocalSearch
|
||||
|
||||
internal data class MessageListConfig(
|
||||
val search: LocalSearch,
|
||||
val showingThreadedList: Boolean,
|
||||
val sortType: SortType,
|
||||
val sortAscending: Boolean,
|
||||
val sortDateAscending: Boolean
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import com.fsck.k9.controller.MessageReference
|
||||
|
||||
internal data class MessageListItem(
|
||||
val displayName: String,
|
||||
val displayDate: String,
|
||||
val subject: String,
|
||||
val preview: String,
|
||||
val isRead: Boolean,
|
||||
val hasAttachments: Boolean,
|
||||
val threadCount: Int,
|
||||
val accountColor: Int,
|
||||
val messageReference: MessageReference,
|
||||
val uniqueId: Long,
|
||||
|
||||
val sortSubject: String?,
|
||||
val sortMessageDate: Long,
|
||||
val sortInternalDate: Long,
|
||||
val sortIsStarred: Boolean,
|
||||
val sortDatabaseId: Long
|
||||
)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.controller.MessageReference
|
||||
import com.fsck.k9.helper.MessageHelper
|
||||
import com.fsck.k9.mailstore.MessageDetailsAccessor
|
||||
import com.fsck.k9.mailstore.MessageMapper
|
||||
import com.fsck.k9.ui.helper.DisplayAddressHelper
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
internal class MessageListItemMapper(
|
||||
private val messageHelper: MessageHelper,
|
||||
private val account: Account
|
||||
) : MessageMapper<MessageListItem> {
|
||||
private val calendar: Calendar = Calendar.getInstance()
|
||||
|
||||
override fun map(message: MessageDetailsAccessor): MessageListItem {
|
||||
val fromAddresses = message.fromAddresses
|
||||
val toAddresses = message.toAddresses
|
||||
val previewResult = message.preview
|
||||
val previewText = if (previewResult.isPreviewTextAvailable) previewResult.previewText else ""
|
||||
val uniqueId = createUniqueId(account, message.id)
|
||||
val showRecipients = DisplayAddressHelper.shouldShowRecipients(account, message.folderId)
|
||||
val displayAddress = if (showRecipients) toAddresses.firstOrNull() else fromAddresses.firstOrNull()
|
||||
val displayName = if (showRecipients) {
|
||||
messageHelper.getRecipientDisplayNames(toAddresses.toTypedArray()).toString()
|
||||
} else {
|
||||
messageHelper.getSenderDisplayName(displayAddress).toString()
|
||||
}
|
||||
|
||||
return MessageListItem(
|
||||
displayName = displayName,
|
||||
displayDate = formatDate(message.messageDate),
|
||||
subject = message.subject.orEmpty(),
|
||||
preview = previewText,
|
||||
isRead = message.isRead,
|
||||
hasAttachments = message.hasAttachments,
|
||||
threadCount = message.threadCount,
|
||||
accountColor = account.chipColor,
|
||||
messageReference = MessageReference(account.uuid, message.folderId, message.messageServerId),
|
||||
uniqueId = uniqueId,
|
||||
sortSubject = message.subject,
|
||||
sortMessageDate = message.messageDate,
|
||||
sortInternalDate = message.internalDate,
|
||||
sortIsStarred = message.isStarred,
|
||||
sortDatabaseId = message.id
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatDate(date: Long): String {
|
||||
calendar.timeInMillis = date
|
||||
val dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH)
|
||||
val month = calendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault())
|
||||
|
||||
return String.format("%d %s", dayOfMonth, month)
|
||||
}
|
||||
|
||||
private fun createUniqueId(account: Account, messageId: Long): Long {
|
||||
return ((account.accountNumber + 1).toLong() shl 52) + messageId
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Account.SortType
|
||||
import com.fsck.k9.Preferences
|
||||
import com.fsck.k9.helper.MessageHelper
|
||||
import com.fsck.k9.mailstore.MessageColumns
|
||||
import com.fsck.k9.mailstore.MessageListRepository
|
||||
import com.fsck.k9.search.SqlQueryBuilder
|
||||
import com.fsck.k9.search.getAccounts
|
||||
import timber.log.Timber
|
||||
|
||||
internal class MessageListLoader(
|
||||
private val preferences: Preferences,
|
||||
private val messageListRepository: MessageListRepository,
|
||||
private val messageHelper: MessageHelper
|
||||
) {
|
||||
|
||||
fun getMessageList(config: MessageListConfig): List<MessageListItem> {
|
||||
return try {
|
||||
getMessageListInfo(config)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error while fetching message list")
|
||||
|
||||
// TODO: Return an error object instead of an empty list
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMessageListInfo(config: MessageListConfig): List<MessageListItem> {
|
||||
val accounts = config.search.getAccounts(preferences)
|
||||
val messageListItems = accounts
|
||||
.flatMap { account ->
|
||||
loadMessageListForAccount(account, config)
|
||||
}
|
||||
.sortedWith(config)
|
||||
|
||||
return messageListItems
|
||||
}
|
||||
|
||||
private fun loadMessageListForAccount(account: Account, config: MessageListConfig): List<MessageListItem> {
|
||||
val accountUuid = account.uuid
|
||||
val sortOrder = buildSortOrder(config)
|
||||
val mapper = MessageListItemMapper(messageHelper, account)
|
||||
|
||||
return if (config.showingThreadedList) {
|
||||
val (selection, selectionArgs) = buildSelection(account, config)
|
||||
messageListRepository.getThreadedMessages(accountUuid, selection, selectionArgs, sortOrder, mapper)
|
||||
} else {
|
||||
val (selection, selectionArgs) = buildSelection(account, config)
|
||||
messageListRepository.getMessages(accountUuid, selection, selectionArgs, sortOrder, mapper)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSelection(account: Account, config: MessageListConfig): Pair<String, Array<String>> {
|
||||
val query = StringBuilder()
|
||||
val queryArgs = mutableListOf<String>()
|
||||
|
||||
SqlQueryBuilder.buildWhereClause(config.search.conditions, query, queryArgs)
|
||||
|
||||
val selection = query.toString()
|
||||
val selectionArgs = queryArgs.toTypedArray()
|
||||
|
||||
return selection to selectionArgs
|
||||
}
|
||||
|
||||
private fun buildSortOrder(config: MessageListConfig): String {
|
||||
val sortColumn = when (config.sortType) {
|
||||
SortType.SORT_ARRIVAL -> MessageColumns.INTERNAL_DATE
|
||||
SortType.SORT_ATTACHMENT -> "(${MessageColumns.ATTACHMENT_COUNT} < 1)"
|
||||
SortType.SORT_FLAGGED -> "(${MessageColumns.FLAGGED} != 1)"
|
||||
SortType.SORT_SENDER -> MessageColumns.SENDER_LIST // FIXME
|
||||
SortType.SORT_SUBJECT -> "${MessageColumns.SUBJECT} COLLATE NOCASE"
|
||||
SortType.SORT_UNREAD -> MessageColumns.READ
|
||||
SortType.SORT_DATE -> MessageColumns.DATE
|
||||
else -> MessageColumns.DATE
|
||||
}
|
||||
|
||||
val sortDirection = if (config.sortAscending) " ASC" else " DESC"
|
||||
val secondarySort = if (config.sortType == SortType.SORT_DATE || config.sortType == SortType.SORT_ARRIVAL) {
|
||||
""
|
||||
} else {
|
||||
if (config.sortDateAscending) {
|
||||
"${MessageColumns.DATE} ASC, "
|
||||
} else {
|
||||
"${MessageColumns.DATE} DESC, "
|
||||
}
|
||||
}
|
||||
|
||||
return "$sortColumn$sortDirection, $secondarySort${MessageColumns.ID} DESC"
|
||||
}
|
||||
|
||||
private fun List<MessageListItem>.sortedWith(config: MessageListConfig): List<MessageListItem> {
|
||||
val comparator = when (config.sortType) {
|
||||
SortType.SORT_DATE -> {
|
||||
compareBy(config.sortAscending) { it.sortMessageDate }
|
||||
}
|
||||
SortType.SORT_ARRIVAL -> {
|
||||
compareBy(config.sortAscending) { it.sortInternalDate }
|
||||
}
|
||||
SortType.SORT_SUBJECT -> {
|
||||
compareStringBy<MessageListItem>(config.sortAscending) { it.sortSubject.orEmpty() }
|
||||
.thenByDate(config)
|
||||
}
|
||||
SortType.SORT_SENDER -> {
|
||||
compareStringBy<MessageListItem>(config.sortAscending) { it.displayName }
|
||||
.thenByDate(config)
|
||||
}
|
||||
SortType.SORT_UNREAD -> {
|
||||
compareBy<MessageListItem>(config.sortAscending) { it.isRead }
|
||||
.thenByDate(config)
|
||||
}
|
||||
SortType.SORT_FLAGGED -> {
|
||||
compareBy<MessageListItem>(!config.sortAscending) { it.sortIsStarred }
|
||||
.thenByDate(config)
|
||||
}
|
||||
SortType.SORT_ATTACHMENT -> {
|
||||
compareBy<MessageListItem>(!config.sortAscending) { it.hasAttachments }
|
||||
.thenByDate(config)
|
||||
}
|
||||
}.thenByDescending { it.sortDatabaseId }
|
||||
|
||||
return this.sortedWith(comparator)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> compareBy(sortAscending: Boolean, crossinline selector: (T) -> Comparable<*>?): Comparator<T> {
|
||||
return if (sortAscending) {
|
||||
compareBy(selector)
|
||||
} else {
|
||||
compareByDescending(selector)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> compareStringBy(sortAscending: Boolean, crossinline selector: (T) -> String): Comparator<T> {
|
||||
return if (sortAscending) {
|
||||
compareBy(String.CASE_INSENSITIVE_ORDER, selector)
|
||||
} else {
|
||||
compareByDescending(String.CASE_INSENSITIVE_ORDER, selector)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Comparator<MessageListItem>.thenByDate(config: MessageListConfig): Comparator<MessageListItem> {
|
||||
return if (config.sortDateAscending) {
|
||||
thenBy { it.sortMessageDate }
|
||||
} else {
|
||||
thenByDescending { it.sortMessageDate }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.text.SpannableString
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService.RemoteViewsFactory
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.fsck.k9.Account.SortType
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.activity.MessageList
|
||||
import com.fsck.k9.search.LocalSearch
|
||||
import com.fsck.k9.search.SearchAccount
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import com.fsck.k9.ui.R as UiR
|
||||
|
||||
internal class MessageListRemoteViewFactory(private val context: Context) : RemoteViewsFactory, KoinComponent {
|
||||
private val messageListLoader: MessageListLoader by inject()
|
||||
|
||||
private lateinit var unifiedInboxSearch: LocalSearch
|
||||
|
||||
private var messageListItems = emptyList<MessageListItem>()
|
||||
private var senderAboveSubject = false
|
||||
private var readTextColor = 0
|
||||
private var unreadTextColor = 0
|
||||
|
||||
override fun onCreate() {
|
||||
unifiedInboxSearch = SearchAccount.createUnifiedInboxAccount().relatedSearch
|
||||
|
||||
senderAboveSubject = K9.isMessageListSenderAboveSubject
|
||||
readTextColor = ContextCompat.getColor(context, R.color.message_list_widget_text_read)
|
||||
unreadTextColor = ContextCompat.getColor(context, R.color.message_list_widget_text_unread)
|
||||
}
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
loadMessageList()
|
||||
}
|
||||
|
||||
private fun loadMessageList() {
|
||||
// TODO: Use same sort order that is used for the Unified Inbox inside the app
|
||||
val messageListConfig = MessageListConfig(
|
||||
search = unifiedInboxSearch,
|
||||
showingThreadedList = K9.isThreadedViewEnabled,
|
||||
sortType = SortType.SORT_DATE,
|
||||
sortAscending = false,
|
||||
sortDateAscending = false
|
||||
)
|
||||
|
||||
messageListItems = messageListLoader.getMessageList(messageListConfig)
|
||||
}
|
||||
|
||||
override fun onDestroy() = Unit
|
||||
|
||||
override fun getCount(): Int = messageListItems.size
|
||||
|
||||
override fun getViewAt(position: Int): RemoteViews {
|
||||
val remoteView = RemoteViews(context.packageName, R.layout.message_list_widget_list_item)
|
||||
|
||||
val item = messageListItems[position]
|
||||
|
||||
val displayName = if (item.isRead) item.displayName else bold(item.displayName)
|
||||
val subject = if (item.isRead) item.subject else bold(item.subject)
|
||||
|
||||
if (senderAboveSubject) {
|
||||
remoteView.setTextViewText(R.id.sender, displayName)
|
||||
remoteView.setTextViewText(R.id.mail_subject, subject)
|
||||
} else {
|
||||
remoteView.setTextViewText(R.id.sender, subject)
|
||||
remoteView.setTextViewText(R.id.mail_subject, displayName)
|
||||
}
|
||||
|
||||
remoteView.setTextViewText(R.id.mail_date, item.displayDate)
|
||||
remoteView.setTextViewText(R.id.mail_preview, item.preview)
|
||||
|
||||
if (item.threadCount > 1) {
|
||||
remoteView.setTextViewText(R.id.thread_count, item.threadCount.toString())
|
||||
remoteView.setInt(R.id.thread_count, "setVisibility", View.VISIBLE)
|
||||
} else {
|
||||
remoteView.setInt(R.id.thread_count, "setVisibility", View.GONE)
|
||||
}
|
||||
|
||||
val textColor = getTextColor(item)
|
||||
remoteView.setTextColor(R.id.sender, textColor)
|
||||
remoteView.setTextColor(R.id.mail_subject, textColor)
|
||||
remoteView.setTextColor(R.id.mail_date, textColor)
|
||||
remoteView.setTextColor(R.id.mail_preview, textColor)
|
||||
|
||||
if (item.hasAttachments) {
|
||||
remoteView.setInt(R.id.attachment, "setVisibility", View.VISIBLE)
|
||||
} else {
|
||||
remoteView.setInt(R.id.attachment, "setVisibility", View.GONE)
|
||||
}
|
||||
|
||||
val intent = MessageList.actionDisplayMessageTemplateFillIntent(item.messageReference)
|
||||
remoteView.setOnClickFillInIntent(R.id.mail_list_item, intent)
|
||||
|
||||
remoteView.setInt(R.id.chip, "setBackgroundColor", item.accountColor)
|
||||
|
||||
return remoteView
|
||||
}
|
||||
|
||||
override fun getLoadingView(): RemoteViews {
|
||||
return RemoteViews(context.packageName, R.layout.message_list_widget_list_item_loading).apply {
|
||||
// Set the text here instead of in the layout so the app language override is used
|
||||
setTextViewText(R.id.loadingText, context.getString(UiR.string.message_list_widget_list_item_loading))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getViewTypeCount(): Int = 2
|
||||
|
||||
override fun getItemId(position: Int): Long = messageListItems[position].uniqueId
|
||||
|
||||
override fun hasStableIds(): Boolean = true
|
||||
|
||||
private fun bold(text: String): CharSequence {
|
||||
return SpannableString(text).apply {
|
||||
setSpan(StyleSpan(Typeface.BOLD), 0, text.length, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTextColor(messageListItem: MessageListItem): Int {
|
||||
return if (messageListItem.isRead) readTextColor else unreadTextColor
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
interface MessageListWidgetConfig {
|
||||
val providerClass: Class<out MessageListWidgetProvider>
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.fsck.k9.core.BuildConfig
|
||||
import com.fsck.k9.mailstore.MessageListChangedListener
|
||||
import com.fsck.k9.mailstore.MessageListRepository
|
||||
import timber.log.Timber
|
||||
|
||||
class MessageListWidgetManager(
|
||||
private val context: Context,
|
||||
private val messageListRepository: MessageListRepository,
|
||||
private val config: MessageListWidgetConfig
|
||||
) {
|
||||
private lateinit var appWidgetManager: AppWidgetManager
|
||||
|
||||
private var listenerAdded = false
|
||||
private val listener = MessageListChangedListener {
|
||||
onMessageListChanged()
|
||||
}
|
||||
|
||||
fun init() {
|
||||
appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
|
||||
if (isAtLeastOneMessageListWidgetAdded()) {
|
||||
resetMessageListWidget()
|
||||
registerMessageListChangedListener()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onMessageListChanged() {
|
||||
try {
|
||||
triggerMessageListWidgetUpdate()
|
||||
} catch (e: RuntimeException) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
throw e
|
||||
} else {
|
||||
Timber.e(e, "Error while updating message list widget")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onWidgetAdded() {
|
||||
Timber.v("Message list widget added")
|
||||
|
||||
registerMessageListChangedListener()
|
||||
}
|
||||
|
||||
internal fun onWidgetRemoved() {
|
||||
Timber.v("Message list widget removed")
|
||||
|
||||
if (!isAtLeastOneMessageListWidgetAdded()) {
|
||||
unregisterMessageListChangedListener()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun registerMessageListChangedListener() {
|
||||
if (!listenerAdded) {
|
||||
listenerAdded = true
|
||||
messageListRepository.addListener(listener)
|
||||
|
||||
Timber.v("Message list widget is now listening for message list changes…")
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun unregisterMessageListChangedListener() {
|
||||
if (listenerAdded) {
|
||||
listenerAdded = false
|
||||
messageListRepository.removeListener(listener)
|
||||
|
||||
Timber.v("Message list widget stopped listening for message list changes.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAtLeastOneMessageListWidgetAdded(): Boolean {
|
||||
return getAppWidgetIds().isNotEmpty()
|
||||
}
|
||||
|
||||
private fun triggerMessageListWidgetUpdate() {
|
||||
val appWidgetIds = getAppWidgetIds()
|
||||
if (appWidgetIds.isNotEmpty()) {
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.listView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetMessageListWidget() {
|
||||
val appWidgetIds = getAppWidgetIds()
|
||||
if (appWidgetIds.isNotEmpty()) {
|
||||
val intent = Intent(context, config.providerClass).apply {
|
||||
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
|
||||
}
|
||||
|
||||
context.sendBroadcast(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAppWidgetIds(): IntArray {
|
||||
val componentName = ComponentName(context, config.providerClass)
|
||||
return appWidgetManager.getAppWidgetIds(componentName)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViews
|
||||
import com.fsck.k9.activity.MessageCompose
|
||||
import com.fsck.k9.activity.MessageList
|
||||
import com.fsck.k9.activity.MessageList.Companion.intentDisplaySearch
|
||||
import com.fsck.k9.helper.PendingIntentCompat.FLAG_IMMUTABLE
|
||||
import com.fsck.k9.helper.PendingIntentCompat.FLAG_MUTABLE
|
||||
import com.fsck.k9.search.SearchAccount.Companion.createUnifiedInboxAccount
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import com.fsck.k9.ui.R as UiR
|
||||
|
||||
open class MessageListWidgetProvider : AppWidgetProvider(), KoinComponent {
|
||||
private val messageListWidgetManager: MessageListWidgetManager by inject()
|
||||
|
||||
override fun onEnabled(context: Context) {
|
||||
messageListWidgetManager.onWidgetAdded()
|
||||
}
|
||||
|
||||
override fun onDisabled(context: Context) {
|
||||
messageListWidgetManager.onWidgetRemoved()
|
||||
}
|
||||
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
updateAppWidget(context, appWidgetManager, appWidgetId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
|
||||
val views = RemoteViews(context.packageName, R.layout.message_list_widget_layout)
|
||||
|
||||
views.setTextViewText(R.id.folder, context.getString(UiR.string.integrated_inbox_title))
|
||||
|
||||
val intent = Intent(context, MessageListWidgetService::class.java)
|
||||
views.setRemoteAdapter(R.id.listView, intent)
|
||||
|
||||
val viewAction = viewActionTemplatePendingIntent(context)
|
||||
views.setPendingIntentTemplate(R.id.listView, viewAction)
|
||||
|
||||
val composeAction = composeActionPendingIntent(context)
|
||||
views.setOnClickPendingIntent(R.id.new_message, composeAction)
|
||||
|
||||
val headerClickAction = viewUnifiedInboxPendingIntent(context)
|
||||
views.setOnClickPendingIntent(R.id.top_controls, headerClickAction)
|
||||
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
}
|
||||
|
||||
private fun viewActionTemplatePendingIntent(context: Context): PendingIntent {
|
||||
val intent = MessageList.actionDisplayMessageTemplateIntent(
|
||||
context,
|
||||
openInUnifiedInbox = true,
|
||||
messageViewOnly = true
|
||||
)
|
||||
|
||||
return PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT or FLAG_MUTABLE)
|
||||
}
|
||||
|
||||
private fun viewUnifiedInboxPendingIntent(context: Context): PendingIntent {
|
||||
val unifiedInboxAccount = createUnifiedInboxAccount()
|
||||
val intent = intentDisplaySearch(
|
||||
context = context,
|
||||
search = unifiedInboxAccount.relatedSearch,
|
||||
noThreading = true,
|
||||
newTask = true,
|
||||
clearTop = true
|
||||
)
|
||||
|
||||
return PendingIntent.getActivity(context, -1, intent, PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
|
||||
}
|
||||
|
||||
private fun composeActionPendingIntent(context: Context): PendingIntent {
|
||||
val intent = Intent(context, MessageCompose::class.java).apply {
|
||||
action = MessageCompose.ACTION_COMPOSE
|
||||
}
|
||||
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package app.k9mail.ui.widget.list
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViewsService
|
||||
|
||||
class MessageListWidgetService : RemoteViewsService() {
|
||||
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
|
||||
return MessageListRemoteViewFactory(applicationContext)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
|
|
@ -0,0 +1,46 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/top_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/message_list_widget_header_background"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/folder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingTop="12dp"
|
||||
android:textSize="20sp"
|
||||
android:textColor="@color/message_list_widget_header_text"
|
||||
tools:text="Unified Inbox" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/new_message"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/compose_action"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_envelope" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ListView android:id="@+id/listView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
android:divider="@color/message_list_widget_divider"
|
||||
android:dividerHeight="0.5dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/mail_list_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:background="#fff">
|
||||
|
||||
<!-- A regular View breaks things for some reason, but a TextView does the job -->
|
||||
<TextView
|
||||
android:id="@+id/chip"
|
||||
android:layout_width="8dip"
|
||||
android:layout_height="match_parent"
|
||||
tools:background="#0099CC"
|
||||
android:visibility="visible" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/widget_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mail_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="4dp"
|
||||
tools:text="25 May" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/attachment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/mail_date"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_toStartOf="@+id/mail_date"
|
||||
android:src="@drawable/ic_messagelist_attachment_light"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/thread_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@id/attachment"
|
||||
android:layout_marginStart="4dp"
|
||||
android:maxLines="1"
|
||||
android:paddingRight="4dip"
|
||||
android:paddingBottom="1dip"
|
||||
android:paddingLeft="4dip"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/colorBackground"
|
||||
android:background="@drawable/thread_count_box_light"
|
||||
tools:text="3" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_gravity="start"
|
||||
android:layout_toStartOf="@id/thread_count"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="16sp"
|
||||
tools:text="Kinda long subject that should be long enough to exceed the available display space" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mail_subject"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/sender"
|
||||
android:layout_alignParentStart="true"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:paddingBottom="2dp"
|
||||
android:textSize="15sp"
|
||||
tools:text="Wikipedia" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mail_preview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/mail_subject"
|
||||
android:layout_alignParentStart="true"
|
||||
android:maxLines="1"
|
||||
android:textSize="13sp"
|
||||
tools:text="Towel Day is celebrated every year on 25 May as a tribute to the author Douglas Adams by his fans." />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/loadingText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:padding="16dp"
|
||||
android:textSize="15sp"
|
||||
tools:text="@string/message_list_widget_list_item_loading" />
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
android:gravity="center"
|
||||
android:padding="16dp"
|
||||
android:text="@string/message_list_widget_initializing"
|
||||
android:textSize="18sp" />
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="message_list_widget_header_background">#737373</color>
|
||||
<color name="message_list_widget_header_text">#e4e4e4</color>
|
||||
<color name="message_list_widget_divider">#e5e5e5</color>
|
||||
<color name="message_list_widget_text_read">#444444</color>
|
||||
<color name="message_list_widget_text_unread">#000000</color>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialKeyguardLayout="@layout/message_list_widget_loading"
|
||||
android:initialLayout="@layout/message_list_widget_loading"
|
||||
android:minHeight="180dp"
|
||||
android:minWidth="250dp"
|
||||
android:minResizeWidth="110dp"
|
||||
android:minResizeHeight="110dp"
|
||||
android:previewImage="@drawable/message_list_widget_preview"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="86400000"
|
||||
android:widgetCategory="home_screen|keyguard">
|
||||
</appwidget-provider>
|
||||
Loading…
Add table
Add a link
Reference in a new issue