Browse Source

add remoteDataSource and repository
show list MeasureTypes for shown fragment ListMeasureTypeFragment.kt

MrOzOn 5 năm trước cách đây
mục cha
commit
8f808228b4

+ 4 - 0
core_api/src/main/java/com/mrozon/core_api/db/dao/MeasureTypeDao.kt

@@ -3,6 +3,7 @@ package com.mrozon.core_api.db.dao
 import androidx.room.*
 import com.mrozon.core_api.db.model.MeasureTypeDb
 import com.mrozon.core_api.db.model.PersonDb
+import kotlinx.coroutines.flow.Flow
 
 @Dao
 interface MeasureTypeDao {
@@ -19,4 +20,7 @@ interface MeasureTypeDao {
         insertAllMeasureType(measureTypes)
     }
 
+    @Query("SELECT * FROM measure_type_table")
+    fun getMeasureTypes(): Flow<List<MeasureTypeDb>>
+
 }

+ 14 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/data/MeasureTypeRemoteDataSource.kt

@@ -0,0 +1,14 @@
+package com.mrozon.feature_measure_type.data
+
+import com.mrozon.core_api.network.HealthDiaryService
+import com.mrozon.utils.base.BaseDataSource
+import javax.inject.Inject
+
+class MeasureTypeRemoteDataSource @Inject constructor(private val service: HealthDiaryService): BaseDataSource() {
+
+    suspend fun getMeasureTypes()
+            = getResult {
+        service.getMeasureTypes()
+    }
+
+}

+ 10 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/data/MeasureTypeRepository.kt

@@ -0,0 +1,10 @@
+package com.mrozon.feature_measure_type.data
+
+import com.mrozon.core_api.entity.MeasureType
+import com.mrozon.utils.network.Result
+import kotlinx.coroutines.flow.Flow
+
+interface MeasureTypeRepository {
+    fun getMeasureTypes(): Flow<Result<List<MeasureType>>>
+    fun refreshMeasureTypes(): Flow<Result<List<MeasureType>>>
+}

+ 78 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/data/MeasureTypeRepositoryImpl.kt

@@ -0,0 +1,78 @@
+package com.mrozon.feature_measure_type.data
+
+import android.content.Context
+import coil.imageLoader
+import coil.request.CachePolicy
+import coil.request.ImageRequest
+import com.mrozon.core_api.db.HealthDiaryDao
+import com.mrozon.core_api.entity.MeasureType
+import com.mrozon.core_api.mapper.MeasureTypeToMeasureTypeDbMapper
+import com.mrozon.core_api.mapper.PersonToPersonDbMapper
+import com.mrozon.core_api.network.HealthDiaryService
+import com.mrozon.core_api.network.model.toMeasureType
+import com.mrozon.core_api.network.model.toPerson
+import com.mrozon.utils.network.Result
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.flow
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class MeasureTypeRepositoryImpl @Inject constructor(private val dao: HealthDiaryDao,
+        private val measureTypeRemoteDataSource: MeasureTypeRemoteDataSource,
+        private val mapper: MeasureTypeToMeasureTypeDbMapper,
+        private val context: Context
+): MeasureTypeRepository {
+
+    override fun getMeasureTypes(): Flow<Result<List<MeasureType>>> {
+        return flow {
+            emit(Result.loading())
+            val query = dao.getMeasureTypes()
+            val result = query.firstOrNull()
+            result?.let {
+                emit(Result.success(mapper.reverseMap(it)))
+            }
+            val networkResult = measureTypeRemoteDataSource.getMeasureTypes()
+            if (networkResult.status == Result.Status.SUCCESS) {
+                val data = networkResult.data!!
+                val measureTypes = data.map { response ->
+                    response.toMeasureType()
+                }
+                val measureTypesDb = mapper.map(measureTypes)
+                dao.reloadMeasureType(measureTypesDb)
+                emit(Result.success(measureTypes))
+            } else if (networkResult.status == Result.Status.ERROR) {
+                emit(Result.error(networkResult.message!!))
+            }
+        }
+    }
+
+    override fun refreshMeasureTypes(): Flow<Result<List<MeasureType>>> {
+        return flow {
+            emit(Result.loading())
+            val networkResult = measureTypeRemoteDataSource.getMeasureTypes()
+            if (networkResult.status == Result.Status.SUCCESS) {
+                val data = networkResult.data!!
+                val measureTypes = data.map { response ->
+                    preloadNetworkImage(response.url)
+                    response.toMeasureType()
+                }
+                val measureTypesDb = mapper.map(measureTypes)
+                dao.reloadMeasureType(measureTypesDb)
+                emit(Result.success(measureTypes))
+            } else if (networkResult.status == Result.Status.ERROR) {
+                emit(Result.error(networkResult.message!!))
+            }
+        }
+    }
+
+    private fun preloadNetworkImage(url: String){
+        val imageLoader = context.imageLoader
+        val request = ImageRequest.Builder(context)
+            .data(HealthDiaryService.ENDPOINT +url)
+            .memoryCachePolicy(CachePolicy.DISABLED)
+            .build()
+        imageLoader.enqueue(request)
+    }
+}

