Skip to content

Commit

Permalink
Merge pull request #19 from inu-appcenter/feat/search
Browse files Browse the repository at this point in the history
검색 구현 (카테고리 및 상태 제외)
  • Loading branch information
jhg3410 authored Jan 10, 2023
2 parents 4455e43 + 80cc2b5 commit a154eb6
Show file tree
Hide file tree
Showing 16 changed files with 342 additions and 21 deletions.
8 changes: 6 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ android {

dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
def lifecycle_version = "2.4.0"
def lifecycle_version = "2.5.1"

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
Expand All @@ -71,9 +71,13 @@ dependencies {
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.fragment:fragment-ktx:1.4.0"

// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

// Glide
implementation 'com.github.bumptech.glide:glide:4.12.0'

Expand Down Expand Up @@ -124,5 +128,5 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

// paging
implementation("androidx.paging:paging-runtime:3.1.1")
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
}
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.InuEvents"
android:usesCleartextTraffic="true">

<activity
android:name=".ui.mypage.shortcut.BlockedAccountActivity"
android:exported="false" />
Expand Down Expand Up @@ -81,6 +82,9 @@
android:name=".ui.register.RegisterEventsActivity"
android:exported="true"
android:windowSoftInputMode="adjustPan" />
<activity
android:name=".ui.home.SearchActivity"
android:exported="false"/>

<service
android:name=".fcm.MyFirebaseMessagingService"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.inu.events.data.httpservice

import androidx.paging.PagingSource
import okhttp3.MultipartBody
import org.inu.events.data.model.dto.AddEventParams
import org.inu.events.data.model.dto.UpdateEventParams
import org.inu.events.data.model.entity.Event
import org.inu.events.data.model.dto.UploadImageResult
import org.inu.events.data.model.entity.Event
import retrofit2.Call
import retrofit2.http.*

Expand Down Expand Up @@ -40,4 +41,12 @@ interface EventHttpService {
@Part file: MultipartBody.Part
): Call<UploadImageResult>

@GET("/events-by-search-with-filtering")
suspend fun searchEvents(
@Query("categoryId") categoryId: Int,
@Query("eventStatus") eventStatus: Boolean,
@Query("content") content: String,
@Query("pageNum") pageNum: Int,
@Query("pageSize") pageSize: Int,
): List<Event>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.inu.events.data.model.dto.AddEventParams
import org.inu.events.data.model.dto.UpdateEventParams
import org.inu.events.data.model.dto.UploadImageResult
import org.inu.events.data.model.entity.Event
import org.inu.events.data.repository.impl.EventPagingSource
import org.inu.events.data.source.EventPagingSource

interface EventRepository {
fun getEvents(categoryId: Int, eventStatus: Boolean): Flow<PagingData<Event>>
Expand All @@ -17,4 +17,10 @@ interface EventRepository {
fun deleteEvent(eventId: Int)
fun uploadImage(image: MultipartBody.Part): UploadImageResult
fun createEventPageSource(categoryId: Int, eventStatus: Boolean): EventPagingSource

fun searchEvents(
categoryId: Int,
eventStatus: Boolean,
content: String,
): Flow<PagingData<Event>>
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package org.inu.events.data.repository.impl

import android.util.Log
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.flow.Flow
import okhttp3.MultipartBody
import org.inu.events.data.httpservice.EventHttpService
Expand All @@ -14,13 +12,15 @@ import org.inu.events.data.model.dto.UpdateEventParams
import org.inu.events.data.model.dto.UploadImageResult
import org.inu.events.data.model.entity.Event
import org.inu.events.data.repository.EventRepository
import org.inu.events.data.source.EventPagingSource
import org.inu.events.data.source.EventSearchPagingSource

class EventRepositoryImpl(
private val httpService: EventHttpService
) : EventRepository {

override fun getEvents(categoryId: Int, eventStatus: Boolean) = Pager(
config = PagingConfig(10),
config = PagingConfig(pageSize = 10),
pagingSourceFactory = {
createEventPageSource(categoryId, eventStatus)
}
Expand All @@ -43,11 +43,29 @@ class EventRepositoryImpl(
}

override fun uploadImage(image: MultipartBody.Part): UploadImageResult {
Log.d("tag","이미지 업로드 함수 호출")
Log.d("tag", "이미지 업로드 함수 호출")
return httpService.uploadImage(image).execute().body()!!
}

override fun createEventPageSource(categoryId: Int, eventStatus: Boolean): EventPagingSource {
return EventPagingSource(httpService, categoryId, eventStatus)
}

override fun searchEvents(
categoryId: Int,
eventStatus: Boolean,
content: String,
): Flow<PagingData<Event>> = Pager(
config = PagingConfig(
pageSize = 10,
),
pagingSourceFactory = {
EventSearchPagingSource(
httpService = httpService,
categoryId = categoryId,
eventStatus = eventStatus,
content = content,
)
}
).flow
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.inu.events.data.repository.impl
package org.inu.events.data.source

import androidx.paging.PagingSource
import androidx.paging.PagingState
Expand All @@ -7,15 +7,15 @@ import kotlinx.coroutines.withContext
import org.inu.events.data.httpservice.EventHttpService
import org.inu.events.data.model.entity.Event

private val STARTING_KEY = 0
private const val STARTING_KEY = 0

class EventPagingSource(
private val httpService: EventHttpService,
private val categoryId: Int = 0,
private val eventStatus: Boolean = false,
) : PagingSource<Int, Event>() {

override fun getRefreshKey(state: PagingState<Int, Event>): Int? {
override fun getRefreshKey(state: PagingState<Int, Event>): Int {
return ensureValidKey(key = 0)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.inu.events.data.source

import androidx.paging.PagingSource
import androidx.paging.PagingState
import org.inu.events.data.httpservice.EventHttpService
import org.inu.events.data.model.entity.Event

private const val STARTING_KEY = 0

class EventSearchPagingSource(
private val httpService: EventHttpService,
private val categoryId: Int,
private val eventStatus: Boolean,
private val content: String,
) : PagingSource<Int, Event>() {

private suspend fun getData(pageNum: Int, pageSize: Int): List<Event> {
return httpService.searchEvents(
categoryId = categoryId,
eventStatus = eventStatus,
content = content,
pageNum = pageNum,
pageSize = pageSize,
)
}

override fun getRefreshKey(state: PagingState<Int, Event>): Int? = null

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Event> {
val start = params.key ?: STARTING_KEY
val page = start / params.loadSize
val data = getData(
pageNum = page,
pageSize = params.loadSize
)

return LoadResult.Page(
data = data,
prevKey = null,
nextKey = if (data.size == params.loadSize) start + params.loadSize else null
)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.inu.events.ui.adapter
package org.inu.events.ui.adapter.like

import android.view.LayoutInflater
import android.view.ViewGroup
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.inu.events.ui.adapter.like

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.inu.events.data.model.entity.Event
import org.inu.events.databinding.ItemLikeEventBinding

class LikePagingAdapter : PagingDataAdapter<Event, LikePagingAdapter.ViewHolder>(LikeDiffUtil) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.from(parent)

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
getItem(position)?.let { event ->
holder.bind(event)
}
}

class ViewHolder private constructor(
val binding: ItemLikeEventBinding
) : RecyclerView.ViewHolder(binding.root) {

fun bind(item: Event) {
binding.item = item
binding.executePendingBindings()
}

companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemLikeEventBinding.inflate(layoutInflater, parent, false)

return ViewHolder(binding)
}
}
}

companion object LikeDiffUtil : DiffUtil.ItemCallback<Event>() {
override fun areItemsTheSame(oldItem: Event, newItem: Event) = oldItem.id == newItem.id

override fun areContentsTheSame(oldItem: Event, newItem: Event) = oldItem == newItem
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.inu.events.data.model.entity.Event
import org.inu.events.data.model.entity.User
import org.inu.events.ui.adapter.*
import org.inu.events.ui.adapter.like.LikeAdapter

@BindingAdapter("app:likes")
fun setLikes(recyclerView: RecyclerView, list: List<Event>?) {
Expand Down
47 changes: 47 additions & 0 deletions app/src/main/java/org/inu/events/ui/home/SearchActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.inu.events.ui.home

import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.inu.events.databinding.ActivitySearchBinding
import org.inu.events.ui.adapter.like.LikePagingAdapter

class SearchActivity : AppCompatActivity() {

private val vm: SearchViewModel by viewModels()
private lateinit var binding: ActivitySearchBinding
private val adapter = LikePagingAdapter()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySearchBinding.inflate(layoutInflater).apply {
lifecycleOwner = this@SearchActivity
vm = this@SearchActivity.vm
}

initRecyclerView()
observeSearch()

setContentView(binding.root)
}


private fun initRecyclerView() {
binding.rvEvent.adapter = adapter
}

private fun observeSearch() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.searchResult.collectLatest {
adapter.submitData(it)
}
}
}
}
}
59 changes: 59 additions & 0 deletions app/src/main/java/org/inu/events/ui/home/SearchViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.inu.events.ui.home

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.inu.events.data.model.entity.Event
import org.inu.events.data.repository.EventRepository
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class SearchViewModel : ViewModel(), KoinComponent {

private val eventRepository: EventRepository by inject()

val searchText = MutableStateFlow("")
private val category = MutableStateFlow(0)
private val eventStatus = MutableStateFlow(false)

var searchResult: MutableStateFlow<PagingData<Event>> = MutableStateFlow(PagingData.empty())
private var job: Job? = null

init {

viewModelScope.launch {
initSearch()
}

}

@OptIn(FlowPreview::class)
private suspend fun initSearch() {
searchText.debounce(500).collect {
job?.cancel()
if (it.isEmpty()) {
searchResult.value = PagingData.empty()
return@collect
}
search().run {
job = this
}
}
}

private fun search(): Job {
return viewModelScope.launch {
eventRepository.searchEvents(
categoryId = category.value,
eventStatus = eventStatus.value,
content = searchText.value
).collectLatest { pagingData ->
searchResult.value = pagingData
}
}
}
}
Loading

0 comments on commit a154eb6

Please sign in to comment.