diff --git a/app/src/main/java/org/android/go/sopt/data/entity/remote/response/ResponseGetFollowerListDto.kt b/app/src/main/java/org/android/go/sopt/data/entity/remote/response/ResponseGetFollowerListDto.kt index 57b83cc..0eefc40 100644 --- a/app/src/main/java/org/android/go/sopt/data/entity/remote/response/ResponseGetFollowerListDto.kt +++ b/app/src/main/java/org/android/go/sopt/data/entity/remote/response/ResponseGetFollowerListDto.kt @@ -43,6 +43,7 @@ data class ResponseGetFollowerListDto( fun toFollower() = data.map { follower -> Follower( + id = follower.id, name = "${follower.firstName} ${follower.lastName}", profile = follower.avatar, email = follower.email, diff --git a/app/src/main/java/org/android/go/sopt/data/service/FollowerService.kt b/app/src/main/java/org/android/go/sopt/data/service/FollowerService.kt index 6d6f72d..125bd00 100644 --- a/app/src/main/java/org/android/go/sopt/data/service/FollowerService.kt +++ b/app/src/main/java/org/android/go/sopt/data/service/FollowerService.kt @@ -6,7 +6,7 @@ import retrofit2.http.Query interface FollowerService { @GET("users") - fun getFollowerList( + suspend fun getFollowerList( @Query("page") page: Int, ): ResponseGetFollowerListDto } diff --git a/app/src/main/java/org/android/go/sopt/data/source/remote/FollowerDataSource.kt b/app/src/main/java/org/android/go/sopt/data/source/remote/FollowerDataSource.kt index 9896650..ea68131 100644 --- a/app/src/main/java/org/android/go/sopt/data/source/remote/FollowerDataSource.kt +++ b/app/src/main/java/org/android/go/sopt/data/source/remote/FollowerDataSource.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class FollowerDataSource @Inject constructor( private val followerService: FollowerService, ) { - fun getFollowerList(page: Int): ResponseGetFollowerListDto = + suspend fun getFollowerList(page: Int): ResponseGetFollowerListDto = followerService.getFollowerList(page) } diff --git a/app/src/main/java/org/android/go/sopt/di/RepositoryModule.kt b/app/src/main/java/org/android/go/sopt/di/RepositoryModule.kt index c8c39b4..7ccc9d1 100644 --- a/app/src/main/java/org/android/go/sopt/di/RepositoryModule.kt +++ b/app/src/main/java/org/android/go/sopt/di/RepositoryModule.kt @@ -5,8 +5,10 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.android.go.sopt.data.repository.AuthRepositoryImpl +import org.android.go.sopt.data.repository.FollowerRepositoryImpl import org.android.go.sopt.data.repository.RepoRepositoryImpl import org.android.go.sopt.domain.repository.AuthRepository +import org.android.go.sopt.domain.repository.FollowerRepository import org.android.go.sopt.domain.repository.RepoRepository import javax.inject.Singleton @@ -24,4 +26,10 @@ abstract class RepositoryModule { abstract fun bindsRepoRepository( repoRepositoryImpl: RepoRepositoryImpl, ): RepoRepository + + @Binds + @Singleton + abstract fun bindsFollowerRepository( + followerRepositoryImpl: FollowerRepositoryImpl, + ): FollowerRepository } diff --git a/app/src/main/java/org/android/go/sopt/di/ServiceModule.kt b/app/src/main/java/org/android/go/sopt/di/ServiceModule.kt index 2d88b02..8f58473 100644 --- a/app/src/main/java/org/android/go/sopt/di/ServiceModule.kt +++ b/app/src/main/java/org/android/go/sopt/di/ServiceModule.kt @@ -6,6 +6,8 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.android.go.sopt.data.service.AuthService import org.android.go.sopt.data.service.FollowerService +import org.android.go.sopt.di.RetrofitModule.Retrofit2 +import org.android.go.sopt.util.type.BaseUrlType import retrofit2.Retrofit import javax.inject.Singleton @@ -14,11 +16,11 @@ import javax.inject.Singleton object ServiceModule { @Provides @Singleton - fun providesAuthService(retrofit: Retrofit): AuthService = + fun providesAuthService(@Retrofit2(BaseUrlType.SOPT) retrofit: Retrofit): AuthService = retrofit.create(AuthService::class.java) @Provides @Singleton - fun providesFollowerService(retrofit: Retrofit): FollowerService = + fun providesFollowerService(@Retrofit2(BaseUrlType.REQRES) retrofit: Retrofit): FollowerService = retrofit.create(FollowerService::class.java) } diff --git a/app/src/main/java/org/android/go/sopt/domain/model/Follower.kt b/app/src/main/java/org/android/go/sopt/domain/model/Follower.kt index b01a604..a393d88 100644 --- a/app/src/main/java/org/android/go/sopt/domain/model/Follower.kt +++ b/app/src/main/java/org/android/go/sopt/domain/model/Follower.kt @@ -1,6 +1,7 @@ package org.android.go.sopt.domain.model data class Follower( + val id: Int, val name: String, val profile: String, val email: String, diff --git a/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerAdapter.kt b/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerAdapter.kt new file mode 100644 index 0000000..3c04077 --- /dev/null +++ b/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerAdapter.kt @@ -0,0 +1,77 @@ +package org.android.go.sopt.presentation.main.follower + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.android.go.sopt.R +import org.android.go.sopt.databinding.HeaderFollowerBinding +import org.android.go.sopt.databinding.ItemFollowerBinding +import org.android.go.sopt.domain.model.Follower +import org.android.go.sopt.util.DiffCallback + +class FollowerAdapter : ListAdapter(diffUtil) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + VIEW_TYPE_HEADER -> FollowerHeaderViewHolder( + HeaderFollowerBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ), + ) + + VIEW_TYPE_ITEM -> FollowerViewHolder( + ItemFollowerBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ), + ) + + else -> throw ClassCastException( + parent.context.getString( + R.string.view_type_error_msg, + viewType, + ), + ) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is FollowerViewHolder) holder.setFollower(getItem(position - HEADER_COUNT)) + } + + override fun getItemCount(): Int { + return super.getItemCount() + HEADER_COUNT + } + + override fun getItemViewType(position: Int): Int { + return when (position) { + 0 -> VIEW_TYPE_HEADER + else -> VIEW_TYPE_ITEM + } + } + + class FollowerHeaderViewHolder(private val binding: HeaderFollowerBinding) : + RecyclerView.ViewHolder(binding.root) + + class FollowerViewHolder(private val binding: ItemFollowerBinding) : + RecyclerView.ViewHolder(binding.root) { + fun setFollower(follower: Follower) { + binding.data = follower + } + } + + companion object { + private val diffUtil = DiffCallback( + onItemsTheSame = { old, new -> old.id == new.id }, + onContentsTheSame = { old, new -> old == new }, + ) + + private const val HEADER_COUNT = 1 + + const val VIEW_TYPE_HEADER = 0 + const val VIEW_TYPE_ITEM = 1 + } +} diff --git a/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerFragment.kt b/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerFragment.kt index 3fa5066..1f54fa3 100644 --- a/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerFragment.kt +++ b/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerFragment.kt @@ -1,10 +1,66 @@ package org.android.go.sopt.presentation.main.follower +import android.os.Bundle +import android.view.View +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup +import dagger.hilt.android.AndroidEntryPoint import org.android.go.sopt.R import org.android.go.sopt.databinding.FragmentFollowerBinding +import org.android.go.sopt.presentation.main.follower.FollowerAdapter.Companion.VIEW_TYPE_HEADER import org.android.go.sopt.util.binding.BindingFragment +import org.android.go.sopt.util.state.RemoteUiState.Error +import org.android.go.sopt.util.state.RemoteUiState.Failure +import org.android.go.sopt.util.state.RemoteUiState.Success +@AndroidEntryPoint class FollowerFragment : BindingFragment(R.layout.fragment_follower) { + private val viewModel by viewModels() + + private var followerAdapter: FollowerAdapter? = null + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.vm = viewModel + + initFollowerAdapter() + initRecyclerViewLayoutManager() + setupGetFollowerListState() + } + + private fun initFollowerAdapter() { + followerAdapter = FollowerAdapter() + binding.rvFollower.adapter = followerAdapter + } + + private fun initRecyclerViewLayoutManager() { + val layoutManager = GridLayoutManager(activity, 2) + layoutManager.spanSizeLookup = object : SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return when (followerAdapter?.getItemViewType(position)) { + VIEW_TYPE_HEADER -> 2 + else -> 1 + } + } + } + binding.rvFollower.layoutManager = layoutManager + } + + private fun setupGetFollowerListState() { + viewModel.getFollowerListState.observe(viewLifecycleOwner) { state -> + when (state) { + is Success -> followerAdapter?.submitList(viewModel.followerList) + is Failure -> {} + is Error -> {} + } + } + } + + override fun onDestroyView() { + super.onDestroyView() + followerAdapter = null + } companion object { @JvmStatic diff --git a/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerViewModel.kt b/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerViewModel.kt new file mode 100644 index 0000000..7c9690a --- /dev/null +++ b/app/src/main/java/org/android/go/sopt/presentation/main/follower/FollowerViewModel.kt @@ -0,0 +1,53 @@ +package org.android.go.sopt.presentation.main.follower + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import org.android.go.sopt.domain.model.Follower +import org.android.go.sopt.domain.repository.FollowerRepository +import org.android.go.sopt.util.state.RemoteUiState +import org.android.go.sopt.util.state.RemoteUiState.Error +import org.android.go.sopt.util.state.RemoteUiState.Success +import retrofit2.HttpException +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class FollowerViewModel @Inject constructor( + private val followerRepository: FollowerRepository, +) : ViewModel() { + private val _followerList = MutableLiveData>() + val followerList: List? + get() = _followerList.value + + private val _getFollowerListState = MutableLiveData() + val getFollowerListState: LiveData + get() = _getFollowerListState + + init { + getFollowerList() + } + + private fun getFollowerList() { + Timber.d("get follower list 시작") + viewModelScope.launch { + followerRepository.getFollowerList(1) + .onSuccess { response -> + // TODO: followerList null 처리 + _followerList.value = response + _getFollowerListState.value = Success + Timber.d("GET FOLLOWER LIST SUCCESS : $response") + } + .onFailure { t -> + if (t is HttpException) { + // TODO : failure 예외 처리 + _getFollowerListState.value = Error + Timber.e("GET FOLLOWER LIST FAIL : ${t.message}") + } + } + } + } +} diff --git a/app/src/main/res/layout/fragment_follower.xml b/app/src/main/res/layout/fragment_follower.xml index 03dc4e5..c0cb34c 100644 --- a/app/src/main/res/layout/fragment_follower.xml +++ b/app/src/main/res/layout/fragment_follower.xml @@ -6,6 +6,9 @@ + + tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager" + tools:listitem="@layout/item_follower" + tools:spanCount="2" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_follower.xml b/app/src/main/res/layout/item_follower.xml index 346be79..a384a83 100644 --- a/app/src/main/res/layout/item_follower.xml +++ b/app/src/main/res/layout/item_follower.xml @@ -5,16 +5,20 @@ + + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="30dp">