+ 5 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/di/ListMeasureTypeFragmentModule.kt

@@ -3,6 +3,8 @@ package com.mrozon.feature_measure_type.di
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import com.mrozon.core_api.viewmodel.ViewModelKey
+import com.mrozon.feature_measure_type.data.MeasureTypeRepository
+import com.mrozon.feature_measure_type.data.MeasureTypeRepositoryImpl
 import com.mrozon.feature_measure_type.presentation.ListMeasureTypeFragmentViewModel
 import dagger.Binds
 import dagger.Module
@@ -19,4 +21,7 @@ interface ListMeasureTypeFragmentModule {
     @Binds
     fun viewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
 
+    @Binds
+    fun provideMeasureTypeRepository(measureTypeRepositoryImpl: MeasureTypeRepositoryImpl): MeasureTypeRepository
+
 }

+ 4 - 4
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/BindingUtils.kt

@@ -4,19 +4,19 @@ import android.annotation.SuppressLint
 import android.widget.ImageView
 import android.widget.TextView
 import androidx.databinding.BindingAdapter
+import coil.decode.SvgDecoder
 import coil.load
-import coil.transform.CircleCropTransformation
 import com.mrozon.core_api.entity.MeasureType
-import com.mrozon.core_api.entity.Person
 import com.mrozon.core_api.network.HealthDiaryService
 import com.mrozon.feature_measure_type.R
 
 @BindingAdapter("load_logo")
 fun ImageView.loadLogo(item: MeasureType) {
-    load(HealthDiaryService.ENDPOINT +item.url) {
+
+    load(HealthDiaryService.ENDPOINT + item.url) {
+        decoder(SvgDecoder(context))
         crossfade(true)
         placeholder(R.drawable.ic_broken_image_24)
-//        transformations(CircleCropTransformation())
     }
 }
 

+ 64 - 1
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/ListMeasureTypeFragment.kt

@@ -1,12 +1,20 @@
 package com.mrozon.feature_measure_type.presentation
 
 import android.content.Context
+import android.os.Bundle
+import android.view.*
 import androidx.fragment.app.viewModels
+import androidx.lifecycle.Observer
 import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.mrozon.core_api.entity.MeasureType
 import com.mrozon.feature_measure_type.R
 import com.mrozon.feature_measure_type.databinding.FragmentListMeasureTypeBinding
 import com.mrozon.feature_measure_type.di.ListMeasureTypeFragmentComponent
 import com.mrozon.utils.base.BaseFragment
+import com.mrozon.utils.extension.hideKeyboard
+import com.mrozon.utils.extension.visible
+import com.mrozon.utils.network.Result
 import javax.inject.Inject
 
 class ListMeasureTypeFragment: BaseFragment<FragmentListMeasureTypeBinding>() {
@@ -18,12 +26,67 @@ class ListMeasureTypeFragment: BaseFragment<FragmentListMeasureTypeBinding>() {
 
     private val viewModel by viewModels<ListMeasureTypeFragmentViewModel> { viewModelFactory }
 
+    private lateinit var adapter: MeasureTypeAdapter
+
     override fun onAttach(context: Context) {
         super.onAttach(context)
         ListMeasureTypeFragmentComponent.injectFragment(this)
     }
 
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        setHasOptionsMenu(true)
+        return super.onCreateView(inflater, container, savedInstanceState)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        adapter = MeasureTypeAdapter(object: MeasureTypeAdapter.TypeMeasureClickListener {
+            override fun onClick(measureType: MeasureType) {
+//                TODO("Not yet implemented")
+                show("will be soon))")
+            }
+        })
+        binding?.rvMeasureTypes?.adapter = adapter
+        val manager = LinearLayoutManager(context)
+        binding?.rvMeasureTypes?.layoutManager = manager
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        inflater.inflate(R.menu.list_measure_type_menu, menu)
+        return super.onCreateOptionsMenu(menu, inflater)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        hideKeyboard()
+        when(item.itemId){
+            R.id.refreshMeasureTypes -> {
+                viewModel.refreshMeasureTypes()
+            }
+        }
+        return false
+    }
+
     override fun subscribeUi() {
-//        TODO("Not yet implemented")
+        viewModel.measure_types.observe(viewLifecycleOwner, Observer { event ->
+            event.peekContent().let { result ->
+                when (result.status) {
+                    Result.Status.LOADING -> {
+                        binding?.progressBar?.visible(true)
+                    }
+                    Result.Status.SUCCESS -> {
+                        binding?.progressBar?.visible(false)
+                        adapter.submitList(result.data)
+                    }
+                    Result.Status.ERROR -> {
+                        binding?.progressBar?.visible(false)
+                        showError(result.message!!)
+                    }
+                }
+            }
+        })
     }
 }

+ 39 - 1
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/ListMeasureTypeFragmentViewModel.kt

@@ -1,11 +1,49 @@
 package com.mrozon.feature_measure_type.presentation
 
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import com.mrozon.core_api.entity.MeasureType
+import com.mrozon.core_api.entity.Person
 import com.mrozon.core_api.providers.CoroutineContextProvider
+import com.mrozon.feature_measure_type.data.MeasureTypeRepository
+import com.mrozon.utils.Event
 import com.mrozon.utils.base.BaseViewModel
+import com.mrozon.utils.network.Result
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import timber.log.Timber
 import javax.inject.Inject
 
 class ListMeasureTypeFragmentViewModel @Inject constructor(
-
+    private val repository: MeasureTypeRepository,
+    private val coroutineContextProvider: CoroutineContextProvider
 ): BaseViewModel() {
 
+    private val _measure_types = MutableLiveData<Event<Result<List<MeasureType>>>>()
+    val measure_types: LiveData<Event<Result<List<MeasureType>>>>
+        get() = _measure_types
+
+    init {
+        Timber.d("init")
+        viewModelScope.launch(coroutineContextProvider.IO){
+            repository.getMeasureTypes().collect {
+                withContext(coroutineContextProvider.Main) {
+                    _measure_types.value = Event(it)
+                }
+            }
+        }
+    }
+
+    fun refreshMeasureTypes() {
+        viewModelScope.launch(coroutineContextProvider.IO){
+            repository.refreshMeasureTypes().collect {
+                withContext(coroutineContextProvider.Main) {
+                    _measure_types.value = Event(it)
+                }
+            }
+        }
+    }
+
 }

+ 60 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/MeasureTypeAdapter.kt

@@ -0,0 +1,60 @@
+package com.mrozon.feature_measure_type.presentation
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.mrozon.core_api.entity.MeasureType
+import com.mrozon.feature_measure_type.databinding.ItemMeasureTypeBinding
+
+class MeasureTypeAdapter(private val clickListener: TypeMeasureClickListener):
+    ListAdapter<MeasureType, MeasureTypeAdapter.ViewHolder>(TypeMeasureDiffCallback()) {
+
+
+    override fun onCreateViewHolder(
+        parent: ViewGroup,
+        viewType: Int
+    ): ViewHolder = ViewHolder.from(parent)
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val item = getItem(position)
+        holder.bind(item, clickListener)
+    }
+
+    class ViewHolder private constructor(val binding: ItemMeasureTypeBinding): RecyclerView.ViewHolder(binding.root) {
+
+        fun bind(
+            item: MeasureType,
+            clickListener: TypeMeasureClickListener) {
+            binding.measureType = item
+            binding.cvMeasureType.setOnClickListener {
+                clickListener.onClick(item)
+            }
+            binding.executePendingBindings()
+        }
+
+        companion object {
+            fun from(parent: ViewGroup): ViewHolder {
+                val layoutInflater = LayoutInflater.from(parent.context)
+                val binding = ItemMeasureTypeBinding.inflate(layoutInflater, parent, false)
+                return ViewHolder(binding)
+            }
+        }
+    }
+
+    class TypeMeasureDiffCallback : DiffUtil.ItemCallback<MeasureType>() {
+        override fun areItemsTheSame(oldItem: MeasureType, newItem: MeasureType): Boolean {
+            return oldItem.id == newItem.id
+        }
+
+        override fun areContentsTheSame(oldItem: MeasureType, newItem: MeasureType): Boolean {
+            return oldItem == newItem
+        }
+    }
+
+    interface TypeMeasureClickListener {
+        fun onClick(measureType: MeasureType)
+    }
+
+}

