Skip to content

Commit

Permalink
Merge pull request #16 from DOSOPT-CDS-APP-TEAM5/feature/#11-neighbor…
Browse files Browse the repository at this point in the history
…hood-life

PR : 동네생활 & 프로필 & 네트워크
  • Loading branch information
lsakee committed Nov 29, 2023
2 parents 6116ece + c0dc703 commit dbafb6d
Show file tree
Hide file tree
Showing 48 changed files with 1,303 additions and 28 deletions.
6 changes: 6 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ plugins {
id 'kotlin-parcelize'
}

Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())

android {
namespace 'org.sopt.carrot'
compileSdk 34
Expand All @@ -17,6 +20,8 @@ android {
versionCode 1
versionName "1.0"

buildConfigField "String", "BASE_URL", properties["base.url"]

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Expand All @@ -36,6 +41,7 @@ android {
buildFeatures {
viewBinding true
dataBinding true
buildConfig true
}
}

Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/org/sopt/carrot/CarrotApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package org.sopt.carrot

import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import org.sopt.carrot.data.api.RetrofitServicePool
import org.sopt.carrot.data.datasource.remote.NeighborhoodLifeRemoteDatasource
import org.sopt.carrot.data.repo.NeighborhoodLifeRepository
import timber.log.Timber

class CarrotApp : Application() {
Expand All @@ -17,4 +20,21 @@ class CarrotApp : Application() {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}

companion object {
private lateinit var neighborhoodLifeRepository: NeighborhoodLifeRepository

@Synchronized
fun getNeighborhoodLifeRepositoryInstance(): NeighborhoodLifeRepository {
if (!::neighborhoodLifeRepository.isInitialized) {
try {
neighborhoodLifeRepository = NeighborhoodLifeRepository(
NeighborhoodLifeRemoteDatasource(RetrofitServicePool.carrotService)
)
} catch (e: ExceptionInInitializerError) {
e.printStackTrace()
}
}
return neighborhoodLifeRepository
}
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/org/sopt/carrot/core/ui/utils/Extensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.sopt.carrot.core.ui.utils

import android.content.res.Resources


fun Int.fromDpToPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt()
15 changes: 15 additions & 0 deletions app/src/main/java/org/sopt/carrot/data/api/CarrotService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.sopt.carrot.data.api

import org.sopt.carrot.data.model.neighborhoodlife.LivesResponse
import retrofit2.http.GET
import retrofit2.http.Query

interface CarrotService {
@GET("api/lives")
fun getLives(
@Query("category") category: String
): LivesResponse

@GET("api/lives")
suspend fun getLives(): LivesResponse
}
33 changes: 33 additions & 0 deletions app/src/main/java/org/sopt/carrot/data/api/RetrofitManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.sopt.carrot.data.api

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.sopt.carrot.BuildConfig
import retrofit2.Retrofit

object RetrofitManager {
private const val BASE_URL = BuildConfig.BASE_URL

private val httpLoggingInterceptor = HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)

private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build()

val retrofit: Retrofit =
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()

inline fun <reified T> create(): T = retrofit.create<T>(T::class.java)
}

object RetrofitServicePool {
val carrotService = RetrofitManager.create<CarrotService>()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.sopt.carrot.data.datasource.remote

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import org.sopt.carrot.data.api.CarrotService
import org.sopt.carrot.data.model.neighborhoodlife.LivesDataResponse

class NeighborhoodLifeRemoteDatasource(
private val carrotService: CarrotService
) {
suspend fun getLives(): List<LivesDataResponse> {
return withContext(Dispatchers.IO) {
async {
carrotService.getLives().data
}.await()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.sopt.carrot.data.model.neighborhoodlife

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LivesDataResponse(
@SerialName("lifeId")
val lifeId: Int,
@SerialName("lifeCategoryContent")
val lifeCategoryContent: String,
@SerialName("lifeTitle")
val lifeTitle: String,
@SerialName("lifeContent")
val lifeContent: String,
@SerialName("contentInformation")
val contentInformation: String,
@SerialName("likeCount")
val likeCount: Int,
@SerialName("commentCount")
val commentCount: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.sopt.carrot.data.model.neighborhoodlife

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class LivesResponse(
@SerialName("code")
val code: Int,
@SerialName("message")
val message: String,
@SerialName("data")
val data: List<LivesDataResponse>
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.sopt.carrot.data.repo

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.sopt.carrot.data.datasource.remote.NeighborhoodLifeRemoteDatasource
import org.sopt.carrot.data.model.neighborhoodlife.LivesDataResponse

class NeighborhoodLifeRepository(
private val neighborhoodLifeRemoteDatasource: NeighborhoodLifeRemoteDatasource
) {
suspend fun getLives(): List<LivesDataResponse> =
withContext(Dispatchers.IO) {
neighborhoodLifeRemoteDatasource.getLives()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.sopt.carrot.data.datasource.TestDataSource
import org.sopt.carrot.data.repo.TestRepoImpl
import org.sopt.carrot.presentation.test.TestViewModel

class ViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(TestViewModel::class.java)) {
return TestViewModel(TestRepoImpl(TestDataSource())) as T
}
throw IllegalArgumentException("Unknown ViewModel Class")
}
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// if (modelClass.isAssignableFrom(TestViewModel::class.java)) {
// return TestViewModel(TestRepoImpl(TestDataSource())) as T
// }
// throw IllegalArgumentException("Unknown ViewModel Class")
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,80 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView.VERTICAL
import org.sopt.carrot.CarrotApp
import org.sopt.carrot.R
import org.sopt.carrot.core.ui.base.BindingFragment
import org.sopt.carrot.core.ui.fragment.snackBar
import org.sopt.carrot.databinding.FragmentNeighborhoodLifeBinding
import org.sopt.carrot.presentation.exploremeeting.ExploreMeetingActivity
import org.sopt.carrot.presentation.neighborhoodlife.adapter.CarouselTagAdapter
import org.sopt.carrot.presentation.neighborhoodlife.adapter.CarouselTextAdapter
import org.sopt.carrot.presentation.neighborhoodlife.adapter.NeighborhoodLifeAdapter
import org.sopt.carrot.presentation.neighborhoodlife.dummy.carouselTagList
import org.sopt.carrot.presentation.neighborhoodlife.dummy.carouselTextList
import org.sopt.carrot.presentation.profile.ProfileActivity

class NeighborhoodLifeFragment :
BindingFragment<FragmentNeighborhoodLifeBinding>(R.layout.fragment_neighborhood_life) {
private lateinit var neighborhoodViewModel: NeighborhoodViewModel

private lateinit var neighborhoodLifeAdapter: NeighborhoodLifeAdapter
private lateinit var carouselTextAdapter: CarouselTextAdapter
private lateinit var carouselTagAdapter: CarouselTagAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

binding.btnSample.setOnClickListener {
startActivity(Intent(requireContext(), ExploreMeetingActivity::class.java))
initNeighborhoodLifeViewModel()
initCarouselTextDummyAdapter()
initCarouselTagDummyAdapter()
initNeighborhoodLifeAdapter()
initLives()
setOnClickProfile()
observeData()
}

private fun initNeighborhoodLifeViewModel() {
neighborhoodViewModel = NeighborhoodLifeViewModelProvider(
CarrotApp.getNeighborhoodLifeRepositoryInstance()
).create(NeighborhoodViewModel::class.java)
}

private fun initLives() {
neighborhoodViewModel.getLives()
}

private fun setOnClickProfile() {
binding.ivProfile.setOnClickListener {
startActivity(Intent(requireContext(), ProfileActivity::class.java))
}
}

private fun initCarouselTextDummyAdapter() {
carouselTextAdapter = CarouselTextAdapter()
binding.rcvMeeting.adapter = carouselTextAdapter
carouselTextAdapter.submitList(carouselTextList)
}

private fun initCarouselTagDummyAdapter() {
carouselTagAdapter = CarouselTagAdapter()
binding.rcvTopic.adapter = carouselTagAdapter
carouselTagAdapter.submitList(carouselTagList)
}

private fun initNeighborhoodLifeAdapter() {
neighborhoodLifeAdapter = NeighborhoodLifeAdapter()
binding.rcvContents.adapter = neighborhoodLifeAdapter
binding.rcvContents.addItemDecoration(DividerItemDecoration(requireContext(), VERTICAL))
}

private fun observeData() {
neighborhoodViewModel.livesList.observe(viewLifecycleOwner) {
if (it.isEmpty()) {
snackBar(binding.root) { getString(R.string.empty_list) }
return@observe
}
neighborhoodLifeAdapter.submitList(it)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.carrot.presentation.neighborhoodlife

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.sopt.carrot.data.repo.NeighborhoodLifeRepository

class NeighborhoodLifeViewModelProvider(
private val neighborhoodLifeRepository: NeighborhoodLifeRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.getConstructor(NeighborhoodLifeRepository::class.java)
.newInstance(neighborhoodLifeRepository)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.sopt.carrot.presentation.neighborhoodlife

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.sopt.carrot.data.model.neighborhoodlife.LivesDataResponse
import org.sopt.carrot.data.repo.NeighborhoodLifeRepository

class NeighborhoodViewModel(
private val neighborhoodLifeRepository: NeighborhoodLifeRepository
): ViewModel() {
private val _livesList = MutableLiveData<List<LivesDataResponse>>()
val livesList: LiveData<List<LivesDataResponse>> get() = _livesList

fun getLives() {
viewModelScope.launch {
kotlin.runCatching {
neighborhoodLifeRepository.getLives()
}.onSuccess {
_livesList.value = it
}.onFailure {
_livesList.value = emptyList()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.sopt.carrot.presentation.neighborhoodlife.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.sopt.carrot.core.ui.view.ItemDiffCallback
import org.sopt.carrot.databinding.ItemTopicBinding
import org.sopt.carrot.presentation.neighborhoodlife.dummy.carouselTagList

class CarouselTagAdapter : ListAdapter<String, CarouselTagViewHolder>(
ItemDiffCallback<String>(
onItemsTheSame = { old, new -> old == new },
onContentsTheSame = { old, new -> old === new }
)
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CarouselTagViewHolder {
return CarouselTagViewHolder(
ItemTopicBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}

override fun onBindViewHolder(holder: CarouselTagViewHolder, position: Int) {
holder.bind(currentList[position])
}
}

class CarouselTagViewHolder(
private val binding: ItemTopicBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
if (item == carouselTagList[0]) binding.ivTopic.visibility = View.VISIBLE
binding.tvTopicTitle.text = item
}
}
Loading

0 comments on commit dbafb6d

Please sign in to comment.