From c1b1fe91204425f91480cc7216afe42940155b34 Mon Sep 17 00:00:00 2001 From: Philoul Date: Mon, 28 Aug 2023 19:14:29 +0200 Subject: [PATCH 1/2] CustomWatchface Complexity reduction And Fix potential crash on phone side --- .../rx/weardata/CustomWatchfaceFormat.kt | 19 ++- .../wear/activities/CwfInfosActivity.kt | 12 +- .../androidaps/watchfaces/CustomWatchface.kt | 127 ++++++++++-------- 3 files changed, 83 insertions(+), 75 deletions(-) diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/CustomWatchfaceFormat.kt b/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/CustomWatchfaceFormat.kt index 9cdf25ddc2..66527227f2 100644 --- a/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/CustomWatchfaceFormat.kt +++ b/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/CustomWatchfaceFormat.kt @@ -260,13 +260,13 @@ class ZipWatchfaceFormat { } // Valid CWF file must contains a valid json file with a name within metadata and a custom watchface image - if (metadata.containsKey(CwfMetadataKey.CWF_NAME) && drawableDatas.containsKey(CwfDrawableFileMap.CUSTOM_WATCHFACE)) - return CwfData(json.toString(4), metadata, drawableDatas) + return if (metadata.containsKey(CwfMetadataKey.CWF_NAME) && drawableDatas.containsKey(CwfDrawableFileMap.CUSTOM_WATCHFACE)) + CwfData(json.toString(4), metadata, drawableDatas) else - return null + null } catch (e: Exception) { - return null + return null // mainly IOException } } @@ -292,6 +292,7 @@ class ZipWatchfaceFormat { zipOutputStream.close() outputStream.close() } catch (_: Exception) { + // Ignore file } } @@ -299,13 +300,9 @@ class ZipWatchfaceFormat { fun loadMetadata(contents: JSONObject): CwfMetadataMap { val metadata: CwfMetadataMap = mutableMapOf() - if (contents.has(JsonKeys.METADATA.key)) { - val meta = contents.getJSONObject(JsonKeys.METADATA.key) - for (key in meta.keys()) { - val metaKey = CwfMetadataKey.fromKey(key) - if (metaKey != null) { - metadata[metaKey] = meta.getString(key) - } + contents.optJSONObject(JsonKeys.METADATA.key)?.also { jsonObject -> // optJSONObject doesn't throw Exception + for (key in jsonObject.keys()) { + CwfMetadataKey.fromKey(key)?.let { metadata[it] = jsonObject.optString(key) } // optString doesn't throw Exception } } return metadata diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/activities/CwfInfosActivity.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/activities/CwfInfosActivity.kt index 6edbb4b22c..638f86ef0b 100644 --- a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/activities/CwfInfosActivity.kt +++ b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/activities/CwfInfosActivity.kt @@ -23,6 +23,8 @@ import info.nightscout.rx.weardata.CUSTOM_VERSION import info.nightscout.rx.weardata.CwfDrawableFileMap import info.nightscout.rx.weardata.CwfMetadataKey import info.nightscout.rx.weardata.CwfMetadataMap +import info.nightscout.rx.weardata.JsonKeyValues +import info.nightscout.rx.weardata.JsonKeys import info.nightscout.rx.weardata.ViewKeys import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.sharedPreferences.SP @@ -82,13 +84,13 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() { private fun updateGui() { wearPlugin.savedCustomWatchface?.let { - val cwf_authorization = sp.getBoolean(info.nightscout.core.utils.R.string.key_wear_custom_watchface_autorization, false) + val cwfAuthorization = sp.getBoolean(info.nightscout.core.utils.R.string.key_wear_custom_watchface_autorization, false) val metadata = it.metadata val drawable = it.drawableDatas[CwfDrawableFileMap.CUSTOM_WATCHFACE]?.toDrawable(resources) binding.customWatchface.setImageDrawable(drawable) title = rh.gs(CwfMetadataKey.CWF_NAME.label, metadata[CwfMetadataKey.CWF_NAME]) - metadata[CwfMetadataKey.CWF_AUTHOR_VERSION]?.let { author_version -> - title = "${metadata[CwfMetadataKey.CWF_NAME]} ($author_version)" + metadata[CwfMetadataKey.CWF_AUTHOR_VERSION]?.let { authorVersion -> + title = "${metadata[CwfMetadataKey.CWF_NAME]} ($authorVersion)" } binding.filelistName.text = rh.gs(CwfMetadataKey.CWF_FILENAME.label, metadata[CwfMetadataKey.CWF_FILENAME] ?: "") binding.author.text = rh.gs(CwfMetadataKey.CWF_AUTHOR.label, metadata[CwfMetadataKey.CWF_AUTHOR] ?: "") @@ -99,7 +101,7 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() { binding.cwfComment.text = rh.gs(CwfMetadataKey.CWF_COMMENT.label, metadata[CwfMetadataKey.CWF_COMMENT] ?: "") if (metadata.count { it.key.isPref } > 0) { binding.prefLayout.visibility = View.VISIBLE - binding.prefTitle.text = rh.gs(if (cwf_authorization) R.string.cwf_infos_pref_locked else R.string.cwf_infos_pref_required) + binding.prefTitle.text = rh.gs(if (cwfAuthorization) R.string.cwf_infos_pref_locked else R.string.cwf_infos_pref_required) binding.prefRecyclerview.layoutManager = LinearLayoutManager(this) binding.prefRecyclerview.adapter = PrefRecyclerViewAdapter( metadata.filter { it.key.isPref && (it.value.lowercase() == "true" || it.value.lowercase() == "false") }.toList() @@ -192,7 +194,7 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() { try { val jsonValue = json.optJSONObject(viewKey.key) if (jsonValue != null) { - val visibility = jsonValue.optString("visibility") == "visible" + val visibility = jsonValue.optString(JsonKeys.VISIBILITY.key) == JsonKeyValues.VISIBLE.key if (visibility || allViews) visibleKeyPairs.add(Pair(viewKey, visibility)) } diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt b/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt index e36ed13137..5e5d9036bc 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt @@ -3,7 +3,6 @@ package info.nightscout.androidaps.watchfaces import android.annotation.SuppressLint -import android.app.ActionBar.LayoutParams import android.content.Context import android.graphics.Color import android.graphics.ColorFilter @@ -148,17 +147,16 @@ class CustomWatchface : BaseWatchFace() { try { val json = JSONObject(it.customWatchfaceData.json) val drawableDataMap = it.customWatchfaceData.drawableDatas - enableSecond = (if (json.has(ENABLESECOND.key)) json.getBoolean(ENABLESECOND.key) else false) && sp.getBoolean(R.string.key_show_seconds, true) - - highColor = if (json.has(HIGHCOLOR.key)) Color.parseColor(json.getString(HIGHCOLOR.key)) else ContextCompat.getColor(this, R.color.dark_highColor) - midColor = if (json.has(MIDCOLOR.key)) Color.parseColor(json.getString(MIDCOLOR.key)) else ContextCompat.getColor(this, R.color.inrange) - lowColor = if (json.has(LOWCOLOR.key)) Color.parseColor(json.getString(LOWCOLOR.key)) else ContextCompat.getColor(this, R.color.low) - lowBatColor = if (json.has(LOWBATCOLOR.key)) Color.parseColor(json.getString(LOWBATCOLOR.key)) else ContextCompat.getColor(this, R.color.dark_uploaderBatteryEmpty) - carbColor = if (json.has(CARBCOLOR.key)) Color.parseColor(json.getString(CARBCOLOR.key)) else ContextCompat.getColor(this, R.color.carbs) - basalBackgroundColor = if (json.has(BASALBACKGROUNDCOLOR.key)) Color.parseColor(json.getString(BASALBACKGROUNDCOLOR.key)) else ContextCompat.getColor(this, R.color.basal_dark) - basalCenterColor = if (json.has(BASALCENTERCOLOR.key)) Color.parseColor(json.getString(BASALCENTERCOLOR.key)) else ContextCompat.getColor(this, R.color.basal_light) - gridColor = if (json.has(GRIDCOLOR.key)) Color.parseColor(json.getString(GRIDCOLOR.key)) else Color.WHITE - pointSize = if (json.has(POINTSIZE.key)) json.getInt(POINTSIZE.key) else 2 + 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)) + midColor = getColor(json.optString(MIDCOLOR.key), ContextCompat.getColor(this, R.color.inrange)) + lowColor = getColor(json.optString(LOWCOLOR.key), ContextCompat.getColor(this, R.color.low)) + lowBatColor = getColor(json.optString(LOWBATCOLOR.key), ContextCompat.getColor(this, R.color.dark_uploaderBatteryEmpty)) + carbColor = getColor(json.optString(CARBCOLOR.key), ContextCompat.getColor(this, R.color.carbs)) + basalBackgroundColor = getColor(json.optString(BASALBACKGROUNDCOLOR.key), ContextCompat.getColor(this, R.color.basal_dark)) + basalCenterColor = getColor(json.optString(BASALCENTERCOLOR.key), ContextCompat.getColor(this, R.color.basal_light)) + gridColor = getColor(json.optString(GRIDCOLOR.key), Color.WHITE) + pointSize = json.optInt(POINTSIZE.key, 2) bgColor = when (singleBg.sgvLevel) { 1L -> highColor 0L -> midColor @@ -176,48 +174,49 @@ class CustomWatchface : BaseWatchFace() { ViewMap.fromId(view.id)?.let { id -> if (json.has(id.key)) { val viewJson = json.getJSONObject(id.key) - val wrapContent = LayoutParams.WRAP_CONTENT - val width = if (viewJson.has(WIDTH.key)) (viewJson.getInt(WIDTH.key) * zoomFactor).toInt() else wrapContent - val height = if (viewJson.has(HEIGHT.key)) (viewJson.getInt(HEIGHT.key) * zoomFactor).toInt() else wrapContent + val width = (viewJson.optInt(WIDTH.key) * zoomFactor).toInt() + val height = (viewJson.optInt(HEIGHT.key, 0) * zoomFactor).toInt() val params = FrameLayout.LayoutParams(width, height) - params.topMargin = if (viewJson.has(TOPMARGIN.key)) (viewJson.getInt(TOPMARGIN.key) * zoomFactor).toInt() else 0 - params.leftMargin = if (viewJson.has(LEFTMARGIN.key)) (viewJson.getInt(LEFTMARGIN.key) * zoomFactor).toInt() else 0 + params.topMargin = (viewJson.optInt(TOPMARGIN.key, 0) * zoomFactor).toInt() + params.leftMargin = (viewJson.optInt(LEFTMARGIN.key, 0) * zoomFactor).toInt() view.layoutParams = params - view.visibility = if (viewJson.has(VISIBILITY.key)) setVisibility(viewJson.getString(VISIBILITY.key), id.visibility(sp)) else View.GONE - if (view is TextView) { - view.rotation = if (viewJson.has(ROTATION.key)) viewJson.getInt(ROTATION.key).toFloat() else 0F - view.setTextSize(TypedValue.COMPLEX_UNIT_PX, ((if (viewJson.has(TEXTSIZE.key)) viewJson.getInt(TEXTSIZE.key) else 22) * zoomFactor).toFloat()) - view.gravity = GravityMap.gravity(if (viewJson.has(GRAVITY.key)) viewJson.getString(GRAVITY.key) else GravityMap.CENTER.key) - view.setTypeface( - FontMap.font(if (viewJson.has(FONT.key)) viewJson.getString(FONT.key) else FontMap.DEFAULT.key), - StyleMap.style(if (viewJson.has(FONTSTYLE.key)) viewJson.getString(FONTSTYLE.key) else StyleMap.NORMAL.key) - ) - if (viewJson.has(FONTCOLOR.key)) - view.setTextColor(getColor(viewJson.getString(FONTCOLOR.key))) + view.visibility = setVisibility(viewJson.optString(VISIBILITY.key, JsonKeyValues.GONE.key), id.visibility(sp)) + when (view) { + is TextView -> { + view.rotation = viewJson.optInt(ROTATION.key).toFloat() + view.setTextSize(TypedValue.COMPLEX_UNIT_PX, (viewJson.optInt(TEXTSIZE.key, 22) * zoomFactor).toFloat()) + view.gravity = GravityMap.gravity(viewJson.optString(GRAVITY.key, GravityMap.CENTER.key)) + view.setTypeface( + FontMap.font(viewJson.optString(FONT.key, FontMap.DEFAULT.key)), + StyleMap.style(viewJson.optString(FONTSTYLE.key, StyleMap.NORMAL.key)) + ) + view.setTextColor(getColor(viewJson.optString(FONTCOLOR.key))) - if (viewJson.has(TEXTVALUE.key)) - view.text = viewJson.getString(TEXTVALUE.key) - } - - if (view is ImageView) { - view.clearColorFilter() - val drawable = if (id.key == CwfDrawableFileMap.BACKGROUND.key) - backGroundDrawable - else - drawableDataMap[CwfDrawableFileMap.fromKey(id.key)]?.toDrawable(resources) - drawable?.let { - if (viewJson.has(COLOR.key)) - it.colorFilter = changeDrawableColor(getColor(viewJson.getString(COLOR.key))) - else - it.clearColorFilter() - view.setImageDrawable(it) - } ?: apply { - view.setImageDrawable(CwfDrawableFileMap.fromKey(id.key).icon?.let { context.getDrawable(it) }) - if (viewJson.has(COLOR.key)) - view.setColorFilter(getColor(viewJson.getString(COLOR.key))) - else - view.clearColorFilter() + if (viewJson.has(TEXTVALUE.key)) + view.text = viewJson.getString(TEXTVALUE.key) } + + is ImageView -> { + view.clearColorFilter() + val drawable = if (id.key == CwfDrawableFileMap.BACKGROUND.key) + backGroundDrawable + else + drawableDataMap[CwfDrawableFileMap.fromKey(id.key)]?.toDrawable(resources) + drawable?.let { + if (viewJson.has(COLOR.key)) + it.colorFilter = changeDrawableColor(getColor(viewJson.getString(COLOR.key))) + else + it.clearColorFilter() + view.setImageDrawable(it) + } ?: apply { + view.setImageDrawable(CwfDrawableFileMap.fromKey(id.key).icon?.let { context.getDrawable(it) }) + if (viewJson.has(COLOR.key)) + view.setColorFilter(getColor(viewJson.getString(COLOR.key))) + else + view.clearColorFilter() + } + } + } } else { view.visibility = View.GONE @@ -227,9 +226,7 @@ class CustomWatchface : BaseWatchFace() { } } } - binding.background.visibility = View.VISIBLE - updateSecondVisibility() - setSecond() // Update second visibility for time view + manageSpecificViews() } catch (e: Exception) { aapsLogger.debug(LTag.WEAR, "Crash during Custom watch load") persistence.store(defaultWatchface(), false) // relaod correct values to avoid crash of watchface @@ -238,8 +235,8 @@ class CustomWatchface : BaseWatchFace() { } private fun updatePref(metadata: CwfMetadataMap) { - val cwf_authorization = metadata[CwfMetadataKey.CWF_AUTHORIZATION]?.toBooleanStrictOrNull() - cwf_authorization?.let { authorization -> + val cwfAuthorization = metadata[CwfMetadataKey.CWF_AUTHORIZATION]?.toBooleanStrictOrNull() + cwfAuthorization?.let { authorization -> if (authorization) { PrefMap.values().forEach { pref -> metadata[CwfMetadataKey.fromKey(pref.key)]?.toBooleanStrictOrNull()?.let { sp.putBoolean(pref.prefKey, it) } @@ -306,8 +303,7 @@ class CustomWatchface : BaseWatchFace() { val metadataMap = ZipWatchfaceFormat.loadMetadata(json) val drawableDataMap: CwfDrawableDataMap = mutableMapOf() getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let { - val drawableData = DrawableData(it, DrawableFormat.PNG) - drawableDataMap[CwfDrawableFileMap.CUSTOM_WATCHFACE] = drawableData + drawableDataMap[CwfDrawableFileMap.CUSTOM_WATCHFACE] = DrawableData(it, DrawableFormat.PNG) } return EventData.ActionSetCustomWatchface(CwfData(json.toString(4), metadataMap, drawableDataMap)) } @@ -369,12 +365,25 @@ class CustomWatchface : BaseWatchFace() { return ColorMatrixColorFilter(colorMatrix) } - private fun getColor(color: String): Int = + private fun getColor(color: String, defaultColor: Int = Color.GRAY): Int = if (color == JsonKeyValues.BGCOLOR.key) bgColor else - try { Color.parseColor(color) } catch (e: Exception) { Color.GRAY } + try { Color.parseColor(color) } catch (e: Exception) { defaultColor } + private fun manageSpecificViews() { + //Background should fill all the watchface and must be visible + val params = FrameLayout.LayoutParams((TEMPLATE_RESOLUTION * zoomFactor).toInt(), (TEMPLATE_RESOLUTION * zoomFactor).toInt()) + params.topMargin = 0 + params.leftMargin = 0 + binding.background.layoutParams = params + binding.background.visibility = View.VISIBLE + // Update second visibility + updateSecondVisibility() + setSecond() // Update second visibility for time view + // Update timePeriod visibility + binding.timePeriod.visibility = (binding.timePeriod.visibility == View.VISIBLE && android.text.format.DateFormat.is24HourFormat(this).not()).toVisibility() + } private enum class ViewMap(val key: String, @IdRes val id: Int, @StringRes val pref: Int?) { BACKGROUND(ViewKeys.BACKGROUND.key, R.id.background, null), From 3f691afb3365351dd5f78ce8e15b001fd6037cff Mon Sep 17 00:00:00 2001 From: Philoul Date: Mon, 28 Aug 2023 20:49:26 +0200 Subject: [PATCH 2/2] Wear CWF Remove unnecessary fallback values --- .../nightscout/androidaps/watchfaces/CustomWatchface.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt b/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt index 5e5d9036bc..23d4ea0739 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt @@ -175,10 +175,10 @@ class CustomWatchface : BaseWatchFace() { if (json.has(id.key)) { val viewJson = json.getJSONObject(id.key) val width = (viewJson.optInt(WIDTH.key) * zoomFactor).toInt() - val height = (viewJson.optInt(HEIGHT.key, 0) * zoomFactor).toInt() + val height = (viewJson.optInt(HEIGHT.key) * zoomFactor).toInt() val params = FrameLayout.LayoutParams(width, height) - params.topMargin = (viewJson.optInt(TOPMARGIN.key, 0) * zoomFactor).toInt() - params.leftMargin = (viewJson.optInt(LEFTMARGIN.key, 0) * zoomFactor).toInt() + params.topMargin = (viewJson.optInt(TOPMARGIN.key) * zoomFactor).toInt() + params.leftMargin = (viewJson.optInt(LEFTMARGIN.key) * zoomFactor).toInt() view.layoutParams = params view.visibility = setVisibility(viewJson.optString(VISIBILITY.key, JsonKeyValues.GONE.key), id.visibility(sp)) when (view) {