浏览代码

Merge pull request #8 from MrOzOn/add_feature_measure_type

add_feature_measure_type
MrOzOn 5 年之前
父节点
当前提交
34e7cd8032
共有 53 个文件被更改,包括 1132 次插入109 次删除
  1. 1 0
      app/build.gradle
  2. 7 3
      app/src/main/java/com/mrozon/healthdiary/data/UserRepositoryImp.kt
  3. 20 7
      app/src/main/java/com/mrozon/healthdiary/presentation/main/MainActivity.kt
  4. 10 5
      app/src/main/java/com/mrozon/healthdiary/presentation/main/MainActivityViewModel.kt
  5. 5 0
      app/src/main/res/drawable/ic_measure_type_24.xml
  6. 5 0
      app/src/main/res/menu/navigation_menu.xml
  7. 13 0
      app/src/main/res/navigation/nav_graph.xml
  8. 1 0
      app/src/main/res/values/strings.xml
  9. 5 46
      core_api/src/main/java/com/mrozon/core_api/db/HealthDiaryDao.kt
  10. 26 0
      core_api/src/main/java/com/mrozon/core_api/db/dao/MeasureTypeDao.kt
  11. 32 0
      core_api/src/main/java/com/mrozon/core_api/db/dao/PersonDao.kt
  12. 22 0
      core_api/src/main/java/com/mrozon/core_api/db/dao/UserDao.kt
  13. 27 0
      core_api/src/main/java/com/mrozon/core_api/db/model/MeasureTypeDb.kt
  14. 11 0
      core_api/src/main/java/com/mrozon/core_api/entity/MeasureType.kt
  15. 40 0
      core_api/src/main/java/com/mrozon/core_api/mapper/MeasureTypeToMeasureTypeDbMapper.kt
  16. 5 2
      core_api/src/main/java/com/mrozon/core_api/network/HealthDiaryService.kt
  17. 26 0
      core_api/src/main/java/com/mrozon/core_api/network/model/MeasureTypeResponse.kt
  18. 2 1
      core_impl/src/main/java/com/mrozon/core_impl/db/HealthDiaryDb.kt
  19. 1 0
      feature_measure_type/.gitignore
  20. 83 0
      feature_measure_type/build.gradle
  21. 0 0
      feature_measure_type/consumer-rules.pro
  22. 21 0
      feature_measure_type/proguard-rules.pro
  23. 24 0
      feature_measure_type/src/androidTest/java/com/mrozon/feature_measure_type/ExampleInstrumentedTest.kt
  24. 5 0
      feature_measure_type/src/main/AndroidManifest.xml
  25. 14 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/data/MeasureTypeRemoteDataSource.kt
  26. 10 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/data/MeasureTypeRepository.kt
  27. 78 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/data/MeasureTypeRepositoryImpl.kt
  28. 31 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/di/DaggerViewModelFactory.kt
  29. 32 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/di/ListMeasureTypeFragmentComponent.kt
  30. 27 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/di/ListMeasureTypeFragmentModule.kt
  31. 28 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/BindingUtils.kt
  32. 92 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/ListMeasureTypeFragment.kt
  33. 49 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/ListMeasureTypeFragmentViewModel.kt
  34. 60 0
      feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/MeasureTypeAdapter.kt
  35. 10 0
      feature_measure_type/src/main/res/drawable/ic_broken_image_24.xml
  36. 5 0
      feature_measure_type/src/main/res/drawable/ic_refresh_24.xml
  37. 39 0
      feature_measure_type/src/main/res/layout/fragment_list_measure_type.xml
  38. 65 0
      feature_measure_type/src/main/res/layout/item_measure_type.xml
  39. 11 0
      feature_measure_type/src/main/res/menu/list_measure_type_menu.xml
  40. 5 0
      feature_measure_type/src/main/res/values/dimens.xml
  41. 5 0
      feature_measure_type/src/main/res/values/strings.xml
  42. 17 0
      feature_measure_type/src/test/java/com/mrozon/feature_measure_type/ExampleUnitTest.kt
  43. 4 0
      feature_splash/build.gradle
  44. 0 9
      feature_splash/src/main/java/com/mrozon/feature_splash/data/LocalUserRepository.kt
  45. 0 22
      feature_splash/src/main/java/com/mrozon/feature_splash/data/LocalUserRepositoryImp.kt
  46. 17 0
      feature_splash/src/main/java/com/mrozon/feature_splash/data/PreloadDataRemoteDataSource.kt
  47. 11 0
      feature_splash/src/main/java/com/mrozon/feature_splash/data/PreloadDataRepository.kt
  48. 75 0
      feature_splash/src/main/java/com/mrozon/feature_splash/data/PreloadDataRepositoryImp.kt
  49. 3 3
      feature_splash/src/main/java/com/mrozon/feature_splash/di/SplashFragmentModule.kt
  50. 18 8
      feature_splash/src/main/java/com/mrozon/feature_splash/presentation/SplashFragment.kt
  51. 28 3
      feature_splash/src/main/java/com/mrozon/feature_splash/presentation/SplashFragmentViewModel.kt
  52. 5 0
      scripts/deps_versions.gradle
  53. 1 0
      settings.gradle

