TimeListEdit -> kt

This commit is contained in:
Milos Kozak 2023-01-08 12:11:13 +01:00
parent 3bc62e9a01
commit 971bf87cac
8 changed files with 482 additions and 526 deletions

View file

@ -1,34 +1,31 @@
package info.nightscout.interfaces.profile
import androidx.fragment.app.FragmentActivity
import info.nightscout.interfaces.Constants
import org.json.JSONArray
interface ProfileSource {
class SingleProfile {
var name: String? = null
var mgdl: Boolean = false
var dia: Double = Constants.defaultDIA
var ic: JSONArray? = null
var isf: JSONArray? = null
var basal: JSONArray? = null
var targetLow: JSONArray? = null
var targetHigh: JSONArray? = null
fun deepClone(): SingleProfile {
val sp = SingleProfile()
sp.name = name
sp.mgdl = mgdl
sp.dia = dia
sp.ic = JSONArray(ic.toString())
sp.isf = JSONArray(isf.toString())
sp.basal = JSONArray(basal.toString())
sp.targetLow = JSONArray(targetLow.toString())
sp.targetHigh = JSONArray(targetHigh.toString())
return sp
}
class SingleProfile(
var name: String,
var mgdl: Boolean,
var dia: Double,
var ic: JSONArray,
var isf: JSONArray,
var basal: JSONArray,
var targetLow: JSONArray,
var targetHigh: JSONArray,
) {
fun deepClone(): SingleProfile =
SingleProfile(
name = name,
mgdl = mgdl,
dia = dia,
ic = JSONArray(ic.toString()),
isf = JSONArray(isf.toString()),
basal = JSONArray(basal.toString()),
targetLow = JSONArray(targetLow.toString()),
targetHigh = JSONArray(targetHigh.toString())
)
}
val profile: ProfileStore?

View file

@ -33,8 +33,8 @@ import info.nightscout.interfaces.queue.CommandQueue
import info.nightscout.interfaces.ui.UiInteraction
import info.nightscout.plugins.R
import info.nightscout.plugins.databinding.ActionsFragmentBinding
import info.nightscout.plugins.general.overview.ui.StatusLightHandler
import info.nightscout.plugins.skins.SkinProvider
import info.nightscout.plugins.ui.StatusLightHandler
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventCustomActionsChanged

View file

@ -74,8 +74,8 @@ import info.nightscout.plugins.databinding.OverviewFragmentBinding
import info.nightscout.plugins.general.overview.graphData.GraphData
import info.nightscout.plugins.general.overview.notifications.NotificationStore
import info.nightscout.plugins.general.overview.notifications.events.EventUpdateOverviewNotification
import info.nightscout.plugins.general.overview.ui.StatusLightHandler
import info.nightscout.plugins.skins.SkinProvider
import info.nightscout.plugins.ui.StatusLightHandler
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAcceptOpenLoopChange

View file

@ -1,4 +1,4 @@
package info.nightscout.plugins.ui
package info.nightscout.plugins.general.overview.ui
import android.annotation.SuppressLint
import android.os.Handler

View file

@ -27,7 +27,7 @@ import info.nightscout.interfaces.utils.DecimalFormatter
import info.nightscout.interfaces.utils.HardLimits
import info.nightscout.plugins.R
import info.nightscout.plugins.databinding.ProfileFragmentBinding
import info.nightscout.plugins.ui.TimeListEdit
import info.nightscout.plugins.profile.ui.TimeListEdit
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventLocalProfileChanged
@ -139,10 +139,10 @@ class ProfileFragment : DaggerFragment() {
binding.dia.setParams(currentProfile.dia, hardLimits.minDia(), hardLimits.maxDia(), 0.1, DecimalFormat("0.0"), false, null, textWatch)
binding.dia.tag = "LP_DIA"
TimeListEdit(
context,
requireContext(),
aapsLogger,
dateUtil,
view,
requireView(),
R.id.ic_holder,
"IC",
rh.gs(info.nightscout.core.ui.R.string.ic_long_label),
@ -156,10 +156,10 @@ class ProfileFragment : DaggerFragment() {
)
basalView =
TimeListEdit(
context,
requireContext(),
aapsLogger,
dateUtil,
view,
requireView(),
R.id.basal_holder,
"BASAL",
rh.gs(info.nightscout.core.ui.R.string.basal_long_label) + ": " + sumLabel(),
@ -173,12 +173,27 @@ class ProfileFragment : DaggerFragment() {
)
if (units == Constants.MGDL) {
val isfRange = doubleArrayOf(HardLimits.MIN_ISF, HardLimits.MAX_ISF)
TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf_holder, "ISF", rh.gs(info.nightscout.core.ui.R.string.isf_long_label), currentProfile.isf, null, isfRange, null, 1.0, DecimalFormat("0"), save)
TimeListEdit(
context,
requireContext(),
aapsLogger,
dateUtil,
view,
requireView(),
R.id.isf_holder,
"ISF",
rh.gs(info.nightscout.core.ui.R.string.isf_long_label),
currentProfile.isf,
null,
isfRange,
null,
1.0,
DecimalFormat("0"),
save
)
TimeListEdit(
requireContext(),
aapsLogger,
dateUtil,
requireView(),
R.id.target_holder,
"TARGET",
rh.gs(info.nightscout.core.ui.R.string.target_long_label),
@ -195,7 +210,9 @@ class ProfileFragment : DaggerFragment() {
roundUp(Profile.fromMgdlToUnits(HardLimits.MIN_ISF, GlucoseUnit.MMOL)),
roundDown(Profile.fromMgdlToUnits(HardLimits.MAX_ISF, GlucoseUnit.MMOL))
)
TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf_holder, "ISF", rh.gs(info.nightscout.core.ui.R.string.isf_long_label), currentProfile.isf, null, isfRange, null, 0.1, DecimalFormat("0.0"), save)
TimeListEdit(requireContext(), aapsLogger, dateUtil, requireView(), R.id.isf_holder, "ISF", rh.gs(info.nightscout.core.ui.R.string.isf_long_label), currentProfile.isf, null, isfRange, null, 0.1,
DecimalFormat
("0.0"), save)
val range1 = doubleArrayOf(
roundUp(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MIN_BG[0], GlucoseUnit.MMOL)),
roundDown(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MIN_BG[1], GlucoseUnit.MMOL))
@ -206,10 +223,10 @@ class ProfileFragment : DaggerFragment() {
)
aapsLogger.info(LTag.CORE, "TimeListEdit", "build: range1" + range1[0] + " " + range1[1] + " range2" + range2[0] + " " + range2[1])
TimeListEdit(
context,
requireContext(),
aapsLogger,
dateUtil,
view,
requireView(),
R.id.target_holder,
"TARGET",
rh.gs(info.nightscout.core.ui.R.string.target_long_label),

View file

@ -99,7 +99,7 @@ class ProfilePlugin @Inject constructor(
ToastUtils.errorToast(activity, rh.gs(info.nightscout.core.ui.R.string.value_out_of_hard_limits, rh.gs(info.nightscout.core.ui.R.string.profile_dia), dia))
return false
}
if (name.isNullOrEmpty()) {
if (name.isEmpty()) {
ToastUtils.errorToast(activity, rh.gs(R.string.missing_profile_name))
return false
}
@ -177,7 +177,6 @@ class ProfilePlugin @Inject constructor(
override fun storeSettings(activity: FragmentActivity?) {
for (i in 0 until numOfProfiles) {
profiles[i].run {
name?.let { name ->
val localProfileNumbered = Constants.LOCAL_PROFILE + "_" + i + "_"
sp.putString(localProfileNumbered + "name", name)
sp.putBoolean(localProfileNumbered + "mgdl", mgdl)
@ -187,7 +186,6 @@ class ProfilePlugin @Inject constructor(
sp.putString(localProfileNumbered + "basal", basal.toString())
sp.putString(localProfileNumbered + "targetlow", targetLow.toString())
sp.putString(localProfileNumbered + "targethigh", targetHigh.toString())
}
}
}
sp.putInt(Constants.LOCAL_PROFILE + "_profiles", numOfProfiles)
@ -198,10 +196,7 @@ class ProfilePlugin @Inject constructor(
aapsLogger.debug(LTag.PROFILE, "Storing settings: " + rawProfile?.data.toString())
rxBus.send(EventProfileStoreChanged())
var namesOK = true
profiles.forEach {
val name = it.name ?: "."
if (name.contains(".")) namesOK = false
}
profiles.forEach { if (it.name.contains(".")) namesOK = false }
if (!namesOK) activity?.let {
OKDialog.show(it, "", rh.gs(R.string.profile_name_contains_dot))
}
@ -214,20 +209,22 @@ class ProfilePlugin @Inject constructor(
// numOfProfiles = max(numOfProfiles, 1) // create at least one default profile if none exists
for (i in 0 until numOfProfiles) {
val p = ProfileSource.SingleProfile()
val localProfileNumbered = Constants.LOCAL_PROFILE + "_" + i + "_"
p.name = sp.getString(localProfileNumbered + "name", Constants.LOCAL_PROFILE + i)
if (isExistingName(p.name)) continue
p.mgdl = sp.getBoolean(localProfileNumbered + "mgdl", false)
p.dia = sp.getDouble(localProfileNumbered + "dia", Constants.defaultDIA)
val name = sp.getString(localProfileNumbered + "name", Constants.LOCAL_PROFILE + i)
if (isExistingName(name)) continue
try {
p.ic = JSONArray(sp.getString(localProfileNumbered + "ic", defaultArray))
p.isf = JSONArray(sp.getString(localProfileNumbered + "isf", defaultArray))
p.basal = JSONArray(sp.getString(localProfileNumbered + "basal", defaultArray))
p.targetLow = JSONArray(sp.getString(localProfileNumbered + "targetlow", defaultArray))
p.targetHigh = JSONArray(sp.getString(localProfileNumbered + "targethigh", defaultArray))
profiles.add(p)
profiles.add(
ProfileSource.SingleProfile(
name = name,
mgdl = sp.getBoolean(localProfileNumbered + "mgdl", false),
dia = sp.getDouble(localProfileNumbered + "dia", Constants.defaultDIA),
ic = JSONArray(sp.getString(localProfileNumbered + "ic", defaultArray)),
isf = JSONArray(sp.getString(localProfileNumbered + "isf", defaultArray)),
basal = JSONArray(sp.getString(localProfileNumbered + "basal", defaultArray)),
targetLow = JSONArray(sp.getString(localProfileNumbered + "targetlow", defaultArray)),
targetHigh = JSONArray(sp.getString(localProfileNumbered + "targethigh", defaultArray))
)
)
} catch (e: JSONException) {
aapsLogger.error("Exception", e)
}
@ -279,16 +276,16 @@ class ProfilePlugin @Inject constructor(
}
val profile = ProfileSealed.Pure(pureProfile)
val pureJson = pureProfile.jsonObject
val sp = ProfileSource.SingleProfile()
sp.name = verifiedName
sp.mgdl = profile.units == GlucoseUnit.MGDL
sp.dia = pureJson.getDouble("dia")
sp.ic = pureJson.getJSONArray("carbratio")
sp.isf = pureJson.getJSONArray("sens")
sp.basal = pureJson.getJSONArray("basal")
sp.targetLow = pureJson.getJSONArray("target_low")
sp.targetHigh = pureJson.getJSONArray("target_high")
return sp
return ProfileSource.SingleProfile(
name = verifiedName,
mgdl = profile.units == GlucoseUnit.MGDL,
dia = pureJson.getDouble("dia"),
ic = pureJson.getJSONArray("carbratio"),
isf = pureJson.getJSONArray("sens"),
basal = pureJson.getJSONArray("basal"),
targetLow = pureJson.getJSONArray("target_low"),
targetHigh = pureJson.getJSONArray("target_high")
)
}
private fun isExistingName(name: String?): Boolean {
@ -348,16 +345,18 @@ class ProfilePlugin @Inject constructor(
break
}
}
val p = ProfileSource.SingleProfile()
p.name = Constants.LOCAL_PROFILE + free
p.mgdl = profileFunction.getUnits() == GlucoseUnit.MGDL
p.dia = Constants.defaultDIA
p.ic = JSONArray(defaultArray)
p.isf = JSONArray(defaultArray)
p.basal = JSONArray(defaultArray)
p.targetLow = JSONArray(defaultArray)
p.targetHigh = JSONArray(defaultArray)
profiles.add(p)
profiles.add(
ProfileSource.SingleProfile(
name = Constants.LOCAL_PROFILE + free,
mgdl = profileFunction.getUnits() == GlucoseUnit.MGDL,
dia = Constants.defaultDIA,
ic = JSONArray(defaultArray),
isf = JSONArray(defaultArray),
basal = JSONArray(defaultArray),
targetLow = JSONArray(defaultArray),
targetHigh = JSONArray(defaultArray)
)
)
currentProfileIndex = profiles.size - 1
createAndStoreConvertedProfile()
storeSettings()
@ -397,18 +396,16 @@ class ProfilePlugin @Inject constructor(
try {
for (i in 0 until numOfProfiles) {
profiles[i].run {
name?.let { name ->
val profile = JSONObject()
profile.put("dia", dia)
profile.put("carbratio", ic)
profile.put("sens", isf)
profile.put("basal", basal)
profile.put("target_low", targetLow)
profile.put("target_high", targetHigh)
profile.put("units", if (mgdl) Constants.MGDL else Constants.MMOL)
profile.put("timezone", TimeZone.getDefault().id)
store.put(name, profile)
}
val profile = JSONObject()
profile.put("dia", dia)
profile.put("carbratio", ic)
profile.put("sens", isf)
profile.put("basal", basal)
profile.put("target_low", targetLow)
profile.put("target_high", targetHigh)
profile.put("units", if (mgdl) Constants.MGDL else Constants.MMOL)
profile.put("timezone", TimeZone.getDefault().id)
store.put(name, profile)
}
}
if (numOfProfiles > 0) json.put("defaultProfile", currentProfile()?.name)

View file

@ -0,0 +1,382 @@
package info.nightscout.plugins.profile.ui
import android.content.Context
import android.text.Editable
import android.text.TextWatcher
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.widget.TextViewCompat
import info.nightscout.core.ui.elements.NumberPicker
import info.nightscout.core.ui.elements.SpinnerHelper
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.shared.SafeParse.stringToDouble
import info.nightscout.shared.utils.DateUtil
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.text.DecimalFormat
import java.text.NumberFormat
class TimeListEdit(
private val context: Context,
private val aapsLogger: AAPSLogger,
private val dateUtil: DateUtil,
private val view: View,
private val resLayoutId: Int,
private val tagPrefix: String,
private var label: String,
private val data1: JSONArray,
private val data2: JSONArray?,
range1: DoubleArray,
range2: DoubleArray?,
private val step: Double,
formatter: NumberFormat,
save: Runnable?
) {
private val intervals = arrayOfNulls<View>(24)
private val spinners = arrayOfNulls<SpinnerHelper>(24)
private val numberPickers1 = arrayOfNulls<NumberPicker>(24)
private val numberPickers2 = arrayOfNulls<NumberPicker>(24)
private val addButtons = arrayOfNulls<ImageView>(24)
private val removeButtons = arrayOfNulls<ImageView>(24)
private var finalAdd: ImageView? = null
private val min: Double
private val max: Double
private val min2: Double
private val max2: Double
private val formatter: NumberFormat
private val save: Runnable?
private var layout: LinearLayout? = null
private var textLabel: TextView? = null
private var inflatedUntil = -1
init {
min = range1[0]
max = range1[1]
min2 = range2?.get(0) ?: 0.0
max2 = range2?.get(1) ?: 0.0
this.formatter = formatter
this.save = save
buildView()
}
private fun buildView() {
val layout = view.findViewById<LinearLayout>(resLayoutId).also {
this.layout = it
it.removeAllViewsInLayout()
} ?: return
TextView(context).also {
this.textLabel = it
it.text = label
it.gravity = Gravity.CENTER
it.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).also { llp ->
llp.setMargins(0, 5, 0, 5)
}
TextViewCompat.setTextAppearance(it, android.R.style.TextAppearance_Medium)
layout.addView(it)
}
var i = 0
while (i < 24 && i < itemsCount()) {
inflateRow(i)
inflatedUntil = i
i++
}
// last "plus" to append new interval
val factor = layout.context.resources.displayMetrics.density
ImageView(context).also {
this.finalAdd = it
it.setImageResource(info.nightscout.core.main.R.drawable.ic_add)
it.contentDescription = layout.context.resources.getString(info.nightscout.plugins.R.string.a11y_add_new_to_list)
layout.addView(it)
it.layoutParams = LinearLayout.LayoutParams((35.0 * factor).toInt(), (35 * factor).toInt()).also { llp ->
llp.setMargins(0, 25, 0, 25) // llp.setMargins(left, top, right, bottom);
llp.gravity = Gravity.CENTER
}
it.setOnClickListener {
addItem(itemsCount(), if (itemsCount() > 0) secondFromMidnight(itemsCount() - 1) + ONE_HOUR_IN_SECONDS else 0)
callSave()
log()
fillView()
}
}
fillView()
}
private fun inflateRow(position: Int) {
val resource =
if (data2 == null) info.nightscout.plugins.R.layout.timelistedit_element
else info.nightscout.plugins.R.layout.timelistedit_element_vertical
val childView = LayoutInflater.from(context).inflate(resource, layout, false).also {
intervals[position] = it
layout?.addView(it)
}
childView.findViewById<ImageView>(info.nightscout.plugins.R.id.timelistedit_add).also {
addButtons[position] = it
it.setOnClickListener {
val seconds = secondFromMidnight(position)
addItem(position, seconds)
// for here for the rest of values
for (i in position + 1 until itemsCount()) {
if (secondFromMidnight(i - 1) >= secondFromMidnight(i)) {
editItem(i, secondFromMidnight(i - 1) + ONE_HOUR_IN_SECONDS, value1(i), value2(i))
}
}
while (itemsCount() > 24 || secondFromMidnight(itemsCount() - 1) > 23 * ONE_HOUR_IN_SECONDS) removeItem(itemsCount() - 1)
callSave()
log()
fillView()
}
}
childView.findViewById<ImageView>(info.nightscout.plugins.R.id.timelistedit_remove).also {
removeButtons[position] = it
it.setOnClickListener {
removeItem(position)
callSave()
log()
fillView()
}
}
SpinnerHelper(childView.findViewById(info.nightscout.plugins.R.id.timelistedit_time)).also {
spinners[position] = it
it.setOnItemSelectedListener(
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View, selected: Int, id: Long) {
val seconds = (it.adapter as SpinnerAdapter).valueForPosition(selected)
editItem(position, seconds, value1(position), value2(position))
log()
callSave()
fillView()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
)
}
childView.findViewById<NumberPicker>(info.nightscout.plugins.R.id.timelistedit_edit1).also {
numberPickers1[position] = it
it.setTextWatcher(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val value1 = stringToDouble(it.text, 0.0)
var value2 = value2(position)
if (data2 != null && value1 > value2) {
value2 = value1
numberPickers2[position]?.value = value2
}
editItem(position, secondFromMidnight(position), value1, value2)
callSave()
log()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
})
it.tag = "$tagPrefix-1-$position"
}
childView.findViewById<NumberPicker>(info.nightscout.plugins.R.id.timelistedit_edit2).also {
numberPickers2[position] = it
it.setTextWatcher(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
var value1 = value1(position)
val value2 = stringToDouble(it.text, 0.0)
if (data2 != null && value2 < value1) {
value1 = value2
numberPickers1[position]?.value = value1
}
editItem(position, secondFromMidnight(position), value1, value2)
callSave()
log()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
})
it.tag = "$tagPrefix-2-$position"
}
}
private fun fillView() {
for (i in 0..23) {
if (i < itemsCount()) {
intervals[i]?.visibility = View.VISIBLE
buildInterval(i)
} else if (i <= inflatedUntil) {
intervals[i]?.visibility = View.GONE
}
}
finalAdd?.visibility =
if (!(itemsCount() > 0 && secondFromMidnight(itemsCount() - 1) == 23 * ONE_HOUR_IN_SECONDS)) View.VISIBLE
else View.GONE
}
private fun buildInterval(i: Int) {
val timeSpinner = spinners[i] ?: return
val editText1 = numberPickers1[i] ?: return
val editText2 = numberPickers2[i] ?: return
val previous = if (i == 0) -1 * ONE_HOUR_IN_SECONDS else secondFromMidnight(i - 1)
var next = if (i == itemsCount() - 1) 24 * ONE_HOUR_IN_SECONDS else secondFromMidnight(i + 1)
if (i == 0) next = ONE_HOUR_IN_SECONDS
fillSpinner(timeSpinner, secondFromMidnight(i), previous, next)
editText1.setParams(value1(i), min, max, step, formatter, false, null)
editText2.setParams(value2(i), min2, max2, step, formatter, false, null)
if (data2 == null) {
editText2.visibility = View.GONE
}
removeButtons[i]?.visibility =
if (itemsCount() == 1 || i == 0) View.INVISIBLE
else View.VISIBLE
addButtons[i]?.visibility =
if (itemsCount() >= 24 || secondFromMidnight(i) >= 82800) View.INVISIBLE
else View.VISIBLE
}
internal class SpinnerAdapter(context: Context, resource: Int, objects: List<CharSequence>, var values: List<Int>) : ArrayAdapter<CharSequence?>(context, resource, objects) {
fun valueForPosition(position: Int): Int = values[position]
}
private fun fillSpinner(spinner: SpinnerHelper, secondsFromMidnight: Int, previous: Int, next: Int) {
var posInList = 0
val timeList = ArrayList<CharSequence>()
val timeListValues = ArrayList<Int>()
var pos = 0
var t = previous + ONE_HOUR_IN_SECONDS
while (t < next) {
timeList.add(dateUtil.timeStringFromSeconds(t))
timeListValues.add(t)
if (secondsFromMidnight == t) posInList = pos
pos++
t += ONE_HOUR_IN_SECONDS
}
val adapter = SpinnerAdapter(
context,
info.nightscout.core.ui.R.layout.spinner_centered, timeList, timeListValues
)
spinner.adapter = adapter
spinner.setSelection(posInList, false)
adapter.notifyDataSetChanged()
}
private fun itemsCount(): Int {
return data1.length()
}
private fun secondFromMidnight(index: Int): Int {
try {
val item = data1[index] as JSONObject
if (item.has("timeAsSeconds")) {
var time = item.getInt("timeAsSeconds")
if (index == 0 && time != 0) {
// fix the bug, every array must start with 0
item.put("timeAsSeconds", 0)
time = 0
}
return time
}
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
return 0
}
private fun value1(index: Int): Double {
try {
val item = data1[index] as JSONObject
if (item.has("value")) {
return item.getDouble("value")
}
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
return 0.0
}
private fun value2(index: Int): Double {
if (data2 != null) {
try {
val item = data2[index] as JSONObject
if (item.has("value")) {
return item.getDouble("value")
}
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
}
return 0.0
}
private fun editItem(index: Int, timeAsSeconds: Int, value1: Double, value2: Double) {
try {
val time: String
val hour = timeAsSeconds / 60 / 60
val df = DecimalFormat("00")
time = df.format(hour.toLong()) + ":00"
val newObject1 = JSONObject()
newObject1.put("time", time)
newObject1.put("timeAsSeconds", timeAsSeconds)
newObject1.put("value", value1)
data1.put(index, newObject1)
if (data2 != null) {
val newObject2 = JSONObject()
newObject2.put("time", time)
newObject2.put("timeAsSeconds", timeAsSeconds)
newObject2.put("value", value2)
data2.put(index, newObject2)
}
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
}
private fun addItem(index: Int, timeAsSeconds: Int) {
if (itemsCount() >= 24) return
if (itemsCount() > inflatedUntil) {
layout?.removeView(finalAdd)
inflateRow(++inflatedUntil)
layout?.addView(finalAdd)
}
try {
// shift data
for (i in data1.length() downTo index + 1) {
data1.put(i, data1[i - 1])
data2?.put(i, data2[i - 1])
}
// add new object
editItem(index, timeAsSeconds, 0.0, 0.0)
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
}
private fun removeItem(index: Int) {
data1.remove(index)
data2?.remove(index)
}
private fun log() {
for (i in 0 until data1.length()) {
aapsLogger.debug(i.toString() + ": @" + dateUtil.timeStringFromSeconds(secondFromMidnight(i)) + " " + value1(i) + if (data2 != null) " " + value2(i) else "")
}
}
private fun callSave() {
save?.run()
}
fun updateLabel(txt: String) {
label = txt
textLabel?.text = txt
}
companion object {
private const val ONE_HOUR_IN_SECONDS = 60 * 60
}
}

View file

@ -1,437 +0,0 @@
package info.nightscout.plugins.ui;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.widget.TextViewCompat;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.core.ui.elements.NumberPicker;
import info.nightscout.core.ui.elements.SpinnerHelper;
import info.nightscout.plugins.R;
import info.nightscout.rx.logging.AAPSLogger;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.utils.DateUtil;
/**
* Created by mike on 29.12.2016.
*/
public class TimeListEdit {
private final AAPSLogger aapsLogger;
private final DateUtil dateUtil;
private final int ONEHOURINSECONDS = 60 * 60;
private final View[] intervals = new View[24];
private final SpinnerHelper[] spinners = new SpinnerHelper[24];
private final NumberPicker[] numberPickers1 = new NumberPicker[24];
private final NumberPicker[] numberPickers2 = new NumberPicker[24];
private final ImageView[] addButtons = new ImageView[24];
private final ImageView[] removeButtons = new ImageView[24];
private ImageView finalAdd;
private final Context context;
private final View view;
private final int resLayoutId;
private final String tagPrefix;
private String label;
private final JSONArray data1;
private final JSONArray data2;
private final double step;
private final double min;
private final double max;
private final double min2;
private final double max2;
private final NumberFormat formatter;
private final Runnable save;
private LinearLayout layout;
private TextView textlabel;
private int inflatedUntil = -1;
public TimeListEdit(
Context context,
AAPSLogger aapsLogger,
DateUtil dateUtil,
View view, int resLayoutId, String tagPrefix, String label, JSONArray data1, JSONArray data2, double[] range1, double[] range2, double step, NumberFormat formatter, Runnable save) {
this.context = context;
this.aapsLogger = aapsLogger;
this.dateUtil = dateUtil;
this.view = view;
this.resLayoutId = resLayoutId;
this.tagPrefix = tagPrefix;
this.label = label;
this.data1 = data1;
this.data2 = data2;
this.step = step;
this.min = range1[0];
this.max = range1[1];
this.min2 = range2 != null ? range2[0] : 0;
this.max2 = range2 != null ? range2[1] : 0;
this.formatter = formatter;
this.save = save;
buildView();
}
private void buildView() {
layout = view.findViewById(resLayoutId);
layout.removeAllViewsInLayout();
textlabel = new TextView(context);
textlabel.setText(label);
textlabel.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
llp.setMargins(0, 5, 0, 5);
textlabel.setLayoutParams(llp);
TextViewCompat.setTextAppearance(textlabel, android.R.style.TextAppearance_Medium);
layout.addView(textlabel);
for (int i = 0; i < 24 && i < itemsCount(); i++) {
inflateRow(i);
inflatedUntil = i;
}
// last "plus" to append new interval
float factor = layout.getContext().getResources().getDisplayMetrics().density;
finalAdd = new ImageView(context);
finalAdd.setImageResource(info.nightscout.core.main.R.drawable.ic_add);
finalAdd.setContentDescription(layout.getContext().getResources().getString(R.string.a11y_add_new_to_list));
LinearLayout.LayoutParams illp = new LinearLayout.LayoutParams((int) (35d * factor), (int) (35 * factor));
illp.setMargins(0, 25, 0, 25); // llp.setMargins(left, top, right, bottom);
illp.gravity = Gravity.CENTER;
layout.addView(finalAdd);
finalAdd.setLayoutParams(illp);
finalAdd.setOnClickListener(view -> {
addItem(itemsCount(), itemsCount() > 0 ? secondFromMidnight(itemsCount() - 1) + ONEHOURINSECONDS : 0, 0, 0);
callSave();
log();
fillView();
});
fillView();
}
private void inflateRow(final int position) {
LayoutInflater inflater = LayoutInflater.from(context);
int resource = data2 == null ? R.layout.timelistedit_element : R.layout.timelistedit_element_vertical;
View childView = intervals[position] = inflater.inflate(resource, layout, false);
spinners[position] = new SpinnerHelper(childView.findViewById(R.id.timelistedit_time));
numberPickers1[position] = childView.findViewById(R.id.timelistedit_edit1);
numberPickers2[position] = childView.findViewById(R.id.timelistedit_edit2);
addButtons[position] = childView.findViewById(R.id.timelistedit_add);
removeButtons[position] = childView.findViewById(R.id.timelistedit_remove);
addButtons[position].setOnClickListener(view -> {
int seconds = secondFromMidnight(position);
addItem(position, seconds, 0, 0);
// for here for the rest of values
for (int i = position + 1; i < itemsCount(); i++) {
if (secondFromMidnight(i - 1) >= secondFromMidnight(i)) {
editItem(i, secondFromMidnight(i - 1) + ONEHOURINSECONDS, value1(i), value2(i));
}
}
while (itemsCount() > 24 || secondFromMidnight(itemsCount() - 1) > 23 * ONEHOURINSECONDS)
removeItem(itemsCount() - 1);
callSave();
log();
fillView();
});
removeButtons[position].setOnClickListener(view -> {
removeItem(position);
callSave();
log();
fillView();
});
spinners[position].setOnItemSelectedListener(
new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int selected, long id) {
int seconds = ((SpinnerAdapter) spinners[position].getAdapter()).valueForPosition(selected);
editItem(position, seconds, value1(position), value2(position));
log();
callSave();
fillView();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
);
numberPickers1[position].setTextWatcher(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
Double value1 = SafeParse.INSTANCE.stringToDouble(numberPickers1[position].getText(), 0.0);
Double value2 = value2(position);
if (data2 != null && value1 > value2) {
value2 = value1;
numberPickers2[position].setValue(value2);
}
editItem(position, secondFromMidnight(position), value1, value2);
callSave();
log();
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
numberPickers1[position].setTag(tagPrefix + "-1-" + position);
numberPickers2[position].setTextWatcher(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
Double value1 = value1(position);
Double value2 = SafeParse.INSTANCE.stringToDouble(numberPickers2[position].getText(), 0.0);
if (data2 != null && value2 < value1) {
value1 = value2;
numberPickers1[position].setValue(value1);
}
editItem(position, secondFromMidnight(position), value1, value2);
callSave();
log();
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
numberPickers2[position].setTag(tagPrefix + "-2-" + position);
layout.addView(childView);
}
private void fillView() {
for (int i = 0; i < 24; i++) {
if (i < itemsCount()) {
intervals[i].setVisibility(View.VISIBLE);
buildInterval(i);
} else if (i <= inflatedUntil) {
intervals[i].setVisibility(View.GONE);
}
}
if (!(itemsCount() > 0 && secondFromMidnight(itemsCount() - 1) == 23 * ONEHOURINSECONDS)) {
finalAdd.setVisibility(View.VISIBLE);
} else {
finalAdd.setVisibility(View.GONE);
}
}
private void buildInterval(int i) {
SpinnerHelper timeSpinner = spinners[i];
final NumberPicker editText1 = numberPickers1[i];
final NumberPicker editText2 = numberPickers2[i];
int previous = i == 0 ? -1 * ONEHOURINSECONDS : secondFromMidnight(i - 1);
int next = i == itemsCount() - 1 ? 24 * ONEHOURINSECONDS : secondFromMidnight(i + 1);
if (i == 0) next = ONEHOURINSECONDS;
fillSpinner(timeSpinner, secondFromMidnight(i), previous, next);
editText1.setParams(value1(i), min, max, step, formatter, false, null);
editText2.setParams(value2(i), min2, max2, step, formatter, false, null);
if (data2 == null) {
editText2.setVisibility(View.GONE);
}
if (itemsCount() == 1 || i == 0) {
removeButtons[i].setVisibility(View.INVISIBLE);
} else
removeButtons[i].setVisibility(View.VISIBLE);
if (itemsCount() >= 24 || secondFromMidnight(i) >= 82800) {
addButtons[i].setVisibility(View.INVISIBLE);
} else {
addButtons[i].setVisibility(View.VISIBLE);
}
}
static class SpinnerAdapter extends ArrayAdapter<CharSequence> {
List<Integer> values;
SpinnerAdapter(@NonNull Context context, int resource, final @NonNull List<CharSequence> objects, final @NonNull List<Integer> values) {
super(context, resource, objects);
this.values = values;
}
int valueForPosition(int position) {
return values.get(position);
}
}
private void fillSpinner(final SpinnerHelper spinner, int secondsFromMidnight, int previous, int next) {
int posInList = 0;
ArrayList<CharSequence> timeList = new ArrayList<>();
ArrayList<Integer> timeListValues = new ArrayList<>();
int pos = 0;
for (int t = previous + ONEHOURINSECONDS; t < next; t += ONEHOURINSECONDS) {
timeList.add(dateUtil.timeStringFromSeconds(t));
timeListValues.add(t);
if (secondsFromMidnight == t) posInList = pos;
pos++;
}
final SpinnerAdapter adapter = new SpinnerAdapter(context,
info.nightscout.core.ui.R.layout.spinner_centered, timeList, timeListValues);
spinner.setAdapter(adapter);
spinner.setSelection(posInList, false);
adapter.notifyDataSetChanged();
}
private int itemsCount() {
return data1.length();
}
private int secondFromMidnight(int index) {
try {
JSONObject item = (JSONObject) data1.get(index);
if (item.has("timeAsSeconds")) {
int time = item.getInt("timeAsSeconds");
if (index == 0 && time != 0) {
// fix the bug, every array must start with 0
item.put("timeAsSeconds", 0);
time = 0;
}
return time;
}
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
return 0;
}
private double value1(int index) {
try {
JSONObject item = (JSONObject) data1.get(index);
if (item.has("value")) {
return item.getDouble("value");
}
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
return 0d;
}
private double value2(int index) {
if (data2 != null) {
try {
JSONObject item = (JSONObject) data2.get(index);
if (item.has("value")) {
return item.getDouble("value");
}
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
return 0d;
}
private void editItem(int index, int timeAsSeconds, double value1, double value2) {
try {
String time;
int hour = timeAsSeconds / 60 / 60;
DecimalFormat df = new DecimalFormat("00");
time = df.format(hour) + ":00";
JSONObject newObject1 = new JSONObject();
newObject1.put("time", time);
newObject1.put("timeAsSeconds", timeAsSeconds);
newObject1.put("value", value1);
data1.put(index, newObject1);
if (data2 != null) {
JSONObject newObject2 = new JSONObject();
newObject2.put("time", time);
newObject2.put("timeAsSeconds", timeAsSeconds);
newObject2.put("value", value2);
data2.put(index, newObject2);
}
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
@SuppressWarnings("SameParameterValue")
private void addItem(int index, int timeAsSeconds, double value1, double value2) {
if (itemsCount() >= 24) return;
if (itemsCount() > inflatedUntil) {
layout.removeView(finalAdd);
inflateRow(++inflatedUntil);
layout.addView(finalAdd);
}
try {
// shift data
for (int i = data1.length(); i > index; i--) {
data1.put(i, data1.get(i - 1));
if (data2 != null)
data2.put(i, data2.get(i - 1));
}
// add new object
editItem(index, timeAsSeconds, value1, value2);
} catch (JSONException e) {
aapsLogger.error("Unhandled exception", e);
}
}
private void removeItem(int index) {
data1.remove(index);
if (data2 != null)
data2.remove(index);
}
private void log() {
for (int i = 0; i < data1.length(); i++) {
aapsLogger.debug(i + ": @" + dateUtil.timeStringFromSeconds(secondFromMidnight(i)) + " " + value1(i) + (data2 != null ? " " + value2(i) : ""));
}
}
private void callSave() {
if (save != null) save.run();
}
public void updateLabel(String txt) {
this.label = txt;
if (textlabel != null)
textlabel.setText(txt);
}
}