diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1d306d60..dbdfe748 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -32,6 +32,10 @@
+
+
diff --git a/data/src/main/java/com/cheocharm/data/error/ErrorData.kt b/data/src/main/java/com/cheocharm/data/error/ErrorData.kt
index afda20cc..0ddd28cc 100644
--- a/data/src/main/java/com/cheocharm/data/error/ErrorData.kt
+++ b/data/src/main/java/com/cheocharm/data/error/ErrorData.kt
@@ -14,4 +14,5 @@ sealed class ErrorData(
data class RefreshAccessTokenUnavailable(override val message: String) : ErrorData(message)
data class SearchGroupUnavailable(override val message: String) : ErrorData(message)
data class JoinGroupUnavailable(override val message: String) : ErrorData(message)
+ data class GroupCreateUnavailable(override val message: String) : ErrorData(message)
}
diff --git a/data/src/main/java/com/cheocharm/data/error/ErrorMapper.kt b/data/src/main/java/com/cheocharm/data/error/ErrorMapper.kt
index 18a50230..25156247 100644
--- a/data/src/main/java/com/cheocharm/data/error/ErrorMapper.kt
+++ b/data/src/main/java/com/cheocharm/data/error/ErrorMapper.kt
@@ -12,4 +12,5 @@ internal fun ErrorData.toDomain(): Error = when (this) {
is ErrorData.RefreshAccessTokenUnavailable -> Error.RefreshAccessTokenUnavailable(message)
is ErrorData.SearchGroupUnavailable -> Error.SearchGroupUnavailable(message)
is ErrorData.JoinGroupUnavailable -> Error.JoinGroupUnavailable(message)
+ is ErrorData.GroupCreateUnavailable -> Error.GroupCreateUnavailable(message)
}
diff --git a/data/src/main/java/com/cheocharm/data/repository/GroupRepositoryImpl.kt b/data/src/main/java/com/cheocharm/data/repository/GroupRepositoryImpl.kt
index 4cfac9eb..1bccd00c 100644
--- a/data/src/main/java/com/cheocharm/data/repository/GroupRepositoryImpl.kt
+++ b/data/src/main/java/com/cheocharm/data/repository/GroupRepositoryImpl.kt
@@ -5,6 +5,8 @@ import com.cheocharm.data.error.ErrorData
import com.cheocharm.data.error.toDomain
import com.cheocharm.data.source.GroupRemoteDataSource
import com.cheocharm.domain.model.Group
+import com.cheocharm.domain.model.GroupJoin
+import com.cheocharm.domain.model.group.GroupCreateRequest
import com.cheocharm.domain.repository.GroupRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@@ -17,7 +19,7 @@ class GroupRepositoryImpl @Inject constructor(
return groupRemoteDataSource.fetchGroupSearchList(searchGroupName)
}
- override suspend fun joinGroup(groupName: String): Result {
+ override suspend fun joinGroup(groupName: String): Result {
val result = groupRemoteDataSource.joinGroup(groupName)
return when (val exception = result.exceptionOrNull()) {
is ErrorData -> Result.failure(exception.toDomain())
@@ -25,4 +27,13 @@ class GroupRepositoryImpl @Inject constructor(
else -> Result.failure(exception)
}
}
+
+ override suspend fun createGroup(groupCreateRequest: GroupCreateRequest): Result {
+ val result = groupRemoteDataSource.createGroup(groupCreateRequest)
+ return when (val exception = result.exceptionOrNull()) {
+ is ErrorData -> Result.failure(exception.toDomain())
+ null -> result
+ else -> Result.failure(exception)
+ }
+ }
}
diff --git a/data/src/main/java/com/cheocharm/data/source/GroupRemoteDataSource.kt b/data/src/main/java/com/cheocharm/data/source/GroupRemoteDataSource.kt
index 61718f52..ff1c9dc0 100644
--- a/data/src/main/java/com/cheocharm/data/source/GroupRemoteDataSource.kt
+++ b/data/src/main/java/com/cheocharm/data/source/GroupRemoteDataSource.kt
@@ -2,11 +2,15 @@ package com.cheocharm.data.source
import androidx.paging.PagingData
import com.cheocharm.domain.model.Group
+import com.cheocharm.domain.model.GroupJoin
+import com.cheocharm.domain.model.group.GroupCreateRequest
import kotlinx.coroutines.flow.Flow
interface GroupRemoteDataSource {
fun fetchGroupSearchList(searchGroupName: String): Flow>
- suspend fun joinGroup(groupName: String): Result
+ suspend fun joinGroup(groupName: String): Result
+
+ suspend fun createGroup(groupCreateRequest: GroupCreateRequest): Result
}
diff --git a/domain/src/main/java/com/cheocharm/domain/model/Error.kt b/domain/src/main/java/com/cheocharm/domain/model/Error.kt
index 74d19b83..231adccc 100644
--- a/domain/src/main/java/com/cheocharm/domain/model/Error.kt
+++ b/domain/src/main/java/com/cheocharm/domain/model/Error.kt
@@ -14,4 +14,5 @@ sealed class Error(
data class RefreshAccessTokenUnavailable(override val message: String) : Error()
data class SearchGroupUnavailable(override val message: String) : Error()
data class JoinGroupUnavailable(override val message: String) : Error()
+ data class GroupCreateUnavailable(override val message: String) : Error()
}
diff --git a/domain/src/main/java/com/cheocharm/domain/model/GroupJoin.kt b/domain/src/main/java/com/cheocharm/domain/model/GroupJoin.kt
new file mode 100644
index 00000000..cc683fb9
--- /dev/null
+++ b/domain/src/main/java/com/cheocharm/domain/model/GroupJoin.kt
@@ -0,0 +1,6 @@
+package com.cheocharm.domain.model
+
+data class GroupJoin(
+ val alreadyJoin: Boolean,
+ val status: String
+)
diff --git a/domain/src/main/java/com/cheocharm/domain/model/group/GroupCreateRequest.kt b/domain/src/main/java/com/cheocharm/domain/model/group/GroupCreateRequest.kt
new file mode 100644
index 00000000..32ef9795
--- /dev/null
+++ b/domain/src/main/java/com/cheocharm/domain/model/group/GroupCreateRequest.kt
@@ -0,0 +1,10 @@
+package com.cheocharm.domain.model.group
+
+import java.io.File
+
+data class GroupCreateRequest(
+ val groupImage: File,
+ val groupName: String,
+ val groupBio: String,
+ val changeStatus: Boolean
+)
diff --git a/domain/src/main/java/com/cheocharm/domain/repository/GroupRepository.kt b/domain/src/main/java/com/cheocharm/domain/repository/GroupRepository.kt
index dfef6d25..56fcc8b6 100644
--- a/domain/src/main/java/com/cheocharm/domain/repository/GroupRepository.kt
+++ b/domain/src/main/java/com/cheocharm/domain/repository/GroupRepository.kt
@@ -2,11 +2,15 @@ package com.cheocharm.domain.repository
import androidx.paging.PagingData
import com.cheocharm.domain.model.Group
+import com.cheocharm.domain.model.GroupJoin
+import com.cheocharm.domain.model.group.GroupCreateRequest
import kotlinx.coroutines.flow.Flow
interface GroupRepository {
fun searchGroup(searchGroupName: String): Flow>
- suspend fun joinGroup(groupName: String): Result
+ suspend fun joinGroup(groupName: String): Result
+
+ suspend fun createGroup(groupCreateRequest: GroupCreateRequest): Result
}
diff --git a/domain/src/main/java/com/cheocharm/domain/usecase/group/GroupCreateUseCase.kt b/domain/src/main/java/com/cheocharm/domain/usecase/group/GroupCreateUseCase.kt
new file mode 100644
index 00000000..eee1f9ed
--- /dev/null
+++ b/domain/src/main/java/com/cheocharm/domain/usecase/group/GroupCreateUseCase.kt
@@ -0,0 +1,13 @@
+package com.cheocharm.domain.usecase.group
+
+import com.cheocharm.domain.model.group.GroupCreateRequest
+import com.cheocharm.domain.repository.GroupRepository
+import javax.inject.Inject
+
+class GroupCreateUseCase @Inject constructor(
+ private val groupRepository: GroupRepository
+) {
+ suspend operator fun invoke(groupCreateRequest: GroupCreateRequest): Result {
+ return groupRepository.createGroup(groupCreateRequest)
+ }
+}
diff --git a/domain/src/main/java/com/cheocharm/domain/usecase/group/JoinGroupUseCase.kt b/domain/src/main/java/com/cheocharm/domain/usecase/group/JoinGroupUseCase.kt
index 9f0bf413..651f9251 100644
--- a/domain/src/main/java/com/cheocharm/domain/usecase/group/JoinGroupUseCase.kt
+++ b/domain/src/main/java/com/cheocharm/domain/usecase/group/JoinGroupUseCase.kt
@@ -1,12 +1,13 @@
package com.cheocharm.domain.usecase.group
+import com.cheocharm.domain.model.GroupJoin
import com.cheocharm.domain.repository.GroupRepository
import javax.inject.Inject
class JoinGroupUseCase @Inject constructor(
private val groupRepository: GroupRepository
) {
- suspend operator fun invoke(groupName: String): Result {
+ suspend operator fun invoke(groupName: String): Result {
return groupRepository.joinGroup(groupName)
}
}
diff --git a/presentation/build.gradle b/presentation/build.gradle
index 2c49d378..8b7cbceb 100644
--- a/presentation/build.gradle
+++ b/presentation/build.gradle
@@ -80,4 +80,7 @@ dependencies {
// paging
implementation "androidx.paging:paging-runtime:$pagingVersion"
+
+ // circle imageview
+ implementation 'de.hdodenhof:circleimageview:3.1.0'
}
diff --git a/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateActivity.kt b/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateActivity.kt
new file mode 100644
index 00000000..bc90d971
--- /dev/null
+++ b/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateActivity.kt
@@ -0,0 +1,36 @@
+package com.cheocharm.presentation.ui.group
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import com.cheocharm.presentation.R
+import com.cheocharm.presentation.base.BaseActivity
+import com.cheocharm.presentation.databinding.ActivityGroupCreateBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class GroupCreateActivity :
+ BaseActivity(R.layout.activity_group_create) {
+
+ override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+ hideKeyboardWhenOutsideTouched(ev)
+ return super.dispatchTouchEvent(ev)
+ }
+
+ private fun hideKeyboardWhenOutsideTouched(ev: MotionEvent?) {
+ if (ev?.action == MotionEvent.ACTION_DOWN) {
+ val v = currentFocus
+ if (v is EditText) {
+ val rect = Rect()
+ v.getGlobalVisibleRect(rect)
+ if (!rect.contains(ev.rawX.toInt(), ev.rawY.toInt())) {
+ v.clearFocus()
+ val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(v.windowToken, 0)
+ }
+ }
+ }
+ }
+}
diff --git a/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateFragment.kt b/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateFragment.kt
new file mode 100644
index 00000000..9d5a9d1a
--- /dev/null
+++ b/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateFragment.kt
@@ -0,0 +1,118 @@
+package com.cheocharm.presentation.ui.group
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.ImageDecoder
+import android.os.Build
+import android.os.Bundle
+import android.provider.MediaStore
+import android.util.Log
+import android.view.View
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.view.isVisible
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.findNavController
+import com.cheocharm.presentation.R
+import com.cheocharm.presentation.base.BaseFragment
+import com.cheocharm.presentation.common.EventObserver
+import com.cheocharm.presentation.databinding.FragmentGroupCreateBinding
+import com.cheocharm.presentation.util.UriUtil
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class GroupCreateFragment :
+ BaseFragment(R.layout.fragment_group_create) {
+
+ private val groupCreateViewModel: GroupCreateViewModel by activityViewModels()
+ private lateinit var galleryImageLauncher: ActivityResultLauncher
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.viewmodel = groupCreateViewModel
+
+ initButtons()
+ initObservers()
+ initGalleryLauncher()
+ }
+
+ private fun initButtons() {
+ binding.toolbarGroupCreate.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.location_confirm -> {
+ if (groupCreateViewModel.isGroupEnabled.value == true)
+ groupCreateViewModel.requestGroupCreate()
+ true
+ }
+ else -> {
+ false
+ }
+ }
+ }
+ binding.toolbarGroupCreate.setNavigationOnClickListener {
+ requireActivity().finish()
+ }
+ binding.ivGroupCreateGroup.setOnClickListener {
+ selectGroupImage()
+ }
+ binding.etGroupCreateName.doOnTextChanged { text, start, before, count ->
+ groupCreateViewModel.setGroupName(text.toString())
+ groupCreateViewModel.checkGroupNameVerified()
+ groupCreateViewModel.checkGroupEnabled()
+ }
+ binding.etGroupCreateBio.doOnTextChanged { text, start, before, count ->
+ groupCreateViewModel.setGroupBio(text.toString())
+ groupCreateViewModel.checkGroupEnabled()
+ }
+ }
+
+ private fun initObservers() {
+ groupCreateViewModel.isGoToSearchEnabled.observe(viewLifecycleOwner, EventObserver {
+ //TODO: 검색화면으로 넘어가기
+// findNavController().navigate(R.id.action_groupCreateFragment_to_groupCreateSearchFragment)
+ })
+ }
+
+ private fun initGalleryLauncher() {
+ galleryImageLauncher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
+ if (activityResult.resultCode == Activity.RESULT_OK && activityResult.data != null) {
+ val imageUri = activityResult.data?.data
+ runCatching {
+ imageUri?.let { uri ->
+ val bitmap = if (Build.VERSION.SDK_INT < 28) {
+ MediaStore.Images.Media.getBitmap(
+ requireActivity().contentResolver, uri
+ )
+ } else {
+ val source = ImageDecoder.createSource(
+ requireActivity().contentResolver,
+ uri
+ )
+ ImageDecoder.decodeBitmap(source)
+ }
+ val file = UriUtil.getFileFromUri(requireActivity(), uri)
+ groupCreateViewModel.setGroupImage(file)
+ binding.ivGroupCreateGroup.setImageBitmap(bitmap)
+ binding.tvGroupCreateImage.isVisible = false
+ binding.ivGroupCreateImage.isVisible = false
+ }
+ }.onFailure {
+ Log.e("SignUpProfileFragment", "${it.message}")
+ }
+ } else if (activityResult.resultCode == Activity.RESULT_CANCELED) {
+ Toast.makeText(requireActivity(), "사진 선택 취소", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ private fun selectGroupImage() {
+ val intent = Intent(Intent.ACTION_PICK).apply {
+ type = "image/*"
+ }
+ galleryImageLauncher.launch(intent)
+ }
+}
diff --git a/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateSearchFragment.kt b/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateSearchFragment.kt
new file mode 100644
index 00000000..f75acacf
--- /dev/null
+++ b/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateSearchFragment.kt
@@ -0,0 +1,15 @@
+package com.cheocharm.presentation.ui.group
+
+import androidx.fragment.app.activityViewModels
+import com.cheocharm.presentation.R
+import com.cheocharm.presentation.base.BaseFragment
+import com.cheocharm.presentation.databinding.FragmentGroupCreateSearchBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class GroupCreateSearchFragment :
+ BaseFragment(R.layout.fragment_group_create_search) {
+
+ private val groupCreateViewModel by activityViewModels()
+
+}
diff --git a/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateViewModel.kt b/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateViewModel.kt
new file mode 100644
index 00000000..34af4077
--- /dev/null
+++ b/presentation/src/main/java/com/cheocharm/presentation/ui/group/GroupCreateViewModel.kt
@@ -0,0 +1,103 @@
+package com.cheocharm.presentation.ui.group
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.cheocharm.domain.model.group.GroupCreateRequest
+import com.cheocharm.domain.model.group.GroupCreateSearchResult
+import com.cheocharm.domain.usecase.group.GroupCreateUseCase
+import com.cheocharm.presentation.common.Event
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import java.io.File
+import java.util.regex.Pattern
+import javax.inject.Inject
+
+@HiltViewModel
+class GroupCreateViewModel @Inject constructor(
+ private val groupCreateUseCase: GroupCreateUseCase
+) : ViewModel() {
+
+ private val _groupImage = MutableLiveData()
+ val groupImage: LiveData
+ get() = _groupImage
+
+ private val _groupName = MutableLiveData()
+ val groupName: LiveData
+ get() = _groupName
+
+ private val _groupBio = MutableLiveData()
+ val groupBio: LiveData
+ get() = _groupBio
+
+ private var isGroupNameVerified = false
+
+ val isSearchEnabled = MutableLiveData()
+
+ private val _isGroupEnabled = MutableLiveData(false)
+ val isGroupEnabled: LiveData
+ get() = _isGroupEnabled
+
+ private val _isGoToSearchEnabled = MutableLiveData>()
+ val isGoToSearchEnabled: LiveData>
+ get() = _isGoToSearchEnabled
+
+ // search
+ private val _searchMemberName = MutableLiveData()
+ val searchMemberName: LiveData
+ get() = _searchMemberName
+
+ private val _searchResultList = MutableLiveData>()
+ val searchResultList: LiveData>
+ get() = _searchResultList
+
+ private val _inviteMemberList = MutableLiveData>()
+ val inviteMemberList: LiveData>
+ get() = _inviteMemberList
+
+ fun setGroupImage(groupImage: File) {
+ _groupImage.value = groupImage
+ }
+
+ fun setGroupName(name: String) {
+ _groupName.value = name
+ }
+
+ fun setGroupBio(bio: String) {
+ _groupBio.value = bio
+ }
+
+ fun checkGroupNameVerified() {
+ val pattern = Pattern.compile("^[가-힣a-zA-Z0-9]{2,12}$")
+ isGroupNameVerified = pattern.matcher(groupName.value ?: return).find() == true
+ }
+
+ fun checkGroupEnabled() {
+ _isGroupEnabled.value = groupImage.value != null && groupName.value.isNullOrEmpty()
+ .not() && groupBio.value.isNullOrEmpty().not() && isGroupNameVerified
+ }
+
+ fun requestGroupCreate() {
+ if (isGroupEnabled.value != true) return
+
+ val image = groupImage.value ?: return
+ val name = groupName.value ?: return
+ val bio = groupBio.value ?: return
+
+ viewModelScope.launch {
+ groupCreateUseCase.invoke(
+ GroupCreateRequest(
+ image,
+ name,
+ bio,
+ isSearchEnabled.value ?: false
+ )
+ )
+ .onSuccess { _isGoToSearchEnabled.value = Event(Unit) }
+ .onFailure {
+ // TODO: 그룹 셍성 실패 토스트 띄우기
+ }
+ }
+ }
+}
diff --git a/presentation/src/main/java/com/cheocharm/presentation/ui/search/SearchFragment.kt b/presentation/src/main/java/com/cheocharm/presentation/ui/search/SearchFragment.kt
index 821c8623..9df89905 100644
--- a/presentation/src/main/java/com/cheocharm/presentation/ui/search/SearchFragment.kt
+++ b/presentation/src/main/java/com/cheocharm/presentation/ui/search/SearchFragment.kt
@@ -1,5 +1,6 @@
package com.cheocharm.presentation.ui.search
+import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
@@ -11,6 +12,7 @@ import com.cheocharm.presentation.R
import com.cheocharm.presentation.databinding.ActivityMainBinding
import com.cheocharm.presentation.databinding.FragmentSearchBinding
import com.cheocharm.presentation.ui.MainActivity
+import com.cheocharm.presentation.ui.group.GroupCreateActivity
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -27,6 +29,7 @@ class SearchFragment : BaseFragment(R.layout.fragment_sea
mainActivityBinding = (activity as MainActivity).getBinding()
initEditTexts()
+ initButtons()
initObservers()
}
@@ -49,6 +52,13 @@ class SearchFragment : BaseFragment(R.layout.fragment_sea
}
}
+ private fun initButtons() {
+ binding.btnSearchGroupCreate.setOnClickListener {
+ val intent = Intent(requireActivity(), GroupCreateActivity::class.java)
+ startActivity(intent)
+ }
+ }
+
private fun initObservers() {
}
diff --git a/presentation/src/main/java/com/cheocharm/presentation/ui/search/SearchViewModel.kt b/presentation/src/main/java/com/cheocharm/presentation/ui/search/SearchViewModel.kt
index 12d0ff5c..967a465d 100644
--- a/presentation/src/main/java/com/cheocharm/presentation/ui/search/SearchViewModel.kt
+++ b/presentation/src/main/java/com/cheocharm/presentation/ui/search/SearchViewModel.kt
@@ -51,8 +51,9 @@ class SearchViewModel @Inject constructor(
viewModelScope.launch {
selectedGroup.value?.name?.let {
joinGroupUseCase.invoke(it)
- .onSuccess {
- _searchGroupJoinBottom.value = Event(Unit)
+ .onSuccess { groupJoin ->
+ if (groupJoin.alreadyJoin.not()) _searchGroupJoinBottom.value = Event(Unit)
+ else setToastMessage(groupJoin.status)
}.onFailure { throwable ->
when (throwable) {
is Error.JoinGroupUnavailable -> setToastMessage(throwable.message)
diff --git a/presentation/src/main/res/color/switch_track.xml b/presentation/src/main/res/color/switch_track.xml
new file mode 100644
index 00000000..8dfcf497
--- /dev/null
+++ b/presentation/src/main/res/color/switch_track.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/presentation/src/main/res/drawable/bg_cursor.xml b/presentation/src/main/res/drawable/bg_cursor.xml
index 01a99f38..4956f243 100644
--- a/presentation/src/main/res/drawable/bg_cursor.xml
+++ b/presentation/src/main/res/drawable/bg_cursor.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/presentation/src/main/res/drawable/bg_outlined_radius_10dp.xml b/presentation/src/main/res/drawable/bg_outlined_radius_10dp.xml
new file mode 100644
index 00000000..1cb93815
--- /dev/null
+++ b/presentation/src/main/res/drawable/bg_outlined_radius_10dp.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/presentation/src/main/res/layout/activity_group_create.xml b/presentation/src/main/res/layout/activity_group_create.xml
new file mode 100644
index 00000000..649a1913
--- /dev/null
+++ b/presentation/src/main/res/layout/activity_group_create.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/presentation/src/main/res/layout/fragment_group_create.xml b/presentation/src/main/res/layout/fragment_group_create.xml
new file mode 100644
index 00000000..91dc4362
--- /dev/null
+++ b/presentation/src/main/res/layout/fragment_group_create.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/presentation/src/main/res/layout/fragment_group_create_search.xml b/presentation/src/main/res/layout/fragment_group_create_search.xml
new file mode 100644
index 00000000..7d480fa6
--- /dev/null
+++ b/presentation/src/main/res/layout/fragment_group_create_search.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/presentation/src/main/res/navigation/nav_group.xml b/presentation/src/main/res/navigation/nav_group.xml
new file mode 100644
index 00000000..de3cf92e
--- /dev/null
+++ b/presentation/src/main/res/navigation/nav_group.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/presentation/src/main/res/values/dimens.xml b/presentation/src/main/res/values/dimens.xml
index bc9960e9..843fd90a 100644
--- a/presentation/src/main/res/values/dimens.xml
+++ b/presentation/src/main/res/values/dimens.xml
@@ -3,6 +3,7 @@
5dp
8dp
12dp
+ 14dp
16dp
18dp
20dp
@@ -45,4 +46,8 @@
120dp
10dp
15dp
+
+ 37dp
+ 2dp
+ 120dp
diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml
index 3edb8562..a5e863fd 100644
--- a/presentation/src/main/res/values/strings.xml
+++ b/presentation/src/main/res/values/strings.xml
@@ -63,4 +63,11 @@
그룹 가입하기
%s의 그룹장에게\n가입요청이 전달되었습니다.
가입이 승인되면 홈에서\n가입된 그룹을 볼 수 있어요!
+
+ 그룹 만들기
+ 그룹 이름 입력
+ 그룹의 소개글을 간략하게 적어주세요.
+ 검색 비허용
+ 그룹이 검색결과로 뜨지 않습니다.\n오직 그룹장의 초대로 멤버가 들어올 수 있습니다.
+ 사진 추가
diff --git a/presentation/src/main/res/values/themes.xml b/presentation/src/main/res/values/themes.xml
index a9730e03..29f37866 100644
--- a/presentation/src/main/res/values/themes.xml
+++ b/presentation/src/main/res/values/themes.xml
@@ -36,4 +36,9 @@
- @color/grey_dark
- @drawable/bg_cursor
+
+
diff --git a/remote/src/main/java/com/cheocharm/remote/api/GroupApi.kt b/remote/src/main/java/com/cheocharm/remote/api/GroupApi.kt
index 997cd516..ce9db3f3 100644
--- a/remote/src/main/java/com/cheocharm/remote/api/GroupApi.kt
+++ b/remote/src/main/java/com/cheocharm/remote/api/GroupApi.kt
@@ -1,20 +1,52 @@
package com.cheocharm.remote.api
import com.cheocharm.remote.model.BaseResponse
+import com.cheocharm.remote.model.request.GroupCreateDto
+import com.cheocharm.remote.model.response.group.GroupJoinResponse
+import com.cheocharm.remote.model.response.group.GroupMemberResponse
+import com.cheocharm.remote.model.response.group.GroupResponse
import com.cheocharm.remote.model.response.group.GroupSearchResponse
-import retrofit2.http.Body
-import retrofit2.http.GET
-import retrofit2.http.POST
-import retrofit2.http.Query
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import retrofit2.http.*
interface GroupApi {
@GET("group")
suspend fun fetchGroupSearchList(
@Query("page") page: Int,
- @Query("searchName") searchName: String
+ @Query("groupName") searchName: String,
+ @Query("cursorId") cursorId: Int
): BaseResponse
+ @Multipart
+ @POST("group")
+ suspend fun createGroup(
+ @Part("dto") dto: GroupCreateDto,
+ @Part file: MultipartBody.Part
+ ): BaseResponse
+
@POST("group/join")
- suspend fun joinGroup(@Body body: HashMap): BaseResponse
+ suspend fun joinGroup(@Body body: HashMap): BaseResponse
+
+ @PATCH("group/join")
+ suspend fun modifyJoinGroup(@Body body: RequestBody): BaseResponse
+
+ @POST("group/invite")
+ suspend fun inviteGroup(@Body body: RequestBody): BaseResponse
+
+ @PATCH("group/status")
+ suspend fun modifyGroupStatus(@Body body: RequestBody): BaseResponse
+
+ @PATCH("group/exit")
+ suspend fun exitGroup(@Body body: RequestBody): BaseResponse
+
+ @PATCH("group/chief")
+ suspend fun modifyChief(@Body body: RequestBody): BaseResponse
+
+ @GET("group/mygroup")
+ suspend fun fetchMyGroup(): BaseResponse>
+
+ @GET("group/member")
+ suspend fun fetchGroupMember(@Query("groupId") groupId: Int): BaseResponse>
}
diff --git a/remote/src/main/java/com/cheocharm/remote/mapper/GroupMapper.kt b/remote/src/main/java/com/cheocharm/remote/mapper/GroupMapper.kt
index d1fcdc01..ab5aff89 100644
--- a/remote/src/main/java/com/cheocharm/remote/mapper/GroupMapper.kt
+++ b/remote/src/main/java/com/cheocharm/remote/mapper/GroupMapper.kt
@@ -1,8 +1,12 @@
package com.cheocharm.remote.mapper
import com.cheocharm.domain.model.Group
+import com.cheocharm.domain.model.GroupJoin
import com.cheocharm.domain.model.GroupMember
import com.cheocharm.domain.model.GroupSearch
+import com.cheocharm.domain.model.group.GroupCreateRequest
+import com.cheocharm.remote.model.request.GroupCreateDto
+import com.cheocharm.remote.model.response.group.GroupJoinResponse
import com.cheocharm.remote.model.response.group.GroupSearchResponse
// remote -> domain
@@ -15,3 +19,13 @@ internal fun GroupSearchResponse.toDomain(): GroupSearch {
}
return GroupSearch(hasNextPage, groupResultList)
}
+
+// remote -> domain
+internal fun GroupJoinResponse.toDomain(): GroupJoin {
+ return GroupJoin(alreadyJoin, status)
+}
+
+// domain -> remote
+internal fun GroupCreateRequest.toDto(): GroupCreateDto {
+ return GroupCreateDto(groupName, groupBio, changeStatus)
+}
diff --git a/remote/src/main/java/com/cheocharm/remote/model/request/GroupCreateDto.kt b/remote/src/main/java/com/cheocharm/remote/model/request/GroupCreateDto.kt
new file mode 100644
index 00000000..ebc55f76
--- /dev/null
+++ b/remote/src/main/java/com/cheocharm/remote/model/request/GroupCreateDto.kt
@@ -0,0 +1,7 @@
+package com.cheocharm.remote.model.request
+
+data class GroupCreateDto(
+ val groupName: String,
+ val bio: String,
+ val changeStatus: Boolean
+)
diff --git a/remote/src/main/java/com/cheocharm/remote/model/response/group/GroupJoinResponse.kt b/remote/src/main/java/com/cheocharm/remote/model/response/group/GroupJoinResponse.kt
new file mode 100644
index 00000000..f5c14181
--- /dev/null
+++ b/remote/src/main/java/com/cheocharm/remote/model/response/group/GroupJoinResponse.kt
@@ -0,0 +1,6 @@
+package com.cheocharm.remote.model.response.group
+
+data class GroupJoinResponse(
+ val alreadyJoin: Boolean,
+ val status: String
+)
diff --git a/remote/src/main/java/com/cheocharm/remote/model/response/group/GroupMemberResponse.kt b/remote/src/main/java/com/cheocharm/remote/model/response/group/GroupMemberResponse.kt
new file mode 100644
index 00000000..5ef1cd13
--- /dev/null
+++ b/remote/src/main/java/com/cheocharm/remote/model/response/group/GroupMemberResponse.kt
@@ -0,0 +1,8 @@
+package com.cheocharm.remote.model.response.group
+
+data class GroupMemberResponse(
+ val userName: String,
+ val userImageUrl: String,
+ val userId: Int,
+ val invitationStatus: String
+)
diff --git a/remote/src/main/java/com/cheocharm/remote/source/GroupRemoteDataSourceImpl.kt b/remote/src/main/java/com/cheocharm/remote/source/GroupRemoteDataSourceImpl.kt
index d1acb7ba..2e0b212b 100644
--- a/remote/src/main/java/com/cheocharm/remote/source/GroupRemoteDataSourceImpl.kt
+++ b/remote/src/main/java/com/cheocharm/remote/source/GroupRemoteDataSourceImpl.kt
@@ -6,8 +6,15 @@ import androidx.paging.PagingData
import com.cheocharm.data.error.ErrorData
import com.cheocharm.data.source.GroupRemoteDataSource
import com.cheocharm.domain.model.Group
+import com.cheocharm.domain.model.GroupJoin
+import com.cheocharm.domain.model.group.GroupCreateRequest
import com.cheocharm.remote.api.GroupApi
+import com.cheocharm.remote.mapper.toDomain
+import com.cheocharm.remote.mapper.toDto
import kotlinx.coroutines.flow.Flow
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.asRequestBody
import java.net.UnknownHostException
import javax.inject.Inject
@@ -21,15 +28,42 @@ class GroupRemoteDataSourceImpl @Inject constructor(
}.flow
}
- override suspend fun joinGroup(groupName: String): Result {
+ override suspend fun joinGroup(groupName: String): Result {
val result = runCatching { groupApi.joinGroup(hashMapOf("groupName" to groupName)) }
return when (val exception = result.exceptionOrNull()) {
null -> {
val response =
result.getOrNull() ?: return Result.failure(Throwable(NullPointerException()))
- if (response.statusCode == 200) Result.success(Unit)
- else Result.failure(ErrorData.JoinGroupUnavailable(response.message))
+ Result.success(
+ response.data?.toDomain() ?: return Result.failure(
+ ErrorData.JoinGroupUnavailable(response.message)
+ )
+ )
+ }
+ is UnknownHostException -> Result.failure(ErrorData.NetworkUnavailable)
+ else -> Result.failure(exception)
+ }
+ }
+
+ override suspend fun createGroup(groupCreateRequest: GroupCreateRequest): Result {
+ val groupCreateDto = groupCreateRequest.toDto()
+ val fileRequestBody = MultipartBody.Part.createFormData(
+ "file",
+ groupCreateRequest.groupImage.name,
+ groupCreateRequest.groupImage.asRequestBody("image/*".toMediaType())
+ )
+
+ val result = runCatching { groupApi.createGroup(groupCreateDto, fileRequestBody) }
+ return when (val exception = result.exceptionOrNull()) {
+ null -> {
+ val response =
+ result.getOrNull() ?: return Result.failure(Throwable(NullPointerException()))
+ Result.success(
+ if (response.statusCode != 200) return Result.failure(
+ ErrorData.GroupCreateUnavailable(response.message)
+ ) else response.data ?: ""
+ )
}
is UnknownHostException -> Result.failure(ErrorData.NetworkUnavailable)
else -> Result.failure(exception)
diff --git a/remote/src/main/java/com/cheocharm/remote/source/GroupSearchPagingSource.kt b/remote/src/main/java/com/cheocharm/remote/source/GroupSearchPagingSource.kt
index 63ef4c69..1a7fa5a5 100644
--- a/remote/src/main/java/com/cheocharm/remote/source/GroupSearchPagingSource.kt
+++ b/remote/src/main/java/com/cheocharm/remote/source/GroupSearchPagingSource.kt
@@ -22,7 +22,7 @@ class GroupSearchPagingSource(
override suspend fun load(params: LoadParams): LoadResult {
val start = params.key ?: 0
- val result = runCatching { groupApi.fetchGroupSearchList(start, groupSearchName) }
+ val result = runCatching { groupApi.fetchGroupSearchList(start, groupSearchName, 0) }
return when (val exception = result.exceptionOrNull()) {
null -> {