Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 7fe09e5b authored by Wolf-Martell Montwé's avatar Wolf-Martell Montwé
Browse files

refactor(search): add SearchField and SearchFieldType to abstract the...

refactor(search): add SearchField and SearchFieldType to abstract the SearchCondition from message specific fields
parent 8f542d6b
Loading
Loading
Loading
Loading
+29 −1
Original line number Original line Diff line number Diff line
@@ -2,7 +2,9 @@ package net.thunderbird.feature.search


import android.os.Parcelable
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.Parcelize
import net.thunderbird.feature.search.api.SearchAttribute
import net.thunderbird.feature.search.api.SearchCondition
import net.thunderbird.feature.search.api.SearchCondition
import net.thunderbird.feature.search.api.SearchFieldType


/**
/**
 * Represents a node in a boolean expression tree for evaluating search conditions.
 * Represents a node in a boolean expression tree for evaluating search conditions.
@@ -97,6 +99,7 @@ class SearchConditionTreeNode private constructor(
                val rightStr = right?.toString() ?: "null"
                val rightStr = right?.toString() ?: "null"
                "($leftStr ${operator.name} $rightStr)"
                "($leftStr ${operator.name} $rightStr)"
            }
            }

            Operator.CONDITION -> condition.toString()
            Operator.CONDITION -> condition.toString()
            Operator.NOT -> "(NOT ${left?.toString() ?: "null"})"
            Operator.NOT -> "(NOT ${left?.toString() ?: "null"})"
        }
        }
@@ -105,14 +108,37 @@ class SearchConditionTreeNode private constructor(
    class Builder(
    class Builder(
        private var root: SearchConditionTreeNode,
        private var root: SearchConditionTreeNode,
    ) {
    ) {

        constructor(condition: SearchCondition) : this(SearchConditionTreeNode(Operator.CONDITION, condition))
        constructor(condition: SearchCondition) : this(SearchConditionTreeNode(Operator.CONDITION, condition))


        private fun validateCondition(condition: SearchCondition) {
            if (condition.field.fieldType == SearchFieldType.CUSTOM &&
                condition.attribute != SearchAttribute.CONTAINS
            ) {
                error("Custom fields can only be used with the CONTAINS attribute")
            }
        }

        private fun validateTree(node: SearchConditionTreeNode?) {
            if (node == null) return

            if (node.operator == Operator.CONDITION) {
                if (node.condition == null) {
                    error("CONDITION nodes must have a condition")
                }
                validateCondition(node.condition)
            } else {
                validateTree(node.left)
                validateTree(node.right)
            }
        }

        fun and(condition: SearchCondition): Builder {
        fun and(condition: SearchCondition): Builder {
            validateCondition(condition)
            return and(SearchConditionTreeNode(Operator.CONDITION, condition))
            return and(SearchConditionTreeNode(Operator.CONDITION, condition))
        }
        }


        fun and(node: SearchConditionTreeNode): Builder {
        fun and(node: SearchConditionTreeNode): Builder {
            validateTree(node)
            root = SearchConditionTreeNode(
            root = SearchConditionTreeNode(
                operator = Operator.AND,
                operator = Operator.AND,
                left = root,
                left = root,
@@ -130,10 +156,12 @@ class SearchConditionTreeNode private constructor(
        }
        }


        fun or(condition: SearchCondition): Builder {
        fun or(condition: SearchCondition): Builder {
            validateCondition(condition)
            return or(SearchConditionTreeNode(Operator.CONDITION, condition))
            return or(SearchConditionTreeNode(Operator.CONDITION, condition))
        }
        }


        fun or(node: SearchConditionTreeNode): Builder {
        fun or(node: SearchConditionTreeNode): Builder {
            validateTree(node)
            root = SearchConditionTreeNode(
            root = SearchConditionTreeNode(
                operator = Operator.OR,
                operator = Operator.OR,
                left = root,
                left = root,
+37 −29
Original line number Original line Diff line number Diff line
package net.thunderbird.feature.search.api
package net.thunderbird.feature.search.api


import kotlinx.parcelize.Parcelize

/**
/**
 * Using an enum in order to have more robust code. Users ( & coders )
 * Represents a field that can be searched in messages.
 * are prevented from passing illegal fields. No database overhead
 * Each enum value corresponds to a specific message attribute that can be used in search queries.
 * when invalid fields passed.
 *
 * By result, only the fields in here are searchable.
 *
 *
 * Fields not in here at this moment ( and by effect not searchable ):
 * @property fieldName The name of the database column associated with this search field.
 *      id, html_content, internal_date, message_id,
 * @property fieldType The type of the search field, which determines how it can be queried.
 *      preview, mime_type
 * @property customQueryTemplate An optional custom query template for fields that require special handling.
 */
 */
