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

feat: add saerch history #143

Merged
merged 13 commits into from
Sep 15, 2024

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import ir.jaamebaade.jaamebaade_client.repository.HistoryRepository
import ir.jaamebaade.jaamebaade_client.repository.SearchHistoryRepository
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
Expand Down Expand Up @@ -204,4 +205,10 @@ object AppModule {
fun provideHistoryRepository(appDatabase: AppDatabase): HistoryRepository {
return HistoryRepository(appDatabase)
}

@Provides
@Singleton
fun provideSearchHistoryRepository(appDatabase: AppDatabase): SearchHistoryRepository {
return SearchHistoryRepository(appDatabase)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ir.jaamebaade.jaamebaade_client.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import ir.jaamebaade.jaamebaade_client.model.SearchHistoryRecord
import kotlinx.coroutines.flow.Flow

@Dao
interface SearchHistoryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSearchHistory(searchHistoryRecord: SearchHistoryRecord)
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved

@Query("SELECT * FROM search_history ORDER BY timestamp DESC")
fun getSearchHistory(): Flow<List<SearchHistoryRecord>>
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved

@Query("DELETE FROM search_history WHERE id = :id")
fun removeHistoryRecord(id: Int)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ir.jaamebaade.jaamebaade_client.dao.HighlightDao
import ir.jaamebaade.jaamebaade_client.dao.HistoryItemDao
import ir.jaamebaade.jaamebaade_client.dao.PoemDao
import ir.jaamebaade.jaamebaade_client.dao.PoetDao
import ir.jaamebaade.jaamebaade_client.dao.SearchHistoryDao
import ir.jaamebaade.jaamebaade_client.dao.VerseDao
import ir.jaamebaade.jaamebaade_client.model.Bookmark
import ir.jaamebaade.jaamebaade_client.model.Category
Expand All @@ -18,15 +19,18 @@ import ir.jaamebaade.jaamebaade_client.model.Highlight
import ir.jaamebaade.jaamebaade_client.model.HistoryRecord
import ir.jaamebaade.jaamebaade_client.model.Poem
import ir.jaamebaade.jaamebaade_client.model.Poet
import ir.jaamebaade.jaamebaade_client.model.SearchHistoryRecord
import ir.jaamebaade.jaamebaade_client.model.Verse

@Database(
entities = [Poet::class, Category::class, Poem::class,
Verse::class, Highlight::class, Bookmark::class, Comment::class,
HistoryRecord::class],
version = 2,
HistoryRecord::class, SearchHistoryRecord::class],
version = 3,
autoMigrations = [
AutoMigration(from = 1, to = 2)
AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3)
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved

]
)
abstract class AppDatabase : RoomDatabase() {
Expand All @@ -38,4 +42,5 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun bookmarkDao(): BookmarkDao
abstract fun commentDao(): CommentDao
abstract fun historyDao(): HistoryItemDao
abstract fun searchHistoryDao(): SearchHistoryDao
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ir.jaamebaade.jaamebaade_client.model

import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey

@Entity(tableName = "search_history",
indices = [Index(value = ["query"], unique = true)])
data class SearchHistoryRecord(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val query: String,
val timestamp: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ir.jaamebaade.jaamebaade_client.repository

import ir.jaamebaade.jaamebaade_client.database.AppDatabase
import ir.jaamebaade.jaamebaade_client.model.SearchHistoryRecord
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject


class SearchHistoryRepository @Inject constructor(
appDatabase: AppDatabase
) {
private val db = appDatabase
private val searchHistoryDao = db.searchHistoryDao()

suspend fun insertSearchHistory(searchHistoryRecord: SearchHistoryRecord) {
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
searchHistoryDao.insertSearchHistory(searchHistoryRecord)
}

fun getSearchHistory(): Flow<List<SearchHistoryRecord>> {
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
return searchHistoryDao.getSearchHistory()
}

fun removeHistoryRecord(historyRecord: SearchHistoryRecord) {
searchHistoryDao.removeHistoryRecord(historyRecord.id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,42 @@ fun SearchScreen(

val results = searchViewModel.results
val poets = searchViewModel.allPoets

val showingSearchHistory = searchViewModel.showingSearchHistory.collectAsState()
Column(modifier = modifier) {
SearchBar(modifier = Modifier.fillMaxWidth(),
SearchBar(
modifier = Modifier.fillMaxWidth(),
poets = poets.collectAsState().value,
onSearchFilterChanged = { searchViewModel.poetFilter = it },
onSearchQueryChanged = { searchViewModel.query = it }) {
searchHistory = showingSearchHistory.value,
onSearchFilterChanged = {
searchViewModel.poetFilter = it
},
onSearchQueryChanged = { it ->
searchViewModel.query = it
searchViewModel.onQueryChanged()
},
onHistoryItemClick = { historyItem ->
searchViewModel.query = historyItem.query
searchStatus = Status.LOADING
searchViewModel.search {
searchStatus = Status.SUCCESS
}
},
onHistoryItemDeleteClick = { historyItem ->
searchViewModel.deleteHistoryItem(historyItem)
},
onSearchClearClick = {
searchViewModel.clearSearch()
searchStatus = Status.NOT_STARTED
}

) {
if (it.length > 2) {
searchStatus = Status.LOADING
searchViewModel.search(callBack = { searchStatus = Status.SUCCESS })
}
}

SearchResults(
results = results,
searchQuery = searchViewModel.query,
Expand All @@ -62,6 +88,7 @@ fun SearchScreen(
}
}


@Composable
fun SearchResults(
results: List<VersePoemCategoriesPoet>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ir.jaamebaade.jaamebaade_client.view.components

import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -9,10 +10,15 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material3.DropdownMenu
Expand All @@ -37,40 +43,60 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import ir.jaamebaade.jaamebaade_client.model.Poet
import ir.jaamebaade.jaamebaade_client.model.SearchHistoryRecord

@Composable
fun SearchBar(
modifier: Modifier,
poets: List<Poet>,
searchHistory: List<SearchHistoryRecord>? = null,
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
onSearchFilterChanged: (Poet?) -> Unit,
onSearchQueryChanged: (String) -> Unit,
onHistoryItemClick: ((SearchHistoryRecord) -> Unit)? = null,
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
onHistoryItemDeleteClick: ((SearchHistoryRecord) -> Unit)? = null,
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
onSearchClearClick: (String) -> Unit,
onSearchQueryIconClicked: (String) -> Unit,
) {
var query by rememberSaveable { mutableStateOf("") }
var expanded by remember { mutableStateOf(false) }
var selectedPoetIndex by rememberSaveable { mutableStateOf<Int?>(null) }
var isSearchIconClicked by remember { mutableStateOf(false) }

val keyboardController = LocalSoftwareKeyboardController.current


Column {
TextField(
value = query,
onValueChange = {
query = it
Log.d("query", query)
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
onSearchQueryChanged(it)
Log.d("his", "${searchHistory?.size}")
},
modifier = modifier
.fillMaxWidth()
.background(Color.Transparent),
trailingIcon = {
IconButton(onClick = {
onSearchQueryIconClicked(query)
keyboardController?.hide()
}) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Search"
)
if (!isSearchIconClicked) {
IconButton(onClick = {
onSearchQueryIconClicked(query)
keyboardController?.hide()
isSearchIconClicked = true
}) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Search"
)
}
} else {
IconButton(onClick = {
onSearchClearClick(query)
query = ""
isSearchIconClicked = false
}) {
Icon(imageVector = Icons.Default.Close, contentDescription = "Close")
}
}
},
label = {
Expand All @@ -84,6 +110,7 @@ fun SearchBar(
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = {
onSearchQueryIconClicked(query)
isSearchIconClicked = true
keyboardController?.hide()
}),
)
Expand Down Expand Up @@ -135,7 +162,12 @@ fun SearchBar(
poets.forEachIndexed { index, poet ->
HorizontalDivider()
DropdownMenuItem(
text = { Text(poet.name, style = MaterialTheme.typography.labelMedium) },
text = {
Text(
poet.name,
style = MaterialTheme.typography.labelMedium
)
},
onClick = {
onSearchFilterChanged(poet)
selectedPoetIndex = index
Expand All @@ -145,5 +177,67 @@ fun SearchBar(
}
}
}

if (searchHistory != null && !isSearchIconClicked) {
MuhammadKhosravi marked this conversation as resolved.
Show resolved Hide resolved
SearchHistoryColumn(
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
searchHistory = searchHistory,
onHistoryItemClick = { historyItem ->
onHistoryItemClick?.invoke(historyItem)
query = historyItem.query
isSearchIconClicked = true
},
onHistoryItemDelete = { historyItem ->
onHistoryItemDeleteClick?.invoke(historyItem)
},
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(8.dp)

)

}


}
}

@Composable
fun SearchHistoryColumn(
modifier: Modifier = Modifier,
searchHistory: List<SearchHistoryRecord>,
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
onHistoryItemClick: (SearchHistoryRecord) -> Unit,
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
onHistoryItemDelete: (SearchHistoryRecord) -> Unit
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
) {
Column(
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
modifier = modifier
.fillMaxWidth()
MuhammadKhosravi marked this conversation as resolved.
Show resolved Hide resolved
.padding(vertical = 4.dp)
.verticalScroll(rememberScrollState())

) {
Text("جستجوهای اخیر شما", style = MaterialTheme.typography.titleMedium)
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
searchHistory.forEach { historyItem ->
Row(
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
modifier = Modifier
.fillMaxWidth()
.clickable { onHistoryItemClick(historyItem) }
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(historyItem.query, style = MaterialTheme.typography.labelMedium)

IconButton(onClick = {
onHistoryItemDelete(historyItem)
}) {
Icon(
imageVector = Icons.Default.Clear,
contentDescription = "Delete history item",
ErfanMojibi marked this conversation as resolved.
Show resolved Hide resolved
modifier = Modifier.size(20.dp)
)
}
}
}
}
}
Loading