diff --git a/core/interfaces/src/main/kotlin/app/aaps/annotations/DisplayAsDate.kt b/core/interfaces/src/main/kotlin/app/aaps/annotations/DisplayAsDate.kt new file mode 100644 index 00000000000..8a3c4b6d12f --- /dev/null +++ b/core/interfaces/src/main/kotlin/app/aaps/annotations/DisplayAsDate.kt @@ -0,0 +1,5 @@ +package app.aaps.annotations + +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class DisplayAsDate \ No newline at end of file diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/APSResult.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/APSResult.kt index 5cf6350388d..90ea73a4f1c 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/APSResult.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/APSResult.kt @@ -1,6 +1,7 @@ package app.aaps.core.interfaces.aps import android.text.Spanned +import app.aaps.annotations.DisplayAsDate import app.aaps.core.data.model.GV import app.aaps.core.interfaces.constraints.Constraint import org.json.JSONObject @@ -9,6 +10,7 @@ interface APSResult { fun with(result: RT): APSResult + @DisplayAsDate var date: Long var reason: String var rate: Double @@ -18,6 +20,7 @@ interface APSResult { var usePercent: Boolean var carbsReq: Int var carbsReqWithin: Int + @DisplayAsDate var deliverAt: Long var targetBG: Double var hasPredictions: Boolean diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/IobTotal.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/IobTotal.kt index 5d1d68eb0de..854992e1c04 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/IobTotal.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/IobTotal.kt @@ -1,10 +1,12 @@ package app.aaps.core.interfaces.aps +import app.aaps.annotations.DisplayAsDate import kotlinx.serialization.Serializable @Suppress("SpellCheckingInspection") @Serializable data class IobTotal( + @DisplayAsDate val time: Long, var iob: Double = 0.0, var activity: Double = 0.0, @@ -14,6 +16,7 @@ data class IobTotal( var hightempinsulin: Double = 0.0, // oref1 + @DisplayAsDate var lastBolusTime: Long = 0, var iobWithZeroTemp: IobTotal? = null, var netInsulin: Double = 0.0, // for calculations from temp basals only diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/MealData.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/MealData.kt index 8c757b0a86e..21c74e52641 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/MealData.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/aps/MealData.kt @@ -1,5 +1,6 @@ package app.aaps.core.interfaces.aps +import app.aaps.annotations.DisplayAsDate import kotlinx.serialization.Serializable @Serializable @@ -9,7 +10,9 @@ data class MealData( var mealCOB: Double = 0.0, var slopeFromMaxDeviation: Double = 0.0, var slopeFromMinDeviation: Double = 999.0, + @DisplayAsDate var lastBolusTime: Long = 0, + @DisplayAsDate var lastCarbTime: Long = 0L, var usedMinCarbsImpact: Double = 0.0 ) \ No newline at end of file diff --git a/plugins/aps/src/main/kotlin/app/aaps/plugins/aps/OpenAPSFragment.kt b/plugins/aps/src/main/kotlin/app/aaps/plugins/aps/OpenAPSFragment.kt index cb52633808b..b6ef161509e 100644 --- a/plugins/aps/src/main/kotlin/app/aaps/plugins/aps/OpenAPSFragment.kt +++ b/plugins/aps/src/main/kotlin/app/aaps/plugins/aps/OpenAPSFragment.kt @@ -14,6 +14,7 @@ import android.view.ViewGroup import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle +import app.aaps.annotations.DisplayAsDate import app.aaps.core.interfaces.logging.AAPSLogger import app.aaps.core.interfaces.plugin.ActivePlugin import app.aaps.core.interfaces.resources.ResourceHelper @@ -31,6 +32,7 @@ import io.reactivex.rxjava3.kotlin.plusAssign import org.apache.commons.lang3.ClassUtils import javax.inject.Inject import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.findAnnotation class OpenAPSFragment : DaggerFragment(), MenuProvider { @@ -125,13 +127,13 @@ class OpenAPSFragment : DaggerFragment(), MenuProvider { if (_binding == null) return val openAPSPlugin = activePlugin.activeAPS openAPSPlugin.lastAPSResult?.let { lastAPSResult -> - binding.result.text = lastAPSResult.rawData().dataClassToHtml() + binding.result.text = lastAPSResult.rawData().dataClassToHtmlDisplayAsDate(dateUtil) binding.request.text = lastAPSResult.resultAsSpanned() binding.glucosestatus.text = lastAPSResult.glucoseStatus?.dataClassToHtml(listOf("glucose", "delta", "shortAvgDelta", "longAvgDelta")) binding.currenttemp.text = lastAPSResult.currentTemp?.dataClassToHtml() binding.iobdata.text = rh.gs(R.string.array_of_elements, lastAPSResult.iobData?.size) + "\n" + lastAPSResult.iob?.dataClassToHtml() binding.profile.text = lastAPSResult.oapsProfile?.dataClassToHtml() ?: lastAPSResult.oapsProfileAutoIsf?.dataClassToHtml() - binding.mealdata.text = lastAPSResult.mealData?.dataClassToHtml() + binding.mealdata.text = lastAPSResult.mealData?.dataClassToHtmlDisplayAsDate(dateUtil) binding.scriptdebugdata.text = lastAPSResult.scriptDebug?.joinToString("\n") binding.constraints.text = lastAPSResult.inputConstraints?.getReasons() binding.autosensdata.text = lastAPSResult.autosensResult?.dataClassToHtml() @@ -181,6 +183,54 @@ class OpenAPSFragment : DaggerFragment(), MenuProvider { } }.toString() ) + private fun Any.dataClassToHtmlDisplayAsDate(dateUtil: DateUtil): Spanned { + return HtmlHelper.fromHtml( + StringBuilder().also { sb -> + this::class.declaredMemberProperties.forEach { property -> + property.call(this)?.let { value -> + // Check for @DisplayAsDate annotation + if (property.findAnnotation() != null && value is Long) { + if (value > 0) { // Good practice to check for valid (positive) timestamps + val formattedDate = dateUtil.dateAndTimeString(value) + sb.append(property.name.bold(), ": "/*, value.toString(), " ("*/, formattedDate/*, ")"*/, br) + } else { + // Handle invalid/default timestamps (e.g., -1 for dateCreated) + sb.append(property.name.bold(), ": ", value.toString(), " (invalid Timestamp)", br) + } + } else if (ClassUtils.isPrimitiveOrWrapper(value::class.java)) { + sb.append(property.name.bold(), ": ", value.toString(), br) + } else if (value is StringBuilder) { + sb.append(property.name.bold(), ": ", value.toString(), br) + } + } + } + }.toString() + ) + } + + private fun Any.dataClassToHtmlDisplayAsDate(properties: List, dateUtil: DateUtil): Spanned { + return HtmlHelper.fromHtml( + StringBuilder().also { sb -> + properties.forEach { propertyName -> + val property = this::class.declaredMemberProperties.firstOrNull { it.name == propertyName } + property?.call(this)?.let { value -> + if (property.findAnnotation() != null && value is Long) { + if (value > 0) { // Good practice for timestamps + val formattedDate = dateUtil.dateAndTimeString(value) + sb.append(property.name.bold(), ": "/*, value.toString(), " ("*/, formattedDate/*, ")"*/, br) + } else { + sb.append(propertyName.bold(), ": ", value.toString(), " (invalid Timestamp)", br) + } + } else if (ClassUtils.isPrimitiveOrWrapper(value::class.java)) { + sb.append(propertyName.bold(), ": ", value.toString(), br) + } else if (value is StringBuilder) { + sb.append(propertyName.bold(), ": ", value.toString(), br) + } + } + } + }.toString() + ) + } private fun String.bold(): String = "$this" private val br = "
"