Refactor Custom Watchface code (simplified)

This commit is contained in:
Philoul 2023-08-06 09:58:52 +02:00
parent b259425361
commit 26dff28e1c
22 changed files with 204 additions and 296 deletions

View file

@ -1,3 +1,5 @@
package info.nightscout.rx.events package info.nightscout.rx.events
class EventWearUpdateGui : Event() import info.nightscout.rx.weardata.CustomWatchfaceData
class EventWearUpdateGui(val customWatchfaceData: CustomWatchfaceData? = null, val exportFile: Boolean = false) : Event()

View file

@ -1,27 +1,30 @@
package info.nightscout.rx.weardata package info.nightscout.rx.weardata
import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Parcelable
import android.util.Xml
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import info.nightscout.shared.R import info.nightscout.shared.R
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.json.JSONObject import org.json.JSONObject
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
enum class CustomWatchfaceDrawableDataKey(val key: String, @DrawableRes val icon: Int?, val fileName: String) { enum class CustomWatchfaceDrawableDataKey(val key: String, @DrawableRes val icon: Int?, val fileName: String) {
UNKNOWN("unknown", null,"Unknown"), UNKNOWN("unknown", null, "Unknown"),
CUSTOM_WATCHFACE("customWatchface", R.drawable.watchface_custom, "CustomWatchface"), CUSTOM_WATCHFACE("customWatchface", R.drawable.watchface_custom, "CustomWatchface"),
BACKGROUND("background", R.drawable.background, "Background"), BACKGROUND("background", R.drawable.background, "Background"),
COVERCHART("cover_chart", null,"CoverChart"), COVERCHART("cover_chart", null, "CoverChart"),
COVERPLATE("cover_plate", R.drawable.simplified_dial, "CoverPlate"), COVERPLATE("cover_plate", R.drawable.simplified_dial, "CoverPlate"),
HOURHAND("hour_hand", R.drawable.hour_hand,"HourHand"), HOURHAND("hour_hand", R.drawable.hour_hand, "HourHand"),
MINUTEHAND("minute_hand", R.drawable.minute_hand,"MinuteHand"), MINUTEHAND("minute_hand", R.drawable.minute_hand, "MinuteHand"),
SECONDHAND("second_hand", R.drawable.second_hand, "SecondHand"); SECONDHAND("second_hand", R.drawable.second_hand, "SecondHand");
companion object { companion object {
@ -36,14 +39,14 @@ enum class CustomWatchfaceDrawableDataKey(val key: String, @DrawableRes val icon
fun fromKey(key: String): CustomWatchfaceDrawableDataKey = fun fromKey(key: String): CustomWatchfaceDrawableDataKey =
if (keyToEnumMap.containsKey(key)) { if (keyToEnumMap.containsKey(key)) {
keyToEnumMap[key] ?:UNKNOWN keyToEnumMap[key] ?: UNKNOWN
} else { } else {
UNKNOWN UNKNOWN
} }
fun fromFileName(file: String): CustomWatchfaceDrawableDataKey = fun fromFileName(file: String): CustomWatchfaceDrawableDataKey =
if (fileNameToEnumMap.containsKey(file.substringBeforeLast("."))) { if (fileNameToEnumMap.containsKey(file.substringBeforeLast("."))) {
fileNameToEnumMap[file.substringBeforeLast(".")] ?:UNKNOWN fileNameToEnumMap[file.substringBeforeLast(".")] ?: UNKNOWN
} else { } else {
UNKNOWN UNKNOWN
} }
@ -53,8 +56,9 @@ enum class CustomWatchfaceDrawableDataKey(val key: String, @DrawableRes val icon
enum class DrawableFormat(val extension: String) { enum class DrawableFormat(val extension: String) {
UNKNOWN(""), UNKNOWN(""),
//XML("xml"), //XML("xml"),
//svg("svg"), //SVG("svg"),
JPG("jpg"), JPG("jpg"),
PNG("png"); PNG("png");
@ -68,7 +72,7 @@ enum class DrawableFormat(val extension: String) {
fun fromFileName(fileName: String): DrawableFormat = fun fromFileName(fileName: String): DrawableFormat =
if (extensionToEnumMap.containsKey(fileName.substringAfterLast("."))) { if (extensionToEnumMap.containsKey(fileName.substringAfterLast("."))) {
extensionToEnumMap[fileName.substringAfterLast(".")] ?:UNKNOWN extensionToEnumMap[fileName.substringAfterLast(".")] ?: UNKNOWN
} else { } else {
UNKNOWN UNKNOWN
} }
@ -85,15 +89,20 @@ data class DrawableData(val value: ByteArray, val format: DrawableFormat) {
val bitmap = BitmapFactory.decodeByteArray(value, 0, value.size) val bitmap = BitmapFactory.decodeByteArray(value, 0, value.size)
BitmapDrawable(resources, bitmap) BitmapDrawable(resources, bitmap)
} }
/* /*
DrawableFormat.XML -> { DrawableFormat.SVG -> {
val xmlInputStream = ByteArrayInputStream(value) //TODO: include svg to Drawable convertor here
val xmlPullParser = Xml.newPullParser() null
xmlPullParser.setInput(xmlInputStream, null) }
Drawable.createFromXml(resources, xmlPullParser) DrawableFormat.XML -> {
} // Always return a null Drawable, even if xml file is a valid xml vector file
*/ val xmlInputStream = ByteArrayInputStream(value)
else -> null val xmlPullParser = Xml.newPullParser()
xmlPullParser.setInput(xmlInputStream, null)
Drawable.createFromXml(resources, xmlPullParser)
}
*/
else -> null
} }
} catch (e: Exception) { } catch (e: Exception) {
return null return null
@ -104,18 +113,13 @@ data class DrawableData(val value: ByteArray, val format: DrawableFormat) {
typealias CustomWatchfaceDrawableDataMap = MutableMap<CustomWatchfaceDrawableDataKey, DrawableData> typealias CustomWatchfaceDrawableDataMap = MutableMap<CustomWatchfaceDrawableDataKey, DrawableData>
typealias CustomWatchfaceMetadataMap = MutableMap<CustomWatchfaceMetadataKey, String> typealias CustomWatchfaceMetadataMap = MutableMap<CustomWatchfaceMetadataKey, String>
data class CustomWatchface(val json: String, var metadata: CustomWatchfaceMetadataMap, val drawableDatas: CustomWatchfaceDrawableDataMap) @Serializable
data class CustomWatchfaceData(val json: String, var metadata: CustomWatchfaceMetadataMap, val drawableDatas: CustomWatchfaceDrawableDataMap)
interface CustomWatchfaceFormat {
fun saveCustomWatchface(file: File, customWatchface: EventData.ActionSetCustomWatchface)
fun loadCustomWatchface(cwfFile: File): CustomWatchface?
fun loadMetadata(contents: JSONObject): CustomWatchfaceMetadataMap
}
enum class CustomWatchfaceMetadataKey(val key: String, @StringRes val label: Int) { enum class CustomWatchfaceMetadataKey(val key: String, @StringRes val label: Int) {
CWF_NAME("name", R.string.metadata_label_watchface_name), CWF_NAME("name", R.string.metadata_label_watchface_name),
CWF_FILENAME("filename", R.string.metadata_wear_import_filename),
CWF_AUTHOR("author", R.string.metadata_label_watchface_author), CWF_AUTHOR("author", R.string.metadata_label_watchface_author),
CWF_CREATED_AT("created_at", R.string.metadata_label_watchface_created_at), CWF_CREATED_AT("created_at", R.string.metadata_label_watchface_created_at),
CWF_VERSION("cwf_version", R.string.metadata_label_watchface_version); CWF_VERSION("cwf_version", R.string.metadata_label_watchface_version);
@ -137,4 +141,100 @@ enum class CustomWatchfaceMetadataKey(val key: String, @StringRes val label: Int
} }
}
class ZipWatchfaceFormat {
companion object {
const val CUSTOM_WF_EXTENTION = ".zip"
const val CUSTOM_JSON_FILE = "CustomWatchface.json"
fun loadCustomWatchface(cwfFile: File): CustomWatchfaceData? {
var json = JSONObject()
var metadata: CustomWatchfaceMetadataMap = mutableMapOf()
val drawableDatas: CustomWatchfaceDrawableDataMap = mutableMapOf()
try {
val zipInputStream = ZipInputStream(cwfFile.inputStream())
var zipEntry: ZipEntry? = zipInputStream.nextEntry
while (zipEntry != null) {
val entryName = zipEntry.name
val buffer = ByteArray(2048)
val byteArrayOutputStream = ByteArrayOutputStream()
var count = zipInputStream.read(buffer)
while (count != -1) {
byteArrayOutputStream.write(buffer, 0, count)
count = zipInputStream.read(buffer)
}
zipInputStream.closeEntry()
if (entryName == CUSTOM_JSON_FILE) {
val jsonString = byteArrayOutputStream.toByteArray().toString(Charsets.UTF_8)
json = JSONObject(jsonString)
metadata = loadMetadata(json)
metadata[CustomWatchfaceMetadataKey.CWF_FILENAME] = cwfFile.name
} else {
val customWatchfaceDrawableDataKey = CustomWatchfaceDrawableDataKey.fromFileName(entryName)
val drawableFormat = DrawableFormat.fromFileName(entryName)
if (customWatchfaceDrawableDataKey != CustomWatchfaceDrawableDataKey.UNKNOWN && drawableFormat != DrawableFormat.UNKNOWN) {
drawableDatas[customWatchfaceDrawableDataKey] = DrawableData(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
if (metadata.containsKey(CustomWatchfaceMetadataKey.CWF_NAME) && drawableDatas.containsKey(CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE))
return CustomWatchfaceData(json.toString(4), metadata, drawableDatas)
else
return null
} catch (e: Exception) {
return null
}
}
fun saveCustomWatchface(file: File, customWatchface: CustomWatchfaceData) {
try {
val outputStream = FileOutputStream(file)
val zipOutputStream = ZipOutputStream(BufferedOutputStream(outputStream))
// Ajouter le fichier JSON au ZIP
val jsonEntry = ZipEntry(CUSTOM_JSON_FILE)
zipOutputStream.putNextEntry(jsonEntry)
zipOutputStream.write(customWatchface.json.toByteArray())
zipOutputStream.closeEntry()
// Ajouter les fichiers divers au ZIP
for (drawableData in customWatchface.drawableDatas) {
val fileEntry = ZipEntry("${drawableData.key.fileName}.${drawableData.value.format.extension}")
zipOutputStream.putNextEntry(fileEntry)
zipOutputStream.write(drawableData.value.value)
zipOutputStream.closeEntry()
}
zipOutputStream.close()
outputStream.close()
} catch (_: Exception) {
}
}
fun loadMetadata(contents: JSONObject): CustomWatchfaceMetadataMap {
val metadata: CustomWatchfaceMetadataMap = mutableMapOf()
if (contents.has("metadata")) {
val meta = contents.getJSONObject("metadata")
for (key in meta.keys()) {
val metaKey = CustomWatchfaceMetadataKey.fromKey(key)
if (metaKey != null) {
metadata[metaKey] = meta.getString(key)
}
}
}
return metadata
}
}
} }

View file

@ -14,15 +14,15 @@ sealed class EventData : Event() {
fun serialize() = Json.encodeToString(serializer(), this) fun serialize() = Json.encodeToString(serializer(), this)
@ExperimentalSerializationApi
fun serializeByte() = ProtoBuf.encodeToByteArray(serializer(), this) fun serializeByte() = ProtoBuf.encodeToByteArray(serializer(), this)
companion object { companion object {
fun deserialize(json: String) = try { fun deserialize(json: String) = try {
Json.decodeFromString(serializer(), json) Json.decodeFromString(serializer(), json)
} catch (ignored: Exception) { } catch (ignored: Exception) {
Error(System.currentTimeMillis()) Error(System.currentTimeMillis())
} }
@ExperimentalSerializationApi
fun deserializeByte(byteArray: ByteArray) = try { fun deserializeByte(byteArray: ByteArray) = try {
ProtoBuf.decodeFromByteArray(serializer(), byteArray) ProtoBuf.decodeFromByteArray(serializer(), byteArray)
} catch (ignored: Exception) { } catch (ignored: Exception) {
@ -153,7 +153,7 @@ sealed class EventData : Event() {
@Serializable @Serializable
data class ActionGetCustomWatchface( data class ActionGetCustomWatchface(
val customWatchface: ActionSetCustomWatchface, val customWatchface: ActionSetCustomWatchface,
val exportFile: Boolean val exportFile: Boolean = false
) : EventData() ) : EventData()
@Serializable @Serializable
@ -283,9 +283,7 @@ sealed class EventData : Event() {
} }
@Serializable @Serializable
data class ActionSetCustomWatchface( data class ActionSetCustomWatchface(
val name: String, val customWatchfaceData: CustomWatchfaceData
val json: String,
val drawableDataMap: CustomWatchfaceDrawableDataMap
) : EventData() ) : EventData()
@Serializable @Serializable

View file

@ -40,10 +40,10 @@
<string name="waiting_for_disconnection">Waiting for disconnection</string> <string name="waiting_for_disconnection">Waiting for disconnection</string>
<!-- Custom Watchface --> <!-- Custom Watchface -->
<string name="key_custom_watchface" translatable="false">key_custom_watchface</string>
<string name="metadata_label_watchface_created_at">Created at: %1$s</string> <string name="metadata_label_watchface_created_at">Created at: %1$s</string>
<string name="metadata_label_watchface_author">Author: %1$s</string> <string name="metadata_label_watchface_author">Author: %1$s</string>
<string name="metadata_label_watchface_name">Name: %1$s</string> <string name="metadata_label_watchface_name">Name: %1$s</string>
<string name="metadata_wear_import_filename">File name: %1$s</string>
<string name="metadata_label_watchface_version">Watchface version: %1$s</string> <string name="metadata_label_watchface_version">Watchface version: %1$s</string>
<string name="wear_default_watchface">Default Watchface</string> <string name="wear_default_watchface">Default Watchface</string>

View file

@ -1,21 +0,0 @@
package info.nightscout.interfaces.maintenance
import android.os.Parcelable
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataMap
import info.nightscout.rx.weardata.CustomWatchfaceMetadataMap
import info.nightscout.rx.weardata.DrawableData
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
import java.io.File
data class CustomWatchfaceFile(
val name: String,
val file: File,
val baseDir: File,
val json: String,
val metadata: @RawValue CustomWatchfaceMetadataMap,
val drawableFiles: @RawValue CustomWatchfaceDrawableDataMap
)

View file

@ -2,7 +2,7 @@ package info.nightscout.interfaces.maintenance
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.CustomWatchfaceData
interface ImportExportPrefs { interface ImportExportPrefs {
@ -11,7 +11,7 @@ interface ImportExportPrefs {
fun importSharedPreferences(fragment: Fragment) fun importSharedPreferences(fragment: Fragment)
fun importCustomWatchface(activity: FragmentActivity) fun importCustomWatchface(activity: FragmentActivity)
fun importCustomWatchface(fragment: Fragment) fun importCustomWatchface(fragment: Fragment)
fun exportCustomWatchface(customWatchface: EventData.ActionSetCustomWatchface) fun exportCustomWatchface(customWatchface: CustomWatchfaceData)
fun prefsFileExists(): Boolean fun prefsFileExists(): Boolean
fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable) fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable)
fun exportSharedPreferences(f: Fragment) fun exportSharedPreferences(f: Fragment)

View file

@ -1,5 +1,6 @@
package info.nightscout.interfaces.maintenance package info.nightscout.interfaces.maintenance
import info.nightscout.rx.weardata.CustomWatchfaceData
import java.io.File import java.io.File
interface PrefFileListProvider { interface PrefFileListProvider {
@ -12,7 +13,7 @@ interface PrefFileListProvider {
fun newExportCsvFile(): File fun newExportCsvFile(): File
fun newCwfFile(filename: String): File fun newCwfFile(filename: String): File
fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList<PrefsFile> fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList<PrefsFile>
fun listCustomWatchfaceFiles(): MutableList<CustomWatchfaceFile> fun listCustomWatchfaceFiles(): MutableList<CustomWatchfaceData>
fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata> fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata>
fun formatExportedAgo(utcTime: String): String fun formatExportedAgo(utcTime: String): String
} }

View file

@ -14,7 +14,6 @@ import info.nightscout.configuration.maintenance.PrefFileListProviderImpl
import info.nightscout.configuration.maintenance.activities.CustomWatchfaceImportListActivity import info.nightscout.configuration.maintenance.activities.CustomWatchfaceImportListActivity
import info.nightscout.configuration.maintenance.activities.LogSettingActivity import info.nightscout.configuration.maintenance.activities.LogSettingActivity
import info.nightscout.configuration.maintenance.activities.PrefImportListActivity import info.nightscout.configuration.maintenance.activities.PrefImportListActivity
import info.nightscout.configuration.maintenance.formats.ZipCustomWatchfaceFormat
import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat
import info.nightscout.interfaces.AndroidPermission import info.nightscout.interfaces.AndroidPermission
import info.nightscout.interfaces.ConfigBuilder import info.nightscout.interfaces.ConfigBuilder
@ -37,7 +36,6 @@ abstract class ConfigurationModule {
@ContributesAndroidInjector abstract fun contributesCsvExportWorker(): ImportExportPrefsImpl.CsvExportWorker @ContributesAndroidInjector abstract fun contributesCsvExportWorker(): ImportExportPrefsImpl.CsvExportWorker
@ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity @ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
@ContributesAndroidInjector abstract fun contributesCustomWatchfaceImportListActivity(): CustomWatchfaceImportListActivity @ContributesAndroidInjector abstract fun contributesCustomWatchfaceImportListActivity(): CustomWatchfaceImportListActivity
@ContributesAndroidInjector abstract fun contributesZipCustomWatchfaceFormat(): ZipCustomWatchfaceFormat
@ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat @ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat
@ContributesAndroidInjector abstract fun prefImportListProviderInjector(): PrefFileListProvider @ContributesAndroidInjector abstract fun prefImportListProviderInjector(): PrefFileListProvider

View file

@ -22,7 +22,6 @@ import dagger.android.HasAndroidInjector
import info.nightscout.configuration.R import info.nightscout.configuration.R
import info.nightscout.configuration.activities.DaggerAppCompatActivityWithResult import info.nightscout.configuration.activities.DaggerAppCompatActivityWithResult
import info.nightscout.configuration.maintenance.dialogs.PrefImportSummaryDialog import info.nightscout.configuration.maintenance.dialogs.PrefImportSummaryDialog
import info.nightscout.configuration.maintenance.formats.ZipCustomWatchfaceFormat
import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat
import info.nightscout.core.ui.dialogs.OKDialog import info.nightscout.core.ui.dialogs.OKDialog
import info.nightscout.core.ui.dialogs.TwoMessagesAlertDialog import info.nightscout.core.ui.dialogs.TwoMessagesAlertDialog
@ -56,7 +55,9 @@ import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventDiaconnG8PumpLogReset import info.nightscout.rx.events.EventDiaconnG8PumpLogReset
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey
import info.nightscout.rx.weardata.ZipWatchfaceFormat
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
@ -86,8 +87,7 @@ class ImportExportPrefsImpl @Inject constructor(
private val prefFileList: PrefFileListProvider, private val prefFileList: PrefFileListProvider,
private val uel: UserEntryLogger, private val uel: UserEntryLogger,
private val dateUtil: DateUtil, private val dateUtil: DateUtil,
private val uiInteraction: UiInteraction, private val uiInteraction: UiInteraction
private val customWatchfaceCWFFormat: ZipCustomWatchfaceFormat
) : ImportExportPrefs { ) : ImportExportPrefs {
override fun prefsFileExists(): Boolean { override fun prefsFileExists(): Boolean {
@ -315,10 +315,10 @@ class ImportExportPrefsImpl @Inject constructor(
} }
} }
override fun exportCustomWatchface(customWatchface: EventData.ActionSetCustomWatchface) { override fun exportCustomWatchface(customWatchface: CustomWatchfaceData) {
prefFileList.ensureExportDirExists() prefFileList.ensureExportDirExists()
val newFile = prefFileList.newCwfFile(customWatchface.name) val newFile = prefFileList.newCwfFile(customWatchface.metadata[CustomWatchfaceMetadataKey.CWF_FILENAME] ?:"")
customWatchfaceCWFFormat.saveCustomWatchface(newFile,customWatchface) ZipWatchfaceFormat.saveCustomWatchface(newFile, customWatchface)
} }
override fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) { override fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) {

View file

@ -6,10 +6,8 @@ import dagger.Lazy
import dagger.Reusable import dagger.Reusable
import info.nightscout.androidaps.annotations.OpenForTesting import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.configuration.R import info.nightscout.configuration.R
import info.nightscout.configuration.maintenance.formats.ZipCustomWatchfaceFormat
import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat
import info.nightscout.interfaces.Config import info.nightscout.interfaces.Config
import info.nightscout.interfaces.maintenance.CustomWatchfaceFile
import info.nightscout.interfaces.maintenance.PrefFileListProvider import info.nightscout.interfaces.maintenance.PrefFileListProvider
import info.nightscout.interfaces.maintenance.PrefMetadata import info.nightscout.interfaces.maintenance.PrefMetadata
import info.nightscout.interfaces.maintenance.PrefMetadataMap import info.nightscout.interfaces.maintenance.PrefMetadataMap
@ -19,6 +17,8 @@ import info.nightscout.interfaces.maintenance.PrefsMetadataKey
import info.nightscout.interfaces.maintenance.PrefsStatus import info.nightscout.interfaces.maintenance.PrefsStatus
import info.nightscout.interfaces.storage.Storage import info.nightscout.interfaces.storage.Storage
import info.nightscout.interfaces.versionChecker.VersionCheckerUtils import info.nightscout.interfaces.versionChecker.VersionCheckerUtils
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.ZipWatchfaceFormat
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import org.joda.time.DateTime import org.joda.time.DateTime
import org.joda.time.Days import org.joda.time.Days
@ -36,7 +36,6 @@ class PrefFileListProviderImpl @Inject constructor(
private val rh: ResourceHelper, private val rh: ResourceHelper,
private val config: Lazy<Config>, private val config: Lazy<Config>,
private val encryptedPrefsFormat: EncryptedPrefsFormat, private val encryptedPrefsFormat: EncryptedPrefsFormat,
private val customWatchfaceCWFFormat: ZipCustomWatchfaceFormat,
private val storage: Storage, private val storage: Storage,
private val versionCheckerUtils: VersionCheckerUtils, private val versionCheckerUtils: VersionCheckerUtils,
context: Context context: Context
@ -91,14 +90,14 @@ class PrefFileListProviderImpl @Inject constructor(
return prefFiles return prefFiles
} }
override fun listCustomWatchfaceFiles(): MutableList<CustomWatchfaceFile> { override fun listCustomWatchfaceFiles(): MutableList<CustomWatchfaceData> {
val customWatchfaceFiles = mutableListOf<CustomWatchfaceFile>() val customWatchfaceFiles = mutableListOf<CustomWatchfaceData>()
// searching dedicated dir, only for new CWF format // searching dedicated dir, only for new CWF format
exportsPath.walk().filter { it.isFile && it.name.endsWith(ZipCustomWatchfaceFormat.CUSTOM_WF_EXTENTION) }.forEach { file -> exportsPath.walk().filter { it.isFile && it.name.endsWith(ZipWatchfaceFormat.CUSTOM_WF_EXTENTION) }.forEach { file ->
// Here loadCustomWatchface will unzip, check and load CustomWatchface // Here loadCustomWatchface will unzip, check and load CustomWatchface
customWatchfaceCWFFormat.loadCustomWatchface(file)?.also { customWatchface -> ZipWatchfaceFormat.loadCustomWatchface(file)?.also { customWatchface ->
customWatchfaceFiles.add(CustomWatchfaceFile(file.name, file, exportsPath, customWatchface.json, customWatchface.metadata, customWatchface.drawableDatas)) customWatchfaceFiles.add(customWatchface)
} }
} }
@ -147,7 +146,7 @@ class PrefFileListProviderImpl @Inject constructor(
} }
override fun newCwfFile(filename: String): File { override fun newCwfFile(filename: String): File {
val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss")) val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss"))
return File(exportsPath, "${filename}_$timeLocal${ZipCustomWatchfaceFormat.CUSTOM_WF_EXTENTION}") return File(exportsPath, "${filename}_$timeLocal${ZipWatchfaceFormat.CUSTOM_WF_EXTENTION}")
} }
// check metadata for known issues, change their status and add info with explanations // check metadata for known issues, change their status and add info with explanations

View file

@ -10,7 +10,6 @@ import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity
import info.nightscout.interfaces.maintenance.CustomWatchfaceFile
import info.nightscout.interfaces.maintenance.PrefFileListProvider import info.nightscout.interfaces.maintenance.PrefFileListProvider
import info.nightscout.configuration.databinding.CustomWatchfaceImportListActivityBinding import info.nightscout.configuration.databinding.CustomWatchfaceImportListActivityBinding
import info.nightscout.configuration.R import info.nightscout.configuration.R
@ -18,6 +17,7 @@ import info.nightscout.configuration.databinding.CustomWatchfaceImportListItemBi
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventMobileDataToWear import info.nightscout.rx.events.EventMobileDataToWear
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey.* import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey.*
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.EventData
@ -51,7 +51,7 @@ class CustomWatchfaceImportListActivity: TranslatedDaggerAppCompatActivity() {
binding.recyclerview.adapter = RecyclerViewAdapter(prefFileListProvider.listCustomWatchfaceFiles()) binding.recyclerview.adapter = RecyclerViewAdapter(prefFileListProvider.listCustomWatchfaceFiles())
} }
inner class RecyclerViewAdapter internal constructor(private var customWatchfaceFileList: List<CustomWatchfaceFile>) : RecyclerView.Adapter<RecyclerViewAdapter.PrefFileViewHolder>() { inner class RecyclerViewAdapter internal constructor(private var customWatchfaceFileList: List<CustomWatchfaceData>) : RecyclerView.Adapter<RecyclerViewAdapter.PrefFileViewHolder>() {
inner class PrefFileViewHolder(val customWatchfaceImportListItemBinding: CustomWatchfaceImportListItemBinding) : RecyclerView.ViewHolder(customWatchfaceImportListItemBinding.root) { inner class PrefFileViewHolder(val customWatchfaceImportListItemBinding: CustomWatchfaceImportListItemBinding) : RecyclerView.ViewHolder(customWatchfaceImportListItemBinding.root) {
@ -59,14 +59,12 @@ class CustomWatchfaceImportListActivity: TranslatedDaggerAppCompatActivity() {
with(customWatchfaceImportListItemBinding) { with(customWatchfaceImportListItemBinding) {
root.isClickable = true root.isClickable = true
customWatchfaceImportListItemBinding.root.setOnClickListener { customWatchfaceImportListItemBinding.root.setOnClickListener {
val customWatchfaceFile = filelistName.tag as CustomWatchfaceFile val customWatchfaceFile = filelistName.tag as CustomWatchfaceData
val customWF = EventData.ActionSetCustomWatchface(customWatchfaceFile.metadata[CWF_NAME] ?:"", customWatchfaceFile.json, customWatchfaceFile.drawableFiles) val customWF = EventData.ActionSetCustomWatchface(customWatchfaceFile)
sp.putString(info.nightscout.shared.R.string.key_custom_watchface, customWF.serialize())
val i = Intent() val i = Intent()
setResult(FragmentActivity.RESULT_OK, i) setResult(FragmentActivity.RESULT_OK, i)
//rxBus.send(EventWearUpdateGui(customWatchfaceFile))
rxBus.send(EventMobileDataToWear(customWF)) rxBus.send(EventMobileDataToWear(customWF))
aapsLogger.debug("XXXXX EventMobileDataToWear sent")
finish() finish()
} }
} }
@ -85,13 +83,12 @@ class CustomWatchfaceImportListActivity: TranslatedDaggerAppCompatActivity() {
override fun onBindViewHolder(holder: PrefFileViewHolder, position: Int) { override fun onBindViewHolder(holder: PrefFileViewHolder, position: Int) {
val customWatchfaceFile = customWatchfaceFileList[position] val customWatchfaceFile = customWatchfaceFileList[position]
val metadata = customWatchfaceFile.metadata val metadata = customWatchfaceFile.metadata
val drawable = customWatchfaceFile.drawableFiles[CustomWatchfaceDrawableDataKey val drawable = customWatchfaceFile.drawableDatas[CustomWatchfaceDrawableDataKey
.CUSTOM_WATCHFACE]?.toDrawable(resources) .CUSTOM_WATCHFACE]?.toDrawable(resources)
with(holder.customWatchfaceImportListItemBinding) { with(holder.customWatchfaceImportListItemBinding) {
filelistName.text = rh.gs(R.string.wear_import_filename, customWatchfaceFile.file.name) filelistName.text = rh.gs(info.nightscout.shared.R.string.metadata_wear_import_filename, metadata[CWF_FILENAME])
filelistName.tag = customWatchfaceFile filelistName.tag = customWatchfaceFile
customWatchface.setImageDrawable(drawable) customWatchface.setImageDrawable(drawable)
filelistDir.text = rh.gs(R.string.wear_import_directory, customWatchfaceFile.file.parentFile?.absolutePath)
customName.text = rh.gs(CWF_NAME.label, metadata[CWF_NAME]) customName.text = rh.gs(CWF_NAME.label, metadata[CWF_NAME])
author.text = rh.gs(CWF_AUTHOR.label, metadata[CWF_AUTHOR] ?:"") author.text = rh.gs(CWF_AUTHOR.label, metadata[CWF_AUTHOR] ?:"")
createdAt.text = rh.gs(CWF_CREATED_AT.label, metadata[CWF_CREATED_AT] ?:"") createdAt.text = rh.gs(CWF_CREATED_AT.label, metadata[CWF_CREATED_AT] ?:"")

View file

@ -1,128 +0,0 @@
package info.nightscout.configuration.maintenance.formats
import info.nightscout.core.utils.CryptoUtil
import info.nightscout.interfaces.storage.Storage
import info.nightscout.rx.weardata.CustomWatchface
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataMap
import info.nightscout.rx.weardata.CustomWatchfaceFormat
import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey
import info.nightscout.rx.weardata.CustomWatchfaceMetadataMap
import info.nightscout.rx.weardata.DrawableData
import info.nightscout.rx.weardata.DrawableFormat
import info.nightscout.rx.weardata.EventData
import info.nightscout.shared.interfaces.ResourceHelper
import org.json.JSONObject
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ZipCustomWatchfaceFormat @Inject constructor(
private var rh: ResourceHelper,
private var cryptoUtil: CryptoUtil,
private var storage: Storage
) : CustomWatchfaceFormat {
companion object {
const val CUSTOM_WF_EXTENTION = ".zip"
const val CUSTOM_JSON_FILE = "CustomWatchface.json"
}
override fun loadCustomWatchface(cwfFile: File): CustomWatchface? {
var json = JSONObject()
var metadata: CustomWatchfaceMetadataMap = mutableMapOf()
val drawableDatas: CustomWatchfaceDrawableDataMap = mutableMapOf()
try {
val zipInputStream = ZipInputStream(cwfFile.inputStream())
var zipEntry: ZipEntry? = zipInputStream.nextEntry
while (zipEntry != null) {
val entryName = zipEntry.name
val buffer = ByteArray(2048)
val byteArrayOutputStream = ByteArrayOutputStream()
var count = zipInputStream.read(buffer)
while (count != -1) {
byteArrayOutputStream.write(buffer, 0, count)
count = zipInputStream.read(buffer)
}
zipInputStream.closeEntry()
if (entryName == CUSTOM_JSON_FILE) {
val jsonString = byteArrayOutputStream.toByteArray().toString(Charsets.UTF_8)
json = JSONObject(jsonString)
metadata = loadMetadata(json)
} else {
val customWatchfaceDrawableDataKey = CustomWatchfaceDrawableDataKey.fromFileName(entryName)
val drawableFormat = DrawableFormat.fromFileName(entryName)
if (customWatchfaceDrawableDataKey != CustomWatchfaceDrawableDataKey.UNKNOWN && drawableFormat != DrawableFormat.UNKNOWN) {
drawableDatas[customWatchfaceDrawableDataKey] = DrawableData(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
if (metadata.containsKey(CustomWatchfaceMetadataKey.CWF_NAME) && drawableDatas.containsKey(CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE))
return CustomWatchface(json.toString(4), metadata, drawableDatas)
else
return null
} catch (e: Exception) {
return null
}
}
override fun saveCustomWatchface(file: File, customWatchface: EventData.ActionSetCustomWatchface) {
try {
val outputStream = FileOutputStream(file)
val zipOutputStream = ZipOutputStream(BufferedOutputStream(outputStream))
// Ajouter le fichier JSON au ZIP
val jsonEntry = ZipEntry(CUSTOM_JSON_FILE)
zipOutputStream.putNextEntry(jsonEntry)
zipOutputStream.write(customWatchface.json.toByteArray())
zipOutputStream.closeEntry()
// Ajouter les fichiers divers au ZIP
for (drawableData in customWatchface.drawableDataMap) {
val fileEntry = ZipEntry("${drawableData.key.fileName}.${drawableData.value.format.extension}")
zipOutputStream.putNextEntry(fileEntry)
zipOutputStream.write(drawableData.value.value)
zipOutputStream.closeEntry()
}
zipOutputStream.close()
outputStream.close()
} catch (e: Exception) {
}
}
override fun loadMetadata(contents: JSONObject): CustomWatchfaceMetadataMap {
val metadata: CustomWatchfaceMetadataMap = mutableMapOf()
if (contents.has("metadata")) {
val meta = contents.getJSONObject("metadata")
for (key in meta.keys()) {
val metaKey = CustomWatchfaceMetadataKey.fromKey(key)
if (metaKey != null) {
metadata[metaKey] = meta.getString(key)
}
}
}
return metadata
}
}

View file

@ -59,23 +59,6 @@
android:textColor="?attr/importListFileNameColor" android:textColor="?attr/importListFileNameColor"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<TextView
android:id="@+id/filelist_dir"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="0dp"
android:ellipsize="none"
android:maxLines="2"
android:paddingStart="0dp"
android:paddingEnd="10dp"
android:scrollHorizontally="false"
android:text="File dir here"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/importListFileNameColor"
android:textSize="11sp"
tools:ignore="HardcodedText" />
<TextView <TextView
android:id="@+id/filelist_name" android:id="@+id/filelist_name"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -166,8 +166,6 @@
<!-- Custom Watchface --> <!-- Custom Watchface -->
<string name="wear_import_custom_watchface_title">Select Custom Watchface</string> <string name="wear_import_custom_watchface_title">Select Custom Watchface</string>
<string name="wear_import_directory" comment="placeholder is for imported watchface path">Directory: %1$s</string>
<string name="wear_import_filename">File name: %1$s</string>
<!-- Permissions --> <!-- Permissions -->
<string name="alert_dialog_storage_permission_text">Please reboot your phone or restart AAPS from the System Settings \notherwise Android APS will not have logging (important to track and verify that the algorithms are working correctly)!</string> <string name="alert_dialog_storage_permission_text">Please reboot your phone or restart AAPS from the System Settings \notherwise Android APS will not have logging (important to track and verify that the algorithms are working correctly)!</string>

View file

@ -17,6 +17,7 @@ import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.events.EventWearUpdateGui import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.EventData
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
@ -61,13 +62,11 @@ class WearFragment : DaggerFragment() {
} }
} }
binding.defaultCustom.setOnClickListener { binding.defaultCustom.setOnClickListener {
sp.remove(info.nightscout.shared.R.string.key_custom_watchface)
wearPlugin.savedCustomWatchface = null
rxBus.send(EventMobileToWear(EventData.ActionrequestSetDefaultWatchface(dateUtil.now()))) rxBus.send(EventMobileToWear(EventData.ActionrequestSetDefaultWatchface(dateUtil.now())))
updateGui() updateGui()
} }
binding.sendCustom.setOnClickListener { binding.sendCustom.setOnClickListener {
wearPlugin.savedCustomWatchface?.let { cwf -> rxBus.send(EventMobileDataToWear(cwf)) } wearPlugin.savedCustomWatchface?.let { cwf -> rxBus.send(EventMobileDataToWear(EventData.ActionSetCustomWatchface(cwf))) }
} }
binding.exportCustom.setOnClickListener { binding.exportCustom.setOnClickListener {
wearPlugin.savedCustomWatchface?.let { importExportPrefs.exportCustomWatchface(it) } wearPlugin.savedCustomWatchface?.let { importExportPrefs.exportCustomWatchface(it) }
@ -80,16 +79,11 @@ class WearFragment : DaggerFragment() {
disposable += rxBus disposable += rxBus
.toObservable(EventWearUpdateGui::class.java) .toObservable(EventWearUpdateGui::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventMobileDataToWear::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ .subscribe({
loadCustom(it.payload) it.customWatchfaceData?.let { wearPlugin.savedCustomWatchface = it }
wearPlugin.customWatchfaceSerialized = "" if (it.exportFile)
wearPlugin.savedCustomWatchface = null ToastUtils.okToast(context,rh.gs(R.string.wear_new_custom_watchface_received))
updateGui() updateGui()
ToastUtils.okToast(context,rh.gs(R.string.wear_new_custom_watchface_received))
}, fabricPrivacy::logException) }, fabricPrivacy::logException)
if (wearPlugin.savedCustomWatchface == null) if (wearPlugin.savedCustomWatchface == null)
rxBus.send(EventMobileToWear(EventData.ActionrequestCustomWatchface(false))) rxBus.send(EventMobileToWear(EventData.ActionrequestCustomWatchface(false)))
@ -110,24 +104,10 @@ class WearFragment : DaggerFragment() {
private fun updateGui() { private fun updateGui() {
_binding ?: return _binding ?: return
sp.getString(info.nightscout.shared.R.string.key_custom_watchface, "").let {
if (it != wearPlugin.customWatchfaceSerialized && it != "") {
aapsLogger.debug("XXXXX Serialisation: ${it.length}")
try {
wearPlugin.savedCustomWatchface = (EventData.deserialize(it) as EventData.ActionSetCustomWatchface)
wearPlugin.customWatchfaceSerialized = it
}
catch(e: Exception) {
wearPlugin.customWatchfaceSerialized = ""
wearPlugin.savedCustomWatchface = null
}
}
sp.remove(info.nightscout.shared.R.string.key_custom_watchface)
}
wearPlugin.savedCustomWatchface?.let { wearPlugin.savedCustomWatchface?.let {
binding.customName.text = rh.gs(R.string.wear_custom_watchface, it.name) binding.customName.text = rh.gs(R.string.wear_custom_watchface, it.metadata[CustomWatchfaceMetadataKey.CWF_NAME])
binding.sendCustom.visibility = View.VISIBLE binding.sendCustom.visibility = View.VISIBLE
binding.coverChart.setImageDrawable(it.drawableDataMap[CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE]?.toDrawable(resources)) binding.coverChart.setImageDrawable(it.drawableDatas[CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE]?.toDrawable(resources))
} ?:apply { } ?:apply {
binding.customName.text = rh.gs(R.string.wear_custom_watchface, rh.gs(info.nightscout.shared.R.string.wear_default_watchface)) binding.customName.text = rh.gs(R.string.wear_custom_watchface, rh.gs(info.nightscout.shared.R.string.wear_default_watchface))
binding.sendCustom.visibility = View.INVISIBLE binding.sendCustom.visibility = View.INVISIBLE
@ -137,7 +117,6 @@ class WearFragment : DaggerFragment() {
} }
private fun loadCustom(cwf: EventData.ActionSetCustomWatchface) { private fun loadCustom(cwf: EventData.ActionSetCustomWatchface) {
aapsLogger.debug("XXXXX EventWearCwfExported received") wearPlugin.savedCustomWatchface = cwf.customWatchfaceData
wearPlugin.savedCustomWatchface = cwf
} }
} }

View file

@ -18,6 +18,7 @@ import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.events.EventOverviewBolusProgress import info.nightscout.rx.events.EventOverviewBolusProgress
import info.nightscout.rx.events.EventPreferenceChange import info.nightscout.rx.events.EventPreferenceChange
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.EventData
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
@ -55,7 +56,7 @@ class WearPlugin @Inject constructor(
var connectedDevice = "---" var connectedDevice = "---"
var customWatchfaceSerialized = "" var customWatchfaceSerialized = ""
var savedCustomWatchface: EventData.ActionSetCustomWatchface? = null var savedCustomWatchface: CustomWatchfaceData? = null
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()

View file

@ -60,7 +60,6 @@ import info.nightscout.plugins.R
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventMobileToWear import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.events.EventWearCwfExported
import info.nightscout.rx.events.EventWearUpdateGui import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
@ -1262,14 +1261,10 @@ class DataHandlerMobile @Inject constructor(
private fun handleGetCustomWatchface(command: EventData.ActionGetCustomWatchface) { private fun handleGetCustomWatchface(command: EventData.ActionGetCustomWatchface) {
val customWatchface = command.customWatchface val customWatchface = command.customWatchface
aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${command.sourceNodeId}: ${customWatchface.json}") aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${command.sourceNodeId}: ${customWatchface.customWatchfaceData.json}")
//Update Wear Fragment rxBus.send(EventWearUpdateGui(customWatchface.customWatchfaceData, command.exportFile))
sp.putString(info.nightscout.shared.R.string.key_custom_watchface, customWatchface.serialize())
//rxBus.send(EventWearCwfExported(customWatchface))
rxBus.send(EventWearUpdateGui())
if (command.exportFile) if (command.exportFile)
importExportPrefs.exportCustomWatchface(customWatchface) importExportPrefs.exportCustomWatchface(customWatchface.customWatchfaceData)
//Implement here a record within SP and a save within exports subfolder as zipFile
} }

View file

@ -45,6 +45,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await import kotlinx.coroutines.tasks.await
import kotlinx.serialization.ExperimentalSerializationApi
import javax.inject.Inject import javax.inject.Inject
class DataLayerListenerServiceMobile : WearableListenerService() { class DataLayerListenerServiceMobile : WearableListenerService() {
@ -82,7 +83,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
private val rxPath get() = getString(info.nightscout.shared.R.string.path_rx_bridge) private val rxPath get() = getString(info.nightscout.shared.R.string.path_rx_bridge)
private val rxDataPath get() = getString(info.nightscout.shared.R.string.path_rx_data_bridge) private val rxDataPath get() = getString(info.nightscout.shared.R.string.path_rx_data_bridge)
@ExperimentalSerializationApi
override fun onCreate() { override fun onCreate() {
AndroidInjection.inject(this) AndroidInjection.inject(this)
super.onCreate() super.onCreate()
@ -131,7 +132,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
} }
super.onDataChanged(dataEvents) super.onDataChanged(dataEvents)
} }
@ExperimentalSerializationApi
override fun onMessageReceived(messageEvent: MessageEvent) { override fun onMessageReceived(messageEvent: MessageEvent) {
super.onMessageReceived(messageEvent) super.onMessageReceived(messageEvent)

View file

@ -186,6 +186,9 @@ class DataHandlerWear @Inject constructor(
.subscribe { .subscribe {
aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${it.sourceNodeId}") aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${it.sourceNodeId}")
persistence.store(it) persistence.store(it)
persistence.readCustomWatchface()?.let {
rxBus.send(EventWearDataToMobile(EventData.ActionGetCustomWatchface(it, false)))
}
} }
disposable += rxBus disposable += rxBus
.toObservable(EventData.ActionrequestSetDefaultWatchface::class.java) .toObservable(EventData.ActionrequestSetDefaultWatchface::class.java)
@ -193,6 +196,9 @@ class DataHandlerWear @Inject constructor(
.subscribe { .subscribe {
aapsLogger.debug(LTag.WEAR, "Set Default Watchface received from ${it.sourceNodeId}") aapsLogger.debug(LTag.WEAR, "Set Default Watchface received from ${it.sourceNodeId}")
persistence.setDefaultWatchface() persistence.setDefaultWatchface()
persistence.readCustomWatchface()?.let {
rxBus.send(EventWearDataToMobile(EventData.ActionGetCustomWatchface(it, false)))
}
} }
disposable += rxBus disposable += rxBus
.toObservable(EventData.ActionrequestCustomWatchface::class.java) .toObservable(EventData.ActionrequestCustomWatchface::class.java)

View file

@ -22,6 +22,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.tasks.await import kotlinx.coroutines.tasks.await
import kotlinx.serialization.ExperimentalSerializationApi
import javax.inject.Inject import javax.inject.Inject
class DataLayerListenerServiceWear : WearableListenerService() { class DataLayerListenerServiceWear : WearableListenerService() {
@ -45,7 +46,7 @@ class DataLayerListenerServiceWear : WearableListenerService() {
private val rxPath get() = getString(info.nightscout.shared.R.string.path_rx_bridge) private val rxPath get() = getString(info.nightscout.shared.R.string.path_rx_bridge)
private val rxDataPath get() = getString(info.nightscout.shared.R.string.path_rx_data_bridge) private val rxDataPath get() = getString(info.nightscout.shared.R.string.path_rx_data_bridge)
@ExperimentalSerializationApi
override fun onCreate() { override fun onCreate() {
AndroidInjection.inject(this) AndroidInjection.inject(this)
super.onCreate() super.onCreate()
@ -95,7 +96,7 @@ class DataLayerListenerServiceWear : WearableListenerService() {
} }
super.onDataChanged(dataEvents) super.onDataChanged(dataEvents)
} }
@ExperimentalSerializationApi
override fun onMessageReceived(messageEvent: MessageEvent) { override fun onMessageReceived(messageEvent: MessageEvent) {
super.onMessageReceived(messageEvent) super.onMessageReceived(messageEvent)

View file

@ -172,7 +172,7 @@ open class Persistence @Inject constructor(
fun store(customWatchface: EventData.ActionSetCustomWatchface, isdefault: Boolean = false) { fun store(customWatchface: EventData.ActionSetCustomWatchface, isdefault: Boolean = false) {
putString(if (isdefault) CUSTOM_DEFAULT_WATCHFACE else CUSTOM_WATCHFACE, customWatchface.serialize()) putString(if (isdefault) CUSTOM_DEFAULT_WATCHFACE else CUSTOM_WATCHFACE, customWatchface.serialize())
aapsLogger.debug(LTag.WEAR, "Stored Custom Watchface ${customWatchface.name} ${isdefault}: $customWatchface") aapsLogger.debug(LTag.WEAR, "Stored Custom Watchface ${customWatchface.customWatchfaceData} ${isdefault}: $customWatchface")
} }
fun setDefaultWatchface() { fun setDefaultWatchface() {

View file

@ -26,12 +26,14 @@ import androidx.viewbinding.ViewBinding
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.ActivityCustomBinding import info.nightscout.androidaps.databinding.ActivityCustomBinding
import info.nightscout.androidaps.watchfaces.utils.BaseWatchFace import info.nightscout.androidaps.watchfaces.utils.BaseWatchFace
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataMap import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataMap
import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey
import info.nightscout.rx.weardata.DrawableData import info.nightscout.rx.weardata.DrawableData
import info.nightscout.rx.weardata.DrawableFormat import info.nightscout.rx.weardata.DrawableFormat
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.EventData
import info.nightscout.rx.weardata.ZipWatchfaceFormat
import info.nightscout.shared.extensions.toVisibility import info.nightscout.shared.extensions.toVisibility
import org.joda.time.TimeOfDay import org.joda.time.TimeOfDay
import org.json.JSONObject import org.json.JSONObject
@ -111,23 +113,14 @@ class CustomWatchface : BaseWatchFace() {
binding.second.text = dateUtil.secondString() binding.second.text = dateUtil.secondString()
// rotate the second hand. // rotate the second hand.
binding.secondHand.rotation = TimeOfDay().secondOfMinute * 6f binding.secondHand.rotation = TimeOfDay().secondOfMinute * 6f
//aapsLogger.debug("XXXXXX Setsecond calles:")
} }
private fun setWatchfaceStyle() { private fun setWatchfaceStyle() {
bgColor = when (singleBg.sgvLevel) {
1L -> highColor
0L -> midColor
-1L -> lowColor
else -> midColor
}
val customWatchface = persistence.readCustomWatchface() ?: persistence.readCustomWatchface(true) val customWatchface = persistence.readCustomWatchface() ?: persistence.readCustomWatchface(true)
//aapsLogger.debug("XXXXX + setWatchfaceStyle Json ${customWatchface?.json}")
customWatchface?.let { customWatchface -> customWatchface?.let { customWatchface ->
val json = JSONObject(customWatchface.json) val json = JSONObject(customWatchface.customWatchfaceData.json)
val drawableDataMap = customWatchface.drawableDataMap val drawableDataMap = customWatchface.customWatchfaceData.drawableDatas
enableSecond = (if (json.has("enableSecond")) json.getBoolean("enableSecond") else false) && sp.getBoolean(R.string.key_show_seconds, true) enableSecond = (if (json.has("enableSecond")) json.getBoolean("enableSecond") else false) && sp.getBoolean(R.string.key_show_seconds, true)
//aapsLogger.debug("XXXXXX json File (beginning):" + customWatchface.json)
highColor = if (json.has("highColor")) Color.parseColor(json.getString("highColor")) else ContextCompat.getColor(this, R.color.dark_highColor) highColor = if (json.has("highColor")) Color.parseColor(json.getString("highColor")) else ContextCompat.getColor(this, R.color.dark_highColor)
midColor = if (json.has("midColor")) Color.parseColor(json.getString("midColor")) else ContextCompat.getColor(this, R.color.inrange) midColor = if (json.has("midColor")) Color.parseColor(json.getString("midColor")) else ContextCompat.getColor(this, R.color.inrange)
@ -136,13 +129,16 @@ class CustomWatchface : BaseWatchFace() {
carbColor = if (json.has("carbColor")) Color.parseColor(json.getString("carbColor")) else ContextCompat.getColor(this, R.color.carbs) carbColor = if (json.has("carbColor")) Color.parseColor(json.getString("carbColor")) else ContextCompat.getColor(this, R.color.carbs)
gridColor = if (json.has("gridColor")) Color.parseColor(json.getString("gridColor")) else ContextCompat.getColor(this, R.color.carbs) gridColor = if (json.has("gridColor")) Color.parseColor(json.getString("gridColor")) else ContextCompat.getColor(this, R.color.carbs)
pointSize = if (json.has("pointSize")) json.getInt("pointSize") else 2 pointSize = if (json.has("pointSize")) json.getInt("pointSize") else 2
aapsLogger.debug("XXXXXX enableSecond $enableSecond ${sp.getBoolean(R.string.key_show_seconds, false)} pointSize $pointSize") bgColor = when (singleBg.sgvLevel) {
1L -> highColor
0L -> midColor
-1L -> lowColor
else -> midColor
}
binding.mainLayout.forEach { view -> binding.mainLayout.forEach { view ->
//aapsLogger.debug("XXXXXX view:" + view.tag.toString())
view.tag?.let { tag -> view.tag?.let { tag ->
if (json.has(tag.toString())) { if (json.has(tag.toString())) {
var viewjson = json.getJSONObject(tag.toString()) var viewjson = json.getJSONObject(tag.toString())
//aapsLogger.debug("XXXXXX \"" + tag.toString() + "\": " + viewjson.toString(4))
var wrapContent = LayoutParams.WRAP_CONTENT var wrapContent = LayoutParams.WRAP_CONTENT
val width = if (viewjson.has("width")) (viewjson.getInt("width") * zoomFactor).toInt() else wrapContent val width = if (viewjson.has("width")) (viewjson.getInt("width") * zoomFactor).toInt() else wrapContent
val height = if (viewjson.has("height")) (viewjson.getInt("height") * zoomFactor).toInt() else wrapContent val height = if (viewjson.has("height")) (viewjson.getInt("height") * zoomFactor).toInt() else wrapContent
@ -191,6 +187,7 @@ class CustomWatchface : BaseWatchFace() {
private fun defaultWatchface(): EventData.ActionSetCustomWatchface { private fun defaultWatchface(): EventData.ActionSetCustomWatchface {
val metadata = JSONObject() val metadata = JSONObject()
.put(CustomWatchfaceMetadataKey.CWF_NAME.key, getString(info.nightscout.shared.R.string.wear_default_watchface)) .put(CustomWatchfaceMetadataKey.CWF_NAME.key, getString(info.nightscout.shared.R.string.wear_default_watchface))
.put(CustomWatchfaceMetadataKey.CWF_FILENAME.key, getString(R.string.wear_default_watchface))
.put(CustomWatchfaceMetadataKey.CWF_AUTHOR.key, "Philoul") .put(CustomWatchfaceMetadataKey.CWF_AUTHOR.key, "Philoul")
.put(CustomWatchfaceMetadataKey.CWF_CREATED_AT.key, dateUtil.dateString(dateUtil.now())) .put(CustomWatchfaceMetadataKey.CWF_CREATED_AT.key, dateUtil.dateString(dateUtil.now()))
.put(CustomWatchfaceMetadataKey.CWF_VERSION.key, CUSTOM_VERSION) .put(CustomWatchfaceMetadataKey.CWF_VERSION.key, CUSTOM_VERSION)
@ -248,12 +245,13 @@ class CustomWatchface : BaseWatchFace() {
) )
} }
} }
val drawableDatas: CustomWatchfaceDrawableDataMap = mutableMapOf() val metadataMap = ZipWatchfaceFormat.loadMetadata(json)
val drawableDataMap: CustomWatchfaceDrawableDataMap = mutableMapOf()
getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let { getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let {
val drawableDataMap = DrawableData(it,DrawableFormat.PNG) val drawableData = DrawableData(it,DrawableFormat.PNG)
drawableDatas[CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE] = drawableDataMap drawableDataMap[CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE] = drawableData
} }
return EventData.ActionSetCustomWatchface(getString(info.nightscout.shared.R.string.wear_default_watchface),json.toString(4),drawableDatas) return EventData.ActionSetCustomWatchface(CustomWatchfaceData(json.toString(4), metadataMap, drawableDataMap))
} }
private fun setDefaultColors() { private fun setDefaultColors() {