Skip to content
Open
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
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ dependencies {
implementation(projects.core.designsystem)
implementation(projects.feature.home)
implementation(projects.feature.chat)
implementation(projects.feature.chatSearch)
implementation(projects.feature.calendar)
implementation(projects.feature.emotion)
implementation(projects.feature.login)
Expand All @@ -88,6 +89,7 @@ dependencies {
implementation(libs.orbit.core)
implementation(libs.orbit.viewmodel)
implementation(libs.orbit.compose)
implementation(libs.kotlinx.serialization.json)

implementation(libs.kotlinx.coroutines.android)

Expand Down
7 changes: 6 additions & 1 deletion app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
<!-- 디버그/검증 전용 화면. adb 로만 실행. -->
<activity
android:name=".debug.CardDebugActivity"
android:exported="true"
android:exported="false"
android:theme="@style/Theme.GAMSS" />

<activity
android:name=".debug.ChatSearchDebugActivity"
android:exported="false"
android:theme="@style/Theme.GAMSS" />
</application>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.gamss.android.app.debug

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.gamss.android.core.designsystem.theme.GamssTheme
import com.gamss.android.feature.chatSearch.SearchChattingScreen
import dagger.hilt.android.AndroidEntryPoint

/** 채팅방 검색 디버그/검증 전용 화면. 프로덕션 아님. */
@AndroidEntryPoint
class ChatSearchDebugActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
GamssTheme {
SearchChattingScreen()
}
}
}
}
2 changes: 2 additions & 0 deletions data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {
implementation(libs.firebase.auth)
implementation(libs.firebase.firestore)
implementation(libs.kotlinx.coroutines.play.services)
implementation(libs.androidx.paging.common)

testImplementation(libs.junit)
testImplementation(libs.mockk)
Expand All @@ -85,4 +86,5 @@ dependencies {
androidTestImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation("androidx.test:runner:1.6.2")

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.gamss.android.data.chattingsearch

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.gamss.android.core.common.AppResult
import com.gamss.android.data.remote.chattingsearch.ChattingRoomSearchService
import com.gamss.android.data.remote.chattingsearch.model.response.toDomain
import com.gamss.android.data.repository.runCatchingApiCall
import com.gamss.android.domain.chattingsearch.ChattingRoomSummary

internal class ChattingRoomSearchPagingSource(
private val chattingRoomSearchService: ChattingRoomSearchService,
private val keyword: String,
) : PagingSource<Int, ChattingRoomSummary>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ChattingRoomSummary> {
val page = params.key ?: DEFAULT_PAGE
val result = runCatchingApiCall {
checkNotNull(
chattingRoomSearchService.searchChattingRooms(
keyword = keyword,
page = page,
size = params.loadSize,
).data,
) { "No available chatting room search data" }.toDomain()
}

return when (result) {
is AppResult.Success -> LoadResult.Page(
data = result.data.rooms,
prevKey = page.takeIf { it > DEFAULT_PAGE }?.minus(1),
nextKey = (page + 1).takeIf { result.data.hasNextPage },
)

is AppResult.Failure -> LoadResult.Error(result.throwable)
}
}

override fun getRefreshKey(state: PagingState<Int, ChattingRoomSummary>): Int? =
state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.let { page ->
page.prevKey?.plus(1) ?: page.nextKey?.minus(1)
}
}

companion object {
const val DEFAULT_PAGE = 0
const val DEFAULT_SIZE = 20
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.gamss.android.data.chattingsearch

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.gamss.android.data.remote.chattingsearch.ChattingRoomSearchService
import com.gamss.android.domain.chattingsearch.ChattingRoomSummary
import com.gamss.android.domain.repository.ChattingRoomSearchRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
internal class ChattingRoomSearchRepositoryImpl @Inject constructor(
private val chattingRoomSearchService: ChattingRoomSearchService,
) : ChattingRoomSearchRepository {

override fun searchChattingRooms(keyword: String): Flow<PagingData<ChattingRoomSummary>> =
Pager(
config = PagingConfig(
pageSize = ChattingRoomSearchPagingSource.DEFAULT_SIZE,
initialLoadSize = ChattingRoomSearchPagingSource.DEFAULT_SIZE,
enablePlaceholders = false,
),
pagingSourceFactory = {
ChattingRoomSearchPagingSource(
chattingRoomSearchService = chattingRoomSearchService,
keyword = keyword,
)
},
).flow
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.gamss.android.data.di

import com.gamss.android.data.remote.chattingsearch.ChattingRoomSearchService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
internal object ChattingRoomSearchModule {
@Provides
@Singleton
fun provideChattingRoomSearchService(retrofit: Retrofit): ChattingRoomSearchService =
retrofit.create(ChattingRoomSearchService::class.java)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gamss.android.data.di

import com.gamss.android.data.chattingsearch.ChattingRoomSearchRepositoryImpl
import com.gamss.android.data.local.auth.AuthTokenLocalDataSource
import com.gamss.android.data.local.auth.EncryptedAuthTokenLocalDataSource
import com.gamss.android.data.local.auth.TinkTokenCipher
Expand All @@ -9,6 +10,7 @@ import com.gamss.android.data.local.auth.TokenProviderImpl
import com.gamss.android.data.repository.AuthRepositoryImpl
import com.gamss.android.data.repository.UserRepositoryImpl
import com.gamss.android.domain.repository.AuthRepository
import com.gamss.android.domain.repository.ChattingRoomSearchRepository
import com.gamss.android.domain.repository.UserRepository
import dagger.Binds
import dagger.Module
Expand Down Expand Up @@ -58,6 +60,11 @@ internal abstract class RepositoryModule {
userRepositoryImpl: UserRepositoryImpl,
): UserRepository

@Binds
abstract fun bindChattingRoomSearchRepository(
chattingRoomSearchRepositoryImpl: ChattingRoomSearchRepositoryImpl,
): ChattingRoomSearchRepository

companion object {
@Provides
@Singleton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.gamss.android.data.remote.chattingsearch

import com.gamss.android.data.remote.chattingsearch.model.response.ChattingRoomSearchResponse
import com.gamss.android.data.remote.model.response.ApiResponse
import retrofit2.http.GET
import retrofit2.http.Query

/**
* 지정한 제목 또는 채팅 내용에 검색어가 포함된 본인 대화방을 최신순으로 조회한다.
* 현재 진행중인 대화를 대상으로 진행한다.
*/
internal interface ChattingRoomSearchService {

@GET("/api/conversations/search")
suspend fun searchChattingRooms(
@Query("keyword") keyword: String,
@Query("page") page: Int,
@Query("size") size: Int,
): ApiResponse<ChattingRoomSearchResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.gamss.android.data.remote.chattingsearch.model.response

import com.gamss.android.domain.chattingsearch.ChattingRoomSearch
import com.gamss.android.domain.chattingsearch.ChattingRoomSummary
import kotlinx.serialization.Serializable

@Serializable
internal data class ChattingRoomSearchResponse(
val content: List<ChattingRoomResponse>,
val page: Int,
val size: Int,
val totalElements: Long,
val totalPages: Int,
)

@Serializable
internal data class ChattingRoomResponse(
val conversationId: Long,
val title: String,
val status: String,
val createdAt: String,
)

internal fun ChattingRoomSearchResponse.toDomain(): ChattingRoomSearch =
ChattingRoomSearch(
rooms = content.map { it.toDomain() },
page = page,
size = size,
totalElements = totalElements,
totalPages = totalPages,
)

internal fun ChattingRoomResponse.toDomain(): ChattingRoomSummary =
ChattingRoomSummary(
conversationId = conversationId,
title = title,
status = status,
createdAt = createdAt,
)
Loading
Loading