Merge pull request #2857 from Philoul/wear/new_custom_watchface

Wear CWFNew customization capabilities with Dyn Datas
This commit is contained in:
Milos Kozak 2023-10-04 18:55:49 +02:00 committed by GitHub
commit ed4c9792d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 191 additions and 34 deletions

View file

@ -238,7 +238,20 @@ enum class JsonKeys(val key: String) {
ALLCAPS("allCaps"), ALLCAPS("allCaps"),
DAYNAMEFORMAT("dayNameFormat"), DAYNAMEFORMAT("dayNameFormat"),
MONTHFORMAT("monthFormat"), MONTHFORMAT("monthFormat"),
BACKGROUND("background") BACKGROUND("background"), // Background image for textView
LEFTOFFSET("leftOffset"),
TOPOFFSET("topOffset"),
ROTATIONOFFSET("rotationOffset"),
DYNDATA("dynData"), //Bloc of DynDatas definition, and DynData keyValue within view
VALUEKEY("valueKey"), // Indentify which value (default is View Value)
MINDATA("minData"), // Min data Value (default defined for each value, note unit mg/dl for all bg, deltas)
MAXDATA("maxData"), // Max data idem min data (note all value below min or above max will be considered as equal min or mas)
MINVALUE("minValue"), // min returned value (when data value equals minData
MAXVALUE("maxValue"), //
INVALIDVALUE("invalidValue"),
IMAGE("image"),
INVALIDIMAGE("invalidImage"),
INVALIDCOLOR("invalidColor")
} }
enum class JsonKeyValues(val key: String) { enum class JsonKeyValues(val key: String) {
@ -260,7 +273,8 @@ enum class JsonKeyValues(val key: String) {
BOLD("bold"), BOLD("bold"),
BOLD_ITALIC("bold_italic"), BOLD_ITALIC("bold_italic"),
ITALIC("italic"), ITALIC("italic"),
BGCOLOR("bgColor") BGCOLOR("bgColor"),
SGVLEVEL("sgvLevel")
} }
class ZipWatchfaceFormat { class ZipWatchfaceFormat {

View file

@ -48,10 +48,12 @@ import app.aaps.core.interfaces.rx.weardata.isEquals
import app.aaps.wear.R import app.aaps.wear.R
import app.aaps.wear.databinding.ActivityCustomBinding import app.aaps.wear.databinding.ActivityCustomBinding
import app.aaps.wear.watchfaces.utils.BaseWatchFace import app.aaps.wear.watchfaces.utils.BaseWatchFace
import org.joda.time.DateTime
import org.joda.time.TimeOfDay import org.joda.time.TimeOfDay
import org.json.JSONObject import org.json.JSONObject
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.floor
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
class CustomWatchface : BaseWatchFace() { class CustomWatchface : BaseWatchFace() {
@ -63,6 +65,7 @@ class CustomWatchface : BaseWatchFace() {
private val TEMPLATE_RESOLUTION = 400 private val TEMPLATE_RESOLUTION = 400
private var lowBatColor = Color.RED private var lowBatColor = Color.RED
private var resDataMap: CwfResDataMap = mutableMapOf() private var resDataMap: CwfResDataMap = mutableMapOf()
private var jsonString = ""
private val bgColor: Int private val bgColor: Int
get() = when (singleBg.sgvLevel) { get() = when (singleBg.sgvLevel) {
1L -> highColor 1L -> highColor
@ -102,18 +105,20 @@ class CustomWatchface : BaseWatchFace() {
override fun setColorDark() { override fun setColorDark() {
setWatchfaceStyle() setWatchfaceStyle()
binding.sgv.setTextColor(bgColor) if ((ViewMap.SGV.dynData?.stepColor ?: 0) == 0)
binding.direction2.colorFilter = changeDrawableColor(bgColor) binding.sgv.setTextColor(bgColor)
if ((ViewMap.DIRECTION.dynData?.stepColor ?: 0) == 0)
if (ageLevel != 1) binding.direction2.colorFilter = changeDrawableColor(bgColor)
if (ageLevel != 1 && (ViewMap.TIMESTAMP.dynData?.stepColor ?: 0) == 0)
binding.timestamp.setTextColor(ContextCompat.getColor(this, R.color.dark_TimestampOld)) binding.timestamp.setTextColor(ContextCompat.getColor(this, R.color.dark_TimestampOld))
if (status.batteryLevel != 1) if (status.batteryLevel != 1 && (ViewMap.UPLOADER_BATTERY.dynData?.stepColor ?: 0) == 0)
binding.uploaderBattery.setTextColor(lowBatColor) binding.uploaderBattery.setTextColor(lowBatColor)
when (loopLevel) { if ((ViewMap.LOOP.dynData?.stepDraw ?: 0) == 0) // Apply automatic background image only if no dynData or no step images
-1 -> binding.loop.setBackgroundResource(R.drawable.loop_grey_25) when (loopLevel) {
1 -> binding.loop.setBackgroundResource(R.drawable.loop_green_25) -1 -> binding.loop.setBackgroundResource(R.drawable.loop_grey_25)
else -> binding.loop.setBackgroundResource(R.drawable.loop_red_25) 1 -> binding.loop.setBackgroundResource(R.drawable.loop_green_25)
} else -> binding.loop.setBackgroundResource(R.drawable.loop_red_25)
}
setupCharts() setupCharts()
} }
@ -147,11 +152,13 @@ class CustomWatchface : BaseWatchFace() {
updatePref(it.customWatchfaceData.metadata) updatePref(it.customWatchfaceData.metadata)
try { try {
val json = JSONObject(it.customWatchfaceData.json) val json = JSONObject(it.customWatchfaceData.json)
if (!resDataMap.isEquals(it.customWatchfaceData.resDatas)) { if (!resDataMap.isEquals(it.customWatchfaceData.resDatas) || jsonString != it.customWatchfaceData.json) {
resDataMap = it.customWatchfaceData.resDatas resDataMap = it.customWatchfaceData.resDatas
jsonString = it.customWatchfaceData.json
FontMap.init(this) FontMap.init(this)
ViewMap.init(this) ViewMap.init(this)
TrendArrowMap.init(this) TrendArrowMap.init(this)
DynProvider.init(json.optJSONObject(DYNDATA.key))
} }
enableSecond = json.optBoolean(ENABLESECOND.key) && sp.getBoolean(R.string.key_show_seconds, true) enableSecond = json.optBoolean(ENABLESECOND.key) && sp.getBoolean(R.string.key_show_seconds, true)
highColor = getColor(json.optString(HIGHCOLOR.key), ContextCompat.getColor(this, R.color.dark_highColor)) highColor = getColor(json.optString(HIGHCOLOR.key), ContextCompat.getColor(this, R.color.dark_highColor))
@ -174,6 +181,7 @@ class CustomWatchface : BaseWatchFace() {
when (view) { when (view) {
is TextView -> viewMap.customizeTextView(view, viewJson) is TextView -> viewMap.customizeTextView(view, viewJson)
is ImageView -> viewMap.customizeImageView(view, viewJson) is ImageView -> viewMap.customizeImageView(view, viewJson)
is lecho.lib.hellocharts.view.LineChartView -> viewMap.customizeGraphView(view, viewJson)
else -> viewMap.customizeViewCommon(view, viewJson) else -> viewMap.customizeViewCommon(view, viewJson)
} }
} ?: apply { } ?: apply {
@ -446,6 +454,7 @@ class CustomWatchface : BaseWatchFace() {
var height = 0 var height = 0
var left = 0 var left = 0
var top = 0 var top = 0
var dynData: DynProvider? = null
var rangeCustom: Drawable? = null var rangeCustom: Drawable? = null
get() = field ?: customDrawable?.let { cd -> cwf.resDataMap[cd.fileName]?.toDrawable(cwf.resources).also { rangeCustom = it } } get() = field ?: customDrawable?.let { cd -> cwf.resDataMap[cd.fileName]?.toDrawable(cwf.resources).also { rangeCustom = it } }
var highCustom: Drawable? = null var highCustom: Drawable? = null
@ -454,7 +463,7 @@ class CustomWatchface : BaseWatchFace() {
get() = field ?: customLow?.let { cd -> cwf.resDataMap[cd.fileName]?.toDrawable(cwf.resources).also { lowCustom = it } } get() = field ?: customLow?.let { cd -> cwf.resDataMap[cd.fileName]?.toDrawable(cwf.resources).also { lowCustom = it } }
var textDrawable: Drawable? = null var textDrawable: Drawable? = null
val drawable: Drawable? val drawable: Drawable?
get() = when (cwf.singleBg.sgvLevel) { get() = dynData?.getDrawable() ?: when (cwf.singleBg.sgvLevel) {
1L -> highCustom ?: rangeCustom 1L -> highCustom ?: rangeCustom
0L -> rangeCustom 0L -> rangeCustom
-1L -> lowCustom ?: rangeCustom -1L -> lowCustom ?: rangeCustom
@ -472,58 +481,68 @@ class CustomWatchface : BaseWatchFace() {
left = (viewJson.optInt(LEFTMARGIN.key) * cwf.zoomFactor).toInt() left = (viewJson.optInt(LEFTMARGIN.key) * cwf.zoomFactor).toInt()
top = (viewJson.optInt(TOPMARGIN.key) * cwf.zoomFactor).toInt() top = (viewJson.optInt(TOPMARGIN.key) * cwf.zoomFactor).toInt()
val params = FrameLayout.LayoutParams(width, height) val params = FrameLayout.LayoutParams(width, height)
params.topMargin = top dynData = DynProvider.getDyn(cwf, viewJson.optString(DYNDATA.key),width, height, key)
params.leftMargin = left val topOffset = if (viewJson.optBoolean(TOPOFFSET.key, false)) dynData?.getTopOffset() ?:0 else 0
params.topMargin = top + topOffset
val leftOffset = if (viewJson.optBoolean(LEFTOFFSET.key, false)) dynData?.getLeftOffset() ?:0 else 0
params.leftMargin = left + leftOffset
view.layoutParams = params view.layoutParams = params
view.visibility = cwf.setVisibility(viewJson.optString(VISIBILITY.key, JsonKeyValues.GONE.key), visibility()) view.visibility = cwf.setVisibility(viewJson.optString(VISIBILITY.key, JsonKeyValues.GONE.key), visibility())
val rotationOffset = if (viewJson.optBoolean(ROTATIONOFFSET.key, false)) dynData?.getRotationOffset()?.toFloat() ?:0F else 0F
view.rotation = viewJson.optInt(ROTATION.key).toFloat() + rotationOffset
} }
fun customizeTextView(view: TextView, viewJson: JSONObject) { fun customizeTextView(view: TextView, viewJson: JSONObject) {
customizeViewCommon(view, viewJson) customizeViewCommon(view, viewJson)
view.rotation = viewJson.optInt(ROTATION.key).toFloat()
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, (viewJson.optInt(TEXTSIZE.key, 22) * cwf.zoomFactor).toFloat()) view.setTextSize(TypedValue.COMPLEX_UNIT_PX, (viewJson.optInt(TEXTSIZE.key, 22) * cwf.zoomFactor).toFloat())
view.gravity = GravityMap.gravity(viewJson.optString(GRAVITY.key, GravityMap.CENTER.key)) view.gravity = GravityMap.gravity(viewJson.optString(GRAVITY.key, GravityMap.CENTER.key))
view.setTypeface( view.setTypeface(
FontMap.font(viewJson.optString(FONT.key, FontMap.DEFAULT.key)), FontMap.font(viewJson.optString(FONT.key, FontMap.DEFAULT.key)),
StyleMap.style(viewJson.optString(FONTSTYLE.key, StyleMap.NORMAL.key)) StyleMap.style(viewJson.optString(FONTSTYLE.key, StyleMap.NORMAL.key))
) )
view.setTextColor(cwf.getColor(viewJson.optString(FONTCOLOR.key))) view.setTextColor(dynData?.getColor() ?: cwf.getColor(viewJson.optString(FONTCOLOR.key)))
view.isAllCaps = viewJson.optBoolean(ALLCAPS.key) view.isAllCaps = viewJson.optBoolean(ALLCAPS.key)
if (viewJson.has(TEXTVALUE.key)) if (viewJson.has(TEXTVALUE.key))
view.text = viewJson.optString(TEXTVALUE.key) view.text = viewJson.optString(TEXTVALUE.key)
view.background = textDrawable(viewJson) view.background = dynData?.getDrawable() ?: textDrawable(viewJson)
} }
fun customizeImageView(view: ImageView, viewJson: JSONObject) { fun customizeImageView(view: ImageView, viewJson: JSONObject) {
customizeViewCommon(view, viewJson) customizeViewCommon(view, viewJson)
view.clearColorFilter() view.clearColorFilter()
drawable?.let { drawable?.let {
if (viewJson.has(COLOR.key)) // Note only works on bitmap (png or jpg) or xml included into res, not for svg files if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) // Note only works on bitmap (png or jpg) or xml included into res, not for svg files
it.colorFilter = cwf.changeDrawableColor(cwf.getColor(viewJson.optString(COLOR.key))) it.colorFilter = cwf.changeDrawableColor(dynData?.getColor() ?: cwf.getColor(viewJson.optString(COLOR.key)))
else else
it.clearColorFilter() it.clearColorFilter()
view.setImageDrawable(it) view.setImageDrawable(it)
} ?: apply { } ?: apply {
view.setImageDrawable(defaultDrawable?.let { cwf.resources.getDrawable(it) }) view.setImageDrawable(defaultDrawable?.let { cwf.resources.getDrawable(it) })
if (viewJson.has(COLOR.key)) if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0)
view.setColorFilter(cwf.getColor(viewJson.optString(COLOR.key))) view.setColorFilter(dynData?.getColor() ?: cwf.getColor(viewJson.optString(COLOR.key)))
else else
view.clearColorFilter() view.clearColorFilter()
} }
} }
fun customizeGraphView(view: lecho.lib.hellocharts.view.LineChartView, viewJson: JSONObject) {
customizeViewCommon(view, viewJson)
view.setBackgroundColor(dynData?.getColor() ?: cwf.getColor(viewJson.optString(COLOR.key, "#0000000000"),Color.TRANSPARENT))
view.background = dynData?.getDrawable() ?: textDrawable(viewJson)
}
} }
private enum class TrendArrowMap(val symbol: String, @DrawableRes val icon: Int, val customDrawable: ResFileMap?) { private enum class TrendArrowMap(val symbol: String, @DrawableRes val icon: Int, val customDrawable: ResFileMap?, val dynValue: Double) {
NONE("??", R.drawable.ic_invalid, ResFileMap.ARROW_NONE), NONE("??", R.drawable.ic_invalid, ResFileMap.ARROW_NONE, 0.0),
TRIPLE_UP("X", R.drawable.ic_doubleup, ResFileMap.ARROW_DOUBLE_UP), TRIPLE_UP("X", R.drawable.ic_doubleup, ResFileMap.ARROW_DOUBLE_UP, 7.0),
DOUBLE_UP("\u21c8", R.drawable.ic_doubleup, ResFileMap.ARROW_DOUBLE_UP), DOUBLE_UP("\u21c8", R.drawable.ic_doubleup, ResFileMap.ARROW_DOUBLE_UP, 7.0),
SINGLE_UP("\u2191", R.drawable.ic_singleup, ResFileMap.ARROW_SINGLE_UP), SINGLE_UP("\u2191", R.drawable.ic_singleup, ResFileMap.ARROW_SINGLE_UP, 6.0),
FORTY_FIVE_UP("\u2197", R.drawable.ic_fortyfiveup, ResFileMap.ARROW_FORTY_FIVE_UP), FORTY_FIVE_UP("\u2197", R.drawable.ic_fortyfiveup, ResFileMap.ARROW_FORTY_FIVE_UP, 5.0),
FLAT("\u2192", R.drawable.ic_flat, ResFileMap.ARROW_FLAT), FLAT("\u2192", R.drawable.ic_flat, ResFileMap.ARROW_FLAT, 4.0),
FORTY_FIVE_DOWN("\u2198", R.drawable.ic_fortyfivedown, ResFileMap.ARROW_FORTY_FIVE_DOWN), FORTY_FIVE_DOWN("\u2198", R.drawable.ic_fortyfivedown, ResFileMap.ARROW_FORTY_FIVE_DOWN, 3.0),
SINGLE_DOWN("\u2193", R.drawable.ic_singledown, ResFileMap.ARROW_SINGLE_DOWN), SINGLE_DOWN("\u2193", R.drawable.ic_singledown, ResFileMap.ARROW_SINGLE_DOWN, 2.0),
DOUBLE_DOWN("\u21ca", R.drawable.ic_doubledown, ResFileMap.ARROW_DOUBLE_DOWN), DOUBLE_DOWN("\u21ca", R.drawable.ic_doubledown, ResFileMap.ARROW_DOUBLE_DOWN, 2.0),
TRIPLE_DOWN("X", R.drawable.ic_doubledown, ResFileMap.ARROW_DOUBLE_DOWN); TRIPLE_DOWN("X", R.drawable.ic_doubledown, ResFileMap.ARROW_DOUBLE_DOWN, 1.0);
companion object { companion object {
@ -532,6 +551,7 @@ class CustomWatchface : BaseWatchFace() {
it.arrowCustom = null it.arrowCustom = null
} }
fun drawable() = values().firstOrNull { it.symbol == it.cwf.singleBg.slopeArrow }?.arrowCustom ?: NONE.arrowCustom fun drawable() = values().firstOrNull { it.symbol == it.cwf.singleBg.slopeArrow }?.arrowCustom ?: NONE.arrowCustom
fun value() = values().firstOrNull { it.symbol == it.cwf.singleBg.slopeArrow }?.dynValue ?: NONE.dynValue
} }
lateinit var cwf: CustomWatchface lateinit var cwf: CustomWatchface
@ -618,6 +638,129 @@ class CustomWatchface : BaseWatchFace() {
SHOW_LOOP_STATUS(CwfMetadataKey.CWF_PREF_WATCH_SHOW_LOOP_STATUS.key, R.string.key_show_external_status), SHOW_LOOP_STATUS(CwfMetadataKey.CWF_PREF_WATCH_SHOW_LOOP_STATUS.key, R.string.key_show_external_status),
SHOW_WEEK_NUMBER(CwfMetadataKey.CWF_PREF_WATCH_SHOW_WEEK_NUMBER.key, R.string.key_show_week_number) SHOW_WEEK_NUMBER(CwfMetadataKey.CWF_PREF_WATCH_SHOW_WEEK_NUMBER.key, R.string.key_show_week_number)
} }
private enum class ValueMap(val key: String, val min: Double, val max: Double) {
SGV(ViewKeys.SGV.key, 39.0, 400.0),
SGVLEVEL(JsonKeyValues.SGVLEVEL.key, -1.0, 1.0),
DIRECTION(ViewKeys.DIRECTION.key, 1.0, 7.0),
DELTA(ViewKeys.DELTA.key, -25.0, 25.0),
AVG_DELTA(ViewKeys.AVG_DELTA.key, -25.0, 25.0),
UPLOADER_BATTERY(ViewKeys.UPLOADER_BATTERY.key, 0.0, 100.0),
RIG_BATTERY(ViewKeys.RIG_BATTERY.key, 0.0, 100.0),
TIMESTAMP(ViewKeys.TIMESTAMP.key, 0.0, 60.0),
LOOP(ViewKeys.LOOP.key, 0.0, 28.0),
DAY(ViewKeys.DAY.key, 1.0, 31.0),
DAY_NAME(ViewKeys.DAY_NAME.key, 1.0, 7.0),
MONTH(ViewKeys.MONTH.key, 1.0, 12.0),
WEEKNUMBER(ViewKeys.WEEKNUMBER.key, 1.0, 53.0);
fun dynValue(dataValue: Double, dataRange: DataRange, valueRange: DataRange): Int = when {
dataValue < dataRange.minData -> dataRange.minData
dataValue > dataRange.maxData -> dataRange.maxData
else -> dataValue
}.let {
if (dataRange.minData != dataRange.maxData)
(valueRange.minData + (it - dataRange.minData) * (valueRange.maxData - valueRange.minData) / (dataRange.maxData - dataRange.minData)).toInt()
else it.toInt()
}
fun stepValue(dataValue: Double, range: DataRange, step: Int): Int = step(dataValue, range, step)
private fun step(dataValue: Double, dataRange: DataRange, step: Int): Int = when {
dataValue < dataRange.minData -> dataRange.minData
dataValue >= dataRange.maxData -> dataRange.maxData * 0.9999 // to avoid dataValue == maxData and be out of range
else -> dataValue
}.let { if (dataRange.minData != dataRange.maxData) (1 + ((it - dataRange.minData) * step) / (dataRange.maxData - dataRange.minData)).toInt() else 0 }
companion object {
fun fromKey(key: String) = values().firstOrNull { it.key == key }
}
}
private class DynProvider(val cwf: CustomWatchface, val dataJson: JSONObject, val valueMap: ValueMap, val width: Int, val height: Int) {
private val dynDrawable = mutableMapOf<Int, Drawable?>()
private val dynColor = mutableMapOf<Int, Int>()
private var dataRange: DataRange? = null
private var topRange: DataRange? = null
private var leftRange: DataRange? = null
private var rotationRange: DataRange? = null
val stepDraw: Int
get() = dynDrawable.size - 1
val stepColor: Int
get() = dynColor.size - 1
val dataValue: Double?
get() = when (valueMap) {
ValueMap.SGV -> if (cwf.singleBg.sgvString != "---") cwf.singleBg.sgv else null
ValueMap.SGVLEVEL -> if (cwf.singleBg.sgvString != "---") cwf.singleBg.sgvLevel.toDouble() else null
ValueMap.DIRECTION -> TrendArrowMap.value()
ValueMap.DELTA -> cwf.singleBg.deltaMgdl
ValueMap.AVG_DELTA -> cwf.singleBg.avgDeltaMgdl
ValueMap.RIG_BATTERY -> cwf.status.rigBattery.replace("%", "").toDoubleOrNull()
ValueMap.UPLOADER_BATTERY -> cwf.status.battery.replace("%", "").toDoubleOrNull()
ValueMap.LOOP -> if (cwf.status.openApsStatus != -1L) ((System.currentTimeMillis() - cwf.status.openApsStatus) / 1000 / 60).toDouble() else null
ValueMap.TIMESTAMP -> if (cwf.singleBg.timeStamp != 0L) floor(cwf.timeSince() / (1000 * 60)) else null
ValueMap.DAY -> DateTime().dayOfMonth.toDouble()
ValueMap.DAY_NAME -> DateTime().dayOfWeek.toDouble()
ValueMap.MONTH -> DateTime().monthOfYear.toDouble()
ValueMap.WEEKNUMBER -> DateTime().weekOfWeekyear.toDouble()
}
fun getTopOffset(): Int = dataRange?.let { dataRange -> topRange?.let { topRange -> dataValue?.let { valueMap.dynValue(it, dataRange, topRange) } ?: topRange.invalidData } } ?: 0
fun getLeftOffset(): Int = dataRange?.let { dataRange -> leftRange?.let { leftRange -> dataValue?.let { valueMap.dynValue(it, dataRange, leftRange) } ?: leftRange.invalidData } } ?: 0
fun getRotationOffset(): Int = dataRange?.let { dataRange -> rotationRange?.let { rotRange -> dataValue?.let { valueMap.dynValue(it, dataRange, rotRange) } ?: rotRange.invalidData } } ?: 0
fun getDrawable() = dataRange?.let { dataRange -> dataValue?.let { dynDrawable[valueMap.stepValue(it, dataRange, stepDraw)] } ?: dynDrawable[0] }
fun getColor() = if (stepColor > 0) dataRange?.let { dataRange -> dataValue?.let { dynColor[valueMap.stepValue(it, dataRange, stepColor)] } ?: dynColor[0] } else null
private fun load() {
dynDrawable[0] = dataJson.optString(INVALIDIMAGE.key)?.let { cwf.resDataMap[it]?.toDrawable(cwf.resources, width, height) }
var idx = 1
while (dataJson.has("${IMAGE.key}$idx")) {
cwf.resDataMap[dataJson.optString("${IMAGE.key}$idx")]?.toDrawable(cwf.resources, width, height).also { dynDrawable[idx] = it }
idx++
}
dynColor[0] = cwf.getColor(dataJson.optString(INVALIDCOLOR.key))
idx = 1
while (dataJson.has("${COLOR.key}$idx")) {
dynColor[idx] = cwf.getColor(dataJson.optString("${COLOR.key}$idx"))
idx++
}
DataRange(dataJson.optDouble(MINDATA.key, valueMap.min), dataJson.optDouble(MAXDATA.key, valueMap.max)).let { defaultRange ->
dataRange = defaultRange
topRange = parseDataRange(dataJson.optJSONObject(TOPOFFSET.key), defaultRange)
leftRange = parseDataRange(dataJson.optJSONObject(LEFTOFFSET.key), defaultRange)
rotationRange = parseDataRange(dataJson.optJSONObject(ROTATIONOFFSET.key), defaultRange)
}
}
companion object {
val dynData = mutableMapOf<String, DynProvider>()
var dynJson: JSONObject? = null
fun init(dynJson: JSONObject?) {
this.dynJson = dynJson
dynData.clear()
}
fun getDyn(cwf: CustomWatchface, key: String, width: Int, height: Int, defaultViewKey: String): DynProvider? = dynData["${defaultViewKey}_$key"]
?: dynJson?.optJSONObject(key)?.let { dataJson ->
ValueMap.fromKey(dataJson.optString(VALUEKEY.key, defaultViewKey))?.let { valueMap ->
DynProvider(cwf, dataJson, valueMap, width, height).also { it.load() }
}
}?.also { dynData["${defaultViewKey}_$key"] = it }
private fun parseDataRange(json: JSONObject?, defaultData: DataRange) =
json?.let {
DataRange(
minData = it.optDouble(MINVALUE.key, defaultData.minData),
maxData = it.optDouble(MAXVALUE.key, defaultData.maxData),
invalidData = it.optInt(INVALIDVALUE.key, defaultData.invalidData)
)
} ?: defaultData
}
}
private class DataRange (val minData: Double, val maxData: Double, val invalidData: Int = 0)
} }