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

[AN] 웹소켓 연결 #336

Merged
merged 13 commits into from
Aug 9, 2024
Merged
3 changes: 3 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ val kakaoNativeAppKey = localProperties.getProperty("KAKAO_NATIVE_APP_KEY") ?: "
val kakaoOauthHost = localProperties.getProperty("KAKAO_OAUTH_HOST") ?: ""
val naverClientId = localProperties.getProperty("NAVER_CLIEND_ID") ?: ""
val baseUrl = localProperties.getProperty("base_url") ?: ""
val websocketUrl = localProperties.getProperty("websocket_url") ?: ""

android {
namespace = "com.happy.friendogly"
Expand All @@ -39,6 +40,7 @@ android {
resValue("string", "KAKAO_OAUTH_HOST", kakaoOauthHost)
buildConfigField("String", "NAVER_CLIEND_ID", naverClientId)
buildConfigField("String", "base_url", baseUrl)
buildConfigField("String", "websocket_url", websocketUrl)
}

buildTypes {
Expand Down Expand Up @@ -82,6 +84,7 @@ dependencies {
implementation(libs.bundles.network)
implementation(libs.bundles.datastore)
implementation(libs.bundles.animation)
implementation(libs.bundles.stomp)
testImplementation(libs.bundles.test)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import com.happy.friendogly.analytics.AnalyticsHelper
import com.happy.friendogly.crashlytics.CrashlyticsHelper
import com.happy.friendogly.data.repository.AddressRepositoryImpl
import com.happy.friendogly.data.repository.AuthRepositoryImpl
import com.happy.friendogly.data.repository.ChatRepositoryImpl
import com.happy.friendogly.data.repository.ClubRepositoryImpl
import com.happy.friendogly.data.repository.KakaoLoginRepositoryImpl
import com.happy.friendogly.data.repository.MemberRepositoryImpl
import com.happy.friendogly.data.repository.PetRepositoryImpl
import com.happy.friendogly.data.repository.TokenRepositoryImpl
import com.happy.friendogly.data.repository.WebSocketRepositoryImpl
import com.happy.friendogly.data.repository.WoofRepositoryImpl
import com.happy.friendogly.data.source.AddressDataSource
import com.happy.friendogly.data.source.AuthDataSource
Expand All @@ -19,14 +21,17 @@ import com.happy.friendogly.data.source.KakaoLoginDataSource
import com.happy.friendogly.data.source.MemberDataSource
import com.happy.friendogly.data.source.PetDataSource
import com.happy.friendogly.data.source.TokenDataSource
import com.happy.friendogly.data.source.WebSocketDataSource
import com.happy.friendogly.data.source.WoofDataSource
import com.happy.friendogly.domain.repository.AddressRepository
import com.happy.friendogly.domain.repository.AuthRepository
import com.happy.friendogly.domain.repository.ChatRepository
import com.happy.friendogly.domain.repository.ClubRepository
import com.happy.friendogly.domain.repository.KakaoLoginRepository
import com.happy.friendogly.domain.repository.MemberRepository
import com.happy.friendogly.domain.repository.PetRepository
import com.happy.friendogly.domain.repository.TokenRepository
import com.happy.friendogly.domain.repository.WebSocketRepository
import com.happy.friendogly.domain.repository.WoofRepository
import com.happy.friendogly.domain.usecase.DeleteAddressUseCase
import com.happy.friendogly.domain.usecase.DeleteClubMemberUseCase
Expand Down Expand Up @@ -59,11 +64,13 @@ import com.happy.friendogly.local.source.AddressDataSourceImpl
import com.happy.friendogly.local.source.TokenDataSourceImpl
import com.happy.friendogly.remote.api.AuthenticationListener
import com.happy.friendogly.remote.api.BaseUrl
import com.happy.friendogly.remote.api.WebSocketService
import com.happy.friendogly.remote.di.RemoteModule
import com.happy.friendogly.remote.source.AuthDataSourceImpl
import com.happy.friendogly.remote.source.ClubDataSourceImpl
import com.happy.friendogly.remote.source.MemberDataSourceImpl
import com.happy.friendogly.remote.source.PetDataSourceImpl
import com.happy.friendogly.remote.source.WebSocketDataSourceImpl
import com.happy.friendogly.remote.source.WoofDataSourceImpl

class AppModule(context: Context) {
Expand All @@ -72,6 +79,7 @@ class AppModule(context: Context) {
val crashlyticsHelper = CrashlyticsHelper()

private val baseUrl = BaseUrl(BuildConfig.base_url)
private val websocketUrl = BaseUrl(BuildConfig.websocket_url)

private val tokenManager = TokenManager(context)
private val authenticationListener: AuthenticationListener =
Expand Down Expand Up @@ -113,6 +121,25 @@ class AppModule(context: Context) {
authenticationListener = authenticationListener,
)

private val webSocketService =
WebSocketService(
client =
RemoteModule.createStumpClient(
baseUrl = baseUrl,
tokenManager = tokenManager,
authenticationListener = authenticationListener,
),
tokenManager = tokenManager,
baseUrl = websocketUrl,
)

private val chatService =
RemoteModule.createChatService(
baseUrl = baseUrl,
tokenManager = tokenManager,
authenticationListener = authenticationListener,
)

// data source
private val authDataSource: AuthDataSource = AuthDataSourceImpl(service = authService)
private val clubDataSource: ClubDataSource = ClubDataSourceImpl(service = clubService)
Expand All @@ -123,6 +150,8 @@ class AppModule(context: Context) {
private val woofDataSource: WoofDataSource = WoofDataSourceImpl(service = woofService)
private val memberDataSource: MemberDataSource = MemberDataSourceImpl(service = memberService)
private val petDataSource: PetDataSource = PetDataSourceImpl(service = petService)
private val webSocketDataSource: WebSocketDataSource =
WebSocketDataSourceImpl(service = webSocketService)

// repository
private val authRepository: AuthRepository = AuthRepositoryImpl(source = authDataSource)
Expand All @@ -135,6 +164,9 @@ class AppModule(context: Context) {
private val petRepository: PetRepository = PetRepositoryImpl(source = petDataSource)
private val addressRepository: AddressRepository =
AddressRepositoryImpl(addressDataSource = addressDataSource)
val webSocketRepository: WebSocketRepository =
WebSocketRepositoryImpl(source = webSocketDataSource)
val chatRepository: ChatRepository = ChatRepositoryImpl(service = chatService)

// use case
val postKakaoLoginUseCase: PostKakaoLoginUseCase =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.happy.friendogly.data.mapper

import com.happy.friendogly.data.model.ChatMemberDto
import com.happy.friendogly.data.model.ChatRoomListDto
import com.happy.friendogly.domain.model.ChatMember
import com.happy.friendogly.domain.model.ChatRoom
import com.happy.friendogly.domain.model.ChatRooms

fun ChatMemberDto.toDomain(): ChatMember =
ChatMember(
isOwner = isOwner,
memberId = memberId,
memberName = memberName,
memberProfileImageUrl = memberProfileImageUrl,
)

fun ChatRoomListDto.toDomain(): ChatRooms =
ChatRooms(
myMemberId = myMemberId,
chatRooms =
chatRooms.map {
ChatRoom(
chatRoomId = it.chatRoomId,
clubName = it.clubName,
memberCount = it.memberCount,
clubImageUrl = it.clubImageUrl,
)
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.happy.friendogly.data.mapper

import com.happy.friendogly.data.model.MessageDto
import com.happy.friendogly.domain.model.ChatComponent
import com.happy.friendogly.domain.model.Message

fun MessageDto.toOther(): Message.Other =
Message.Other(
memberId = senderMemberId,
name = senderName,
content = content!!,
dateTime = createdAt,
profileUrl = profilePictureUrl,
)

fun MessageDto.toMine(): Message.Mine =
Message.Mine(
content = content!!,
dateTime = createdAt,
)

fun MessageDto.toEnter(): ChatComponent.Enter =
ChatComponent.Enter(
name = senderName,
)

fun MessageDto.toLeave(): ChatComponent.Leave =
ChatComponent.Leave(
name = senderName,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.happy.friendogly.data.model

data class ChatMemberDto(
val isOwner: Boolean,
val memberId: Long,
val memberName: String,
val memberProfileImageUrl: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.happy.friendogly.data.model

data class ChatRoomDto(
val chatRoomId: Long,
val clubName: String,
val memberCount: Int,
val clubImageUrl: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.happy.friendogly.data.model

data class ChatRoomListDto(
val myMemberId: Long,
val chatRooms: List<ChatRoomDto>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.happy.friendogly.data.model

import java.time.LocalDateTime

data class MessageDto(
val messageType: MessageTypeDto,
val senderMemberId: Long,
val senderName: String,
val content: String?,
val createdAt: LocalDateTime,
val profilePictureUrl: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.happy.friendogly.data.model

enum class MessageTypeDto {
ENTER,
CHAT,
LEAVE,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.happy.friendogly.data.repository

import com.happy.friendogly.data.mapper.toDomain
import com.happy.friendogly.domain.model.ChatMember
import com.happy.friendogly.domain.model.ChatRooms
import com.happy.friendogly.domain.repository.ChatRepository
import com.happy.friendogly.remote.api.ChatService
import com.happy.friendogly.remote.mapper.toData

class ChatRepositoryImpl(private val service: ChatService) : ChatRepository {
override suspend fun getChatList(): Result<ChatRooms> =
runCatching {
service.getChatList().data.toData().toDomain()
}

override suspend fun getMembers(chatRoomId: Long): Result<List<ChatMember>> =
runCatching {
service.getChatMembers(chatRoomId).data.map { it.toData() }.map { it.toDomain() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.happy.friendogly.data.repository

import com.happy.friendogly.data.mapper.toEnter
import com.happy.friendogly.data.mapper.toLeave
import com.happy.friendogly.data.mapper.toOther
import com.happy.friendogly.data.model.MessageTypeDto
import com.happy.friendogly.data.source.WebSocketDataSource
import com.happy.friendogly.domain.model.ChatComponent
import com.happy.friendogly.domain.repository.WebSocketRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class WebSocketRepositoryImpl(private val source: WebSocketDataSource) : WebSocketRepository {
override suspend fun publishInvite(memberId: Long) = source.publishInvite(memberId)

override suspend fun publishSend(
chatRoomId: Long,
content: String,
) = source.publishSend(chatRoomId, content)

override suspend fun publishLeave(chatRoomId: Long) = source.publishLeave(chatRoomId)

override suspend fun subscribeMessage(chatRoomId: Long): Flow<ChatComponent> =
source.subscribeMessage(chatRoomId).map {
when (it.messageType) {
MessageTypeDto.ENTER -> it.toEnter()
MessageTypeDto.LEAVE -> it.toLeave()
MessageTypeDto.CHAT -> it.toOther()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.happy.friendogly.data.source

import com.happy.friendogly.data.model.MessageDto
import kotlinx.coroutines.flow.Flow

interface WebSocketDataSource {
suspend fun publishInvite(memberId: Long)

suspend fun publishSend(
chatRoomId: Long,
content: String,
)

suspend fun publishLeave(chatRoomId: Long)

suspend fun subscribeMessage(chatRoomId: Long): Flow<MessageDto>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.happy.friendogly.domain.model

import java.time.LocalDate

sealed interface ChatComponent {
data class Date(val created: LocalDate) : ChatComponent

data class Enter(val name: String) : ChatComponent

data class Leave(val name: String) : ChatComponent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.happy.friendogly.domain.model

data class ChatMember(
val isOwner: Boolean,
val memberId: Long,
val memberName: String,
val memberProfileImageUrl: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.happy.friendogly.domain.model

data class ChatRoom(
val chatRoomId: Long,
val clubName: String,
val memberCount: Int,
val clubImageUrl: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.happy.friendogly.domain.model

data class ChatRooms(
val myMemberId: Long,
val chatRooms: List<ChatRoom>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.happy.friendogly.domain.model

import java.time.LocalDateTime

sealed interface Message : ChatComponent {
val content: String

data class Mine(
override val content: String,
val dateTime: LocalDateTime,
) : Message

data class Other(
val memberId: Long,
val name: String,
override val content: String,
val dateTime: LocalDateTime,
val profileUrl: String?,
) : Message
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.happy.friendogly.domain.repository

import com.happy.friendogly.domain.model.ChatMember
import com.happy.friendogly.domain.model.ChatRooms

interface ChatRepository {
suspend fun getChatList(): Result<ChatRooms>

suspend fun getMembers(chatRoomId: Long): Result<List<ChatMember>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.happy.friendogly.domain.repository

import com.happy.friendogly.domain.model.ChatComponent
import kotlinx.coroutines.flow.Flow

interface WebSocketRepository {
suspend fun publishInvite(chatRoomId: Long)

suspend fun publishSend(
chatRoomId: Long,
content: String,
)

suspend fun publishLeave(chatRoomId: Long)

suspend fun subscribeMessage(chatRoomId: Long): Flow<ChatComponent>
}
Loading
Loading