+ 1 - 0
app/build.gradle

@@ -65,4 +65,5 @@ dependencies {
     implementation project(':feature_splash')
     implementation project(':feature_auth')
     implementation project(':feature_person')
+    implementation project(':feature_measure_type')
 }

+ 7 - 3
app/src/main/java/com/mrozon/healthdiary/data/UserRepositoryImp.kt

@@ -6,16 +6,20 @@ import com.mrozon.core_api.db.HealthDiaryDao
 import com.mrozon.core_api.entity.User
 import com.mrozon.core_api.mapper.UserToUserDbMapper
 import com.mrozon.core_api.security.SecurityTokenService
+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
 
 class UserRepositoryImp @Inject constructor(
-    private val healthDiaryDao: HealthDiaryDao,
+    private val dao: HealthDiaryDao,
     private val mapper: UserToUserDbMapper,
     private val securityTokenService: SecurityTokenService
 ): UserRepository {
 
     override fun getLocalUser(): LiveData<User> {
-        val userDb = healthDiaryDao.getUser()
+        val userDb = dao.getLiveUser()
         return Transformations.map(userDb) {
             mapper.reverseMap(it)
         }
@@ -25,7 +29,7 @@ class UserRepositoryImp @Inject constructor(
         securityTokenService.clearAccessToken()
         val userDb = mapper.map(user)
             userDb?.let {
-                healthDiaryDao.deleteUser(userDb)
+                dao.deleteUser(userDb)
         }
     }
 

+ 20 - 7
app/src/main/java/com/mrozon/healthdiary/presentation/main/MainActivity.kt

@@ -12,15 +12,18 @@ import androidx.lifecycle.Observer
 import androidx.lifecycle.ViewModelProvider
 import androidx.navigation.NavController
 import androidx.navigation.findNavController
+import androidx.navigation.fragment.findNavController
 import androidx.navigation.ui.AppBarConfiguration
 import androidx.navigation.ui.navigateUp
 import androidx.navigation.ui.setupActionBarWithNavController
 import com.google.android.material.navigation.NavigationView
+import com.mrozon.core_api.entity.User
 import com.mrozon.core_api.providers.AppWithFacade
 import com.mrozon.healthdiary.R
 import com.mrozon.healthdiary.databinding.ActivityMainBinding
 import com.mrozon.healthdiary.di.main.MainActivityComponent
 import com.mrozon.utils.base.BaseActivity
+import com.mrozon.utils.network.Result
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -56,7 +59,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(){
         navView = binding.navView
         navController = findNavController(R.id.nav_host_fragment)
         appBarConfiguration = AppBarConfiguration(
-            setOf(R.id.listPersonFragment),
+            setOf(R.id.listPersonFragment, R.id.listMeasureTypeFragment),
             binding.drawerLayout
         )
         setupActionBarWithNavController(navController, appBarConfiguration)
@@ -67,6 +70,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>(){
                     if(currentDestinationId!=R.id.listPersonFragment)
                         navController.navigate(R.id.action_global_listPersonFragment)
                 }
+                R.id.show_measure_types -> {
+                    if(currentDestinationId!=R.id.listMeasureTypeFragment)
+                        navController.navigate(R.id.action_global_listMeasureTypeFragment)
+                }
             }
             drawerLayout.closeDrawer(GravityCompat.START)
             true
@@ -94,13 +101,19 @@ class MainActivity : BaseActivity<ActivityMainBinding>(){
             }
         })
 
+
         viewModel.currentUser.observe(this, Observer { user ->
-            val headerView = binding.navView.getHeaderView(0)
-            val tvUserEmail = headerView.findViewById<TextView>(R.id.tvUserEmail)
-            val tvUserName = headerView.findViewById<TextView>(R.id.tvUserName)
-            val ivLogout = headerView.findViewById<ImageView>(R.id.ivLogout)
+            showUserProfile(user)
+        })
+    }
+
+    private fun showUserProfile(user: User?) {
+        val headerView = binding.navView.getHeaderView(0)
+        val tvUserEmail = headerView.findViewById<TextView>(R.id.tvUserEmail)
+        val tvUserName = headerView.findViewById<TextView>(R.id.tvUserName)
+        val ivLogout = headerView.findViewById<ImageView>(R.id.ivLogout)
 
-            if (user == null) {
+        if (user == null) {
                 Timber.d("user is null")
                 supportActionBar?.hide()
                 drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED)
@@ -116,8 +129,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(){
                         drawerLayout.closeDrawer(GravityCompat.START)
                     }
                     viewModel.logoutUser(user)
+                    showUserProfile(null)
                 }
             }
-        })
     }
 }

+ 10 - 5
app/src/main/java/com/mrozon/healthdiary/presentation/main/MainActivityViewModel.kt

@@ -3,31 +3,36 @@ package com.mrozon.healthdiary.presentation.main
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
 import com.mrozon.core_api.entity.User
+import com.mrozon.core_api.providers.CoroutineContextProvider
 import com.mrozon.healthdiary.data.UserRepository
 import com.mrozon.utils.Event
