فهرست منبع

add adapter for show list measures
bind element into layout for item

MrOzOn 5 سال پیش
والد
کامیت
81523f67c8

+ 3 - 0
core_api/src/main/java/com/mrozon/core_api/db/model/MeasureDb.kt

@@ -3,9 +3,12 @@ package com.mrozon.core_api.db.model
 import androidx.room.ColumnInfo
 import androidx.room.Entity
 import androidx.room.PrimaryKey
+import androidx.room.TypeConverters
+import com.mrozon.core_api.db.BlobTransmogrifier
 import java.util.*
 
 @Entity(tableName = "measure_table")
+@TypeConverters(BlobTransmogrifier::class)
 data class MeasureDb (
     @PrimaryKey(autoGenerate = true)
     @ColumnInfo(name = "measure_id")

+ 5 - 1
core_api/src/main/java/com/mrozon/core_api/network/model/MeasureResponse.kt

@@ -1,5 +1,6 @@
 package com.mrozon.core_api.network.model
 
+import com.google.gson.annotations.SerializedName
 import com.mrozon.core_api.entity.Measure
 import com.mrozon.utils.extension.toSimpleDate
 
@@ -10,9 +11,12 @@ data class MeasureResponse(
 	val patient: Int,
 	val observing: Int,
 	val id: Int,
+	@SerializedName("created_date")
 	val createdDate: String,
+	@SerializedName("value_added")
 	val valueAdded: String,
 	val type: Int,
+	@SerializedName("last_modified")
 	val lastModified: String
 )
 
@@ -21,7 +25,7 @@ fun MeasureResponse.toMeasure(): Measure {
 		id = id.toLong(),
 		value1 = value1,
 		value2 = value2?:"",
-		valueAdded = valueAdded.toSimpleDate(format = "yyyy-MM-dd'T'HH:mm:ss.SSXXX"), //2020-07-23T17:39:02+03:00
+		valueAdded = valueAdded.toSimpleDate(format = "yyyy-MM-dd'T'HH:mm:ss"), //2020-07-23T17:39:02+03:00
 		comment = comments?:"",
 		personId = patient.toLong(),
 		measureTypeId = type.toLong()

+ 9 - 0
core_impl/build.gradle

@@ -13,6 +13,15 @@ android {
         versionCode 1
         versionName "1.0"
 
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments += [
+                        "room.schemaLocation":"$projectDir/schemas".toString(),
+                        "room.incremental":"true",
+                        "room.expandProjection":"true"]
+            }
+        }
+
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles 'consumer-rules.pro'
     }

+ 202 - 0
core_impl/schemas/com.mrozon.core_impl.db.HealthDiaryDb/1.json

