Merge pull request #2758 from Philoul/wear/new_custom_watchface

Wear CWF Refactoring and cleanup
This commit is contained in:
Milos Kozak 2023-09-10 17:59:00 +02:00 committed by GitHub
commit ebe64d8678
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 108 deletions

View file

@ -50,11 +50,7 @@ enum class ResFileMap(val fileName: String) {
ARROW_FLAT("ArrowFlat"), ARROW_FLAT("ArrowFlat"),
ARROW_FORTY_FIVE_DOWN("Arrow45Down"), ARROW_FORTY_FIVE_DOWN("Arrow45Down"),
ARROW_SINGLE_DOWN("ArrowSingleDown"), ARROW_SINGLE_DOWN("ArrowSingleDown"),
ARROW_DOUBLE_DOWN("ArrowDoubleDown"), ARROW_DOUBLE_DOWN("ArrowDoubleDown");
FONT1("Font1"),
FONT2("Font2"),
FONT3("Font3"),
FONT4("Font4");
companion object { companion object {
@ -67,7 +63,8 @@ enum class ResFormat(val extension: String) {
SVG("svg"), SVG("svg"),
JPG("jpg"), JPG("jpg"),
PNG("png"), PNG("png"),
TTF("ttf"); TTF("ttf"),
OTF("otf");
companion object { companion object {
@ -106,9 +103,9 @@ data class ResData(val value: ByteArray, val format: ResFormat) {
fun toTypeface(): Typeface? { fun toTypeface(): Typeface? {
try { try {
return when (format) { return when (format) {
ResFormat.TTF -> { ResFormat.TTF, ResFormat.OTF -> {
// Workaround with temporary File, Typeface.createFromFileDescriptor(null, value, 0, value.size) more simple not available // Workaround with temporary File, Typeface.createFromFileDescriptor(null, value, 0, value.size) more simple not available
File.createTempFile("temp", ".ttf").let { tempFile -> File.createTempFile("temp", format.extension).let { tempFile ->
FileOutputStream(tempFile).let { fileOutputStream -> FileOutputStream(tempFile).let { fileOutputStream ->
fileOutputStream.write(value) fileOutputStream.write(value)
fileOutputStream.close() fileOutputStream.close()
@ -134,7 +131,7 @@ data class ResData(val value: ByteArray, val format: ResFormat) {
} }
} }
typealias CwfResDataMap = MutableMap<ResFileMap, ResData> typealias CwfResDataMap = MutableMap<String, ResData>
typealias CwfMetadataMap = MutableMap<CwfMetadataKey, String> typealias CwfMetadataMap = MutableMap<CwfMetadataKey, String>
@Serializable @Serializable
@ -206,71 +203,60 @@ enum class ViewKeys(val key: String, @StringRes val comment: Int) {
COVER_PLATE("cover_plate", R.string.cwf_comment_cover_plate), COVER_PLATE("cover_plate", R.string.cwf_comment_cover_plate),
HOUR_HAND("hour_hand", R.string.cwf_comment_hour_hand), HOUR_HAND("hour_hand", R.string.cwf_comment_hour_hand),
MINUTE_HAND("minute_hand", R.string.cwf_comment_minute_hand), MINUTE_HAND("minute_hand", R.string.cwf_comment_minute_hand),
SECOND_HAND("second_hand", R.string.cwf_comment_second_hand); SECOND_HAND("second_hand", R.string.cwf_comment_second_hand)
} }
enum class JsonKeys(val key: String, val viewType: ViewType, @StringRes val comment: Int?) { enum class JsonKeys(val key: String) {
METADATA("metadata", ViewType.NONE, null), METADATA("metadata"),
ENABLESECOND("enableSecond", ViewType.NONE, null), ENABLESECOND("enableSecond"),
HIGHCOLOR("highColor", ViewType.NONE, null), HIGHCOLOR("highColor"),
MIDCOLOR("midColor", ViewType.NONE, null), MIDCOLOR("midColor"),
LOWCOLOR("lowColor", ViewType.NONE, null), LOWCOLOR("lowColor"),
LOWBATCOLOR("lowBatColor", ViewType.NONE, null), LOWBATCOLOR("lowBatColor"),
CARBCOLOR("carbColor", ViewType.NONE, null), CARBCOLOR("carbColor"),
BASALBACKGROUNDCOLOR("basalBackgroundColor", ViewType.NONE, null), BASALBACKGROUNDCOLOR("basalBackgroundColor"),
BASALCENTERCOLOR("basalCenterColor", ViewType.NONE, null), BASALCENTERCOLOR("basalCenterColor"),
GRIDCOLOR("gridColor", ViewType.NONE, null), GRIDCOLOR("gridColor"),
POINTSIZE("pointSize", ViewType.NONE, null), POINTSIZE("pointSize"),
WIDTH("width", ViewType.ALLVIEWS, null), WIDTH("width"),
HEIGHT("height", ViewType.ALLVIEWS, null), HEIGHT("height"),
TOPMARGIN("topmargin", ViewType.ALLVIEWS, null), TOPMARGIN("topmargin"),
LEFTMARGIN("leftmargin", ViewType.ALLVIEWS, null), LEFTMARGIN("leftmargin"),
ROTATION("rotation", ViewType.TEXTVIEW, null), ROTATION("rotation"),
VISIBILITY("visibility", ViewType.ALLVIEWS, null), VISIBILITY("visibility"),
TEXTSIZE("textsize", ViewType.TEXTVIEW, null), TEXTSIZE("textsize"),
TEXTVALUE("textvalue", ViewType.TEXTVIEW, null), TEXTVALUE("textvalue"),
GRAVITY("gravity", ViewType.TEXTVIEW, null), GRAVITY("gravity"),
FONT("font", ViewType.TEXTVIEW, null), FONT("font"),
FONTSTYLE("fontStyle", ViewType.TEXTVIEW, null), FONTSTYLE("fontStyle"),
FONTCOLOR("fontColor", ViewType.TEXTVIEW, null), FONTCOLOR("fontColor"),
COLOR("color", ViewType.IMAGEVIEW, null), COLOR("color"),
ALLCAPS("allCaps", ViewType.TEXTVIEW, null), ALLCAPS("allCaps"),
DAYNAMEFORMAT("dayNameFormat", ViewType.NONE, null), DAYNAMEFORMAT("dayNameFormat"),
MONTHFORMAT("monthFormat", ViewType.NONE, null) MONTHFORMAT("monthFormat")
} }
enum class JsonKeyValues(val key: String, val jsonKey: JsonKeys) { enum class JsonKeyValues(val key: String) {
GONE("gone", JsonKeys.VISIBILITY), GONE("gone"),
VISIBLE("visible", JsonKeys.VISIBILITY), VISIBLE("visible"),
INVISIBLE("invisible", JsonKeys.VISIBILITY), INVISIBLE("invisible"),
CENTER("center", JsonKeys.GRAVITY), CENTER("center"),
LEFT("left", JsonKeys.GRAVITY), LEFT("left"),
RIGHT("right", JsonKeys.GRAVITY), RIGHT("right"),
SANS_SERIF("sans_serif", JsonKeys.FONT), SANS_SERIF("sans_serif"),
DEFAULT("default", JsonKeys.FONT), DEFAULT("default"),
DEFAULT_BOLD("default_bold", JsonKeys.FONT), DEFAULT_BOLD("default_bold"),
MONOSPACE("monospace", JsonKeys.FONT), MONOSPACE("monospace"),
SERIF("serif", JsonKeys.FONT), SERIF("serif"),
ROBOTO_CONDENSED_BOLD("roboto_condensed_bold", JsonKeys.FONT), ROBOTO_CONDENSED_BOLD("roboto_condensed_bold"),
ROBOTO_CONDENSED_LIGHT("roboto_condensed_light", JsonKeys.FONT), ROBOTO_CONDENSED_LIGHT("roboto_condensed_light"),
ROBOTO_CONDENSED_REGULAR("roboto_condensed_regular", JsonKeys.FONT), ROBOTO_CONDENSED_REGULAR("roboto_condensed_regular"),
ROBOTO_SLAB_LIGHT("roboto_slab_light", JsonKeys.FONT), ROBOTO_SLAB_LIGHT("roboto_slab_light"),
NORMAL("normal", JsonKeys.FONTSTYLE), NORMAL("normal"),
BOLD("bold", JsonKeys.FONTSTYLE), BOLD("bold"),
BOLD_ITALIC("bold_italic", JsonKeys.FONTSTYLE), BOLD_ITALIC("bold_italic"),
ITALIC("italic", JsonKeys.FONTSTYLE), ITALIC("italic"),
BGCOLOR("bgColor", JsonKeys.COLOR), BGCOLOR("bgColor")
FONT1("font1", JsonKeys.FONTCOLOR),
FONT2("font2", JsonKeys.FONTCOLOR),
FONT3("font3", JsonKeys.FONTCOLOR),
FONT4("font4", JsonKeys.FONTCOLOR)
}
enum class ViewType(@StringRes val comment: Int?) {
NONE(null),
TEXTVIEW(null),
IMAGEVIEW(null),
ALLVIEWS(null)
} }
class ZipWatchfaceFormat { class ZipWatchfaceFormat {
@ -309,14 +295,15 @@ class ZipWatchfaceFormat {
val cwfResFileMap = ResFileMap.fromFileName(entryName) val cwfResFileMap = ResFileMap.fromFileName(entryName)
val drawableFormat = ResFormat.fromFileName(entryName) val drawableFormat = ResFormat.fromFileName(entryName)
if (cwfResFileMap != ResFileMap.UNKNOWN && drawableFormat != ResFormat.UNKNOWN) { if (cwfResFileMap != ResFileMap.UNKNOWN && drawableFormat != ResFormat.UNKNOWN) {
resDatas[cwfResFileMap] = ResData(byteArrayOutputStream.toByteArray(), drawableFormat) resDatas[cwfResFileMap.fileName] = ResData(byteArrayOutputStream.toByteArray(), drawableFormat)
} } else if (drawableFormat != ResFormat.UNKNOWN)
resDatas[entryName.substringBeforeLast(".")] = ResData(byteArrayOutputStream.toByteArray(), drawableFormat)
} }
zipEntry = zipInputStream.nextEntry zipEntry = zipInputStream.nextEntry
} }
// Valid CWF file must contains a valid json file with a name within metadata and a custom watchface image // 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) && resDatas.containsKey(ResFileMap.CUSTOM_WATCHFACE)) return if (metadata.containsKey(CwfMetadataKey.CWF_NAME) && resDatas.containsKey(ResFileMap.CUSTOM_WATCHFACE.fileName))
CwfData(json.toString(4), metadata, resDatas) CwfData(json.toString(4), metadata, resDatas)
else else
null null
@ -331,14 +318,14 @@ class ZipWatchfaceFormat {
try { try {
val outputStream = FileOutputStream(file) val outputStream = FileOutputStream(file)
val zipOutputStream = ZipOutputStream(BufferedOutputStream(outputStream)) val zipOutputStream = ZipOutputStream(BufferedOutputStream(outputStream))
val jsonEntry = ZipEntry(CWF_JSON_FILE) val jsonEntry = ZipEntry(CWF_JSON_FILE)
zipOutputStream.putNextEntry(jsonEntry) zipOutputStream.putNextEntry(jsonEntry)
zipOutputStream.write(customWatchface.json.toByteArray()) zipOutputStream.write(customWatchface.json.toByteArray())
zipOutputStream.closeEntry() zipOutputStream.closeEntry()
for (resData in customWatchface.resDatas) { for (resData in customWatchface.resDatas) {
val fileEntry = ZipEntry("${resData.key.fileName}.${resData.value.format.extension}") val fileEntry = ZipEntry("${resData.key}.${resData.value.format.extension}")
zipOutputStream.putNextEntry(fileEntry) zipOutputStream.putNextEntry(fileEntry)
zipOutputStream.write(resData.value.value) zipOutputStream.write(resData.value.value)
zipOutputStream.closeEntry() zipOutputStream.closeEntry()
@ -362,5 +349,4 @@ class ZipWatchfaceFormat {
return metadata return metadata
} }
} }
} }

View file

@ -89,7 +89,7 @@ class CustomWatchfaceImportListActivity: TranslatedDaggerAppCompatActivity() {
override fun onBindViewHolder(holder: CwfFileViewHolder, position: Int) { override fun onBindViewHolder(holder: CwfFileViewHolder, position: Int) {
val customWatchfaceFile = customWatchfaceFileList[position] val customWatchfaceFile = customWatchfaceFileList[position]
val metadata = customWatchfaceFile.metadata val metadata = customWatchfaceFile.metadata
val drawable = customWatchfaceFile.resDatas[ResFileMap.CUSTOM_WATCHFACE]?.toDrawable(resources) val drawable = customWatchfaceFile.resDatas[ResFileMap.CUSTOM_WATCHFACE.fileName]?.toDrawable(resources)
with(holder.customWatchfaceImportListItemBinding) { with(holder.customWatchfaceImportListItemBinding) {
filelistName.text = rh.gs(info.nightscout.shared.R.string.metadata_wear_import_filename, metadata[CWF_FILENAME]) filelistName.text = rh.gs(info.nightscout.shared.R.string.metadata_wear_import_filename, metadata[CWF_FILENAME])
filelistName.tag = customWatchfaceFile filelistName.tag = customWatchfaceFile

View file

@ -111,7 +111,7 @@ class WearFragment : DaggerFragment() {
wearPlugin.savedCustomWatchface?.let { wearPlugin.savedCustomWatchface?.let {
wearPlugin.checkCustomWatchfacePreferences() wearPlugin.checkCustomWatchfacePreferences()
binding.customName.text = rh.gs(R.string.wear_custom_watchface, it.metadata[CwfMetadataKey.CWF_NAME]) binding.customName.text = rh.gs(R.string.wear_custom_watchface, it.metadata[CwfMetadataKey.CWF_NAME])
binding.coverChart.setImageDrawable(it.resDatas[ResFileMap.CUSTOM_WATCHFACE]?.toDrawable(resources)) binding.coverChart.setImageDrawable(it.resDatas[ResFileMap.CUSTOM_WATCHFACE.fileName]?.toDrawable(resources))
binding.infosCustom.visibility = View.VISIBLE binding.infosCustom.visibility = View.VISIBLE
} ?:apply { } ?:apply {
binding.customName.text = rh.gs(R.string.wear_custom_watchface, "") binding.customName.text = rh.gs(R.string.wear_custom_watchface, "")

View file

@ -86,7 +86,7 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() {
wearPlugin.savedCustomWatchface?.let { wearPlugin.savedCustomWatchface?.let {
val cwfAuthorization = 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 metadata = it.metadata
val drawable = it.resDatas[ResFileMap.CUSTOM_WATCHFACE]?.toDrawable(resources) val drawable = it.resDatas[ResFileMap.CUSTOM_WATCHFACE.fileName]?.toDrawable(resources)
binding.customWatchface.setImageDrawable(drawable) binding.customWatchface.setImageDrawable(drawable)
title = rh.gs(CwfMetadataKey.CWF_NAME.label, metadata[CwfMetadataKey.CWF_NAME]) title = rh.gs(CwfMetadataKey.CWF_NAME.label, metadata[CwfMetadataKey.CWF_NAME])
metadata[CwfMetadataKey.CWF_AUTHOR_VERSION]?.let { authorVersion -> metadata[CwfMetadataKey.CWF_AUTHOR_VERSION]?.let { authorVersion ->

View file

@ -301,7 +301,7 @@ class CustomWatchface : BaseWatchFace() {
val metadataMap = ZipWatchfaceFormat.loadMetadata(json) val metadataMap = ZipWatchfaceFormat.loadMetadata(json)
val drawableDataMap: CwfResDataMap = mutableMapOf() val drawableDataMap: CwfResDataMap = mutableMapOf()
getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let { getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let {
drawableDataMap[ResFileMap.CUSTOM_WATCHFACE] = ResData(it, ResFormat.PNG) drawableDataMap[ResFileMap.CUSTOM_WATCHFACE.fileName] = ResData(it, ResFormat.PNG)
} }
return EventData.ActionSetCustomWatchface(CwfData(json.toString(4), metadataMap, drawableDataMap)) return EventData.ActionSetCustomWatchface(CwfData(json.toString(4), metadataMap, drawableDataMap))
} }
@ -489,10 +489,10 @@ class CustomWatchface : BaseWatchFace() {
fun drawable(resources: Resources, drawableDataMap: CwfResDataMap, sgvLevel: Long): Drawable? = customDrawable?.let { cd -> fun drawable(resources: Resources, drawableDataMap: CwfResDataMap, sgvLevel: Long): Drawable? = customDrawable?.let { cd ->
when (sgvLevel) { when (sgvLevel) {
1L -> { drawableDataMap[customHigh]?.toDrawable(resources) ?: drawableDataMap[cd]?.toDrawable(resources) } 1L -> { customHigh?.let {drawableDataMap[customHigh.fileName]}?.toDrawable(resources) ?: drawableDataMap[cd.fileName]?.toDrawable(resources) }
0L -> { drawableDataMap[cd]?.toDrawable(resources) } 0L -> { drawableDataMap[cd.fileName]?.toDrawable(resources) }
-1L -> { drawableDataMap[customLow]?.toDrawable(resources) ?: drawableDataMap[cd]?.toDrawable(resources) } -1L -> { customLow?.let {drawableDataMap[customLow.fileName]}?.toDrawable(resources) ?: drawableDataMap[cd.fileName]?.toDrawable(resources) }
else -> drawableDataMap[cd]?.toDrawable(resources) else -> drawableDataMap[cd.fileName]?.toDrawable(resources)
} }
} }
} }
@ -514,7 +514,7 @@ private enum class TrendArrowMap(val symbol: String, @DrawableRes val icon: Int,
fun drawable(direction: String?, resources: Resources, drawableDataMap: CwfResDataMap): Drawable { fun drawable(direction: String?, resources: Resources, drawableDataMap: CwfResDataMap): Drawable {
val arrow = values().firstOrNull { it.symbol == direction } ?:NONE val arrow = values().firstOrNull { it.symbol == direction } ?:NONE
return drawableDataMap[arrow.customDrawable]?.toDrawable(resources) ?:resources.getDrawable(arrow.icon) return arrow.customDrawable?. let {drawableDataMap[arrow.customDrawable.fileName]}?.toDrawable(resources) ?:resources.getDrawable(arrow.icon)
} }
} }
@ -532,36 +532,35 @@ private enum class GravityMap(val key: String, val gravity: Int) {
} }
} }
private enum class FontMap(val key: String, var font: Typeface, @FontRes val fontRessources: Int?, val customFont: ResFileMap?) { private enum class FontMap(val key: String, var font: Typeface, @FontRes val fontRessources: Int?) {
SANS_SERIF(JsonKeyValues.SANS_SERIF.key, Typeface.SANS_SERIF, null, null), SANS_SERIF(JsonKeyValues.SANS_SERIF.key, Typeface.SANS_SERIF, null),
DEFAULT(JsonKeyValues.DEFAULT.key, Typeface.DEFAULT, null, null), DEFAULT(JsonKeyValues.DEFAULT.key, Typeface.DEFAULT, null),
DEFAULT_BOLD(JsonKeyValues.DEFAULT_BOLD.key, Typeface.DEFAULT_BOLD, null, null), DEFAULT_BOLD(JsonKeyValues.DEFAULT_BOLD.key, Typeface.DEFAULT_BOLD, null),
MONOSPACE(JsonKeyValues.MONOSPACE.key, Typeface.MONOSPACE, null, null), MONOSPACE(JsonKeyValues.MONOSPACE.key, Typeface.MONOSPACE, null),
SERIF(JsonKeyValues.SERIF.key, Typeface.SERIF, null, null), SERIF(JsonKeyValues.SERIF.key, Typeface.SERIF, null),
ROBOTO_CONDENSED_BOLD(JsonKeyValues.ROBOTO_CONDENSED_BOLD.key, Typeface.DEFAULT, R.font.roboto_condensed_bold, 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, null), 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, null), 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, null), ROBOTO_SLAB_LIGHT(JsonKeyValues.ROBOTO_SLAB_LIGHT.key, Typeface.DEFAULT, R.font.roboto_slab_light);
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 { companion object {
fun init(context: Context, resDataMap: CwfResDataMap) = values().forEach { fontMap -> private val customFonts = mutableMapOf<String, Typeface>()
fontMap.customFont?.let { customFont -> fun init(context: Context, resDataMap: CwfResDataMap) {
fontMap.font = Typeface.DEFAULT customFonts.clear()
resDataMap[customFont]?.toTypeface()?.let { resData -> values().forEach { fontMap ->
fontMap.font = resData customFonts[fontMap.key.lowercase()] = fontMap.fontRessources?.let { fontResource ->
}
} ?: run {
fontMap.font = fontMap.fontRessources?.let { fontResource ->
ResourcesCompat.getFont(context, fontResource) ResourcesCompat.getFont(context, fontResource)
} ?: fontMap.font } ?: fontMap.font
} }
resDataMap.filter { (_, resData) ->
resData.format == ResFormat.TTF || resData.format == ResFormat.OTF
}.forEach { (key, resData) ->
customFonts[key.lowercase()] = resData.toTypeface() ?:Typeface.DEFAULT
}
} }
fun font(key: String) = values().firstOrNull { it.key == key }?.font ?: DEFAULT.font
fun font(key: String) = customFonts[key.lowercase()] ?: DEFAULT.font
fun key() = DEFAULT.key fun key() = DEFAULT.key
} }
} }