+import com.mrozon.utils.network.Result
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.flow.collect
 import timber.log.Timber
 import javax.inject.Inject
 
 class MainActivityViewModel @Inject constructor(
-    private val userRepository: UserRepository
+    private val repository: UserRepository,
+    private val coroutineContextProvider: CoroutineContextProvider
 ): ViewModel() {
 
-    val currentUser = userRepository.getLocalUser()
+    val currentUser = repository.getLocalUser()
 
     private val _cleared = MutableLiveData<Event<Unit>>()
     val cleared: LiveData<Event<Unit>>
         get() = _cleared
 
     fun logoutUser(user: User) {
-        CoroutineScope(Dispatchers.IO).launch(getJobErrorHandler()) {
-            userRepository.clearLocalUser(user)
-            withContext(Dispatchers.Main){
+        CoroutineScope(coroutineContextProvider.IO).launch(getJobErrorHandler()) {
+            repository.clearLocalUser(user)
+            withContext(coroutineContextProvider.Main){
                 _cleared.value = Event(Unit)
             }
         }

+ 5 - 0
app/src/main/res/drawable/ic_measure_type_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="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z"/>
+</vector>

+ 5 - 0
app/src/main/res/menu/navigation_menu.xml

@@ -6,4 +6,9 @@
         android:title="@string/list_persons"
         />
 
+    <item android:id="@+id/show_measure_types"
+        android:icon="@drawable/ic_measure_type_24"
+        android:title="@string/list_measure_types"
+        />
+
 </menu>

+ 13 - 0
app/src/main/res/navigation/nav_graph.xml

@@ -115,4 +115,17 @@
         app:popEnterAnim="@anim/slide_in_left"
         app:popExitAnim="@anim/slide_out_right"/>
 
+    <fragment
+        android:id="@+id/listMeasureTypeFragment"
+        android:name="com.mrozon.feature_measure_type.presentation.ListMeasureTypeFragment"
+        tools:layout="@layout/fragment_list_measure_type"
+        android:label="@string/list_measure_types" />
+
+    <action android:id="@+id/action_global_listMeasureTypeFragment"
+        app:destination="@id/listMeasureTypeFragment"
+        app:enterAnim="@anim/slide_in_right"
+        app:exitAnim="@anim/slide_out_left"
+        app:popEnterAnim="@anim/slide_in_left"
+        app:popExitAnim="@anim/slide_out_right"/>
+
 </navigation>

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

@@ -1,4 +1,5 @@
 <resources>
     <string name="app_name">HealthDiary</string>
     <string name="list_persons">Persons</string>
+    <string name="list_measure_types">Measure types</string>
 </resources>

+ 5 - 46
core_api/src/main/java/com/mrozon/core_api/db/HealthDiaryDao.kt

@@ -1,50 +1,9 @@
 package com.mrozon.core_api.db
 
-import androidx.lifecycle.LiveData
-import androidx.room.*
-import com.mrozon.core_api.db.model.PersonDb
-import com.mrozon.core_api.db.model.UserDb
-import kotlinx.coroutines.flow.Flow
+import androidx.room.Dao
+import com.mrozon.core_api.db.dao.MeasureTypeDao
+import com.mrozon.core_api.db.dao.PersonDao
+import com.mrozon.core_api.db.dao.UserDao
 
 @Dao
-interface HealthDiaryDao {
-
-    // USER
-    @Query("SELECT * FROM user_table LIMIT 1")
-    fun getUser(): LiveData<UserDb>
-
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    suspend fun insertUser(userDb: UserDb)
-
-    @Delete
-    suspend fun deleteUser(userDb: UserDb)
-
-//    // TOKEN
-//    @Query("SELECT user_token from user_table LIMIT 1")
-//    fun getAccessToken(): String
-
-    // PERSON
-    @Query("SELECT * FROM person_table")
-    fun getPersons(): Flow<List<PersonDb>>
-
-    @Query("SELECT * FROM person_table WHERE person_id=:id LIMIT 1")
-    suspend fun getPerson(id: Long): PersonDb
-
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    suspend fun insertAllPerson(persons: List<PersonDb>)
-
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    suspend fun insertPerson(person: PersonDb)
-
-    @Query("DELETE FROM person_table WHERE person_id=:id")
-    suspend fun deletePerson(id: Long)
-
-    @Query("DELETE FROM person_table")
-    suspend fun deleteAllPerson()
-
-    @Transaction
-    suspend fun reloadPersons(persons: List<PersonDb>) {
-        deleteAllPerson()
-        insertAllPerson(persons)
-    }
-}
+interface HealthDiaryDao: UserDao, PersonDao, MeasureTypeDao

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

@@ -0,0 +1,26 @@
+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 {
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insertAllMeasureType(persons: List<MeasureTypeDb>)
+
+    @Query("DELETE FROM measure_type_table")
+    suspend fun deleteAllMeasureType()
+
+    @Transaction
+    suspend fun reloadMeasureType(measureTypes: List<MeasureTypeDb>) {
+        deleteAllMeasureType()
+        insertAllMeasureType(measureTypes)
+    }
+
+    @Query("SELECT * FROM measure_type_table")
+    fun getMeasureTypes(): Flow<List<MeasureTypeDb>>
+
+}

+ 32 - 0
core_api/src/main/java/com/mrozon/core_api/db/dao/PersonDao.kt

@@ -0,0 +1,32 @@
+package com.mrozon.core_api.db.dao
+
+import androidx.room.*
+import com.mrozon.core_api.db.model.PersonDb
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface PersonDao {
+    @Query("SELECT * FROM person_table")
+    fun getPersons(): Flow<List<PersonDb>>
+
+    @Query("SELECT * FROM person_table WHERE person_id=:id LIMIT 1")
+    suspend fun getPerson(id: Long): PersonDb
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insertAllPerson(persons: List<PersonDb>)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insertPerson(person: PersonDb)
+
+    @Query("DELETE FROM person_table WHERE person_id=:id")
+    suspend fun deletePerson(id: Long)
+
+    @Query("DELETE FROM person_table")
+    suspend fun deleteAllPerson()
+
+    @Transaction
+    suspend fun reloadPersons(persons: List<PersonDb>) {
+        deleteAllPerson()
+        insertAllPerson(persons)
+    }
+}

+ 22 - 0
core_api/src/main/java/com/mrozon/core_api/db/dao/UserDao.kt

@@ -0,0 +1,22 @@
+package com.mrozon.core_api.db.dao
+
+import androidx.lifecycle.LiveData
+import androidx.room.*
+import com.mrozon.core_api.db.model.UserDb
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface UserDao {
+
+    @Query("SELECT * FROM user_table LIMIT 1")
+    fun getUser(): Flow<UserDb>
+
+    @Query("SELECT * FROM user_table LIMIT 1")
+    fun getLiveUser(): LiveData<UserDb>
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    suspend fun insertUser(userDb: UserDb)
+
+    @Delete
+    suspend fun deleteUser(userDb: UserDb)
+}

+ 27 - 0
core_api/src/main/java/com/mrozon/core_api/db/model/MeasureTypeDb.kt

@@ -0,0 +1,27 @@
+package com.mrozon.core_api.db.model
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "measure_type_table")
+data class MeasureTypeDb (
+    @PrimaryKey(autoGenerate = true)
+    @ColumnInfo(name = "measure_type_id")
+    var id: Long = 0L,
+
+    @ColumnInfo(name = "measure_type_name")
+    val name: String = "",
+
+    @ColumnInfo(name = "measure_type_mark")
+    val mark: String = "",
+
+    @ColumnInfo(name = "measure_type_regexp")
+    val regexp: String = "",
+
+    @ColumnInfo(name = "measure_type_hint")
+    val hint: String = "",
+
+    @ColumnInfo(name = "measure_type_url")
+    val url: String = ""
+)

+ 11 - 0
core_api/src/main/java/com/mrozon/core_api/entity/MeasureType.kt

@@ -0,0 +1,11 @@
+package com.mrozon.core_api.entity
+
+
+data class MeasureType (
+    val id: Long = 0L,
+    val name: String = "",
+    val mark: String = "",
+    val regexp: String = "",
+    val hint: String = "",
+    val url: String = ""
+)

+ 40 - 0
core_api/src/main/java/com/mrozon/core_api/mapper/MeasureTypeToMeasureTypeDbMapper.kt

@@ -0,0 +1,40 @@
+package com.mrozon.core_api.mapper
+
+import com.mrozon.core_api.db.model.MeasureTypeDb
+import com.mrozon.core_api.db.model.PersonDb
+import com.mrozon.core_api.entity.Gender
+import com.mrozon.core_api.entity.MeasureType
+import com.mrozon.core_api.entity.Person
+import com.mrozon.utils.base.BaseMapper
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class MeasureTypeToMeasureTypeDbMapper @Inject constructor(): BaseMapper<MeasureType, MeasureTypeDb>()  {
+
+    override fun map(entity: MeasureType?): MeasureTypeDb? {
+        entity?.let {
+            return MeasureTypeDb(id = it.id,
+                name = it.name,
+                hint = it.hint,
+                regexp = it.regexp,
+                url = it.url,
+                mark = it.mark
+            )
+        }
+        return null
+    }
+
+    override fun reverseMap(model: MeasureTypeDb?): MeasureType? {
+        model?.let {
+            return MeasureType(id = it.id,
+                name = it.name,
+                hint = it.hint,
+                regexp = it.regexp,
+                url = it.url,
+                mark = it.mark
+            )
+        }
+        return null
+    }
+}

+ 5 - 2
core_api/src/main/java/com/mrozon/core_api/network/HealthDiaryService.kt

@@ -6,8 +6,8 @@ import retrofit2.http.*
 
 interface HealthDiaryService {
     companion object {
-//        const val ENDPOINT = "http://10.0.2.2:8000/"
-        const val ENDPOINT = "https://hdb.mr-ozon-1982.tk/"
+        const val ENDPOINT = "http://10.0.2.2:8000/"
+//        const val ENDPOINT = "https://hdb.mr-ozon-1982.tk/"
     }
 
     @POST("login/")
@@ -32,6 +32,9 @@ interface HealthDiaryService {
     @POST("add-user-to-patient/")
     suspend fun sharePerson(@Body body: SharePersonRequest): Response<PersonResponse>
 
+    @GET("indicatortypes/")
+    suspend fun getMeasureTypes(): Response<List<MeasureTypeResponse>>
+
 //    @GET("lego/themes/")
 //    suspend fun getThemes(@Query("page") page: Int? = null,
 //                          @Query("page_size") pageSize: Int? = null,

+ 26 - 0
core_api/src/main/java/com/mrozon/core_api/network/model/MeasureTypeResponse.kt

@@ -0,0 +1,26 @@
+package com.mrozon.core_api.network.model
+
+import com.mrozon.core_api.entity.Gender
+import com.mrozon.core_api.entity.MeasureType
+import com.mrozon.core_api.entity.Person
+import com.mrozon.utils.extension.toSimpleDate
+
+data class MeasureTypeResponse(
+	val regexp: String,
+	val hint: String,
+	val name: String,
+	val id: Int,
+	val mark: String,
+	val url: String
+)
+
+fun MeasureTypeResponse.toMeasureType(): MeasureType {
+	return MeasureType(
+		id = id.toLong(),
+		name = name,
+		mark = mark,
+		hint = hint,
+		url = url,
+		regexp = regexp
+	)
+}

+ 2 - 1
core_impl/src/main/java/com/mrozon/core_impl/db/HealthDiaryDb.kt

@@ -3,8 +3,9 @@ package com.mrozon.core_impl.db
 import androidx.room.Database
 import androidx.room.RoomDatabase
 import com.mrozon.core_api.db.HealthDiaryDatabaseContract
+import com.mrozon.core_api.db.model.MeasureTypeDb
 import com.mrozon.core_api.db.model.PersonDb
 import com.mrozon.core_api.db.model.UserDb
 
-@Database(entities = [UserDb::class, PersonDb::class], version = 1)
+@Database(entities = [UserDb::class, PersonDb::class, MeasureTypeDb::class], version = 1)
 abstract class HealthDiaryDb : RoomDatabase(), HealthDiaryDatabaseContract

+ 1 - 0
feature_measure_type/.gitignore

@@ -0,0 +1 @@
+/build

+ 83 - 0
feature_measure_type/build.gradle

@@ -0,0 +1,83 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+
+apply from: "$project.rootDir/jacoco.gradle"
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.3"
+
+    defaultConfig {
+        minSdkVersion 21
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        debug {
+            testCoverageEnabled true
+        }
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    dataBinding {
+        enabled = true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+apply from: "$project.rootDir/scripts/deps_versions.gradle"
+
+dependencies {
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+    implementation kotlinStdlib
+
+    api project(':core_api')
+    implementation project(':core')
+    implementation project(':utils')
+    //DAGGER
+    implementation dagger
+    kapt daggerCompiler
+    implementation constraintlayout
+    //Timber
+    implementation timber
+    implementation navigationFragment
+    implementation retrofit
+    //AndroidX
+    implementation legacySupport
+    implementation appCompat
+    implementation androidxCore
+    implementation cardview
+    implementation recyclerview
+    implementation material
+    implementation coil
+    implementation coilBase
+    implementation coilSvg
+    //Unit test
+    testImplementation junit
+    testImplementation mockitoCore
+    testImplementation kotlinxCoroutinesTest
+    testImplementation robolectric
+    testImplementation androidXCoreTest
+    //Instrumental Test
+    androidTestImplementation junitInstrumental
+    androidTestImplementation androidXRulesTest
+    androidTestImplementation androidXRunnerTest
+    androidTestImplementation espressoCore
+    androidTestImplementation androidXFragmentTest
+    kaptAndroidTest daggerCompiler
+}

+ 0 - 0
feature_measure_type/consumer-rules.pro


+ 21 - 0
feature_measure_type/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
feature_measure_type/src/androidTest/java/com/mrozon/feature_measure_type/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.mrozon.feature_measure_type
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.mrozon.feature_measure_type.test", appContext.packageName)
+    }
+}

+ 5 - 0
feature_measure_type/src/main/AndroidManifest.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.mrozon.feature_measure_type">
+
+</manifest>

+ 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)
+    }
+}

+ 31 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/di/DaggerViewModelFactory.kt

@@ -0,0 +1,31 @@
+package com.mrozon.feature_measure_type.di
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import javax.inject.Inject
+import javax.inject.Provider
+
+class DaggerViewModelFactory @Inject constructor(
+    private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
+) : ViewModelProvider.Factory {
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        var creator: Provider<out ViewModel>? = creators[modelClass]
+        if (creator == null) {
+            for ((key, value) in creators) {
+                if (modelClass.isAssignableFrom(key)) {
+                    creator = value
+                    break
+                }
+            }
+        }
+        if (creator == null) {
+            throw IllegalArgumentException("Unknown model class: $modelClass")
+        }
+        try {
+            @Suppress("UNCHECKED_CAST")
+            return creator.get() as T
+        } catch (e: Exception) {
+            throw RuntimeException(e)
+        }
+    }
+}

+ 32 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/di/ListMeasureTypeFragmentComponent.kt

@@ -0,0 +1,32 @@
+package com.mrozon.feature_measure_type.di
+
+import com.mrozon.core_api.providers.AppWithFacade
+import com.mrozon.core_api.providers.ProvidersFacade
+import com.mrozon.core_api.viewmodel.ViewModelsFactoryProvider
+import com.mrozon.feature_measure_type.presentation.ListMeasureTypeFragment
+import dagger.Component
+import javax.inject.Singleton
+
+@Singleton
+@Component(
+    modules = [ListMeasureTypeFragmentModule::class],
+    dependencies = [ProvidersFacade::class]
+)
+interface ListMeasureTypeFragmentComponent: ViewModelsFactoryProvider {
+
+    companion object {
+        fun create(providersFacade: ProvidersFacade): ListMeasureTypeFragmentComponent {
+            return DaggerListMeasureTypeFragmentComponent.builder()
+                .providersFacade(providersFacade)
+                .build()
+        }
+        fun injectFragment(fragment: ListMeasureTypeFragment): ListMeasureTypeFragmentComponent  {
+            val component = create((fragment.activity?.application
+                    as AppWithFacade).getFacade())
+            component.inject(fragment)
+            return component
+        }
+    }
+
+    fun inject(fragment: ListMeasureTypeFragment)
+}

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

@@ -0,0 +1,27 @@
+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
+import dagger.multibindings.IntoMap
+
+@Module
+interface ListMeasureTypeFragmentModule {
+
+    @Binds
+    @IntoMap
+    @ViewModelKey(ListMeasureTypeFragmentViewModel::class)
+    fun bindViewModel(viewmodel: ListMeasureTypeFragmentViewModel): ViewModel
+
+    @Binds
+    fun viewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
+
+    @Binds
+    fun provideMeasureTypeRepository(measureTypeRepositoryImpl: MeasureTypeRepositoryImpl): MeasureTypeRepository
+
+}

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

@@ -0,0 +1,28 @@
+package com.mrozon.feature_measure_type.presentation
+
+import android.annotation.SuppressLint
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.databinding.BindingAdapter
+import coil.decode.SvgDecoder
+import coil.load
+import com.mrozon.core_api.entity.MeasureType
+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) {
+        decoder(SvgDecoder(context))
+        crossfade(true)
+        placeholder(R.drawable.ic_broken_image_24)
+    }
+}
+
+@SuppressLint("SetTextI18n")
+@BindingAdapter("name_with_mark")
+fun TextView.setNameWithMark(item: MeasureType) {
+    text = "${item.name} (${item.mark})"
+}
+

