Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for searching feeds #119

Merged
merged 6 commits into from
Oct 27, 2023
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
Expand Up @@ -45,7 +45,7 @@ val EnTwineStrings =
errorTooManyRedirects = "The given URL has too many redirects. Please use a different URL.",
errorUnAuthorized = { "($it): You are not authorized to access content at this link." },
errorUnknownHttpStatus = { "Failed to load content with HTTP code: ($it)" },
searchHint = "Search posts",
postsSearchHint = "Search posts",
searchSortNewest = "Newest",
searchSortNewestFirst = "Newest first",
searchSortOldest = "Oldest",
Expand Down Expand Up @@ -81,4 +81,5 @@ val EnTwineStrings =
aboutSocialThreads = "Threads",
aboutSocialGitHub = "GitHub",
aboutSocialWebsite = "Website",
feedsSearchHint = "Search feeds"
)
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ data class TwineStrings(
val errorTooManyRedirects: String,
val errorUnAuthorized: (Int) -> String,
val errorUnknownHttpStatus: (Int) -> String,
val searchHint: String,
val postsSearchHint: String,
val searchSortNewest: String,
val searchSortNewestFirst: String,
val searchSortOldest: String,
Expand Down Expand Up @@ -73,6 +73,7 @@ data class TwineStrings(
val aboutSocialGitHub: String,
// don't translate
val aboutSocialWebsite: String,
val feedsSearchHint: String,
)

object Locales {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ internal interface DataComponent : SqlDriverPlatformComponent, DataStorePlatform
fun providesPostSearchFTSQueries(database: ReaderDatabase) = database.postSearchFTSQueries

@Provides fun providesBookmarkQueries(database: ReaderDatabase) = database.bookmarkQueries

@Provides
fun providesFeedSearchFTSQueries(database: ReaderDatabase) = database.feedSearchFTSQueries
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package dev.sasikanth.rss.reader.feeds

import androidx.compose.ui.text.input.TextFieldValue
import dev.sasikanth.rss.reader.models.local.Feed

sealed interface FeedsEvent {
Expand All @@ -30,4 +31,8 @@ sealed interface FeedsEvent {
data class OnFeedNameUpdated(val newFeedName: String, val feedLink: String) : FeedsEvent

data class OnFeedPinClicked(val feed: Feed) : FeedsEvent

data class SearchQueryChanged(val searchQuery: TextFieldValue) : FeedsEvent

object ClearSearchQuery : FeedsEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
*/
package dev.sasikanth.rss.reader.feeds

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.text.input.TextFieldValue
import app.cash.paging.cachedIn
import app.cash.paging.createPager
import app.cash.paging.createPagingConfig
import app.cash.paging.map
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.getOrCreate
Expand All @@ -27,14 +31,17 @@ import dev.sasikanth.rss.reader.models.local.Feed
import dev.sasikanth.rss.reader.repository.ObservableSelectedFeed
import dev.sasikanth.rss.reader.repository.RssRepository
import dev.sasikanth.rss.reader.utils.DispatchersProvider
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
Expand Down Expand Up @@ -65,6 +72,8 @@ class FeedsPresenter(

internal val state: StateFlow<FeedsState> = presenterInstance.state
internal val effects = presenterInstance.effects.asSharedFlow()
internal val searchQuery
get() = presenterInstance.searchQuery

init {
lifecycle.doOnCreate { presenterInstance.dispatch(FeedsEvent.Init) }
Expand All @@ -78,6 +87,9 @@ class FeedsPresenter(
private val observableSelectedFeed: ObservableSelectedFeed
) : InstanceKeeper.Instance {

var searchQuery by mutableStateOf(TextFieldValue())
private set

private val coroutineScope = CoroutineScope(SupervisorJob() + dispatchersProvider.main)

private val _state = MutableStateFlow(FeedsState.DEFAULT)
Expand All @@ -98,9 +110,19 @@ class FeedsPresenter(
is FeedsEvent.OnFeedSelected -> onFeedSelected(event.feed)
is FeedsEvent.OnFeedNameUpdated -> onFeedNameUpdated(event.newFeedName, event.feedLink)
is FeedsEvent.OnFeedPinClicked -> onFeedPinClicked(event.feed)
FeedsEvent.ClearSearchQuery -> clearSearchQuery()
is FeedsEvent.SearchQueryChanged -> onSearchQueryChanged(event.searchQuery)
}
}

private fun onSearchQueryChanged(searchQuery: TextFieldValue) {
this.searchQuery = searchQuery
}

private fun clearSearchQuery() {
searchQuery = TextFieldValue()
}

private fun onFeedPinClicked(feed: Feed) {
coroutineScope.launch { rssRepository.toggleFeedPinStatus(feed) }
}
Expand Down Expand Up @@ -129,6 +151,7 @@ class FeedsPresenter(
coroutineScope.launch { effects.emit(FeedsEffect.MinimizeSheet) }
}

@OptIn(FlowPreview::class)
private fun init() {
observableSelectedFeed.selectedFeed
.flatMapLatest { selectedFeed ->
Expand All @@ -152,6 +175,29 @@ class FeedsPresenter(
}
}
.launchIn(coroutineScope)

val searchQueryFlow = snapshotFlow { searchQuery }.debounce(500.milliseconds)
searchQueryFlow
.distinctUntilChanged()
.onEach { searchQuery ->
val searchQueryText = searchQuery.text
val transformedSearchQuery =
if (searchQueryText.length >= 3) {
searchQueryText
} else {
""
}

val feedSearchResults =
createPager(config = createPagingConfig(pageSize = 20)) {
rssRepository.searchFeed(transformedSearchQuery)
}
.flow
.cachedIn(coroutineScope)

_state.update { it.copy(feedsSearchResults = feedSearchResults) }
}
.launchIn(coroutineScope)
}

override fun onDestroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ import kotlinx.coroutines.flow.map
@Immutable
internal data class FeedsState(
val feeds: Flow<PagingData<Feed>>,
val feedsSearchResults: Flow<PagingData<Feed>>,
val selectedFeed: Feed?,
val numberOfPinnedFeeds: Long
) {

val feedsListInExpandedState: Flow<PagingData<FeedsListItemType>> =
feeds.map { feeds ->
feedsSearchResults.map { feeds ->
feeds
.map { feed -> FeedsListItemType.FeedListItem(feed = feed) }
.insertSeparators { before, after ->
Expand All @@ -56,6 +57,12 @@ internal data class FeedsState(

companion object {

val DEFAULT = FeedsState(feeds = emptyFlow(), selectedFeed = null, numberOfPinnedFeeds = 0)
val DEFAULT =
FeedsState(
feeds = emptyFlow(),
feedsSearchResults = emptyFlow(),
selectedFeed = null,
numberOfPinnedFeeds = 0
)
}
}
Loading