@@ -0,0 +1,202 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "2079a1d49cc2a92c04780e48139987df",
+    "entities": [
+      {
+        "tableName": "user_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `user_email` TEXT NOT NULL, `user_firstname` TEXT NOT NULL, `user_token` TEXT NOT NULL, `user_lastname` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "user_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "email",
+            "columnName": "user_email",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "firstname",
+            "columnName": "user_firstname",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "token",
+            "columnName": "user_token",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lastname",
+            "columnName": "user_lastname",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "user_id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "person_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`person_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `person_name` TEXT NOT NULL, `person_gender` INTEGER NOT NULL, `user_born` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "person_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "person_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gender",
+            "columnName": "person_gender",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "born",
+            "columnName": "user_born",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "person_id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "measure_type_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`measure_type_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `measure_type_name` TEXT NOT NULL, `measure_type_mark` TEXT NOT NULL, `measure_type_regexp` TEXT NOT NULL, `measure_type_hint` TEXT NOT NULL, `measure_type_url` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "measure_type_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "measure_type_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "mark",
+            "columnName": "measure_type_mark",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "regexp",
+            "columnName": "measure_type_regexp",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hint",
+            "columnName": "measure_type_hint",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "measure_type_url",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "measure_type_id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "measure_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`measure_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `measure_value1` TEXT NOT NULL, `measure_value2` TEXT NOT NULL, `measure_value_added` INTEGER NOT NULL, `measure_comment` TEXT NOT NULL, `measure_person` INTEGER NOT NULL, `measure_mtype` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "measure_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value1",
+            "columnName": "measure_value1",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value2",
+            "columnName": "measure_value2",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "added",
+            "columnName": "measure_value_added",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "comment",
+            "columnName": "measure_comment",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "personID",
+            "columnName": "measure_person",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "measureTypeId",
+            "columnName": "measure_mtype",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "measure_id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2079a1d49cc2a92c04780e48139987df')"
+    ]
+  }
+}

+ 43 - 0
feature_measure/src/main/java/com/mrozon/feature_measure/presentation/BindingUtils.kt

@@ -0,0 +1,43 @@
+package com.mrozon.feature_measure.presentation
+
+import android.annotation.SuppressLint
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.databinding.BindingAdapter
+import com.mrozon.core_api.entity.Measure
+import com.mrozon.core_api.entity.MeasureType
+import com.mrozon.feature_measure.R
+import com.mrozon.utils.extension.toDateString
+import java.util.*
+
+@BindingAdapter("time_of_day")
+fun ImageView.setTimeOfDay(measure: Measure) {
+    val date = measure.valueAdded
+    val cal = Calendar.getInstance()
+    cal.time = date
+    setImageResource(when (cal.get(Calendar.HOUR_OF_DAY)) {
+        in 0..5 -> R.drawable.ic_night
+        in 6..10 -> R.drawable.ic_morning
+        in 11..18 -> R.drawable.ic_day
+        in 19..22 -> R.drawable.ic_evening
+        in 23..24 -> R.drawable.ic_night
+        else -> R.drawable.ic_day
+    })
+}
+
+@BindingAdapter("added_date")
+fun TextView.setDateAdded(measure: Measure) {
+    text = measure.valueAdded.toDateString("EEE, d MMM HH:mm")
+}
+
+@SuppressLint("SetTextI18n")
+@BindingAdapter(value = ["measure","measure_type"])
+fun TextView.setMeasureValue(measure: Measure, measureType: MeasureType) {
+    text = if (measure.value2.isEmpty()) {
+        "${measure.value1} ${measureType.mark}"
+    }
+    else {
+        "${measure.value1}/${measure.value2} ${measureType.mark}"
+    }
+
+}

+ 67 - 0
feature_measure/src/main/java/com/mrozon/feature_measure/presentation/ListMeasureAdapter.kt

@@ -0,0 +1,67 @@
+package com.mrozon.feature_measure.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.Measure
+import com.mrozon.core_api.entity.MeasureType
+import com.mrozon.core_api.entity.Person
+import com.mrozon.feature_measure.databinding.ItemMeasureBinding
+
+class ListMeasureAdapter(private val measureType: MeasureType, private val clickListener: ListMeasureClickListener): ListAdapter<Measure, ListMeasureAdapter.ViewHolder>(ListMeasureDiffCallback()) {
+
+    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, measureType, clickListener)
+    }
+
+    class ViewHolder private constructor(val binding: ItemMeasureBinding ): RecyclerView.ViewHolder(binding.root){
+        fun bind(
+            item: Measure,
+            measureType: MeasureType,
+            clickListener: ListMeasureClickListener) {
+            binding.measure = item
+            binding.measureType = measureType
+            binding.layoutMeasure.setOnClickListener {
+                clickListener.onClick(item)
+            }
+            binding.layoutMeasure.setOnLongClickListener{
+                clickListener.onLongClick(item)
+                true
+            }
+            binding.executePendingBindings()
+        }
+
+        companion object {
+            fun from(parent: ViewGroup): ViewHolder {
+                val layoutInflater = LayoutInflater.from(parent.context)
+                val binding =
+                    ItemMeasureBinding.inflate(layoutInflater, parent, false)
+                return ViewHolder(binding)
+            }
+        }
+    }
+
+    class ListMeasureDiffCallback : DiffUtil.ItemCallback<Measure>() {
+
+        override fun areItemsTheSame(oldItem: Measure, newItem: Measure): Boolean {
+            return oldItem.id == newItem.id
+        }
+
+        override fun areContentsTheSame(oldItem: Measure, newItem: Measure): Boolean {
+            return oldItem == newItem
+        }
+    }
+
+    interface ListMeasureClickListener {
+        fun onClick(measure: Measure)
+        fun onLongClick(measure: Measure)
+    }
+}

+ 49 - 1
feature_measure/src/main/java/com/mrozon/feature_measure/presentation/ListMeasureFragment.kt

@@ -2,12 +2,20 @@ package com.mrozon.feature_measure.presentation
 
 import android.content.Context
 import android.os.Bundle
+import android.view.View
+import android.widget.Toast
 import androidx.fragment.app.viewModels
+import androidx.lifecycle.Observer
 import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.mrozon.core_api.entity.Measure
 import com.mrozon.feature_measure.R
 import com.mrozon.feature_measure.databinding.FragmentListMeasureBinding
 import com.mrozon.feature_measure.di.TabMeasureFragmentComponent
 import com.mrozon.utils.base.BaseFragment
+import com.mrozon.utils.extension.setTitleActionBar
+import com.mrozon.utils.extension.visible
+import com.mrozon.utils.network.Result
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -27,12 +35,18 @@ class ListMeasureFragment : BaseFragment<FragmentListMeasureBinding>() {
         }
     }
 
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        val manager = LinearLayoutManager(context)
+        binding?.rvMeasure?.layoutManager = manager
+    }
+
     override fun onResume() {
         super.onResume()
         arguments?.let {
             val personId = requireArguments().getLong(ARG_PERSON_ID, -1)
             val measureTypeId = requireArguments().getLong(ARG_MEASURE_TYPE_ID, -1)
-            show("personId=$personId, measureTypeId=$measureTypeId")
+            viewModel.initialLoadData(personId, measureTypeId)
         }
     }
 
@@ -49,8 +63,42 @@ class ListMeasureFragment : BaseFragment<FragmentListMeasureBinding>() {
         Timber.d("onAttach")
     }
 
+
+
     override fun subscribeUi() {
+        viewModel.initialData.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)
+                        val person = result.data?.first
+                        setTitleActionBar(person?.name?:"")
+                        val measureType = result.data?.second
+                        val measures = result.data?.third
+                        Timber.d("measures contains ${measures?.size} items")
+                        val adapter = ListMeasureAdapter(measureType!!, object:
+                            ListMeasureAdapter.ListMeasureClickListener {
+                            override fun onClick(measure: Measure) {
 
+                            }
+
+                            override fun onLongClick(measure: Measure) {
+
+                            }
+                        })
+                        binding?.rvMeasure?.adapter = adapter
+                        adapter.submitList(measures)
+                    }
+                    Result.Status.ERROR -> {
+                        binding?.progressBar?.visible(false)
+                        showError(result.message!!)
+                    }
+                }
+            }
+        })
     }
 
 }

+ 19 - 0
feature_measure/src/main/java/com/mrozon/feature_measure/presentation/ListMeasureFragmentViewModel.kt

@@ -3,6 +3,8 @@ package com.mrozon.feature_measure.presentation
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.viewModelScope
+import com.mrozon.core_api.entity.MeasureHistory
+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.data.MeasureRepository
@@ -12,6 +14,7 @@ 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 ListMeasureFragmentViewModel @Inject constructor(
@@ -19,5 +22,21 @@ class ListMeasureFragmentViewModel @Inject constructor(
     private val coroutineContextProvider: CoroutineContextProvider
 ): BaseViewModel() {
 
+    private var _initialData = MutableLiveData<Event<Result<MeasureHistory>>>()
+    val initialData: LiveData<Event<Result<MeasureHistory>>>
+        get() = _initialData
+
+    fun initialLoadData(personId: Long, measureTypeId: Long) {
+        if(_initialData.value == null) {
+            viewModelScope.launch(coroutineContextProvider.IO) {
+                repository.loadMeasure(personId, measureTypeId).collect {
+                    withContext(coroutineContextProvider.Main) {
+                        _initialData.value = Event(it)
+                    }
+                }
+            }
+        }
+    }
+
 
 }

+ 19 - 10
feature_measure/src/main/res/layout/item_measure.xml

@@ -4,7 +4,12 @@
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <data>
-
+        <variable
+            name="measure"
+            type="com.mrozon.core_api.entity.Measure" />
+        <variable
+            name="measureType"
+            type="com.mrozon.core_api.entity.MeasureType" />
     </data>
 
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -24,6 +29,7 @@
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent"
+            app:time_of_day="@{measure}"
             app:srcCompat="@drawable/ic_day" />
 
 
@@ -34,19 +40,21 @@
             android:layout_marginStart="8dp"
             android:layout_marginTop="8dp"
             android:contentDescription="@string/ivMeasureComment"
-            app:layout_constraintBottom_toTopOf="@+id/indicator_value"
+            app:layout_constraintBottom_toTopOf="@+id/tvMeasureValue"
             app:layout_constraintStart_toEndOf="@+id/ivTimeOfDay"
             app:layout_constraintTop_toTopOf="parent"
             app:srcCompat="@drawable/ic_edit" />
 
         <TextView
-            android:id="@+id/indicator_value"
+            android:id="@+id/tvMeasureValue"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_centerVertical="true"
             android:layout_marginEnd="8dp"
             android:gravity="center"
-            android:text="120/70 мм рт.ст."
+            tools:text="120/70 мм рт.ст."
+            app:measure="@{measure}"
+            app:measure_type="@{measureType}"
             android:textAlignment="center"
             android:textColor="#000000"
             android:textSize="24sp"
@@ -57,7 +65,7 @@
             tools:ignore="UnknownId" />
 
         <TextView
-            android:id="@+id/indicator_dt"
+            android:id="@+id/tvMeasureDate"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_centerVertical="true"
@@ -65,25 +73,26 @@
             android:layout_marginRight="16dp"
             android:layout_marginBottom="2dp"
             android:gravity="center"
-            android:text="27 мар. 17:40"
+            app:added_date="@{measure}"
             android:textAlignment="center"
             android:textColor="#99000000"
             android:textStyle="bold"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/indicator_value"
+            app:layout_constraintTop_toBottomOf="@+id/tvMeasureValue"
             tools:text="27 мар. 17:40" />
 
         <TextView
-            android:id="@+id/indicator_comment_text"
+            android:id="@+id/tvMeasureComment"
             android:layout_width="0dp"
             android:layout_height="20dp"
             android:layout_marginStart="8dp"
             android:layout_marginLeft="8dp"
             android:layout_marginEnd="8dp"
             android:layout_marginRight="8dp"
-            android:text="TextView"
-            app:layout_constraintBottom_toTopOf="@+id/indicator_value"
+            android:text="@{measure.comment}"
+            tools:text="Это комментарий"
+            app:layout_constraintBottom_toTopOf="@+id/tvMeasureValue"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toEndOf="@+id/ivMeasureComment" />