+ 92 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/ListMeasureTypeFragment.kt

@@ -0,0 +1,92 @@
+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>() {
+
+    override fun getLayoutId(): Int = R.layout.fragment_list_measure_type
+
+    @Inject
+    lateinit var viewModelFactory: ViewModelProvider.Factory
+
+    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() {
+        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!!)
+                    }
+                }
+            }
+        })
+    }
+}

+ 49 - 0
feature_measure_type/src/main/java/com/mrozon/feature_measure_type/presentation/ListMeasureTypeFragmentViewModel.kt

@@ -0,0 +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)
+    }
+
+}

+ 10 - 0
feature_measure_type/src/main/res/drawable/ic_broken_image_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/black"
+      android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
+</vector>

+ 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>

+ 39 - 0
feature_measure_type/src/main/res/layout/fragment_list_measure_type.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <data>
+
+    </data>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rvMeasureTypes"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="59dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:listitem="@layout/item_measure_type" />
+
+        <ProgressBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="128dp"
+            android:layout_height="128dp"
+            android:indeterminate="true"
+            android:visibility="invisible"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:visibility="visible" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</layout>

+ 65 - 0
feature_measure_type/src/main/res/layout/item_measure_type.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:tools="http://schemas.android.com/tools"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <data>
+        <variable
+            name="measure_type"
+            type="com.mrozon.core_api.entity.MeasureType" />
+    </data>
+
+    <androidx.cardview.widget.CardView
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:id="@+id/cvMeasureType"
+        android:layout_gravity="center"
+        android:layout_margin="@dimen/card_margin"
+        app:cardCornerRadius="5dp"
+        app:elevation="@dimen/cardview_elevation">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:id="@+id/ivLogo"
+                android:layout_width="40dp"
+                android:layout_height="40dp"
+                android:layout_marginStart="8dp"
+                android:layout_marginTop="8dp"
+                android:layout_marginBottom="8dp"
+                android:alpha="0.75"
+                app:load_logo="@{measure_type}"
+                android:contentDescription="@string/ivLogo"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:srcCompat="@drawable/ic_logo_24" />
+
+            <TextView
+                android:id="@+id/person_name"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_centerVertical="true"
+                android:layout_marginStart="16dp"
+                android:layout_marginRight="16dp"
+                android:layout_toEndOf="@id/ivLogo"
+                android:paddingTop="16dp"
+                android:textAppearance="@style/TextAppearance.AppCompat.Large"
+                android:textColor="#000000"
+                android:textSize="20sp"
+                app:name_with_mark="@{measure_type}"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintHorizontal_bias="0.0"
+                app:layout_constraintStart_toEndOf="@+id/ivLogo"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintVertical_chainStyle="packed"
+                tools:text="Важный показатель" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </androidx.cardview.widget.CardView>
+
+</layout>

