Merge pull request #2730 from Philoul/wear/new_custom_watchface

Wear CWF : Reduce complexity and add new customized capabilities
This commit is contained in:
Milos Kozak 2023-09-04 10:13:39 +02:00 committed by GitHub
commit c50bdd0f78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 324 additions and 187 deletions

View file

@ -2,10 +2,10 @@ package info.nightscout.rx.weardata
import android.content.res.Resources
import android.graphics.BitmapFactory
import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.PictureDrawable
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.caverock.androidsvg.SVG
import info.nightscout.shared.R
@ -22,53 +22,73 @@ import java.util.zip.ZipOutputStream
val CUSTOM_VERSION = "1.0"
enum class CwfDrawableFileMap(val key: String, @DrawableRes val icon: Int?, val fileName: String) {
UNKNOWN("unknown", null, "Unknown"),
CUSTOM_WATCHFACE("customWatchface", R.drawable.watchface_custom, "CustomWatchface"),
BACKGROUND(ViewKeys.BACKGROUND.key, R.drawable.background, "Background"),
BACKGROUND_HIGH(ViewKeys.BACKGROUND.key, R.drawable.background, "BackgroundHigh"),
BACKGROUND_LOW(ViewKeys.BACKGROUND.key, R.drawable.background, "BackgroundLow"),
COVER_CHART(ViewKeys.COVER_CHART.key, null, "CoverChart"),
COVER_PLATE(ViewKeys.COVER_PLATE.key, R.drawable.simplified_dial, "CoverPlate"),
HOUR_HAND(ViewKeys.HOUR_HAND.key, R.drawable.hour_hand, "HourHand"),
MINUTE_HAND(ViewKeys.MINUTE_HAND.key, R.drawable.minute_hand, "MinuteHand"),
SECOND_HAND(ViewKeys.SECOND_HAND.key, R.drawable.second_hand, "SecondHand");
enum class ResFileMap(val fileName: String) {
UNKNOWN("Unknown"),
CUSTOM_WATCHFACE("CustomWatchface"),
BACKGROUND("Background"),
BACKGROUND_HIGH("BackgroundHigh"),
BACKGROUND_LOW("BackgroundLow"),
COVER_CHART("CoverChart"),
COVER_CHART_HIGH("CoverChartHigh"),
COVER_CHART_LOW("CoverChartLow"),
COVER_PLATE("CoverPlate"),
COVER_PLATE_HIGH("CoverPlateHigh"),
COVER_PLATE_LOW("CoverPlateLow"),
HOUR_HAND("HourHand"),
HOUR_HAND_HIGH("HourHandHigh"),
HOUR_HAND_LOW("HourHandLow"),
MINUTE_HAND("MinuteHand"),
MINUTE_HAND_HIGH("MinuteHandHigh"),
MINUTE_HAND_LOW("MinuteHandLow"),
SECOND_HAND("SecondHand"),
SECOND_HAND_HIGH("SecondHandHigh"),
SECOND_HAND_LOW("SecondHandLow"),
ARROW_NONE("ArrowNone"),
ARROW_DOUBLE_UP("ArrowDoubleUp"),
ARROW_SINGLE_UP("ArrowSingleUp"),
ARROW_FORTY_FIVE_UP("Arrow45Up"),
ARROW_FLAT("ArrowFlat"),
ARROW_FORTY_FIVE_DOWN("Arrow45Down"),
ARROW_SINGLE_DOWN("ArrowSingleDown"),
ARROW_DOUBLE_DOWN("ArrowDoubleDown"),
FONT1("Font1"),
FONT2("Font2"),
FONT3("Font3"),
FONT4("Font4");
companion object {
fun fromKey(key: String): CwfDrawableFileMap =
values().firstOrNull { it.key == key } ?: UNKNOWN
fun fromFileName(file: String): CwfDrawableFileMap = values().firstOrNull { it.fileName == file.substringBeforeLast(".") } ?: UNKNOWN
fun fromFileName(file: String): ResFileMap = values().firstOrNull { it.fileName == file.substringBeforeLast(".") } ?: UNKNOWN
}
}
enum class DrawableFormat(val extension: String) {
enum class ResFormat(val extension: String) {
UNKNOWN(""),
SVG("svg"),
JPG("jpg"),
PNG("png");
PNG("png"),
TTF("ttf");
companion object {
fun fromFileName(fileName: String): DrawableFormat =
values().firstOrNull { it.extension == fileName.substringAfterLast(".") } ?: UNKNOWN
fun fromFileName(fileName: String): ResFormat =
values().firstOrNull { it.extension == fileName.substringAfterLast(".").lowercase() } ?: UNKNOWN
}
}
@Serializable
data class DrawableData(val value: ByteArray, val format: DrawableFormat) {
data class ResData(val value: ByteArray, val format: ResFormat) {
fun toDrawable(resources: Resources): Drawable? {
try {
return when (format) {
DrawableFormat.PNG, DrawableFormat.JPG -> {
ResFormat.PNG, ResFormat.JPG -> {
val bitmap = BitmapFactory.decodeByteArray(value, 0, value.size)
BitmapDrawable(resources, bitmap)
}
DrawableFormat.SVG -> {
ResFormat.SVG -> {
val svg = SVG.getFromInputStream(ByteArrayInputStream(value))
val picture = svg.renderToPicture()
PictureDrawable(picture).apply {
@ -76,7 +96,37 @@ data class DrawableData(val value: ByteArray, val format: DrawableFormat) {
}
}
else -> null
else -> null
}
} catch (e: Exception) {
return null
}
}
fun toTypeface(): Typeface? {
try {
return when (format) {
ResFormat.TTF -> {
// Workaround with temporary File, Typeface.createFromFileDescriptor(null, value, 0, value.size) more simple not available
File.createTempFile("temp", ".ttf").let { tempFile ->
FileOutputStream(tempFile).let { fileOutputStream ->
fileOutputStream.write(value)
fileOutputStream.close()
}
Typeface.createFromFile(tempFile).let {
if (!tempFile.delete()) {
// delete tempfile after usage
}
it
}
}
}
else -> {
null
}
}
} catch (e: Exception) {
return null
@ -84,11 +134,11 @@ data class DrawableData(val value: ByteArray, val format: DrawableFormat) {
}
}
typealias CwfDrawableDataMap = MutableMap<CwfDrawableFileMap, DrawableData>
typealias CwfResDataMap = MutableMap<ResFileMap, ResData>
typealias CwfMetadataMap = MutableMap<CwfMetadataKey, String>
@Serializable
data class CwfData(val json: String, var metadata: CwfMetadataMap, val drawableDatas: CwfDrawableDataMap)
data class CwfData(val json: String, var metadata: CwfMetadataMap, val resDatas: CwfResDataMap)
enum class CwfMetadataKey(val key: String, @StringRes val label: Int, val isPref: Boolean) {
@ -98,7 +148,7 @@ enum class CwfMetadataKey(val key: String, @StringRes val label: Int, val isPref
CWF_CREATED_AT("created_at", R.string.metadata_label_watchface_created_at, false),
CWF_VERSION("cwf_version", R.string.metadata_label_plugin_version, false),
CWF_AUTHOR_VERSION("author_version", R.string.metadata_label_watchface_name_version, false),
CWF_COMMENT("comment", R.string.metadata_label_watchface_comment, false), // label not planed to be used for CWF_COMMENT
CWF_COMMENT("comment", R.string.metadata_label_watchface_infos, false), // label not planed to be used for CWF_COMMENT
CWF_AUTHORIZATION("cwf_authorization", R.string.metadata_label_watchface_authorization, false),
CWF_PREF_WATCH_SHOW_DETAILED_IOB("key_show_detailed_iob", R.string.pref_show_detailed_iob, true),
CWF_PREF_WATCH_SHOW_DETAILED_DELTA("key_show_detailed_delta", R.string.pref_show_detailed_delta, true),
@ -210,7 +260,10 @@ enum class JsonKeyValues(val key: String, val jsonKey: JsonKeys) {
BOLD_ITALIC("bold_italic", JsonKeys.FONTSTYLE),
ITALIC("italic", JsonKeys.FONTSTYLE),
BGCOLOR("bgColor", JsonKeys.COLOR),
BGCOLOR1("bgColor", JsonKeys.FONTCOLOR)
FONT1("font1", JsonKeys.FONTCOLOR),
FONT2("font2", JsonKeys.FONTCOLOR),
FONT3("font3", JsonKeys.FONTCOLOR),
FONT4("font4", JsonKeys.FONTCOLOR)
}
enum class ViewType(@StringRes val comment: Int?) {
@ -229,7 +282,7 @@ class ZipWatchfaceFormat {
fun loadCustomWatchface(cwfFile: File, authorization: Boolean): CwfData? {
var json = JSONObject()
var metadata: CwfMetadataMap = mutableMapOf()
val drawableDatas: CwfDrawableDataMap = mutableMapOf()
val resDatas: CwfResDataMap = mutableMapOf()
try {
val zipInputStream = ZipInputStream(cwfFile.inputStream())
@ -253,18 +306,18 @@ class ZipWatchfaceFormat {
metadata[CwfMetadataKey.CWF_FILENAME] = cwfFile.name
metadata[CwfMetadataKey.CWF_AUTHORIZATION] = authorization.toString()
} else {
val cwfDrawableFileMap = CwfDrawableFileMap.fromFileName(entryName)
val drawableFormat = DrawableFormat.fromFileName(entryName)
if (cwfDrawableFileMap != CwfDrawableFileMap.UNKNOWN && drawableFormat != DrawableFormat.UNKNOWN) {
drawableDatas[cwfDrawableFileMap] = DrawableData(byteArrayOutputStream.toByteArray(), drawableFormat)
val cwfResFileMap = ResFileMap.fromFileName(entryName)
val drawableFormat = ResFormat.fromFileName(entryName)
if (cwfResFileMap != ResFileMap.UNKNOWN && drawableFormat != ResFormat.UNKNOWN) {
resDatas[cwfResFileMap] = ResData(byteArrayOutputStream.toByteArray(), drawableFormat)
}
}
zipEntry = zipInputStream.nextEntry
}
// Valid CWF file must contains a valid json file with a name within metadata and a custom watchface image
return if (metadata.containsKey(CwfMetadataKey.CWF_NAME) && drawableDatas.containsKey(CwfDrawableFileMap.CUSTOM_WATCHFACE))
CwfData(json.toString(4), metadata, drawableDatas)
return if (metadata.containsKey(CwfMetadataKey.CWF_NAME) && resDatas.containsKey(ResFileMap.CUSTOM_WATCHFACE))
CwfData(json.toString(4), metadata, resDatas)
else
null
@ -286,10 +339,10 @@ class ZipWatchfaceFormat {
zipOutputStream.closeEntry()
// Ajouter les fichiers divers au ZIP
for (drawableData in customWatchface.drawableDatas) {
val fileEntry = ZipEntry("${drawableData.key.fileName}.${drawableData.value.format.extension}")
for (resData in customWatchface.resDatas) {
val fileEntry = ZipEntry("${resData.key.fileName}.${resData.value.format.extension}")
zipOutputStream.putNextEntry(fileEntry)
zipOutputStream.write(drawableData.value.value)
zipOutputStream.write(resData.value.value)
zipOutputStream.closeEntry()
}
zipOutputStream.close()

View file

@ -40,7 +40,7 @@
<string name="metadata_wear_import_filename">Failo pavadinimas: %1$s</string>
<string name="metadata_label_plugin_version">Įskiepio versija: %1$s</string>
<string name="metadata_label_watchface_name_version">Pavadinimas: %1$s (%2$s)</string>
<string name="metadata_label_watchface_comment">Komentaras: %1$s</string>
<string name="metadata_label_watchface_info">Komentaras: %1$s</string>
<string name="pref_show_iob">Rodyti AIO</string>
<string name="pref_show_detailed_iob">Rodyti detalų AIO</string>
<string name="pref_show_cob">Rodyti AAO</string>

View file

@ -46,7 +46,7 @@
<string name="metadata_wear_import_filename">File name: %1$s</string>
<string name="metadata_label_plugin_version">Plugin version: %1$s</string>
<string name="metadata_label_watchface_name_version">Name: %1$s (%2$s)</string>
<string name="metadata_label_watchface_comment">Comment: %1$s</string>
<string name="metadata_label_watchface_infos">Info: %1$s</string>
<string name="metadata_label_watchface_authorization" translatable="false">%1$s</string>
<string name="pref_show_iob">Show IOB</string>
<string name="pref_show_detailed_iob">Show detailed IOB</string>

View file

@ -18,7 +18,7 @@ import info.nightscout.rx.events.EventMobileDataToWear
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.CUSTOM_VERSION
import info.nightscout.rx.weardata.CwfData
import info.nightscout.rx.weardata.CwfDrawableFileMap
import info.nightscout.rx.weardata.ResFileMap
import info.nightscout.rx.weardata.CwfMetadataKey.CWF_AUTHOR
import info.nightscout.rx.weardata.CwfMetadataKey.CWF_AUTHOR_VERSION
import info.nightscout.rx.weardata.CwfMetadataKey.CWF_CREATED_AT
@ -89,7 +89,7 @@ class CustomWatchfaceImportListActivity: TranslatedDaggerAppCompatActivity() {
override fun onBindViewHolder(holder: CwfFileViewHolder, position: Int) {
val customWatchfaceFile = customWatchfaceFileList[position]
val metadata = customWatchfaceFile.metadata
val drawable = customWatchfaceFile.drawableDatas[CwfDrawableFileMap
val drawable = customWatchfaceFile.resDatas[ResFileMap
.CUSTOM_WATCHFACE]?.toDrawable(resources)
with(holder.customWatchfaceImportListItemBinding) {
filelistName.text = rh.gs(info.nightscout.shared.R.string.metadata_wear_import_filename, metadata[CWF_FILENAME])

View file

@ -18,7 +18,7 @@ import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.CwfData
import info.nightscout.rx.weardata.CwfDrawableFileMap
import info.nightscout.rx.weardata.ResFileMap
import info.nightscout.rx.weardata.CwfMetadataKey
import info.nightscout.rx.weardata.EventData
import info.nightscout.shared.extensions.toVisibility
@ -111,7 +111,7 @@ class WearFragment : DaggerFragment() {
wearPlugin.savedCustomWatchface?.let {
wearPlugin.checkCustomWatchfacePreferences()
binding.customName.text = rh.gs(R.string.wear_custom_watchface, it.metadata[CwfMetadataKey.CWF_NAME])
binding.coverChart.setImageDrawable(it.drawableDatas[CwfDrawableFileMap.CUSTOM_WATCHFACE]?.toDrawable(resources))
binding.coverChart.setImageDrawable(it.resDatas[ResFileMap.CUSTOM_WATCHFACE]?.toDrawable(resources))
binding.infosCustom.visibility = View.VISIBLE
} ?:apply {
binding.customName.text = rh.gs(R.string.wear_custom_watchface, "")

View file

@ -20,7 +20,7 @@ import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.rx.weardata.CUSTOM_VERSION
import info.nightscout.rx.weardata.CwfDrawableFileMap
import info.nightscout.rx.weardata.ResFileMap
import info.nightscout.rx.weardata.CwfMetadataKey
import info.nightscout.rx.weardata.CwfMetadataMap
import info.nightscout.rx.weardata.JsonKeyValues
@ -86,7 +86,7 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() {
wearPlugin.savedCustomWatchface?.let {
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)
val drawable = it.resDatas[ResFileMap.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 { authorVersion ->

View file

@ -4,12 +4,14 @@ package info.nightscout.androidaps.watchfaces
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Point
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.support.wearable.watchface.WatchFaceStyle
import android.util.TypedValue
import android.view.Gravity
@ -33,12 +35,12 @@ import info.nightscout.androidaps.watchfaces.utils.BaseWatchFace
import info.nightscout.rx.logging.LTag
import info.nightscout.rx.weardata.CUSTOM_VERSION
import info.nightscout.rx.weardata.CwfData
import info.nightscout.rx.weardata.CwfDrawableFileMap
import info.nightscout.rx.weardata.CwfDrawableDataMap
import info.nightscout.rx.weardata.ResFileMap
import info.nightscout.rx.weardata.CwfResDataMap
import info.nightscout.rx.weardata.CwfMetadataKey
import info.nightscout.rx.weardata.CwfMetadataMap
import info.nightscout.rx.weardata.DrawableData
import info.nightscout.rx.weardata.DrawableFormat
import info.nightscout.rx.weardata.ResData
import info.nightscout.rx.weardata.ResFormat
import info.nightscout.rx.weardata.EventData
import info.nightscout.rx.weardata.JsonKeyValues
import info.nightscout.rx.weardata.JsonKeys.*
@ -61,10 +63,11 @@ class CustomWatchface : BaseWatchFace() {
private val TEMPLATE_RESOLUTION = 400
private var lowBatColor = Color.RED
private var bgColor = Color.WHITE
private var resDataMap: CwfResDataMap = mutableMapOf()
override fun onCreate() {
super.onCreate()
FontMap.init(context)
FontMap.init(context, resDataMap)
}
@Suppress("DEPRECATION")
@ -88,7 +91,7 @@ class CustomWatchface : BaseWatchFace() {
@SuppressLint("UseCompatLoadingForDrawables")
override fun setDataFields() {
super.setDataFields()
binding.direction2.setImageDrawable(this.resources.getDrawable(TrendArrowMap.icon(singleBg.slopeArrow)))
binding.direction2.setImageDrawable(TrendArrowMap.drawable(singleBg.slopeArrow, resources, resDataMap))
// rotate the second hand.
binding.secondHand.rotation = TimeOfDay().secondOfMinute * 6f
// rotate the minute hand.
@ -100,7 +103,7 @@ class CustomWatchface : BaseWatchFace() {
override fun setColorDark() {
setWatchfaceStyle()
binding.sgv.setTextColor(bgColor)
binding.direction2.colorFilter = changeDrawableColor(bgColor)
binding.direction2.setColorFilter(changeDrawableColor(bgColor))
if (ageLevel != 1)
binding.timestamp.setTextColor(ContextCompat.getColor(this, R.color.dark_TimestampOld))
@ -128,7 +131,6 @@ class CustomWatchface : BaseWatchFace() {
getString(R.string.hour_minute_second, dateUtil.hourString(), dateUtil.minuteString(), dateUtil.secondString())
else
getString(R.string.hour_minute, dateUtil.hourString(), dateUtil.minuteString())
//binding.time.text = "${dateUtil.hourString()}:${dateUtil.minuteString()}" + if (showSecond) ":${dateUtil.secondString()}" else ""
binding.second.text = dateUtil.secondString()
// rotate the second hand.
binding.secondHand.rotation = TimeOfDay().secondOfMinute * 6f
@ -146,7 +148,8 @@ class CustomWatchface : BaseWatchFace() {
updatePref(it.customWatchfaceData.metadata)
try {
val json = JSONObject(it.customWatchfaceData.json)
val drawableDataMap = it.customWatchfaceData.drawableDatas
resDataMap = it.customWatchfaceData.resDatas
FontMap.init(context, resDataMap)
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))
@ -167,17 +170,10 @@ class CustomWatchface : BaseWatchFace() {
-1L -> lowColor
else -> midColor
}
val backGroundDrawable = when (singleBg.sgvLevel) {
1L -> drawableDataMap[CwfDrawableFileMap.BACKGROUND_HIGH]?.toDrawable(resources) ?: drawableDataMap[CwfDrawableFileMap.BACKGROUND]?.toDrawable(resources)
0L -> drawableDataMap[CwfDrawableFileMap.BACKGROUND]?.toDrawable(resources)
-1L -> drawableDataMap[CwfDrawableFileMap.BACKGROUND_LOW]?.toDrawable(resources) ?: drawableDataMap[CwfDrawableFileMap.BACKGROUND]?.toDrawable(resources)
else -> drawableDataMap[CwfDrawableFileMap.BACKGROUND]?.toDrawable(resources)
}
binding.mainLayout.forEach { view ->
ViewMap.fromId(view.id)?.let { id ->
if (json.has(id.key)) {
val viewJson = json.getJSONObject(id.key)
json.optJSONObject(id.key)?.also { viewJson ->
val width = (viewJson.optInt(WIDTH.key) * zoomFactor).toInt()
val height = (viewJson.optInt(HEIGHT.key) * zoomFactor).toInt()
val params = FrameLayout.LayoutParams(width, height)
@ -202,18 +198,14 @@ class CustomWatchface : BaseWatchFace() {
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))
id.drawable(resources, resDataMap, singleBg.sgvLevel)?.let {
if (viewJson.has(COLOR.key)) // Note only works on bitmap (png or jpg) or xml included into res, not for svg files
it.colorFilter = changeDrawableColor(getColor(viewJson.optString(COLOR.key)))
else
it.clearColorFilter()
view.setImageDrawable(it)
} ?: apply {
view.setImageDrawable(CwfDrawableFileMap.fromKey(id.key).icon?.let { context.getDrawable(it) })
view.setImageDrawable(id.defaultDrawable?.let {resources.getDrawable(it)})
if (viewJson.has(COLOR.key))
view.setColorFilter(getColor(viewJson.optString(COLOR.key)))
else
@ -222,7 +214,7 @@ class CustomWatchface : BaseWatchFace() {
}
}
} else {
} ?:apply {
view.visibility = View.GONE
if (view is TextView) {
view.text = ""
@ -305,9 +297,9 @@ class CustomWatchface : BaseWatchFace() {
}
}
val metadataMap = ZipWatchfaceFormat.loadMetadata(json)
val drawableDataMap: CwfDrawableDataMap = mutableMapOf()
val drawableDataMap: CwfResDataMap = mutableMapOf()
getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let {
drawableDataMap[CwfDrawableFileMap.CUSTOM_WATCHFACE] = DrawableData(it, DrawableFormat.PNG)
drawableDataMap[ResFileMap.CUSTOM_WATCHFACE] = ResData(it, ResFormat.PNG)
}
return EventData.ActionSetCustomWatchface(CwfData(json.toString(4), metadataMap, drawableDataMap))
}
@ -327,7 +319,7 @@ class CustomWatchface : BaseWatchFace() {
JsonKeyValues.VISIBLE.key -> pref.toVisibility()
JsonKeyValues.INVISIBLE.key -> pref.toVisibilityKeepSpace()
JsonKeyValues.GONE.key -> View.GONE
else -> View.GONE
else -> View.GONE
}
private fun getVisibility(visibility: Int): String = when (visibility) {
@ -373,7 +365,11 @@ class CustomWatchface : BaseWatchFace() {
if (color == JsonKeyValues.BGCOLOR.key)
bgColor
else
try { Color.parseColor(color) } catch (e: Exception) { defaultColor }
try {
Color.parseColor(color)
} catch (e: Exception) {
defaultColor
}
private fun manageSpecificViews() {
//Background should fill all the watchface and must be visible
@ -388,41 +384,98 @@ class CustomWatchface : BaseWatchFace() {
// 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),
CHART(ViewKeys.CHART.key, R.id.chart, null),
COVER_CHART(ViewKeys.COVER_CHART.key, R.id.cover_chart, null),
FREETEXT1(ViewKeys.FREETEXT1.key, R.id.freetext1, null),
FREETEXT2(ViewKeys.FREETEXT2.key, R.id.freetext2, null),
FREETEXT3(ViewKeys.FREETEXT3.key, R.id.freetext3, null),
FREETEXT4(ViewKeys.FREETEXT4.key, R.id.freetext4, null),
IOB1(ViewKeys.IOB1.key, R.id.iob1, R.string.key_show_iob),
IOB2(ViewKeys.IOB2.key, R.id.iob2, R.string.key_show_iob),
COB1(ViewKeys.COB1.key, R.id.cob1, R.string.key_show_cob),
COB2(ViewKeys.COB2.key, R.id.cob2, R.string.key_show_cob),
DELTA(ViewKeys.DELTA.key, R.id.delta, R.string.key_show_delta),
AVG_DELTA(ViewKeys.AVG_DELTA.key, R.id.avg_delta, R.string.key_show_avg_delta),
UPLOADER_BATTERY(ViewKeys.UPLOADER_BATTERY.key, R.id.uploader_battery, R.string.key_show_uploader_battery),
RIG_BATTERY(ViewKeys.RIG_BATTERY.key, R.id.rig_battery, R.string.key_show_rig_battery),
BASALRATE(ViewKeys.BASALRATE.key, R.id.basalRate, R.string.key_show_temp_basal),
BGI(ViewKeys.BGI.key, R.id.bgi, R.string.key_show_bgi),
TIME(ViewKeys.TIME.key, R.id.time, null),
HOUR(ViewKeys.HOUR.key, R.id.hour, null),
MINUTE(ViewKeys.MINUTE.key, R.id.minute, null),
SECOND(ViewKeys.SECOND.key, R.id.second, R.string.key_show_seconds),
TIMEPERIOD(ViewKeys.TIMEPERIOD.key, R.id.timePeriod, null),
DAY_NAME(ViewKeys.DAY_NAME.key, R.id.day_name, null),
DAY(ViewKeys.DAY.key, R.id.day, null),
MONTH(ViewKeys.MONTH.key, R.id.month, null),
LOOP(ViewKeys.LOOP.key, R.id.loop, R.string.key_show_external_status),
DIRECTION(ViewKeys.DIRECTION.key, R.id.direction2, R.string.key_show_direction),
TIMESTAMP(ViewKeys.TIMESTAMP.key, R.id.timestamp, R.string.key_show_ago),
SGV(ViewKeys.SGV.key, R.id.sgv, R.string.key_show_bg),
COVER_PLATE(ViewKeys.COVER_PLATE.key, R.id.cover_plate, null),
HOUR_HAND(ViewKeys.HOUR_HAND.key, R.id.hour_hand, null),
MINUTE_HAND(ViewKeys.MINUTE_HAND.key, R.id.minute_hand, null),
SECOND_HAND(ViewKeys.SECOND_HAND.key, R.id.second_hand, R.string.key_show_seconds);
private enum class ViewMap(
val key: String,
@IdRes val id: Int,
@StringRes val pref: Int?,
@IdRes val defaultDrawable: Int?,
val customDrawable: ResFileMap?,
val customHigh:ResFileMap?,
val customLow: ResFileMap?
) {
BACKGROUND(
ViewKeys.BACKGROUND.key,
R.id.background,
null,
info.nightscout.shared.R.drawable.background,
ResFileMap.BACKGROUND,
ResFileMap.BACKGROUND_HIGH,
ResFileMap.BACKGROUND_LOW
),
CHART(ViewKeys.CHART.key, R.id.chart, null, null, null, null, null),
COVER_CHART(
ViewKeys.COVER_CHART.key,
R.id.cover_chart,
null,
null,
ResFileMap.COVER_CHART,
ResFileMap.COVER_CHART_HIGH,
ResFileMap.COVER_CHART_LOW
),
FREETEXT1(ViewKeys.FREETEXT1.key, R.id.freetext1, null, null, null, null, null),
FREETEXT2(ViewKeys.FREETEXT2.key, R.id.freetext2, null, null, null, null, null),
FREETEXT3(ViewKeys.FREETEXT3.key, R.id.freetext3, null, null, null, null, null),
FREETEXT4(ViewKeys.FREETEXT4.key, R.id.freetext4, null, null, null, null, null),
IOB1(ViewKeys.IOB1.key, R.id.iob1, R.string.key_show_iob, null, null, null, null),
IOB2(ViewKeys.IOB2.key, R.id.iob2, R.string.key_show_iob, null, null, null, null),
COB1(ViewKeys.COB1.key, R.id.cob1, R.string.key_show_cob, null, null, null, null),
COB2(ViewKeys.COB2.key, R.id.cob2, R.string.key_show_cob, null, null, null, null),
DELTA(ViewKeys.DELTA.key, R.id.delta, R.string.key_show_delta, null, null, null, null),
AVG_DELTA(ViewKeys.AVG_DELTA.key, R.id.avg_delta, R.string.key_show_avg_delta, null, null, null, null),
UPLOADER_BATTERY(ViewKeys.UPLOADER_BATTERY.key, R.id.uploader_battery, R.string.key_show_uploader_battery, null, null, null, null),
RIG_BATTERY(ViewKeys.RIG_BATTERY.key, R.id.rig_battery, R.string.key_show_rig_battery, null, null, null, null),
BASALRATE(ViewKeys.BASALRATE.key, R.id.basalRate, R.string.key_show_temp_basal, null, null, null, null),
BGI(ViewKeys.BGI.key, R.id.bgi, R.string.key_show_bgi, null, null, null, null),
TIME(ViewKeys.TIME.key, R.id.time, null, null, null, null, null),
HOUR(ViewKeys.HOUR.key, R.id.hour, null, null, null, null, null),
MINUTE(ViewKeys.MINUTE.key, R.id.minute, null, null, null, null, null),
SECOND(ViewKeys.SECOND.key, R.id.second, R.string.key_show_seconds, null, null, null, null),
TIMEPERIOD(ViewKeys.TIMEPERIOD.key, R.id.timePeriod, null, null, null, null, null),
DAY_NAME(ViewKeys.DAY_NAME.key, R.id.day_name, null, null, null, null, null),
DAY(ViewKeys.DAY.key, R.id.day, null, null, null, null, null),
MONTH(ViewKeys.MONTH.key, R.id.month, null, null, null, null, null),
LOOP(ViewKeys.LOOP.key, R.id.loop, R.string.key_show_external_status, null, null, null, null),
DIRECTION(ViewKeys.DIRECTION.key, R.id.direction2, R.string.key_show_direction, null, null, null, null),
TIMESTAMP(ViewKeys.TIMESTAMP.key, R.id.timestamp, R.string.key_show_ago, null, null, null, null),
SGV(ViewKeys.SGV.key, R.id.sgv, R.string.key_show_bg, null, null, null, null),
COVER_PLATE(
ViewKeys.COVER_PLATE.key,
R.id.cover_plate,
null,
null,
ResFileMap.COVER_PLATE,
ResFileMap.COVER_PLATE_HIGH,
ResFileMap.COVER_PLATE_LOW
),
HOUR_HAND(
ViewKeys.HOUR_HAND.key,
R.id.hour_hand,
null,
info.nightscout.shared.R.drawable.hour_hand,
ResFileMap.HOUR_HAND,
ResFileMap.HOUR_HAND_HIGH,
ResFileMap.HOUR_HAND_LOW
),
MINUTE_HAND(
ViewKeys.MINUTE_HAND.key,
R.id.minute_hand,
null,
info.nightscout.shared.R.drawable.minute_hand,
ResFileMap.MINUTE_HAND,
ResFileMap.MINUTE_HAND_HIGH,
ResFileMap.MINUTE_HAND_LOW
),
SECOND_HAND(
ViewKeys.SECOND_HAND.key,
R.id.second_hand,
R.string.key_show_seconds,
info.nightscout.shared.R.drawable.second_hand,
ResFileMap.SECOND_HAND,
ResFileMap.SECOND_HAND_HIGH,
ResFileMap.SECOND_HAND_LOW
);
companion object {
@ -431,85 +484,116 @@ class CustomWatchface : BaseWatchFace() {
fun visibility(sp: SP): Boolean = this.pref?.let { sp.getBoolean(it, true) }
?: true
}
private enum class TrendArrowMap(val symbol: String, @DrawableRes val icon: Int) {
NONE("??", R.drawable.ic_invalid),
TRIPLE_UP("X", R.drawable.ic_doubleup),
DOUBLE_UP("\u21c8", R.drawable.ic_doubleup),
SINGLE_UP("\u2191", R.drawable.ic_singleup),
FORTY_FIVE_UP("\u2197", R.drawable.ic_fortyfiveup),
FLAT("\u2192", R.drawable.ic_flat),
FORTY_FIVE_DOWN("\u2198", R.drawable.ic_fortyfivedown),
SINGLE_DOWN("\u2193", R.drawable.ic_singledown),
DOUBLE_DOWN("\u21ca", R.drawable.ic_doubledown),
TRIPLE_DOWN("X", R.drawable.ic_doubledown);
companion object {
fun icon(direction: String?) = values().firstOrNull { it.symbol == direction }?.icon ?: NONE.icon
fun drawable(resources: Resources, drawableDataMap: CwfResDataMap, sgvLevel: Long): Drawable? = customDrawable?.let { cd ->
when (sgvLevel) {
1L -> { drawableDataMap[customHigh]?.toDrawable(resources) ?: drawableDataMap[cd]?.toDrawable(resources) }
0L -> { drawableDataMap[cd]?.toDrawable(resources) }
-1L -> { drawableDataMap[customLow]?.toDrawable(resources) ?: drawableDataMap[cd]?.toDrawable(resources) }
else -> drawableDataMap[cd]?.toDrawable(resources)
}
}
}
private enum class GravityMap(val key: String, val gravity: Int) {
CENTER(JsonKeyValues.CENTER.key, Gravity.CENTER),
LEFT(JsonKeyValues.LEFT.key, Gravity.LEFT),
RIGHT(JsonKeyValues.RIGHT.key, Gravity.RIGHT);
companion object {
fun gravity(key: String?) = values().firstOrNull { it.key == key }?.gravity ?: CENTER.gravity
fun key(gravity: Int) = values().firstOrNull { it.gravity == gravity }?.key ?: CENTER.key
}
}
private enum class FontMap(val key: String, var font: Typeface, @FontRes val fontRessources: Int?) {
SANS_SERIF(JsonKeyValues.SANS_SERIF.key, Typeface.SANS_SERIF, null),
DEFAULT(JsonKeyValues.DEFAULT.key, Typeface.DEFAULT, null),
DEFAULT_BOLD(JsonKeyValues.DEFAULT_BOLD.key, Typeface.DEFAULT_BOLD, null),
MONOSPACE(JsonKeyValues.MONOSPACE.key, Typeface.MONOSPACE, null),
SERIF(JsonKeyValues.SERIF.key, Typeface.SERIF, null),
ROBOTO_CONDENSED_BOLD(JsonKeyValues.ROBOTO_CONDENSED_BOLD.key, Typeface.DEFAULT, R.font.roboto_condensed_bold),
ROBOTO_CONDENSED_LIGHT(JsonKeyValues.ROBOTO_CONDENSED_LIGHT.key, Typeface.DEFAULT, R.font.roboto_condensed_light),
ROBOTO_CONDENSED_REGULAR(JsonKeyValues.ROBOTO_CONDENSED_REGULAR.key, Typeface.DEFAULT, R.font.roboto_condensed_regular),
ROBOTO_SLAB_LIGHT(JsonKeyValues.ROBOTO_SLAB_LIGHT.key, Typeface.DEFAULT, R.font.roboto_slab_light);
companion object {
fun init(context: Context) = values().forEach { it.font = it.fontRessources?.let { font -> ResourcesCompat.getFont(context, font) } ?: it.font }
fun font(key: String) = values().firstOrNull { it.key == key }?.font ?: DEFAULT.font
fun key() = DEFAULT.key
}
}
private enum class StyleMap(val key: String, val style: Int) {
NORMAL(JsonKeyValues.NORMAL.key, Typeface.NORMAL),
BOLD(JsonKeyValues.BOLD.key, Typeface.BOLD),
BOLD_ITALIC(JsonKeyValues.BOLD_ITALIC.key, Typeface.BOLD_ITALIC),
ITALIC(JsonKeyValues.ITALIC.key, Typeface.ITALIC);
companion object {
fun style(key: String?) = values().firstOrNull { it.key == key }?.style ?: NORMAL.style
fun key(style: Int) = values().firstOrNull { it.style == style }?.key ?: NORMAL.key
}
}
// This class containt mapping between keys used within json of Custom Watchface and preferences
private enum class PrefMap(val key: String, @StringRes val prefKey: Int) {
SHOW_IOB(CwfMetadataKey.CWF_PREF_WATCH_SHOW_IOB.key, R.string.key_show_iob),
SHOW_DETAILED_IOB(CwfMetadataKey.CWF_PREF_WATCH_SHOW_DETAILED_IOB.key, R.string.key_show_detailed_iob),
SHOW_COB(CwfMetadataKey.CWF_PREF_WATCH_SHOW_COB.key, R.string.key_show_cob),
SHOW_DELTA(CwfMetadataKey.CWF_PREF_WATCH_SHOW_DELTA.key, R.string.key_show_delta),
SHOW_AVG_DELTA(CwfMetadataKey.CWF_PREF_WATCH_SHOW_AVG_DELTA.key, R.string.key_show_avg_delta),
SHOW_DETAILED_DELTA(CwfMetadataKey.CWF_PREF_WATCH_SHOW_DETAILED_DELTA.key, R.string.key_show_detailed_delta),
SHOW_UPLOADER_BATTERY(CwfMetadataKey.CWF_PREF_WATCH_SHOW_UPLOADER_BATTERY.key, R.string.key_show_uploader_battery),
SHOW_RIG_BATTERY(CwfMetadataKey.CWF_PREF_WATCH_SHOW_RIG_BATTERY.key, R.string.key_show_rig_battery),
SHOW_TEMP_BASAL(CwfMetadataKey.CWF_PREF_WATCH_SHOW_TEMP_BASAL.key, R.string.key_show_temp_basal),
SHOW_DIRECTION(CwfMetadataKey.CWF_PREF_WATCH_SHOW_DIRECTION.key, R.string.key_show_direction),
SHOW_AGO(CwfMetadataKey.CWF_PREF_WATCH_SHOW_AGO.key, R.string.key_show_ago),
SHOW_BG(CwfMetadataKey.CWF_PREF_WATCH_SHOW_BG.key, R.string.key_show_bg),
SHOW_BGI(CwfMetadataKey.CWF_PREF_WATCH_SHOW_BGI.key, R.string.key_show_bgi),
SHOW_LOOP_STATUS(CwfMetadataKey.CWF_PREF_WATCH_SHOW_LOOP_STATUS.key, R.string.key_show_external_status)
}
}
private enum class TrendArrowMap(val symbol: String, @DrawableRes val icon: Int,val customDrawable: ResFileMap?) {
NONE("??", R.drawable.ic_invalid, ResFileMap.ARROW_NONE),
TRIPLE_UP("X", R.drawable.ic_doubleup, ResFileMap.ARROW_DOUBLE_UP),
DOUBLE_UP("\u21c8", R.drawable.ic_doubleup, ResFileMap.ARROW_DOUBLE_UP),
SINGLE_UP("\u2191", R.drawable.ic_singleup, ResFileMap.ARROW_SINGLE_UP),
FORTY_FIVE_UP("\u2197", R.drawable.ic_fortyfiveup, ResFileMap.ARROW_FORTY_FIVE_UP),
FLAT("\u2192", R.drawable.ic_flat, ResFileMap.ARROW_FLAT),
FORTY_FIVE_DOWN("\u2198", R.drawable.ic_fortyfivedown, ResFileMap.ARROW_FORTY_FIVE_DOWN),
SINGLE_DOWN("\u2193", R.drawable.ic_singledown, ResFileMap.ARROW_SINGLE_DOWN),
DOUBLE_DOWN("\u21ca", R.drawable.ic_doubledown, ResFileMap.ARROW_DOUBLE_DOWN),
TRIPLE_DOWN("X", R.drawable.ic_doubledown, ResFileMap.ARROW_DOUBLE_DOWN);
companion object {
fun drawable(direction: String?, resources: Resources, drawableDataMap: CwfResDataMap): Drawable {
val arrow = values().firstOrNull { it.symbol == direction } ?:NONE
return drawableDataMap[arrow.customDrawable]?.toDrawable(resources) ?:resources.getDrawable(arrow.icon)
}
}
}
private enum class GravityMap(val key: String, val gravity: Int) {
CENTER(JsonKeyValues.CENTER.key, Gravity.CENTER),
LEFT(JsonKeyValues.LEFT.key, Gravity.LEFT),
RIGHT(JsonKeyValues.RIGHT.key, Gravity.RIGHT);
companion object {
fun gravity(key: String?) = values().firstOrNull { it.key == key }?.gravity ?: CENTER.gravity
fun key(gravity: Int) = values().firstOrNull { it.gravity == gravity }?.key ?: CENTER.key
}
}
private enum class FontMap(val key: String, var font: Typeface, @FontRes val fontRessources: Int?, val customFont: ResFileMap?) {
SANS_SERIF(JsonKeyValues.SANS_SERIF.key, Typeface.SANS_SERIF, null, null),
DEFAULT(JsonKeyValues.DEFAULT.key, Typeface.DEFAULT, null, null),
DEFAULT_BOLD(JsonKeyValues.DEFAULT_BOLD.key, Typeface.DEFAULT_BOLD, null, null),
MONOSPACE(JsonKeyValues.MONOSPACE.key, Typeface.MONOSPACE, null, null),
SERIF(JsonKeyValues.SERIF.key, Typeface.SERIF, null, null),
ROBOTO_CONDENSED_BOLD(JsonKeyValues.ROBOTO_CONDENSED_BOLD.key, Typeface.DEFAULT, R.font.roboto_condensed_bold, null),
ROBOTO_CONDENSED_LIGHT(JsonKeyValues.ROBOTO_CONDENSED_LIGHT.key, Typeface.DEFAULT, R.font.roboto_condensed_light, null),
ROBOTO_CONDENSED_REGULAR(JsonKeyValues.ROBOTO_CONDENSED_REGULAR.key, Typeface.DEFAULT, R.font.roboto_condensed_regular, null),
ROBOTO_SLAB_LIGHT(JsonKeyValues.ROBOTO_SLAB_LIGHT.key, Typeface.DEFAULT, R.font.roboto_slab_light, null),
FONT1(JsonKeyValues.FONT1.key, Typeface.DEFAULT, null, ResFileMap.FONT1),
FONT2(JsonKeyValues.FONT2.key, Typeface.DEFAULT, null, ResFileMap.FONT2),
FONT3(JsonKeyValues.FONT3.key, Typeface.DEFAULT, null, ResFileMap.FONT3),
FONT4(JsonKeyValues.FONT4.key, Typeface.DEFAULT, null, ResFileMap.FONT4);
companion object {
fun init(context: Context, resDataMap: CwfResDataMap) = values().forEach { fontMap ->
fontMap.customFont?.let { customFont ->
fontMap.font = Typeface.DEFAULT
resDataMap[customFont]?.toTypeface()?.let { resData ->
fontMap.font = resData
}
} ?: run {
fontMap.font = fontMap.fontRessources?.let { fontResource ->
ResourcesCompat.getFont(context, fontResource)
} ?: fontMap.font
}
}
fun font(key: String) = values().firstOrNull { it.key == key }?.font ?: DEFAULT.font
fun key() = DEFAULT.key
}
}
private enum class StyleMap(val key: String, val style: Int) {
NORMAL(JsonKeyValues.NORMAL.key, Typeface.NORMAL),
BOLD(JsonKeyValues.BOLD.key, Typeface.BOLD),
BOLD_ITALIC(JsonKeyValues.BOLD_ITALIC.key, Typeface.BOLD_ITALIC),
ITALIC(JsonKeyValues.ITALIC.key, Typeface.ITALIC);
companion object {
fun style(key: String?) = values().firstOrNull { it.key == key }?.style ?: NORMAL.style
fun key(style: Int) = values().firstOrNull { it.style == style }?.key ?: NORMAL.key
}
}
// This class containt mapping between keys used within json of Custom Watchface and preferences
private enum class PrefMap(val key: String, @StringRes val prefKey: Int) {
SHOW_IOB(CwfMetadataKey.CWF_PREF_WATCH_SHOW_IOB.key, R.string.key_show_iob),
SHOW_DETAILED_IOB(CwfMetadataKey.CWF_PREF_WATCH_SHOW_DETAILED_IOB.key, R.string.key_show_detailed_iob),
SHOW_COB(CwfMetadataKey.CWF_PREF_WATCH_SHOW_COB.key, R.string.key_show_cob),
SHOW_DELTA(CwfMetadataKey.CWF_PREF_WATCH_SHOW_DELTA.key, R.string.key_show_delta),
SHOW_AVG_DELTA(CwfMetadataKey.CWF_PREF_WATCH_SHOW_AVG_DELTA.key, R.string.key_show_avg_delta),
SHOW_DETAILED_DELTA(CwfMetadataKey.CWF_PREF_WATCH_SHOW_DETAILED_DELTA.key, R.string.key_show_detailed_delta),
SHOW_UPLOADER_BATTERY(CwfMetadataKey.CWF_PREF_WATCH_SHOW_UPLOADER_BATTERY.key, R.string.key_show_uploader_battery),
SHOW_RIG_BATTERY(CwfMetadataKey.CWF_PREF_WATCH_SHOW_RIG_BATTERY.key, R.string.key_show_rig_battery),
SHOW_TEMP_BASAL(CwfMetadataKey.CWF_PREF_WATCH_SHOW_TEMP_BASAL.key, R.string.key_show_temp_basal),
SHOW_DIRECTION(CwfMetadataKey.CWF_PREF_WATCH_SHOW_DIRECTION.key, R.string.key_show_direction),
SHOW_AGO(CwfMetadataKey.CWF_PREF_WATCH_SHOW_AGO.key, R.string.key_show_ago),
SHOW_BG(CwfMetadataKey.CWF_PREF_WATCH_SHOW_BG.key, R.string.key_show_bg),
SHOW_BGI(CwfMetadataKey.CWF_PREF_WATCH_SHOW_BGI.key, R.string.key_show_bgi),
SHOW_LOOP_STATUS(CwfMetadataKey.CWF_PREF_WATCH_SHOW_LOOP_STATUS.key, R.string.key_show_external_status)
}