+ 5 - 0
feature_measure_type/src/main/res/drawable/ic_refresh_24.xml

@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FFFFFF"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
+</vector>

+ 1 - 1
feature_measure_type/src/main/res/layout/fragment_list_measure_type.xml

@@ -12,7 +12,7 @@
         android:layout_height="match_parent">
 
         <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/rvPerson"
+            android:id="@+id/rvMeasureTypes"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginBottom="59dp"

+ 11 - 0
feature_measure_type/src/main/res/menu/list_measure_type_menu.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:id="@+id/refreshMeasureTypes"
+        android:icon="@drawable/ic_refresh_24"
+        android:title="@string/refresh"
+        app:showAsAction="ifRoom" />
+
+</menu>

+ 1 - 0
feature_measure_type/src/main/res/values/strings.xml

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="ivLogo">logo measure type</string>
+    <string name="refresh">Refresh</string>
 </resources>

+ 6 - 5
feature_splash/src/main/java/com/mrozon/feature_splash/data/PreloadDataRepositoryImp.kt

@@ -32,11 +32,11 @@ class PreloadDataRepositoryImp @Inject constructor(
     private val context: Context
 ): PreloadDataRepository {
 
-    private val imageLoader = ImageLoader.Builder(context)
-        .componentRegistry {
-            add(SvgDecoder(context))
-        }
-        .build()
+//    private val imageLoader = ImageLoader.Builder(context)
+//        .componentRegistry {
+//            add(SvgDecoder(context))
+//        }
+//        .build()
 
     override fun getPreloadData(): Flow<Result<User?>> {
         return flow {
@@ -64,6 +64,7 @@ class PreloadDataRepositoryImp @Inject constructor(
     }
 
     private fun preloadNetworkImage(url: String){
+        val imageLoader = context.imageLoader
         val request = ImageRequest.Builder(context)
             .data(ENDPOINT+url)
             .memoryCachePolicy(CachePolicy.DISABLED)