+ 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>

+ 5 - 0
feature_measure_type/src/main/res/values/dimens.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="card_margin">3dp</dimen>
+    <dimen name="cardview_elevation">3dp</dimen>
+</resources>

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

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

+ 17 - 0
feature_measure_type/src/test/java/com/mrozon/feature_measure_type/ExampleUnitTest.kt

@@ -0,0 +1,17 @@
+package com.mrozon.feature_measure_type
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}

+ 4 - 0
feature_splash/build.gradle

@@ -50,6 +50,10 @@ dependencies {
     //Timber
     implementation timber
     implementation navigationFragment
+    implementation retrofit
+    implementation coil
+    implementation coilBase
+    implementation coilSvg
 
 //    implementation 'androidx.core:core-ktx:1.3.1'
     implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

+ 0 - 9
feature_splash/src/main/java/com/mrozon/feature_splash/data/LocalUserRepository.kt

@@ -1,9 +0,0 @@
-package com.mrozon.feature_splash.data
-
-import androidx.lifecycle.LiveData
-import com.mrozon.core_api.entity.User
-
-interface LocalUserRepository {
-
-    fun getLocalUser(): LiveData<User>
-}

+ 0 - 22
feature_splash/src/main/java/com/mrozon/feature_splash/data/LocalUserRepositoryImp.kt

@@ -1,22 +0,0 @@
-package com.mrozon.feature_splash.data
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.Transformations
-import com.mrozon.core_api.db.HealthDiaryDao
-import com.mrozon.core_api.entity.User
-import com.mrozon.core_api.mapper.UserToUserDbMapper
-import javax.inject.Inject
-
-class LocalUserRepositoryImp @Inject constructor(
-    private val healthDiaryDao: HealthDiaryDao,
-    private val mapper: UserToUserDbMapper
-): LocalUserRepository {
-
-    override fun getLocalUser(): LiveData<User> {
-        val userDb = healthDiaryDao.getUser()
-        return Transformations.map(userDb) {
-            mapper.reverseMap(it)
-        }
-    }
-
-}

+ 17 - 0
feature_splash/src/main/java/com/mrozon/feature_splash/data/PreloadDataRemoteDataSource.kt

@@ -0,0 +1,17 @@
+package com.mrozon.feature_splash.data
+
+import com.mrozon.core_api.db.HealthDiaryDao
+import com.mrozon.core_api.network.HealthDiaryService
+import com.mrozon.core_api.network.model.PersonRequest
+import com.mrozon.core_api.network.model.SharePersonRequest
+import com.mrozon.utils.base.BaseDataSource
+import javax.inject.Inject
+
+class PreloadDataRemoteDataSource @Inject constructor(private val service: HealthDiaryService): BaseDataSource() {
+
+    suspend fun getMeasureTypes()
+            = getResult {
+                service.getMeasureTypes()
+            }
+
+}

+ 11 - 0
feature_splash/src/main/java/com/mrozon/feature_splash/data/PreloadDataRepository.kt

@@ -0,0 +1,11 @@
+package com.mrozon.feature_splash.data
+
+import androidx.lifecycle.LiveData
+import com.mrozon.core_api.entity.User
+import com.mrozon.utils.network.Result
+import kotlinx.coroutines.flow.Flow
+
+interface PreloadDataRepository {
+
+    fun getPreloadData(): Flow<Result<User?>>
+}

+ 75 - 0
feature_splash/src/main/java/com/mrozon/feature_splash/data/PreloadDataRepositoryImp.kt

@@ -0,0 +1,75 @@
+package com.mrozon.feature_splash.data
+
+import android.content.Context
+import android.graphics.Insets.add
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
+import coil.ImageLoader
+import coil.decode.SvgDecoder
+import coil.imageLoader
+import coil.request.CachePolicy
+import coil.request.ImageRequest
+import com.mrozon.core_api.db.HealthDiaryDao
+import com.mrozon.core_api.entity.User
+import com.mrozon.core_api.mapper.MeasureTypeToMeasureTypeDbMapper
+import com.mrozon.core_api.mapper.UserToUserDbMapper
+import com.mrozon.core_api.network.HealthDiaryService.Companion.ENDPOINT
+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 PreloadDataRepositoryImp @Inject constructor(
+    private val dao: HealthDiaryDao,
+    private val remoteDataSource: PreloadDataRemoteDataSource,
+    private val mapper: UserToUserDbMapper,
+    private val mapperMT: MeasureTypeToMeasureTypeDbMapper,
+    private val context: Context
+): PreloadDataRepository {
+
+//    private val imageLoader = ImageLoader.Builder(context)
+//        .componentRegistry {
+//            add(SvgDecoder(context))
+//        }
+//        .build()
+
+    override fun getPreloadData(): Flow<Result<User?>> {
+        return flow {
+            emit(Result.loading())
+            val networkResult = remoteDataSource.getMeasureTypes()
+            if (networkResult.status == Result.Status.SUCCESS) {
+                val data = networkResult.data!!
+                val measureTypes = data.map { measureType ->
+                    preloadNetworkImage(measureType.url)
+                    measureType.toMeasureType()
+                }
+                val measureTypesDb = mapperMT.map(measureTypes)
+                dao.reloadMeasureType(measureTypesDb)
+            }
+            val query = dao.getUser()
+            val userDb = query.firstOrNull()
+            if(userDb==null){
+                emit(Result.success(null))
+            }
+            else
+            {
+                emit(Result.success(mapper.reverseMap(userDb)))
+            }
+        }
+    }
+
+    private fun preloadNetworkImage(url: String){
+        val imageLoader = context.imageLoader
+        val request = ImageRequest.Builder(context)
+            .data(ENDPOINT+url)
+            .memoryCachePolicy(CachePolicy.DISABLED)
+            .build()
+        imageLoader.enqueue(request)
+    }
+
+}

+ 3 - 3
feature_splash/src/main/java/com/mrozon/feature_splash/di/SplashFragmentModule.kt

@@ -6,8 +6,8 @@ import com.mrozon.core_api.db.HealthDiaryDao
 import com.mrozon.core_api.mapper.UserToUserDbMapper
 import com.mrozon.core_api.viewmodel.ViewModelKey
 import com.mrozon.feature_splash.presentation.SplashFragmentViewModel
-import com.mrozon.feature_splash.data.LocalUserRepository
-import com.mrozon.feature_splash.data.LocalUserRepositoryImp
+import com.mrozon.feature_splash.data.PreloadDataRepository
+import com.mrozon.feature_splash.data.PreloadDataRepositoryImp
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -25,6 +25,6 @@ interface SplashFragmentModule {
     fun viewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
 
     @Binds
-    fun provideLocalUserRepository(repository: LocalUserRepositoryImp): LocalUserRepository
+    fun providePreloadDataRepository(repository: PreloadDataRepositoryImp): PreloadDataRepository
 
 }

+ 18 - 8
feature_splash/src/main/java/com/mrozon/feature_splash/presentation/SplashFragment.kt

@@ -16,6 +16,8 @@ import com.mrozon.feature_splash.R
 import com.mrozon.feature_splash.databinding.FragmentSplashBinding
 import com.mrozon.feature_splash.di.SplashFragmentComponent
 import com.mrozon.utils.base.BaseFragment
+import com.mrozon.utils.extension.visible
+import com.mrozon.utils.network.Result
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -39,14 +41,22 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>() {
     }
 
     override fun subscribeUi() {
-        viewModel.currentUser.observe(viewLifecycleOwner, Observer {
-            if(it==null){
-                //auth user
-                navigator.navigateToAuth(findNavController())
-            }
-            else
-            {
-                navigator.navigateToListPerson(findNavController())
+        viewModel.currentUser.observe(viewLifecycleOwner, Observer { event ->
+            event.peekContent().let { result ->
+                when (result.status) {
+                    Result.Status.SUCCESS -> {
+                        if(result.data==null){
+                            navigator.navigateToAuth(findNavController())
+                        }
+                        else
+                        {
+                            navigator.navigateToListPerson(findNavController())
+                        }
+                    }
+                    else -> {
+
+                    }
+                }
             }
         })
     }

+ 28 - 3
feature_splash/src/main/java/com/mrozon/feature_splash/presentation/SplashFragmentViewModel.kt

@@ -1,13 +1,38 @@
 package com.mrozon.feature_splash.presentation
 
-import com.mrozon.feature_splash.data.LocalUserRepository
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import com.mrozon.core_api.entity.Person
+import com.mrozon.core_api.entity.User
+import com.mrozon.core_api.providers.CoroutineContextProvider
+import com.mrozon.feature_splash.data.PreloadDataRepository
+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 SplashFragmentViewModel @Inject constructor(
-    localUser: LocalUserRepository
+    repository: PreloadDataRepository,
+    private val coroutineContextProvider: CoroutineContextProvider
 ): BaseViewModel() {
 
-    val currentUser = localUser.getLocalUser()
+    private val _currentUser = MutableLiveData<Event<Result<User?>>>()
+    val currentUser: LiveData<Event<Result<User?>>>
+        get() = _currentUser
+
+    init {
+        viewModelScope.launch(coroutineContextProvider.IO){
+            repository.getPreloadData().collect {
+                withContext(coroutineContextProvider.Main) {
+                    _currentUser.value = Event(it)
+                }
+            }
+        }
+    }
 
 }

+ 5 - 0
scripts/deps_versions.gradle

@@ -25,6 +25,7 @@ ext {
     androidXCoreTestVersion = "2.1.0"
     androidXTestVersion = "1.1.0"
     securityCryptoVersion = "1.1.0-alpha02"
+    coilVersion = "1.0.0"
 
     // DI
     dagger = "com.google.dagger:dagger:$daggerVersion"
@@ -75,4 +76,8 @@ ext {
     kotlinStdlib = "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     //Crytpo
     securityCrypto = "androidx.security:security-crypto:$securityCryptoVersion"
+    //Coil
+    coil = "io.coil-kt:coil:$coilVersion"
+    coilBase = "io.coil-kt:coil-base:$coilVersion"
+    coilSvg = "io.coil-kt:coil-svg:$coilVersion"
 }

+ 1 - 0
settings.gradle

@@ -1,3 +1,4 @@
+include ':feature_measure_type'
 include ':feature_person'
 include ':feature_auth'
 rootProject.name='HealthDiary'