diff --git a/data/src/main/java/org/gdsc/data/database/RestaurantByMapPagingSource.kt b/data/src/main/java/org/gdsc/data/database/RestaurantByMapPagingSource.kt index 01ffc2c3..2768f349 100644 --- a/data/src/main/java/org/gdsc/data/database/RestaurantByMapPagingSource.kt +++ b/data/src/main/java/org/gdsc/data/database/RestaurantByMapPagingSource.kt @@ -6,6 +6,7 @@ import org.gdsc.data.model.RegisteredRestaurantResponse import org.gdsc.data.network.RestaurantAPI import org.gdsc.domain.SortType import org.gdsc.domain.model.request.RestaurantSearchRequest +import org.gdsc.domain.model.response.Group class RestaurantByMapPagingSource( private val api: RestaurantAPI, diff --git a/data/src/main/java/org/gdsc/data/datasource/GroupDataSource.kt b/data/src/main/java/org/gdsc/data/datasource/GroupDataSource.kt new file mode 100644 index 00000000..d6c2680d --- /dev/null +++ b/data/src/main/java/org/gdsc/data/datasource/GroupDataSource.kt @@ -0,0 +1,10 @@ +package org.gdsc.data.datasource + +import org.gdsc.domain.model.response.Group + + +interface GroupDataSource { + suspend fun getMyGroups(): List + + suspend fun selectGroup(groupId: Int): String +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/GroupDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/GroupDataSourceImpl.kt new file mode 100644 index 00000000..9dc80400 --- /dev/null +++ b/data/src/main/java/org/gdsc/data/datasource/GroupDataSourceImpl.kt @@ -0,0 +1,32 @@ +package org.gdsc.data.datasource + +import android.util.Log +import org.gdsc.data.network.GroupAPI +import org.gdsc.domain.model.response.Group +import javax.inject.Inject + +class GroupDataSourceImpl @Inject constructor( + private val groupAPI: GroupAPI, +): GroupDataSource { + override suspend fun getMyGroups(): List { + runCatching { + groupAPI.getMyGroups() + }.onSuccess { + return it.data + }.onFailure { + return emptyList() + } + return emptyList() + } + + override suspend fun selectGroup(groupId: Int): String { + runCatching { + groupAPI.selectGroup(groupId) + }.onSuccess { + return it.data + }.onFailure { + return "" + } + return "" + } +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt index bb8ffcea..c0417a97 100644 --- a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt +++ b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSource.kt @@ -15,6 +15,7 @@ import org.gdsc.domain.model.RestaurantLocationInfo import org.gdsc.domain.model.UserLocation import org.gdsc.domain.model.request.ModifyRestaurantInfoRequest import org.gdsc.domain.model.request.RestaurantRegistrationRequest +import org.gdsc.domain.model.response.Group import org.gdsc.domain.model.response.RestaurantInfoResponse interface RestaurantDataSource { @@ -39,7 +40,13 @@ interface RestaurantDataSource { suspend fun putRestaurantInfo(putRestaurantInfoRequest: ModifyRestaurantInfoRequest): String suspend fun getRestaurantsByMap( - userLocation: Location?, startLocation: Location?, endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, drinkPossibility: DrinkPossibility? + userLocation: Location?, + startLocation: Location?, + endLocation: Location?, + sortType: SortType, + foodCategory: FoodCategory?, + drinkPossibility: DrinkPossibility?, + currentGroup: Group?, ): Flow> suspend fun getRegisteredRestaurantsBySearch(keyword: String?, userLocation: Location?): Flow> diff --git a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt index e3a0507a..2faa69fb 100644 --- a/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt +++ b/data/src/main/java/org/gdsc/data/datasource/RestaurantDataSourceImpl.kt @@ -34,6 +34,7 @@ import org.gdsc.domain.model.UserLocation import org.gdsc.domain.model.request.ModifyRestaurantInfoRequest import org.gdsc.domain.model.request.RestaurantRegistrationRequest import org.gdsc.domain.model.request.RestaurantSearchRequest +import org.gdsc.domain.model.response.Group import org.gdsc.domain.model.response.RestaurantInfoResponse import retrofit2.HttpException import javax.inject.Inject @@ -203,12 +204,14 @@ class RestaurantDataSourceImpl @Inject constructor( endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, - drinkPossibility: DrinkPossibility? + drinkPossibility: DrinkPossibility?, + currentGroup: Group?, ): Flow> { val restaurantSearchRequest = RestaurantSearchRequest( userLocation = userLocation, startLocation = startLocation, endLocation = endLocation, + groupId = currentGroup?.groupId, filter = Filter( categoryFilter = when (foodCategory) { FoodCategory.INIT, FoodCategory.ETC -> String.Empty diff --git a/data/src/main/java/org/gdsc/data/di/ApiModule.kt b/data/src/main/java/org/gdsc/data/di/ApiModule.kt index 598998be..2395def9 100644 --- a/data/src/main/java/org/gdsc/data/di/ApiModule.kt +++ b/data/src/main/java/org/gdsc/data/di/ApiModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import org.gdsc.data.network.GroupAPI import org.gdsc.data.network.LoginAPI import org.gdsc.data.network.RestaurantAPI import org.gdsc.data.network.TokenAPI @@ -39,4 +40,10 @@ class ApiModule { return retrofit.create(TokenAPI::class.java) } + @Provides + @Singleton + fun provideGroupApi(@AuthClient retrofit: Retrofit): GroupAPI { + return retrofit.create(GroupAPI::class.java) + } + } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/di/GroupModule.kt b/data/src/main/java/org/gdsc/data/di/GroupModule.kt new file mode 100644 index 00000000..a4c9962f --- /dev/null +++ b/data/src/main/java/org/gdsc/data/di/GroupModule.kt @@ -0,0 +1,25 @@ +package org.gdsc.data.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.gdsc.data.datasource.GroupDataSource +import org.gdsc.data.datasource.GroupDataSourceImpl +import org.gdsc.data.repository.GroupRepositoryImpl +import org.gdsc.domain.repository.GroupRepository +import javax.inject.Singleton + + +@Module +@InstallIn(SingletonComponent::class) +abstract class GroupModule { + + @Singleton + @Binds + abstract fun bindGroupDataSource(groupDataSourceImpl: GroupDataSourceImpl): GroupDataSource + + @Singleton + @Binds + abstract fun bindGroupRepository(groupRepositoryImpl: GroupRepositoryImpl): GroupRepository +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/model/Group.kt b/data/src/main/java/org/gdsc/data/model/Group.kt new file mode 100644 index 00000000..cc2ac80b --- /dev/null +++ b/data/src/main/java/org/gdsc/data/model/Group.kt @@ -0,0 +1,14 @@ +package org.gdsc.data.model + +import com.google.gson.annotations.SerializedName + +data class Group( + @SerializedName("groupId") val groupId: Int, + @SerializedName("groupIntroduce") val groupIntroduce: String, + @SerializedName("groupProfileImageUrl") val groupProfileImageUrl: String, + @SerializedName("groupBackgroundImageUrl") val groupBackgroundImageUrl: String, + @SerializedName("memberCnt") val memberCnt: Int, + @SerializedName("restaurantCnt") val restaurantCnt: Int, + @SerializedName("privateGroup") val privateGroup: Boolean, + @SerializedName("isSelected") val isSelected: Boolean, +) diff --git a/data/src/main/java/org/gdsc/data/network/GroupAPI.kt b/data/src/main/java/org/gdsc/data/network/GroupAPI.kt new file mode 100644 index 00000000..4cbdd088 --- /dev/null +++ b/data/src/main/java/org/gdsc/data/network/GroupAPI.kt @@ -0,0 +1,19 @@ +package org.gdsc.data.network + +import org.gdsc.data.model.Response +import org.gdsc.domain.model.response.Group +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path + +interface GroupAPI { + + @GET("api/v1/group/my") + suspend fun getMyGroups(): Response> + + @POST("api/v1/group/{groupId}/select") + suspend fun selectGroup( + @Path("groupId") groupId: Int + ): Response + +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/repository/GroupRepositoryImpl.kt b/data/src/main/java/org/gdsc/data/repository/GroupRepositoryImpl.kt new file mode 100644 index 00000000..4174d164 --- /dev/null +++ b/data/src/main/java/org/gdsc/data/repository/GroupRepositoryImpl.kt @@ -0,0 +1,18 @@ +package org.gdsc.data.repository + +import org.gdsc.data.datasource.GroupDataSource +import org.gdsc.domain.model.response.Group +import org.gdsc.domain.repository.GroupRepository +import javax.inject.Inject + +class GroupRepositoryImpl @Inject constructor( + private val groupDataSource: GroupDataSource +): GroupRepository { + override suspend fun getMyGroups(): List { + return groupDataSource.getMyGroups() + } + + override suspend fun selectGroup(groupId: Int): String { + return groupDataSource.selectGroup(groupId) + } +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt b/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt index 3f4a7319..aa582bab 100644 --- a/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt +++ b/data/src/main/java/org/gdsc/data/repository/RestaurantRepositoryImpl.kt @@ -17,6 +17,7 @@ import org.gdsc.domain.model.Review import org.gdsc.domain.model.UserLocation import org.gdsc.domain.model.request.ModifyRestaurantInfoRequest import org.gdsc.domain.model.request.RestaurantRegistrationRequest +import org.gdsc.domain.model.response.Group import org.gdsc.domain.model.response.RestaurantInfoResponse import org.gdsc.domain.repository.RestaurantRepository import javax.inject.Inject @@ -103,7 +104,8 @@ class RestaurantRepositoryImpl @Inject constructor( endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, - drinkPossibility: DrinkPossibility? + drinkPossibility: DrinkPossibility?, + currentGroup: Group?, ): Flow> { return restaurantDataSource.getRestaurantsByMap( userLocation, @@ -111,7 +113,8 @@ class RestaurantRepositoryImpl @Inject constructor( endLocation, sortType, foodCategory, - drinkPossibility + drinkPossibility, + currentGroup, ).map { result -> result.map { restaurant -> RegisteredRestaurant( diff --git a/domain/src/main/java/org/gdsc/domain/model/ScreenLocation.kt b/domain/src/main/java/org/gdsc/domain/model/ScreenLocation.kt new file mode 100644 index 00000000..11e06ced --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/model/ScreenLocation.kt @@ -0,0 +1,6 @@ +package org.gdsc.domain.model + +data class ScreenLocation( + val startLocation: Location, + val endLocation: Location, +) \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchRequest.kt b/domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchRequest.kt index d35e2750..f072bf2c 100644 --- a/domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchRequest.kt +++ b/domain/src/main/java/org/gdsc/domain/model/request/RestaurantSearchRequest.kt @@ -13,6 +13,8 @@ data class RestaurantSearchRequest( val startLocation: Location? = null, @SerializedName("endLocation") val endLocation: Location? = null, + @SerializedName("groupId") + val groupId: Int? = null, @SerializedName("keyword") val keyword: String? = null, ) diff --git a/domain/src/main/java/org/gdsc/domain/model/response/Group.kt b/domain/src/main/java/org/gdsc/domain/model/response/Group.kt new file mode 100644 index 00000000..6c3727a8 --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/model/response/Group.kt @@ -0,0 +1,13 @@ +package org.gdsc.domain.model.response + +data class Group( + val groupId: Int, + val groupName: String, + val groupIntroduce: String, + val groupProfileImageUrl: String, + val groupBackgroundImageUrl: String, + val memberCnt: Int, + val restaurantCnt: Int, + val privateGroup: Boolean, + val isSelected: Boolean, +) \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/repository/GroupRepository.kt b/domain/src/main/java/org/gdsc/domain/repository/GroupRepository.kt new file mode 100644 index 00000000..2a95577f --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/repository/GroupRepository.kt @@ -0,0 +1,9 @@ +package org.gdsc.domain.repository + +import org.gdsc.domain.model.response.Group + +interface GroupRepository { + suspend fun getMyGroups(): List + + suspend fun selectGroup(groupId: Int): String +} \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt b/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt index 77eb872f..80e609d2 100644 --- a/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt +++ b/domain/src/main/java/org/gdsc/domain/repository/RestaurantRepository.kt @@ -14,6 +14,7 @@ import org.gdsc.domain.model.Review import org.gdsc.domain.model.UserLocation import org.gdsc.domain.model.request.ModifyRestaurantInfoRequest import org.gdsc.domain.model.request.RestaurantRegistrationRequest +import org.gdsc.domain.model.response.Group import org.gdsc.domain.model.response.RestaurantInfoResponse interface RestaurantRepository { @@ -38,7 +39,13 @@ interface RestaurantRepository { suspend fun putRestaurantInfo(putRestaurantInfoRequest: ModifyRestaurantInfoRequest): String suspend fun getRestaurantsByMap( - userLocation: Location?, startLocation: Location?, endLocation: Location?, sortType: SortType, foodCategory: FoodCategory?, drinkPossibility: DrinkPossibility? + userLocation: Location?, + startLocation: Location?, + endLocation: Location?, + sortType: SortType, + foodCategory: FoodCategory?, + drinkPossibility: DrinkPossibility?, + currentGroup: Group?, ): Flow> suspend fun getRegisteredRestaurantsBySearch(keyword: String?, userLocation: Location?): Flow> diff --git a/domain/src/main/java/org/gdsc/domain/usecase/GetMyGroupUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/GetMyGroupUseCase.kt new file mode 100644 index 00000000..ce21dbb1 --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/GetMyGroupUseCase.kt @@ -0,0 +1,10 @@ +package org.gdsc.domain.usecase + +import org.gdsc.domain.repository.GroupRepository +import javax.inject.Inject + +class GetMyGroupUseCase @Inject constructor( + private val groupRepository: GroupRepository +){ + suspend operator fun invoke() = groupRepository.getMyGroups() +} \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantsByMapUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantsByMapUseCase.kt index 828287df..cb3a9a96 100644 --- a/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantsByMapUseCase.kt +++ b/domain/src/main/java/org/gdsc/domain/usecase/GetRestaurantsByMapUseCase.kt @@ -7,6 +7,7 @@ import org.gdsc.domain.FoodCategory import org.gdsc.domain.SortType import org.gdsc.domain.model.Location import org.gdsc.domain.model.RegisteredRestaurant +import org.gdsc.domain.model.response.Group import org.gdsc.domain.repository.RestaurantRepository import javax.inject.Inject @@ -14,9 +15,23 @@ class GetRestaurantsByMapUseCase @Inject constructor( private val restaurantRepository: RestaurantRepository ) { suspend operator fun invoke( - sortType: SortType, foodCategory: FoodCategory?, drinkPossibility: DrinkPossibility?, userLocation: Location?, startLocation: Location? = null, endLocation: Location? = null + sortType: SortType, + foodCategory: FoodCategory?, + drinkPossibility: DrinkPossibility?, + userLocation: Location?, + startLocation: Location? = null, + endLocation: Location? = null, + currentGroup: Group? = null, ): Flow> { - return restaurantRepository.getRestaurantsByMap( userLocation, startLocation, endLocation, sortType, foodCategory, drinkPossibility) + return restaurantRepository.getRestaurantsByMap( + userLocation, + startLocation, + endLocation, + sortType, + foodCategory, + drinkPossibility, + currentGroup, + ) } } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/PostSelectGroupUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/PostSelectGroupUseCase.kt new file mode 100644 index 00000000..7d157e19 --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/PostSelectGroupUseCase.kt @@ -0,0 +1,12 @@ +package org.gdsc.domain.usecase + +import org.gdsc.domain.repository.GroupRepository +import javax.inject.Inject + +class PostSelectGroupUseCase @Inject constructor( + private val groupRepository: GroupRepository +) { + suspend operator fun invoke(groupId: Int): String { + return groupRepository.selectGroup(groupId) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/WebViewActivity.kt b/presentation/src/main/java/org/gdsc/presentation/view/WebViewActivity.kt index 4e1f98d0..c94f8c0b 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/WebViewActivity.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/WebViewActivity.kt @@ -15,6 +15,7 @@ import android.webkit.WebViewClient import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity import org.gdsc.presentation.databinding.ActivityWebViewBinding +import org.gdsc.presentation.utils.repeatWhenUiStarted class WebViewActivity : AppCompatActivity() { @@ -25,24 +26,40 @@ class WebViewActivity : AppCompatActivity() { binding = ActivityWebViewBinding.inflate(layoutInflater) setContentView(binding.root) - onBackPressedDispatcher.addCallback { - if(webView.canGoBack()) { - webView.goBack() - } else { - // TODO(): 메인으로 이동 등, 페이지 이동 기능 추가 - finish() - } - } +// webViewInit() webView = binding.webView - webViewInit() + + setWebViewBackPress() intent.extras?.let { - binding.webView.loadUrl(it.getString("url")!!) + binding.webView.apply { + + repeatWhenUiStarted { + loadUrl(it.getString("url") ?: WEB_BASE_URL) + } + + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + webViewClient = WebViewClient() + + addJavascriptInterface(WebAppInterface(context), "webviewBridge") + } + } // val actionBar: ActionBar? = supportActionBar // actionBar!!.hide() } + private fun setWebViewBackPress() { + this.onBackPressedDispatcher.addCallback(this) { + if (binding.webView.canGoBack()) { + binding.webView.goBack() + } else { + finish() + } + } + } + fun webViewInit() { val webSettings: WebSettings = webView.getSettings() webSettings.javaScriptEnabled = true // allow the js @@ -51,6 +68,6 @@ class WebViewActivity : AppCompatActivity() { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_USER webView.webViewClient = WebViewClient() - webView.addJavascriptInterface(WebAppInterface(this), "Android") + webView.addJavascriptInterface(WebAppInterface(this), "webviewBridge") } } diff --git a/presentation/src/main/java/org/gdsc/presentation/view/group/MyGroupFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/group/MyGroupFragment.kt index 33707ac8..c2602793 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/group/MyGroupFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/group/MyGroupFragment.kt @@ -39,11 +39,7 @@ class MyGroupFragment: Fragment() { binding.webView.apply { repeatWhenUiStarted { - loadUrl( - WEB_BASE_URL, - hashMapOf().apply { - put("token", specificWebViewViewModel.getAccessToken()) - }) + loadUrl(WEB_BASE_URL) } settings.javaScriptEnabled = true diff --git a/presentation/src/main/java/org/gdsc/presentation/view/home/GroupSelectAdapter.kt b/presentation/src/main/java/org/gdsc/presentation/view/home/GroupSelectAdapter.kt new file mode 100644 index 00000000..3187f3fe --- /dev/null +++ b/presentation/src/main/java/org/gdsc/presentation/view/home/GroupSelectAdapter.kt @@ -0,0 +1,41 @@ +package org.gdsc.presentation.view.home + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.gdsc.domain.model.response.Group +import org.gdsc.presentation.base.BaseViewHolder +import org.gdsc.presentation.base.ViewHolderBindListener +import org.gdsc.presentation.databinding.ItemGroupSelectBinding + +class GroupSelectAdapter( + private val listener: ViewHolderBindListener +): RecyclerView.Adapter() { + + private val groupList = mutableListOf() + + override fun onBindViewHolder(holder: GroupSelectViewHolder, position: Int) { + holder.bind(groupList[position]) + } + + class GroupSelectViewHolder( + binding: ItemGroupSelectBinding, + listener: ViewHolderBindListener + ): BaseViewHolder(binding, listener) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupSelectViewHolder { + return GroupSelectViewHolder( + ItemGroupSelectBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ), listener + ) + } + + override fun getItemCount(): Int = groupList.size + + fun submitList(list: List) { + groupList.clear() + groupList.addAll(list) + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/view/home/HomeFragment.kt b/presentation/src/main/java/org/gdsc/presentation/view/home/HomeFragment.kt index baee982e..051282dd 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/home/HomeFragment.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/home/HomeFragment.kt @@ -1,11 +1,14 @@ package org.gdsc.presentation.view.home +import android.content.Intent import android.graphics.PointF import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -14,6 +17,7 @@ import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding +import com.bumptech.glide.Glide import com.google.android.material.bottomsheet.BottomSheetBehavior import com.naver.maps.geometry.LatLng import com.naver.maps.map.CameraUpdate @@ -23,6 +27,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.gdsc.domain.DrinkPossibility @@ -30,11 +35,17 @@ import org.gdsc.domain.FoodCategory import org.gdsc.domain.SortType import org.gdsc.domain.model.Location import org.gdsc.domain.model.RegisteredRestaurant +import org.gdsc.domain.model.response.Group import org.gdsc.presentation.R import org.gdsc.presentation.base.BaseViewHolder import org.gdsc.presentation.base.ViewHolderBindListener +import org.gdsc.presentation.databinding.ContentSheetEmptyGroupBinding +import org.gdsc.presentation.databinding.ContentSheetGroupSelectBinding import org.gdsc.presentation.databinding.FragmentHomeBinding +import org.gdsc.presentation.model.ResultState import org.gdsc.presentation.utils.repeatWhenUiStarted +import org.gdsc.presentation.view.WebViewActivity +import org.gdsc.presentation.view.custom.BottomSheetDialog import org.gdsc.presentation.view.custom.JmtSpinner @@ -82,7 +93,7 @@ class HomeFragment : Fragment(), ViewHolderBindListener { restaurantListAdapter ) } - setRecyclerView() + return binding.root } @@ -95,6 +106,8 @@ class HomeFragment : Fragment(), ViewHolderBindListener { super.onViewCreated(view, savedInstanceState) setRestaurantListBottomSheet() + setGroup() + } override fun onViewHolderBind(holder: BaseViewHolder, _item: Any) { @@ -134,6 +147,66 @@ class HomeFragment : Fragment(), ViewHolderBindListener { } } + private fun setGroup() { + binding.groupArrow.setOnClickListener { + repeatWhenUiStarted { + viewModel.getMyGroup().let { groupList -> + viewModel.setGroupList(groupList) + } + } + + BottomSheetDialog(requireContext()) + .bindBuilder( + ContentSheetGroupSelectBinding.inflate(LayoutInflater.from(requireContext())) + ) { dialog -> + + val listener: ViewHolderBindListener = object : ViewHolderBindListener { + override fun onViewHolderBind( + holder: BaseViewHolder, + _item: Any + ) { + if (holder is GroupSelectAdapter.GroupSelectViewHolder && _item is Group) { + with(holder.itemView) { + findViewById(R.id.group_name).text = _item.groupName + findViewById(R.id.select_button).isVisible = _item.isSelected + Glide.with(this).load(_item.groupProfileImageUrl).into(findViewById(R.id.group_image)) + + setOnClickListener { + // TODO GroupList : 추후에 Room에 Current Group 넣어주게 되면, 반영해야 하는 부분 + repeatWhenUiStarted { + viewModel.selectGroup(_item.groupId) + viewModel.setCurrentGroup(_item) + } + dialog.dismiss() + } + } + + } + } + } + + with(dialog) { + val groupSelectAdapter = GroupSelectAdapter(listener) + viewModel.myGroupList.value.let { + when(it) { + is ResultState.OnSuccess -> { + groupSelectAdapter.submitList(it.response ?: emptyList()) + } + else -> {} + } + } + + findViewById(R.id.group_select_recycler_view)?.apply { + adapter = groupSelectAdapter + layoutManager = LinearLayoutManager(requireContext()) + } + + show() + } + } + } + } + private fun setMap(savedInstanceState: Bundle?) { mapView = binding.mapView mapView.onCreate(savedInstanceState) @@ -219,37 +292,38 @@ class HomeFragment : Fragment(), ViewHolderBindListener { standardBottomSheetBehavior.addBottomSheetCallback( object : BottomSheetBehavior.BottomSheetCallback() { - fun setBottomSheetRelatedView(isVisible: Boolean) { - binding.bottomSheetHandle.isVisible = isVisible - binding.bottomSheetHandleSpace.isVisible = isVisible - binding.mapOptionContainer.isVisible = isVisible + fun setBottomSheetRelatedView(isExpanded: Boolean) { + viewModel.currentGroup.value.let { + val isNotFullExpanded = isExpanded.not() || it == null || it.restaurantCnt == 0 + + with(binding) { + binding.bottomSheetActionButtons.isVisible = isExpanded && it != null && it.restaurantCnt != 0 + bottomSheetHandle.isVisible = isNotFullExpanded + bottomSheetHandleSpace.isVisible = isNotFullExpanded + mapOptionContainer.isVisible = isNotFullExpanded + groupHeader.elevation = if (isNotFullExpanded) 0F else 10F + bottomSheet.background = + ResourcesCompat.getDrawable( + resources, + if (isNotFullExpanded) + R.drawable.bg_bottom_sheet_top_round + else + R.color.white, + null + ) + + } + } } override fun onStateChanged(bottomSheet: View, newState: Int) { when (newState) { BottomSheetBehavior.STATE_EXPANDED -> { - binding.groupHeader.elevation = 10F - binding.bottomSheetActionButtons.isVisible = true - - setBottomSheetRelatedView(false) - binding.bottomSheet.background = ResourcesCompat.getDrawable( - resources, - R.color.white, - null - ) - } - BottomSheetBehavior.STATE_HALF_EXPANDED -> { - binding.mapOptionContainer.isVisible = true + setBottomSheetRelatedView(true) } BottomSheetBehavior.STATE_DRAGGING -> { - binding.groupHeader.elevation = 0F - setBottomSheetRelatedView(true) - binding.bottomSheet.background = ResourcesCompat.getDrawable( - resources, - R.drawable.bg_bottom_sheet_top_round, - null - ) + setBottomSheetRelatedView(false) } } } @@ -275,6 +349,82 @@ class HomeFragment : Fragment(), ViewHolderBindListener { repeatWhenUiStarted { viewModel.setSortType(SortType.DISTANCE) } + + repeatWhenUiStarted { + viewModel.myGroupList.collect { + binding.groupName.text + } + } + + repeatWhenUiStarted { + viewModel.getMyGroup().let { groupList -> + viewModel.setGroupList(groupList) + } + } + + repeatWhenUiStarted { + viewModel.myGroupList.collect { state -> + when(state) { + is ResultState.OnSuccess -> { + val groupList = state.response + + if (groupList.isNullOrEmpty()) { + viewModel.setCurrentGroup(null) + + BottomSheetDialog(requireContext()) + .bindBuilder( + ContentSheetEmptyGroupBinding.inflate(LayoutInflater.from(requireContext())) + ) { dialog -> + with(dialog) { + binding.groupHeader.isVisible = false + dialog.setCancelable(false) + dialog.behavior.isDraggable = false + createGroupButton.setOnClickListener { + startActivity( + Intent(requireContext(), WebViewActivity::class.java) + ) + } + show() + } + } + } else { + groupList.forEach { + if (it.isSelected) { + viewModel.setCurrentGroup(it) + return@forEach + } + } + } + } + + else -> {} + } + } + } + + repeatWhenUiStarted { + viewModel.currentGroup.collect { + if (it != null) { + binding.groupHeader.isVisible = true + binding.groupName.text = it.groupName + binding.groupImage.apply { + Glide.with(this.context).load(it.groupProfileImageUrl) + .into(this) + } + + if (it.restaurantCnt == 0) { + binding.bottomSheet.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT + binding.recyclerView.isVisible = false + binding.registLayout.isVisible = true + } else { + binding.bottomSheet.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT + binding.recyclerView.isVisible = true + binding.registLayout.isVisible = false + } + } + } + + } } override fun onDestroyView() { diff --git a/presentation/src/main/java/org/gdsc/presentation/view/home/HomeViewModel.kt b/presentation/src/main/java/org/gdsc/presentation/view/home/HomeViewModel.kt index d5f2a5e1..0f331a0d 100644 --- a/presentation/src/main/java/org/gdsc/presentation/view/home/HomeViewModel.kt +++ b/presentation/src/main/java/org/gdsc/presentation/view/home/HomeViewModel.kt @@ -1,5 +1,6 @@ package org.gdsc.presentation.view.home +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData @@ -9,24 +10,33 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn import org.gdsc.domain.DrinkPossibility import org.gdsc.domain.FoodCategory import org.gdsc.domain.SortType import org.gdsc.domain.model.Location import org.gdsc.domain.model.RegisteredRestaurant +import org.gdsc.domain.model.ScreenLocation +import org.gdsc.domain.model.response.Group +import org.gdsc.domain.usecase.GetMyGroupUseCase import org.gdsc.domain.usecase.GetRestaurantsByMapUseCase +import org.gdsc.domain.usecase.PostSelectGroupUseCase import org.gdsc.presentation.JmtLocationManager +import org.gdsc.presentation.model.ResultState import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( private val locationManager: JmtLocationManager, - private val getRestaurantsByMapUseCase: GetRestaurantsByMapUseCase + private val getRestaurantsByMapUseCase: GetRestaurantsByMapUseCase, + private val getMyGroupUseCase: GetMyGroupUseCase, + private val postSelectGroupUserCase: PostSelectGroupUseCase, ) : ViewModel() { suspend fun getCurrentLocation() = locationManager.getCurrentLocation() @@ -40,11 +50,24 @@ class HomeViewModel @Inject constructor( val startLocationState: StateFlow get() = _startLocationState - private var _endLocationState = MutableStateFlow(Location("0", "0")) val endLocationState: StateFlow get() = _endLocationState + + val _screenLocationState: StateFlow = + combine( + startLocationState, + endLocationState + ) { startLocationState, endLocationState -> + ScreenLocation(startLocationState, endLocationState) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ScreenLocation(Location("0", "0"), Location("0", "0"))) + + val screenLocationState: StateFlow + get() = _screenLocationState + + + private var _sortTypeState = MutableStateFlow(SortType.DISTANCE) val sortTypeState: StateFlow get() = _sortTypeState @@ -57,13 +80,15 @@ class HomeViewModel @Inject constructor( val drinkPossibilityState: StateFlow get() = _drinkPossibilityState - private var _markerState = MutableStateFlow(listOf()) - val markerState: StateFlow> - get() = _markerState + private var _myGroupList = MutableStateFlow>>(ResultState.OnLoading()) + val myGroupList: StateFlow>> + get() = _myGroupList + + + private var _currentGroup = MutableStateFlow(null) + val currentGroup: StateFlow + get() = _currentGroup - fun setMarkerState(value: List) { - _markerState.value = value - } fun setUserLocation(userLocation: Location) { _userLocationState.value = userLocation @@ -89,6 +114,14 @@ class HomeViewModel @Inject constructor( _drinkPossibilityState.value = drinkPossibility } + fun setGroupList(groupList: List) { + _myGroupList.value = ResultState.OnSuccess(groupList) + } + + fun setCurrentGroup(group: Group?) { + _currentGroup.value = group + } + @OptIn(ExperimentalCoroutinesApi::class) suspend fun registeredPagingDataByMap(): Flow> { @@ -97,13 +130,13 @@ class HomeViewModel @Inject constructor( return run { return@run combine( - startLocationState, - endLocationState, + screenLocationState, sortTypeState, foodCategoryState, - drinkPossibilityState - ) { startLoc, endLoc, sortType, foodCategory, drinkPossibility -> - getRestaurantsByMapUseCase(sortType, foodCategory, drinkPossibility, userLoc, startLoc, endLoc) + drinkPossibilityState, + currentGroup, + ) { screenLoc, sortType, foodCategory, drinkPossibility, group -> + getRestaurantsByMapUseCase(sortType, foodCategory, drinkPossibility, userLoc, screenLoc.startLocation, screenLoc.endLocation, group) }.distinctUntilChanged() .flatMapLatest { it } }.cachedIn(viewModelScope) @@ -117,11 +150,20 @@ class HomeViewModel @Inject constructor( userLocationState, sortTypeState, foodCategoryState, - drinkPossibilityState - ) { userLoc, sortType, foodCategory, drinkPossibility -> - getRestaurantsByMapUseCase(sortType, foodCategory, drinkPossibility, userLoc, null, null) + drinkPossibilityState, + currentGroup, + ) { userLoc, sortType, foodCategory, drinkPossibility, group -> + getRestaurantsByMapUseCase(sortType, foodCategory, drinkPossibility, userLoc, null, null, group) }.distinctUntilChanged() .flatMapLatest { it } }.cachedIn(viewModelScope) } + + suspend fun getMyGroup(): List { + return getMyGroupUseCase() + } + + suspend fun selectGroup(groupID: Int) { + postSelectGroupUserCase(groupID) + } } \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_check_main.xml b/presentation/src/main/res/drawable/ic_check_main.xml new file mode 100644 index 00000000..058ef62d --- /dev/null +++ b/presentation/src/main/res/drawable/ic_check_main.xml @@ -0,0 +1,9 @@ + + + diff --git a/presentation/src/main/res/drawable/ic_jmt_new_logo.xml b/presentation/src/main/res/drawable/ic_jmt_new_logo.xml new file mode 100644 index 00000000..57b8df42 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_jmt_new_logo.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/presentation/src/main/res/layout/content_sheet_empty_group.xml b/presentation/src/main/res/layout/content_sheet_empty_group.xml new file mode 100644 index 00000000..dce5945a --- /dev/null +++ b/presentation/src/main/res/layout/content_sheet_empty_group.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/content_sheet_group_select.xml b/presentation/src/main/res/layout/content_sheet_group_select.xml new file mode 100644 index 00000000..0028ec28 --- /dev/null +++ b/presentation/src/main/res/layout/content_sheet_group_select.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_home.xml b/presentation/src/main/res/layout/fragment_home.xml index ae8ae4c6..959a44f5 100644 --- a/presentation/src/main/res/layout/fragment_home.xml +++ b/presentation/src/main/res/layout/fragment_home.xml @@ -178,6 +178,55 @@ app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_handle_space" app:layout_constraintBottom_toBottomOf="parent"/> + + + + + + + + + + + + + + + + + + \ No newline at end of file