Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-core-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.android.core.api.filter

import io.getstream.android.core.annotations.StreamInternalApi
import io.getstream.android.core.annotations.StreamPublishedApi
import io.getstream.android.core.internal.filter.FilterOperator

/**
* Base interface for filters used in Stream API operations.
*
* Filters are used to specify criteria for querying and retrieving data from Stream services. Each
* filter implementation defines specific matching logic for different comparison operations.
*/
@StreamPublishedApi public sealed interface Filter<F : FilterField>

internal data class BinaryOperationFilter<F : FilterField, V : Any>(
val operator: FilterOperator,
val field: F,
val value: V,
) : Filter<F>

internal data class CollectionOperationFilter<F : FilterField>(
internal val operator: FilterOperator,
val filters: Set<Filter<F>>,
) : Filter<F>

/** Converts a [Filter] instance to a request map suitable for API queries. */
@StreamInternalApi
public fun Filter<*>.toRequest(): Map<String, Any> =
when (this) {
is BinaryOperationFilter<*, *> -> mapOf(field.remote to mapOf(operator.remote to value))
is CollectionOperationFilter<*> ->
mapOf(operator.remote to filters.map(Filter<*>::toRequest))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-core-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.android.core.api.filter

/**
* Interface representing a field that can be used in filters for querying data from the Stream API.
*
* Implementations of this interface should provide [remote], which is the name of the field as
* expected by the Stream API.
*/
public interface FilterField {
/** The name of this field as expected by the Stream API. */
public val remote: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-core-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.android.core.api.filter

import io.getstream.android.core.internal.filter.FilterOperator

/** Utility class for building filters. */
public object Filters {
/**
* Creates a filter that combines multiple filters with a logical AND operation.
*
* @param filters The filters to combine.
* @return A filter that matches when all provided filters match.
*/
public fun <F : FilterField> and(vararg filters: Filter<F>): Filter<F> =
CollectionOperationFilter(FilterOperator.AND, filters.toSet())

/**
* Creates a filter that combines multiple filters with a logical OR operation.
*
* @param filters The filters to combine.
* @return A filter that matches when any of the specified filters match.
*/
public fun <F : FilterField> or(vararg filters: Filter<F>): Filter<F> =
CollectionOperationFilter(FilterOperator.OR, filters.toSet())
}

/**
* Creates a filter that checks if this field equals a specific value.
*
* @param value The value to check equality against.
* @return A filter that matches when this field equals the specified value.
*/
public fun <F : FilterField> F.equal(value: Any): Filter<F> =
BinaryOperationFilter(FilterOperator.EQUAL, this, value)

/**
* Creates a filter that checks if this field is greater than a specific value.
*
* @param value The value to check against.
* @return A filter that matches when this field is greater than the specified value.
*/
public fun <F : FilterField> F.greater(value: Any): Filter<F> =
BinaryOperationFilter(FilterOperator.GREATER, this, value)

/**
* Creates a filter that checks if this field is greater than or equal to a specific value.
*
* @param value The value to check against.
* @return A filter that matches when this field is greater than or equal to the specified value.
*/
public fun <F : FilterField> F.greaterOrEqual(value: Any): Filter<F> =
BinaryOperationFilter(FilterOperator.GREATER_OR_EQUAL, this, value)

/**
* Creates a filter that checks if this field is less than a specific value.
*
* @param value The value to check against.
* @return A filter that matches when this field is less than the specified value.
*/
public fun <F : FilterField> F.less(value: Any): Filter<F> =
BinaryOperationFilter(FilterOperator.LESS, this, value)

/**
* Creates a filter that checks if this field is less than or equal to a specific value.
*
* @param value The value to check against.
* @return A filter that matches when this field is less than or equal to the specified value.
*/
public fun <F : FilterField> F.lessOrEqual(value: Any): Filter<F> =
BinaryOperationFilter(FilterOperator.LESS_OR_EQUAL, this, value)

/**
* Creates a filter that checks if this field's value is in a specific list of values.
*
* @param values The list of values to check against.
* @return A filter that matches when this field's value is in the specified list.
*/
public fun <F : FilterField> F.`in`(values: List<Any>): Filter<F> =
BinaryOperationFilter(FilterOperator.IN, this, values.toSet())

/**
* Creates a filter that checks if this field's value is in a specific set of values.
*
* @param values The values to check against.
* @return A filter that matches when this field's value is in the specified values.
*/
public fun <F : FilterField> F.`in`(vararg values: Any): Filter<F> =
BinaryOperationFilter(FilterOperator.IN, this, values.toSet())

/**
* Creates a filter that performs a full-text query on this field.
*
* @param value The query string to search for.
* @return A filter that matches based on the full-text query.
*/
public fun <F : FilterField> F.query(value: String): Filter<F> =
BinaryOperationFilter(FilterOperator.QUERY, this, value)

/**
* Creates a filter that performs autocomplete matching on this field.
*
* @param value The string to autocomplete against.
* @return A filter that matches based on autocomplete functionality.
*/
public fun <F : FilterField> F.autocomplete(value: String): Filter<F> =
BinaryOperationFilter(FilterOperator.AUTOCOMPLETE, this, value)

/**
* Creates a filter that checks if this field exists.
*
* @return A filter that matches when this field exists.
*/
public fun <F : FilterField> F.exists(): Filter<F> =
BinaryOperationFilter(FilterOperator.EXISTS, this, true)

/**
* Creates a filter that checks if this field does not exist.
*
* @return A filter that matches when this field does not exist.
*/
public fun <F : FilterField> F.doesNotExist(): Filter<F> =
BinaryOperationFilter(FilterOperator.EXISTS, this, false)

/**
* Creates a filter that checks if this field contains a specific value.
*
* @param value The value to check for within this field.
* @return A filter that matches when this field contains the specified value.
*/
public fun <F : FilterField> F.contains(value: Any): Filter<F> =
BinaryOperationFilter(FilterOperator.CONTAINS, this, value)

/**
* Creates a filter that checks if a specific path exists within this field.
*
* @param value The path to check for existence.
* @return A filter that matches when the specified path exists in this field.
*/
public fun <F : FilterField> F.pathExists(value: String): Filter<F> =
BinaryOperationFilter(FilterOperator.PATH_EXISTS, this, value)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-core-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.android.core.api.filter

/**
* A sort configuration that combines a sort field with a direction.
*
* This class represents a complete sort specification that can be applied to collections of the
* associated model type. It provides both local sorting capabilities and the ability to generate
* remote API request parameters.
*/
public open class Sort<T>(public val field: SortField<T>, public val direction: SortDirection) :
Comparator<T> {

/** Converts this sort configuration to a DTO map for API requests. */
public fun toDto(): Map<String, Any> =
mapOf("field" to field.remote, "direction" to direction.value)

override fun compare(o1: T?, o2: T?): Int {
return field.comparator.compare(o1, o2, direction)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-core-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.getstream.android.core.api.filter

import io.getstream.android.core.annotations.StreamInternalApi

/**
* A comparator that can sort model instances by extracting comparable values.
*
* This class provides the foundation for local sorting operations by wrapping a lambda that
* extracts comparable values from model instances. It handles the comparison logic and direction
* handling internally.
*
* @param T The type of the model instances to be compared.
* @param V The type of the comparable value extracted from the model instances.
* @property value A lambda that extracts a comparable value from a model instance.
*/
public class SortComparator<T, V : Comparable<V>>(public val value: (T) -> V) {

/**
* Compares two model instances using the extracted values and sort direction.
*
* @param lhs The first model instance to compare
* @param rhs The second model instance to compare
* @param direction The direction of the sort
* @return A comparison result indicating the relative ordering
*/
public fun compare(lhs: T?, rhs: T?, direction: SortDirection): Int {
val value1 = lhs?.let(value)
val value2 = rhs?.let(value)

return when {
value1 == null && value2 == null -> 0
value1 == null -> -direction.value
value2 == null -> direction.value
else -> value1.compareTo(value2) * direction.value
}
}

/**
* Converts this comparator to a type-erased version.
*
* @return An AnySortComparator that wraps this comparator
*/
public fun toAny(): AnySortComparator<T> {
return AnySortComparator(this)
}
}

/**
* A type-erased wrapper for sort comparators that can work with any model type.
*
* This class provides a way to store and use sort comparators without knowing their specific
* generic type parameters. It's useful for creating collections of different sort configurations
* that can all work with the same model type.
*
* Type erased type avoids making SortField generic while keeping the underlying value type intact
* (no runtime type checks while sorting).
*/
public class AnySortComparator<T>(private val compare: (T?, T?, SortDirection) -> Int) {

/**
* Creates a type-erased comparator from a specific comparator instance.
*
* @param sort The specific comparator to wrap
*/
public constructor(sort: SortComparator<T, *>) : this(sort::compare)

/**
* Compares two model instances using the wrapped comparator.
*
* @param lhs The left-hand side model instance
* @param rhs The right-hand side model instance
* @param direction The direction of the sort
* @return A comparison result indicating the relative ordering
*/
public fun compare(lhs: T?, rhs: T?, direction: SortDirection): Int {
return this.compare.invoke(lhs, rhs, direction)
}
}

/**
* Extension function to sort a list of models using a list of sort configurations.
*
* @param T The type of elements in the list.
* @param sort A list of sort configurations to apply to the list.
*/
@StreamInternalApi
public fun <T> List<T>.sortedWith(sort: List<Sort<T>>): List<T> =
sortedWith(CompositeComparator(sort))

/**
* A composite comparator that combines multiple sort comparators. This class allows for sorting
* based on multiple criteria, where each comparator is applied in sequence.
*
* This implementation mirrors the Swift Array.sorted(using:) extension behavior:
* - Iterates through each sort comparator in order
* - Returns the first non-equal comparison result
* - If all comparators return equal (0), returns 0 to maintain stable sort order
*
* @param T The type of elements to be compared.
* @param comparators The list of comparators to be combined.
*/
@StreamInternalApi
public class CompositeComparator<T>(private val comparators: List<Comparator<T>>) : Comparator<T> {

override fun compare(o1: T, o2: T): Int {
for (comparator in comparators) {
val result = comparator.compare(o1, o2)
when (result) {
0 -> continue // Equal, move to the next comparator
else -> return result // Return the first non-equal comparison result
}
}
return 0 // All comparators returned equal, maintain original order
}
}
Loading