enum class MessageSearchField {
@Parcelize
    SUBJECT,
enum class MessageSearchField(
    DATE,
    override val fieldName: String,
    UID,
    override val fieldType: SearchFieldType,
    FLAG,
    override val customQueryTemplate: String? = null,
    SENDER,
) : SearchField {
    TO,
    CC("cc_list", SearchFieldType.TEXT),
    CC,
    DATE("date", SearchFieldType.NUMBER),
    FOLDER,
    FLAG("flags", SearchFieldType.TEXT),
    BCC,
    ID("id", SearchFieldType.NUMBER),
    REPLY_TO,
    SENDER("sender_list", SearchFieldType.TEXT),
    MESSAGE_CONTENTS,
    SUBJECT("subject", SearchFieldType.TEXT),
    ATTACHMENT_COUNT,
    UID("uid", SearchFieldType.TEXT),
    DELETED,
    TO("to_list", SearchFieldType.TEXT),
    THREAD_ID,
    FOLDER("folder_id", SearchFieldType.NUMBER),
    ID,
    BCC("bcc_list", SearchFieldType.TEXT),
    INTEGRATE,
    REPLY_TO("reply_to_list", SearchFieldType.TEXT),
    NEW_MESSAGE,
    MESSAGE_CONTENTS(
    READ,
        fieldName = "message_contents",
    FLAGGED,
        fieldType = SearchFieldType.CUSTOM,
    VISIBLE,
        customQueryTemplate = "messages.id IN (SELECT docid FROM messages_fulltext WHERE fulltext MATCH ?)",
    ),
    ATTACHMENT_COUNT("attachment_count", SearchFieldType.NUMBER),
    DELETED("deleted", SearchFieldType.NUMBER),
    THREAD_ID("threads.root", SearchFieldType.NUMBER),
    INTEGRATE("integrate", SearchFieldType.NUMBER),
    NEW_MESSAGE("new_message", SearchFieldType.NUMBER),
    READ("read", SearchFieldType.NUMBER),
    FLAGGED("flagged", SearchFieldType.NUMBER),
    VISIBLE("visible", SearchFieldType.NUMBER),
}
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -14,7 +14,7 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@Parcelize
data class SearchCondition(
data class SearchCondition(
    @JvmField
    @JvmField
    val field: MessageSearchField,
    val field: SearchField,


    @JvmField
    @JvmField
    val attribute: SearchAttribute,
    val attribute: SearchAttribute,
+26 −0
Original line number Original line Diff line number Diff line
package net.thunderbird.feature.search.api

import android.os.Parcelable

/**
 * Represents a field that can be searched.
 */
interface SearchField : Parcelable {
    /**
     * The name of the field.
     */
    val fieldName: String

    /**
     * The type of the field.
     */
    val fieldType: SearchFieldType

    /**
     * An optional custom query template for this field.
     * This can be used to define how the field should be queried in a custom way.
     *
     * Only applicable for fields with [SearchFieldType.CUSTOM].
     */
    val customQueryTemplate: String?
}
+24 −0
Original line number Original line Diff line number Diff line
package net.thunderbird.feature.search.api

/**
 * Represents the type of a search field.
 *
 * This enum defines the different types of fields that can be used in a search operation.
 * Each type corresponds to a specific kind of data that the field can hold.
 */
enum class SearchFieldType {
    /**
     * Represents a field that contains text.
     */
    TEXT,

    /**
     * Represents a field that contains numeric values.
     */
    NUMBER,

    /**
     * Represents a field that contains custom search capabilities.
     */
    CUSTOM,
}
Loading