diff --git a/_docs/icons/HourHand.svg b/_docs/icons/HourHand.svg
new file mode 100644
index 0000000000..421b2d37a7
--- /dev/null
+++ b/_docs/icons/HourHand.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/_docs/icons/MinuteHand.svg b/_docs/icons/MinuteHand.svg
new file mode 100644
index 0000000000..07e4f81fb1
--- /dev/null
+++ b/_docs/icons/MinuteHand.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/_docs/icons/SecondHand.svg b/_docs/icons/SecondHand.svg
new file mode 100644
index 0000000000..0d66040491
--- /dev/null
+++ b/_docs/icons/SecondHand.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/_docs/icons/SimplifiedDial.svg b/_docs/icons/SimplifiedDial.svg
new file mode 100644
index 0000000000..fbce895e01
--- /dev/null
+++ b/_docs/icons/SimplifiedDial.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/_docs/icons/background.svg b/_docs/icons/background.svg
new file mode 100644
index 0000000000..ea30c0c8a5
--- /dev/null
+++ b/_docs/icons/background.svg
@@ -0,0 +1,8 @@
+
+
+
diff --git a/_docs/icons/detailed_dial.svg b/_docs/icons/detailed_dial.svg
new file mode 100644
index 0000000000..3c079740f0
--- /dev/null
+++ b/_docs/icons/detailed_dial.svg
@@ -0,0 +1,70 @@
+
+
\ No newline at end of file
diff --git a/_docs/icons/export_custom.svg b/_docs/icons/export_custom.svg
new file mode 100644
index 0000000000..b4c4d12ae8
--- /dev/null
+++ b/_docs/icons/export_custom.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/_docs/icons/load_custom.svg b/_docs/icons/load_custom.svg
new file mode 100644
index 0000000000..90fe7bfe37
--- /dev/null
+++ b/_docs/icons/load_custom.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/_docs/icons/send_custom.svg b/_docs/icons/send_custom.svg
new file mode 100644
index 0000000000..70168f1472
--- /dev/null
+++ b/_docs/icons/send_custom.svg
@@ -0,0 +1,16 @@
+
+
+
diff --git a/_docs/icons/set_default.svg b/_docs/icons/set_default.svg
new file mode 100644
index 0000000000..90e36585a2
--- /dev/null
+++ b/_docs/icons/set_default.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/app-wear-shared/shared/build.gradle b/app-wear-shared/shared/build.gradle
index 5e24d768e0..e69dd02310 100644
--- a/app-wear-shared/shared/build.gradle
+++ b/app-wear-shared/shared/build.gradle
@@ -34,6 +34,7 @@ dependencies {
api 'com.github.tony19:logback-android:2.0.0'
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version"
+ api "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinx_serialization_version"
api "org.apache.commons:commons-lang3:$commonslang3_version"
//RxBus
diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventMobileDataToWear.kt b/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventMobileDataToWear.kt
new file mode 100644
index 0000000000..9cc327ae92
--- /dev/null
+++ b/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventMobileDataToWear.kt
@@ -0,0 +1,5 @@
+package info.nightscout.rx.events
+
+import info.nightscout.rx.weardata.EventData
+
+class EventMobileDataToWear(val payload: EventData.ActionSetCustomWatchface) : Event()
\ No newline at end of file
diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearCwfExported.kt b/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearCwfExported.kt
new file mode 100644
index 0000000000..1d95780241
--- /dev/null
+++ b/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearCwfExported.kt
@@ -0,0 +1,5 @@
+package info.nightscout.rx.events
+
+import info.nightscout.rx.weardata.EventData
+
+class EventWearCwfExported(val payload: EventData.ActionSetCustomWatchface): Event()
\ No newline at end of file
diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearDataToMobile.kt b/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearDataToMobile.kt
new file mode 100644
index 0000000000..c26c8fd8b6
--- /dev/null
+++ b/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearDataToMobile.kt
@@ -0,0 +1,5 @@
+package info.nightscout.rx.events
+
+import info.nightscout.rx.weardata.EventData
+
+class EventWearDataToMobile(val payload: EventData) : Event()
\ No newline at end of file
diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearUpdateGui.kt b/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearUpdateGui.kt
new file mode 100644
index 0000000000..7aae63204a
--- /dev/null
+++ b/app-wear-shared/shared/src/main/java/info/nightscout/rx/events/EventWearUpdateGui.kt
@@ -0,0 +1,3 @@
+package info.nightscout.rx.events
+
+class EventWearUpdateGui : Event()
\ No newline at end of file
diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/CustomWatchfaceFormat.kt b/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/CustomWatchfaceFormat.kt
new file mode 100644
index 0000000000..cef6aa181a
--- /dev/null
+++ b/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/CustomWatchfaceFormat.kt
@@ -0,0 +1,140 @@
+package info.nightscout.rx.weardata
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.BitmapFactory
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.os.Parcelable
+import android.util.Xml
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import info.nightscout.shared.R
+import kotlinx.serialization.Serializable
+import org.json.JSONObject
+import java.io.File
+
+enum class CustomWatchfaceDrawableDataKey(val key: String, @DrawableRes val icon: Int?, val fileName: String) {
+ UNKNOWN("unknown", null,"Unknown"),
+ CUSTOM_WATCHFACE("customWatchface", R.drawable.watchface_custom, "CustomWatchface"),
+ BACKGROUND("background", R.drawable.background, "Background"),
+ COVERCHART("cover_chart", null,"CoverChart"),
+ COVERPLATE("cover_plate", R.drawable.simplified_dial, "CoverPlate"),
+ HOURHAND("hour_hand", R.drawable.hour_hand,"HourHand"),
+ MINUTEHAND("minute_hand", R.drawable.minute_hand,"MinuteHand"),
+ SECONDHAND("second_hand", R.drawable.second_hand, "SecondHand");
+
+ companion object {
+
+ private val keyToEnumMap = HashMap()
+ private val fileNameToEnumMap = HashMap()
+
+ init {
+ for (value in values()) keyToEnumMap[value.key] = value
+ for (value in values()) fileNameToEnumMap[value.fileName] = value
+ }
+
+ fun fromKey(key: String): CustomWatchfaceDrawableDataKey =
+ if (keyToEnumMap.containsKey(key)) {
+ keyToEnumMap[key] ?:UNKNOWN
+ } else {
+ UNKNOWN
+ }
+
+ fun fromFileName(file: String): CustomWatchfaceDrawableDataKey =
+ if (fileNameToEnumMap.containsKey(file.substringBeforeLast("."))) {
+ fileNameToEnumMap[file.substringBeforeLast(".")] ?:UNKNOWN
+ } else {
+ UNKNOWN
+ }
+ }
+
+}
+
+enum class DrawableFormat(val extension: String) {
+ UNKNOWN(""),
+ //XML("xml"),
+ //svg("svg"),
+ JPG("jpg"),
+ PNG("png");
+
+ companion object {
+
+ private val extensionToEnumMap = HashMap()
+
+ init {
+ for (value in values()) extensionToEnumMap[value.extension] = value
+ }
+
+ fun fromFileName(fileName: String): DrawableFormat =
+ if (extensionToEnumMap.containsKey(fileName.substringAfterLast("."))) {
+ extensionToEnumMap[fileName.substringAfterLast(".")] ?:UNKNOWN
+ } else {
+ UNKNOWN
+ }
+ }
+}
+
+@Serializable
+data class DrawableData(val value: ByteArray, val format: DrawableFormat) {
+
+ fun toDrawable(resources: Resources): Drawable? {
+ try {
+ return when (format) {
+ DrawableFormat.PNG, DrawableFormat.JPG -> {
+ val bitmap = BitmapFactory.decodeByteArray(value, 0, value.size)
+ BitmapDrawable(resources, bitmap)
+ }
+/*
+ DrawableFormat.XML -> {
+ val xmlInputStream = ByteArrayInputStream(value)
+ val xmlPullParser = Xml.newPullParser()
+ xmlPullParser.setInput(xmlInputStream, null)
+ Drawable.createFromXml(resources, xmlPullParser)
+ }
+*/
+ else -> null
+ }
+ } catch (e: Exception) {
+ return null
+ }
+ }
+}
+
+typealias CustomWatchfaceDrawableDataMap = MutableMap
+typealias CustomWatchfaceMetadataMap = MutableMap
+
+data class CustomWatchface(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) {
+
+ CWF_NAME("name", R.string.metadata_label_watchface_name),
+ CWF_AUTHOR("author", R.string.metadata_label_watchface_author),
+ CWF_CREATED_AT("created_at", R.string.metadata_label_watchface_created_at),
+ CWF_VERSION("cwf_version", R.string.metadata_label_watchface_version);
+
+ companion object {
+
+ private val keyToEnumMap = HashMap()
+
+ init {
+ for (value in values()) keyToEnumMap[value.key] = value
+ }
+
+ fun fromKey(key: String): CustomWatchfaceMetadataKey? =
+ if (keyToEnumMap.containsKey(key)) {
+ keyToEnumMap[key]
+ } else {
+ null
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/EventData.kt b/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/EventData.kt
index 59c8bc6fa7..88bf4fa1bc 100644
--- a/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/EventData.kt
+++ b/app-wear-shared/shared/src/main/java/info/nightscout/rx/weardata/EventData.kt
@@ -3,6 +3,7 @@ package info.nightscout.rx.weardata
import info.nightscout.rx.events.Event
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
+import kotlinx.serialization.protobuf.ProtoBuf
import org.joda.time.DateTime
import java.util.Objects
@@ -13,6 +14,7 @@ sealed class EventData : Event() {
fun serialize() = Json.encodeToString(serializer(), this)
+ fun serializeByte() = ProtoBuf.encodeToByteArray(serializer(), this)
companion object {
fun deserialize(json: String) = try {
@@ -20,6 +22,12 @@ sealed class EventData : Event() {
} catch (ignored: Exception) {
Error(System.currentTimeMillis())
}
+
+ fun deserializeByte(byteArray: ByteArray) = try {
+ ProtoBuf.decodeFromByteArray(serializer(), byteArray)
+ } catch (ignored: Exception) {
+ Error(System.currentTimeMillis())
+ }
}
// Mobile <- Wear
@@ -142,6 +150,12 @@ sealed class EventData : Event() {
@Serializable
data class CancelNotification(val timeStamp: Long) : EventData()
+ @Serializable
+ data class ActionGetCustomWatchface(
+ val customWatchface: ActionSetCustomWatchface,
+ val exportFile: Boolean
+ ) : EventData()
+
@Serializable
data class ActionPing(val timeStamp: Long) : EventData()
@@ -267,6 +281,18 @@ sealed class EventData : Event() {
val validTo: Int
) : EventData()
}
+ @Serializable
+ data class ActionSetCustomWatchface(
+ val name: String,
+ val json: String,
+ val drawableDataMap: CustomWatchfaceDrawableDataMap
+ ) : EventData()
+
+ @Serializable
+ data class ActionrequestCustomWatchface(val exportFile: Boolean) : EventData()
+
+ @Serializable
+ data class ActionrequestSetDefaultWatchface(val timeStamp: Long) : EventData()
@Serializable
data class ActionProfileSwitchOpenActivity(val timeShift: Int, val percentage: Int) : EventData()
diff --git a/app-wear-shared/shared/src/main/java/info/nightscout/shared/utils/DateUtil.kt b/app-wear-shared/shared/src/main/java/info/nightscout/shared/utils/DateUtil.kt
index 1ed083888d..7958f7403a 100644
--- a/app-wear-shared/shared/src/main/java/info/nightscout/shared/utils/DateUtil.kt
+++ b/app-wear-shared/shared/src/main/java/info/nightscout/shared/utils/DateUtil.kt
@@ -150,6 +150,10 @@ class DateUtil @Inject constructor(private val context: Context) {
return DateTime(mills).toString(DateTimeFormat.forPattern(format))
}
+ fun secondString(): String = secondString(now())
+ fun secondString(mills: Long): String =
+ DateTime(mills).toString(DateTimeFormat.forPattern("ss"))
+
fun minuteString(): String = minuteString(now())
fun minuteString(mills: Long): String =
DateTime(mills).toString(DateTimeFormat.forPattern("mm"))
diff --git a/app-wear-shared/shared/src/main/res/drawable/background.xml b/app-wear-shared/shared/src/main/res/drawable/background.xml
new file mode 100644
index 0000000000..76226923e6
--- /dev/null
+++ b/app-wear-shared/shared/src/main/res/drawable/background.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app-wear-shared/shared/src/main/res/drawable/detailed_dial.xml b/app-wear-shared/shared/src/main/res/drawable/detailed_dial.xml
new file mode 100644
index 0000000000..fa5c92bde2
--- /dev/null
+++ b/app-wear-shared/shared/src/main/res/drawable/detailed_dial.xml
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app-wear-shared/shared/src/main/res/drawable/hour_hand.xml b/app-wear-shared/shared/src/main/res/drawable/hour_hand.xml
new file mode 100644
index 0000000000..c29364421b
--- /dev/null
+++ b/app-wear-shared/shared/src/main/res/drawable/hour_hand.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app-wear-shared/shared/src/main/res/drawable/minute_hand.xml b/app-wear-shared/shared/src/main/res/drawable/minute_hand.xml
new file mode 100644
index 0000000000..ae37534d34
--- /dev/null
+++ b/app-wear-shared/shared/src/main/res/drawable/minute_hand.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app-wear-shared/shared/src/main/res/drawable/second_hand.xml b/app-wear-shared/shared/src/main/res/drawable/second_hand.xml
new file mode 100644
index 0000000000..96464ba7a8
--- /dev/null
+++ b/app-wear-shared/shared/src/main/res/drawable/second_hand.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app-wear-shared/shared/src/main/res/drawable/simplified_dial.xml b/app-wear-shared/shared/src/main/res/drawable/simplified_dial.xml
new file mode 100644
index 0000000000..63cca01bfb
--- /dev/null
+++ b/app-wear-shared/shared/src/main/res/drawable/simplified_dial.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app-wear-shared/shared/src/main/res/drawable/watchface_custom.png b/app-wear-shared/shared/src/main/res/drawable/watchface_custom.png
new file mode 100644
index 0000000000..9678177c64
Binary files /dev/null and b/app-wear-shared/shared/src/main/res/drawable/watchface_custom.png differ
diff --git a/app-wear-shared/shared/src/main/res/values/strings.xml b/app-wear-shared/shared/src/main/res/values/strings.xml
index 9f363f5cfc..df0c6b7bfd 100644
--- a/app-wear-shared/shared/src/main/res/values/strings.xml
+++ b/app-wear-shared/shared/src/main/res/values/strings.xml
@@ -39,4 +39,12 @@
Disconnecting
Waiting for disconnection
+
+ key_custom_watchface
+ Created at: %1$s
+ Author: %1$s
+ Name: %1$s
+ Watchface version: %1$s
+ Default Watchface
+
\ No newline at end of file
diff --git a/app-wear-shared/shared/src/main/res/values/wear_paths.xml b/app-wear-shared/shared/src/main/res/values/wear_paths.xml
index 36d7325849..377785cc8e 100644
--- a/app-wear-shared/shared/src/main/res/values/wear_paths.xml
+++ b/app-wear-shared/shared/src/main/res/values/wear_paths.xml
@@ -1,4 +1,5 @@
/rx_bridge
+ /rx_data_bridge
\ No newline at end of file
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/CustomWatchfaceFile.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/CustomWatchfaceFile.kt
new file mode 100644
index 0000000000..5019ae87e4
--- /dev/null
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/CustomWatchfaceFile.kt
@@ -0,0 +1,21 @@
+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
+
+)
\ No newline at end of file
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/ImportExportPrefs.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/ImportExportPrefs.kt
index 18ea0f32d3..99657fe7c3 100644
--- a/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/ImportExportPrefs.kt
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/ImportExportPrefs.kt
@@ -2,12 +2,16 @@ package info.nightscout.interfaces.maintenance
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
+import info.nightscout.rx.weardata.EventData
interface ImportExportPrefs {
fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile)
fun importSharedPreferences(activity: FragmentActivity)
fun importSharedPreferences(fragment: Fragment)
+ fun importCustomWatchface(activity: FragmentActivity)
+ fun importCustomWatchface(fragment: Fragment)
+ fun exportCustomWatchface(customWatchface: EventData.ActionSetCustomWatchface)
fun prefsFileExists(): Boolean
fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable)
fun exportSharedPreferences(f: Fragment)
diff --git a/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/PrefFileListProvider.kt b/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/PrefFileListProvider.kt
index 8bf0d497dd..9d131e9fdb 100644
--- a/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/PrefFileListProvider.kt
+++ b/core/interfaces/src/main/java/info/nightscout/interfaces/maintenance/PrefFileListProvider.kt
@@ -10,7 +10,9 @@ interface PrefFileListProvider {
fun ensureExtraDirExists(): File
fun newExportFile(): File
fun newExportCsvFile(): File
+ fun newCwfFile(filename: String): File
fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList
+ fun listCustomWatchfaceFiles(): MutableList
fun checkMetadata(metadata: Map): Map
fun formatExportedAgo(utcTime: String): String
}
\ No newline at end of file
diff --git a/plugins/configuration/src/main/AndroidManifest.xml b/plugins/configuration/src/main/AndroidManifest.xml
index 2d2811718b..700ebdafae 100644
--- a/plugins/configuration/src/main/AndroidManifest.xml
+++ b/plugins/configuration/src/main/AndroidManifest.xml
@@ -18,6 +18,10 @@
android:name=".maintenance.activities.PrefImportListActivity"
android:exported="false"
android:theme="@style/AppTheme" />
+
() {
+
+ companion object {
+ const val OUTPUT_PARAM = "custom_file"
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): Unit? {
+ return when (resultCode) {
+ FragmentActivity.RESULT_OK -> Unit
+ else -> null
+ }
+ }
+
+ override fun createIntent(context: Context, input: Void?): Intent {
+ return Intent(context, info.nightscout.configuration.maintenance.activities.CustomWatchfaceImportListActivity::class.java)
+ }
+}
\ No newline at end of file
diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/ImportExportPrefsImpl.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/ImportExportPrefsImpl.kt
index 941a5b9cb3..567e61fca7 100644
--- a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/ImportExportPrefsImpl.kt
+++ b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/ImportExportPrefsImpl.kt
@@ -22,6 +22,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.configuration.R
import info.nightscout.configuration.activities.DaggerAppCompatActivityWithResult
import info.nightscout.configuration.maintenance.dialogs.PrefImportSummaryDialog
+import info.nightscout.configuration.maintenance.formats.ZipCustomWatchfaceFormat
import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat
import info.nightscout.core.ui.dialogs.OKDialog
import info.nightscout.core.ui.dialogs.TwoMessagesAlertDialog
@@ -55,6 +56,7 @@ import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventDiaconnG8PumpLogReset
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
+import info.nightscout.rx.weardata.EventData
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
@@ -84,7 +86,8 @@ class ImportExportPrefsImpl @Inject constructor(
private val prefFileList: PrefFileListProvider,
private val uel: UserEntryLogger,
private val dateUtil: DateUtil,
- private val uiInteraction: UiInteraction
+ private val uiInteraction: UiInteraction,
+ private val customWatchfaceCWFFormat: ZipCustomWatchfaceFormat
) : ImportExportPrefs {
override fun prefsFileExists(): Boolean {
@@ -297,6 +300,27 @@ class ImportExportPrefsImpl @Inject constructor(
}
}
+ override fun importCustomWatchface(fragment: Fragment) {
+ fragment.activity?.let { importCustomWatchface(it) }
+ }
+ override fun importCustomWatchface(activity: FragmentActivity) {
+ try {
+ if (activity is DaggerAppCompatActivityWithResult)
+ activity.callForCustomWatchfaceFile.launch(null)
+ } catch (e: IllegalArgumentException) {
+ // this exception happens on some early implementations of ActivityResult contracts
+ // when registered and called for the second time
+ ToastUtils.errorToast(activity, rh.gs(R.string.goto_main_try_again))
+ log.error(LTag.CORE, "Internal android framework exception", e)
+ }
+ }
+
+ override fun exportCustomWatchface(customWatchface: EventData.ActionSetCustomWatchface) {
+ prefFileList.ensureExportDirExists()
+ val newFile = prefFileList.newCwfFile(customWatchface.name)
+ customWatchfaceCWFFormat.saveCustomWatchface(newFile,customWatchface)
+ }
+
override fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) {
askToConfirmImport(activity, importFile) { password ->
diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/PrefFileListProviderImpl.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/PrefFileListProviderImpl.kt
index d060dc1304..b8d66e3bd5 100644
--- a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/PrefFileListProviderImpl.kt
+++ b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/PrefFileListProviderImpl.kt
@@ -6,8 +6,10 @@ import dagger.Lazy
import dagger.Reusable
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.configuration.R
+import info.nightscout.configuration.maintenance.formats.ZipCustomWatchfaceFormat
import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat
import info.nightscout.interfaces.Config
+import info.nightscout.interfaces.maintenance.CustomWatchfaceFile
import info.nightscout.interfaces.maintenance.PrefFileListProvider
import info.nightscout.interfaces.maintenance.PrefMetadata
import info.nightscout.interfaces.maintenance.PrefMetadataMap
@@ -34,6 +36,7 @@ class PrefFileListProviderImpl @Inject constructor(
private val rh: ResourceHelper,
private val config: Lazy,
private val encryptedPrefsFormat: EncryptedPrefsFormat,
+ private val customWatchfaceCWFFormat: ZipCustomWatchfaceFormat,
private val storage: Storage,
private val versionCheckerUtils: VersionCheckerUtils,
context: Context
@@ -88,6 +91,20 @@ class PrefFileListProviderImpl @Inject constructor(
return prefFiles
}
+ override fun listCustomWatchfaceFiles(): MutableList {
+ val customWatchfaceFiles = mutableListOf()
+
+ // searching dedicated dir, only for new CWF format
+ exportsPath.walk().filter { it.isFile && it.name.endsWith(ZipCustomWatchfaceFormat.CUSTOM_WF_EXTENTION) }.forEach { file ->
+ // Here loadCustomWatchface will unzip, check and load CustomWatchface
+ customWatchfaceCWFFormat.loadCustomWatchface(file)?.also { customWatchface ->
+ customWatchfaceFiles.add(CustomWatchfaceFile(file.name, file, exportsPath, customWatchface.json, customWatchface.metadata, customWatchface.drawableDatas))
+ }
+ }
+
+ return customWatchfaceFiles
+ }
+
private fun metadataFor(loadMetadata: Boolean, contents: String): PrefMetadataMap {
if (!loadMetadata) {
return mapOf()
@@ -128,6 +145,10 @@ class PrefFileListProviderImpl @Inject constructor(
val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss"))
return File(exportsPath, timeLocal + "_UserEntry.csv")
}
+ override fun newCwfFile(filename: String): File {
+ val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss"))
+ return File(exportsPath, "${filename}_$timeLocal${ZipCustomWatchfaceFormat.CUSTOM_WF_EXTENTION}")
+ }
// check metadata for known issues, change their status and add info with explanations
override fun checkMetadata(metadata: Map): Map {
diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/CustomWatchfaceImportListActivity.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/CustomWatchfaceImportListActivity.kt
new file mode 100644
index 0000000000..d225ae790d
--- /dev/null
+++ b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/activities/CustomWatchfaceImportListActivity.kt
@@ -0,0 +1,112 @@
+package info.nightscout.configuration.maintenance.activities
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.ViewGroup
+
+import androidx.fragment.app.FragmentActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity
+import info.nightscout.interfaces.maintenance.CustomWatchfaceFile
+import info.nightscout.interfaces.maintenance.PrefFileListProvider
+import info.nightscout.configuration.databinding.CustomWatchfaceImportListActivityBinding
+import info.nightscout.configuration.R
+import info.nightscout.configuration.databinding.CustomWatchfaceImportListItemBinding
+import info.nightscout.rx.bus.RxBus
+import info.nightscout.rx.events.EventMobileDataToWear
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
+import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey.*
+import info.nightscout.rx.weardata.EventData
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.sharedPreferences.SP
+import javax.inject.Inject
+
+class CustomWatchfaceImportListActivity: TranslatedDaggerAppCompatActivity() {
+
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var sp: SP
+ @Inject lateinit var prefFileListProvider: PrefFileListProvider
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var aapsLogger: AAPSLogger
+
+ private lateinit var binding: CustomWatchfaceImportListActivityBinding
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = CustomWatchfaceImportListActivityBinding.inflate(layoutInflater)
+ val view = binding.root
+ setContentView(view)
+
+ title = rh.gs(R.string.wear_import_custom_watchface_title)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ supportActionBar?.setDisplayShowHomeEnabled(true)
+ supportActionBar?.setDisplayShowTitleEnabled(true)
+
+ binding.recyclerview.layoutManager = LinearLayoutManager(this)
+ binding.recyclerview.adapter = RecyclerViewAdapter(prefFileListProvider.listCustomWatchfaceFiles())
+ }
+
+ inner class RecyclerViewAdapter internal constructor(private var customWatchfaceFileList: List) : RecyclerView.Adapter() {
+
+ inner class PrefFileViewHolder(val customWatchfaceImportListItemBinding: CustomWatchfaceImportListItemBinding) : RecyclerView.ViewHolder(customWatchfaceImportListItemBinding.root) {
+
+ init {
+ with(customWatchfaceImportListItemBinding) {
+ root.isClickable = true
+ customWatchfaceImportListItemBinding.root.setOnClickListener {
+ val customWatchfaceFile = filelistName.tag as CustomWatchfaceFile
+ val customWF = EventData.ActionSetCustomWatchface(customWatchfaceFile.metadata[CWF_NAME] ?:"", customWatchfaceFile.json, customWatchfaceFile.drawableFiles)
+ sp.putString(info.nightscout.shared.R.string.key_custom_watchface, customWF.serialize())
+ val i = Intent()
+ setResult(FragmentActivity.RESULT_OK, i)
+ rxBus.send(EventMobileDataToWear(customWF))
+ aapsLogger.debug("XXXXX EventMobileDataToWear sent")
+
+ finish()
+ }
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PrefFileViewHolder {
+ val binding = CustomWatchfaceImportListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return PrefFileViewHolder(binding)
+ }
+
+ override fun getItemCount(): Int {
+ return customWatchfaceFileList.size
+ }
+
+ override fun onBindViewHolder(holder: PrefFileViewHolder, position: Int) {
+ val customWatchfaceFile = customWatchfaceFileList[position]
+ val metadata = customWatchfaceFile.metadata
+ val drawable = customWatchfaceFile.drawableFiles[CustomWatchfaceDrawableDataKey
+ .CUSTOM_WATCHFACE]?.toDrawable(resources)
+ with(holder.customWatchfaceImportListItemBinding) {
+ filelistName.text = rh.gs(R.string.wear_import_filename, customWatchfaceFile.file.name)
+ filelistName.tag = customWatchfaceFile
+ 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])
+ author.text = rh.gs(CWF_AUTHOR.label, metadata[CWF_AUTHOR] ?:"")
+ createdAt.text = rh.gs(CWF_CREATED_AT.label, metadata[CWF_CREATED_AT] ?:"")
+ cwfVersion.text = rh.gs(CWF_VERSION.label, metadata[CWF_VERSION] ?:"")
+
+ }
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ finish()
+ return true
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/formats/ZipCustomWatchfaceFormat.kt b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/formats/ZipCustomWatchfaceFormat.kt
new file mode 100644
index 0000000000..6414187d1a
--- /dev/null
+++ b/plugins/configuration/src/main/java/info/nightscout/configuration/maintenance/formats/ZipCustomWatchfaceFormat.kt
@@ -0,0 +1,128 @@
+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
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/configuration/src/main/res/layout/custom_watchface_import_list_activity.xml b/plugins/configuration/src/main/res/layout/custom_watchface_import_list_activity.xml
new file mode 100644
index 0000000000..5095328ce9
--- /dev/null
+++ b/plugins/configuration/src/main/res/layout/custom_watchface_import_list_activity.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/plugins/configuration/src/main/res/layout/custom_watchface_import_list_item.xml b/plugins/configuration/src/main/res/layout/custom_watchface_import_list_item.xml
new file mode 100644
index 0000000000..dfd624b429
--- /dev/null
+++ b/plugins/configuration/src/main/res/layout/custom_watchface_import_list_item.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/configuration/src/main/res/values/strings.xml b/plugins/configuration/src/main/res/values/strings.xml
index 1cec48dfba..15e68dacd6 100644
--- a/plugins/configuration/src/main/res/values/strings.xml
+++ b/plugins/configuration/src/main/res/values/strings.xml
@@ -164,6 +164,11 @@
Missing encryption configuration, settings format is invalid!
Unsupported or not specified encryption algorithm!
+
+ Select Custom Watchface
+ Directory: %1$s
+ File name: %1$s
+
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)!
diff --git a/plugins/main/src/main/AndroidManifest.xml b/plugins/main/src/main/AndroidManifest.xml
index 6d24217b2e..75ecc0da85 100644
--- a/plugins/main/src/main/AndroidManifest.xml
+++ b/plugins/main/src/main/AndroidManifest.xml
@@ -51,6 +51,15 @@
android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" />
+
+
+
+
+
+
diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/WearFragment.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/WearFragment.kt
index 57cd89ec9d..848facfbec 100644
--- a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/WearFragment.kt
+++ b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/WearFragment.kt
@@ -5,13 +5,21 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import dagger.android.support.DaggerFragment
+import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy
+import info.nightscout.interfaces.maintenance.ImportExportPrefs
+import info.nightscout.plugins.R
import info.nightscout.plugins.databinding.WearFragmentBinding
-import info.nightscout.plugins.general.wear.events.EventWearUpdateGui
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
+import info.nightscout.rx.events.EventMobileDataToWear
import info.nightscout.rx.events.EventMobileToWear
+import info.nightscout.rx.events.EventWearUpdateGui
+import info.nightscout.rx.logging.AAPSLogger
+import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
import info.nightscout.rx.weardata.EventData
+import info.nightscout.shared.interfaces.ResourceHelper
+import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
@@ -24,9 +32,12 @@ class WearFragment : DaggerFragment() {
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var importExportPrefs: ImportExportPrefs
+ @Inject lateinit var sp:SP
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var aapsLogger: AAPSLogger
private var _binding: WearFragmentBinding? = null
-
private val disposable = CompositeDisposable()
// This property is only valid between onCreateView and
@@ -43,6 +54,25 @@ class WearFragment : DaggerFragment() {
super.onViewCreated(view, savedInstanceState)
binding.resend.setOnClickListener { rxBus.send(EventData.ActionResendData("WearFragment")) }
binding.openSettings.setOnClickListener { rxBus.send(EventMobileToWear(EventData.OpenSettings(dateUtil.now()))) }
+
+ binding.loadCustom.setOnClickListener {
+ importExportPrefs.verifyStoragePermissions(this) {
+ importExportPrefs.importCustomWatchface(this)
+ }
+ }
+ binding.defaultCustom.setOnClickListener {
+ sp.remove(info.nightscout.shared.R.string.key_custom_watchface)
+ wearPlugin.savedCustomWatchface = null
+ rxBus.send(EventMobileToWear(EventData.ActionrequestSetDefaultWatchface(dateUtil.now())))
+ updateGui()
+ }
+ binding.sendCustom.setOnClickListener {
+ wearPlugin.savedCustomWatchface?.let { cwf -> rxBus.send(EventMobileDataToWear(cwf)) }
+ }
+ binding.exportCustom.setOnClickListener {
+ wearPlugin.savedCustomWatchface?.let { importExportPrefs.exportCustomWatchface(it) }
+ ?: apply { rxBus.send(EventMobileToWear(EventData.ActionrequestCustomWatchface(true)))}
+ }
}
override fun onResume() {
@@ -51,6 +81,19 @@ class WearFragment : DaggerFragment() {
.toObservable(EventWearUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventMobileDataToWear::class.java)
+ .observeOn(aapsSchedulers.main)
+ .subscribe({
+ loadCustom(it.payload)
+ wearPlugin.customWatchfaceSerialized = ""
+ wearPlugin.savedCustomWatchface = null
+ updateGui()
+ ToastUtils.okToast(context,rh.gs(R.string.wear_new_custom_watchface_received))
+ }, fabricPrivacy::logException)
+ if (wearPlugin.savedCustomWatchface == null)
+ rxBus.send(EventMobileToWear(EventData.ActionrequestCustomWatchface(false)))
+ //EventMobileDataToWear
updateGui()
}
@@ -67,6 +110,34 @@ class WearFragment : DaggerFragment() {
private fun updateGui() {
_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 {
+ binding.customName.text = rh.gs(R.string.wear_custom_watchface, it.name)
+ binding.sendCustom.visibility = View.VISIBLE
+ binding.coverChart.setImageDrawable(it.drawableDataMap[CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE]?.toDrawable(resources))
+ } ?:apply {
+ 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.coverChart.setImageDrawable(null)
+ }
binding.connectedDevice.text = wearPlugin.connectedDevice
}
+
+ private fun loadCustom(cwf: EventData.ActionSetCustomWatchface) {
+ aapsLogger.debug("XXXXX EventWearCwfExported received")
+ wearPlugin.savedCustomWatchface = cwf
+ }
}
\ No newline at end of file
diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/WearPlugin.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/WearPlugin.kt
index a17e63134e..21516d6d1e 100644
--- a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/WearPlugin.kt
+++ b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/WearPlugin.kt
@@ -54,6 +54,8 @@ class WearPlugin @Inject constructor(
private val disposable = CompositeDisposable()
var connectedDevice = "---"
+ var customWatchfaceSerialized = ""
+ var savedCustomWatchface: EventData.ActionSetCustomWatchface? = null
override fun onStart() {
super.onStart()
diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataHandlerMobile.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataHandlerMobile.kt
index 066e49bf2b..a1fe993f56 100644
--- a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataHandlerMobile.kt
+++ b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataHandlerMobile.kt
@@ -41,6 +41,7 @@ import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.logging.UserEntryLogger
+import info.nightscout.interfaces.maintenance.ImportExportPrefs
import info.nightscout.interfaces.nsclient.ProcessedDeviceStatusData
import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase
@@ -59,6 +60,8 @@ import info.nightscout.plugins.R
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventMobileToWear
+import info.nightscout.rx.events.EventWearCwfExported
+import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.rx.weardata.EventData
@@ -107,7 +110,8 @@ class DataHandlerMobile @Inject constructor(
private val commandQueue: CommandQueue,
private val fabricPrivacy: FabricPrivacy,
private val uiInteraction: UiInteraction,
- private val persistenceLayer: PersistenceLayer
+ private val persistenceLayer: PersistenceLayer,
+ private val importExportPrefs: ImportExportPrefs
) {
private val disposable = CompositeDisposable()
@@ -314,6 +318,13 @@ class DataHandlerMobile @Inject constructor(
.toObservable(EventData.ActionHeartRate::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ handleHeartRate(it) }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionGetCustomWatchface::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "Custom Watch face ${it.customWatchface} received from ${it.sourceNodeId}")
+ handleGetCustomWatchface(it)
+ }, fabricPrivacy::logException)
}
private fun handleTddStatus() {
@@ -1247,4 +1258,19 @@ class DataHandlerMobile @Inject constructor(
device = actionHeartRate.device)
repository.runTransaction(InsertOrUpdateHeartRateTransaction(hr)).blockingAwait()
}
+
+
+ private fun handleGetCustomWatchface(command: EventData.ActionGetCustomWatchface) {
+ val customWatchface = command.customWatchface
+ aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${command.sourceNodeId}: ${customWatchface.json}")
+ //Update Wear Fragment
+ sp.putString(info.nightscout.shared.R.string.key_custom_watchface, customWatchface.serialize())
+ //rxBus.send(EventWearCwfExported(customWatchface))
+ rxBus.send(EventWearUpdateGui())
+ if (command.exportFile)
+ importExportPrefs.exportCustomWatchface(customWatchface)
+ //Implement here a record within SP and a save within exports subfolder as zipFile
+
+ }
+
}
diff --git a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt
index 13a8ff47f9..dac0274681 100644
--- a/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt
+++ b/plugins/main/src/main/java/info/nightscout/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt
@@ -26,9 +26,10 @@ import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.receivers.ReceiverStatusStore
import info.nightscout.plugins.R
import info.nightscout.plugins.general.wear.WearPlugin
-import info.nightscout.plugins.general.wear.events.EventWearUpdateGui
+import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
+import info.nightscout.rx.events.EventMobileDataToWear
import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
@@ -80,6 +81,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
private val disposable = CompositeDisposable()
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)
override fun onCreate() {
AndroidInjection.inject(this)
@@ -90,6 +92,10 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
.toObservable(EventMobileToWear::class.java)
.observeOn(aapsSchedulers.io)
.subscribe { sendMessage(rxPath, it.payload.serialize()) }
+ disposable += rxBus
+ .toObservable(EventMobileDataToWear::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe { sendMessage(rxDataPath, it.payload.serializeByte()) }
}
override fun onCapabilityChanged(p0: CapabilityInfo) {
@@ -136,6 +142,11 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
val command = EventData.deserialize(String(messageEvent.data))
rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId })
}
+ rxDataPath -> {
+ aapsLogger.debug(LTag.WEAR, "onMessageReceived rxDataPath: ${String(messageEvent.data)}")
+ val command = EventData.deserializeByte(messageEvent.data)
+ rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId })
+ }
}
}
}
@@ -164,7 +175,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
private fun pickBestNodeId(nodes: Set): Node? =
nodes.firstOrNull { it.isNearby } ?: nodes.firstOrNull()
- @Suppress("unused")
+ //@Suppress("unused")
private fun sendData(path: String, vararg params: DataMap) {
if (wearPlugin.isEnabled()) {
scope.launch {
@@ -201,7 +212,6 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
}
}
- @Suppress("unused")
private fun sendMessage(path: String, data: ByteArray) {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path")
transcriptionNodeId?.also { nodeId ->
diff --git a/plugins/main/src/main/res/drawable/export_custom.xml b/plugins/main/src/main/res/drawable/export_custom.xml
new file mode 100644
index 0000000000..3779b637df
--- /dev/null
+++ b/plugins/main/src/main/res/drawable/export_custom.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/main/src/main/res/drawable/load_custom.xml b/plugins/main/src/main/res/drawable/load_custom.xml
new file mode 100644
index 0000000000..26a7dda859
--- /dev/null
+++ b/plugins/main/src/main/res/drawable/load_custom.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/main/src/main/res/drawable/send_custom.xml b/plugins/main/src/main/res/drawable/send_custom.xml
new file mode 100644
index 0000000000..2602e2953d
--- /dev/null
+++ b/plugins/main/src/main/res/drawable/send_custom.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/plugins/main/src/main/res/drawable/set_default.xml b/plugins/main/src/main/res/drawable/set_default.xml
new file mode 100644
index 0000000000..ff43d0d255
--- /dev/null
+++ b/plugins/main/src/main/res/drawable/set_default.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/plugins/main/src/main/res/layout/wear_fragment.xml b/plugins/main/src/main/res/layout/wear_fragment.xml
index cfd22a1f4f..1eb374529d 100644
--- a/plugins/main/src/main/res/layout/wear_fragment.xml
+++ b/plugins/main/src/main/res/layout/wear_fragment.xml
@@ -1,42 +1,201 @@
-
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginTop="4dp"
+ app:cardCornerRadius="4dp"
+ app:contentPadding="2dp"
+ app:cardElevation="2dp"
+ app:cardUseCompatPadding="false"
+ android:layout_gravity="center">
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginTop="4dp"
+ app:cardCornerRadius="4dp"
+ app:contentPadding="2dp"
+ app:cardElevation="2dp"
+ app:cardUseCompatPadding="false"
+ android:layout_gravity="center">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/main/src/main/res/values/strings.xml b/plugins/main/src/main/res/values/strings.xml
index c7c4789b6f..b8d2b4eccf 100644
--- a/plugins/main/src/main/res/values/strings.xml
+++ b/plugins/main/src/main/res/values/strings.xml
@@ -361,6 +361,11 @@
Show SMB on the watch like a standard bolus.
Show the predictions on the watchface.
Predictions
+ Custom Watchface: %1$s
+ Load Watchface
+ Send Watchface
+ Export Watchface
+ New watchface received from watch
Resend All Data
Open Settings on Wear
diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
index 2a08a7f623..ab50e950d1 100644
--- a/wear/src/main/AndroidManifest.xml
+++ b/wear/src/main/AndroidManifest.xml
@@ -241,6 +241,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -267,6 +292,15 @@
android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" />
+
+
+
+
+
+
+
diff --git a/wear/src/main/java/info/nightscout/androidaps/comm/DataHandlerWear.kt b/wear/src/main/java/info/nightscout/androidaps/comm/DataHandlerWear.kt
index 389ee261d9..5d5e197670 100644
--- a/wear/src/main/java/info/nightscout/androidaps/comm/DataHandlerWear.kt
+++ b/wear/src/main/java/info/nightscout/androidaps/comm/DataHandlerWear.kt
@@ -25,6 +25,7 @@ import info.nightscout.androidaps.tile.QuickWizardTileService
import info.nightscout.androidaps.tile.TempTargetTileService
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
+import info.nightscout.rx.events.EventWearDataToMobile
import info.nightscout.rx.events.EventWearToMobile
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
@@ -179,6 +180,29 @@ class DataHandlerWear @Inject constructor(
TileService.getUpdater(context).requestUpdate(QuickWizardTileService::class.java)
}
}
+ disposable += rxBus
+ .toObservable(EventData.ActionSetCustomWatchface::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe {
+ aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${it.sourceNodeId}")
+ persistence.store(it)
+ }
+ disposable += rxBus
+ .toObservable(EventData.ActionrequestSetDefaultWatchface::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe {
+ aapsLogger.debug(LTag.WEAR, "Set Default Watchface received from ${it.sourceNodeId}")
+ persistence.setDefaultWatchface()
+ }
+ disposable += rxBus
+ .toObservable(EventData.ActionrequestCustomWatchface::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe { eventData ->
+ aapsLogger.debug(LTag.WEAR, "Custom Watchface requested from ${eventData.sourceNodeId}")
+ persistence.readCustomWatchface()?.let {
+ rxBus.send(EventWearDataToMobile(EventData.ActionGetCustomWatchface(it, eventData.exportFile)))
+ }
+ }
}
private fun handleBolusProgress(bolusProgress: EventData.BolusProgress) {
diff --git a/wear/src/main/java/info/nightscout/androidaps/comm/DataLayerListenerServiceWear.kt b/wear/src/main/java/info/nightscout/androidaps/comm/DataLayerListenerServiceWear.kt
index fabc65b8fb..e4c62dd83d 100644
--- a/wear/src/main/java/info/nightscout/androidaps/comm/DataLayerListenerServiceWear.kt
+++ b/wear/src/main/java/info/nightscout/androidaps/comm/DataLayerListenerServiceWear.kt
@@ -12,6 +12,7 @@ import info.nightscout.androidaps.interaction.utils.Persistence
import info.nightscout.androidaps.interaction.utils.WearUtil
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
+import info.nightscout.rx.events.EventWearDataToMobile
import info.nightscout.rx.events.EventWearToMobile
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
@@ -43,6 +44,7 @@ class DataLayerListenerServiceWear : WearableListenerService() {
private val disposable = CompositeDisposable()
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)
override fun onCreate() {
AndroidInjection.inject(this)
@@ -54,6 +56,12 @@ class DataLayerListenerServiceWear : WearableListenerService() {
.subscribe {
sendMessage(rxPath, it.payload.serialize())
}
+ disposable += rxBus
+ .toObservable(EventWearDataToMobile::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe {
+ sendMessage(rxDataPath, it.payload.serializeByte())
+ }
}
override fun onCapabilityChanged(p0: CapabilityInfo) {
@@ -100,6 +108,14 @@ class DataLayerListenerServiceWear : WearableListenerService() {
transcriptionNodeId = messageEvent.sourceNodeId
aapsLogger.debug(LTag.WEAR, "Updated node: $transcriptionNodeId")
}
+ rxDataPath -> {
+ aapsLogger.debug(LTag.WEAR, "onMessageReceived: ${messageEvent.data}")
+ val command = EventData.deserializeByte(messageEvent.data)
+ rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId })
+ // Use this sender
+ transcriptionNodeId = messageEvent.sourceNodeId
+ aapsLogger.debug(LTag.WEAR, "Updated node: $transcriptionNodeId")
+ }
}
}
diff --git a/wear/src/main/java/info/nightscout/androidaps/di/WearServicesModule.kt b/wear/src/main/java/info/nightscout/androidaps/di/WearServicesModule.kt
index 909c0aa21a..3cc8baa387 100644
--- a/wear/src/main/java/info/nightscout/androidaps/di/WearServicesModule.kt
+++ b/wear/src/main/java/info/nightscout/androidaps/di/WearServicesModule.kt
@@ -40,6 +40,7 @@ abstract class WearServicesModule {
@ContributesAndroidInjector abstract fun contributesBIGChart(): BigChartWatchface
@ContributesAndroidInjector abstract fun contributesNOChart(): NoChartWatchface
@ContributesAndroidInjector abstract fun contributesCircleWatchface(): CircleWatchface
+ @ContributesAndroidInjector abstract fun contributesCustomWatchface(): CustomWatchface
@ContributesAndroidInjector abstract fun contributesTileBase(): TileBase
@ContributesAndroidInjector abstract fun contributesQuickWizardTileService(): QuickWizardTileService
diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.kt
index 557300ca6d..ef97aa4270 100644
--- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.kt
+++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.kt
@@ -36,6 +36,8 @@ open class Persistence @Inject constructor(
const val KEY_STALE_REPORTED = "staleReported"
const val KEY_DATA_UPDATED = "data_updated_at"
+ const val CUSTOM_WATCHFACE = "custom_watchface"
+ const val CUSTOM_DEFAULT_WATCHFACE = "custom_default_watchface"
}
fun getString(key: String, defaultValue: String): String {
@@ -130,6 +132,23 @@ open class Persistence @Inject constructor(
return null
}
+ fun readCustomWatchface(isDefault: Boolean = false): EventData.ActionSetCustomWatchface? {
+ try {
+ var s = sp.getStringOrNull(if (isDefault) CUSTOM_DEFAULT_WATCHFACE else CUSTOM_WATCHFACE, null)
+ if (s != null) {
+ return deserialize(s) as EventData.ActionSetCustomWatchface
+ } else {
+ s = sp.getStringOrNull(CUSTOM_DEFAULT_WATCHFACE, null)
+ if (s != null) {
+ return deserialize(s) as EventData.ActionSetCustomWatchface
+ }
+ }
+ } catch (exception: Exception) {
+ aapsLogger.error(LTag.WEAR, exception.toString())
+ }
+ return null
+ }
+
fun store(singleBg: SingleBg) {
putString(BG_DATA_PERSISTENCE_KEY, singleBg.serialize())
aapsLogger.debug(LTag.WEAR, "Stored BG data: $singleBg")
@@ -151,6 +170,16 @@ open class Persistence @Inject constructor(
aapsLogger.debug(LTag.WEAR, "Stored Status data: $status")
}
+ fun store(customWatchface: EventData.ActionSetCustomWatchface, isdefault: Boolean = false) {
+ putString(if (isdefault) CUSTOM_DEFAULT_WATCHFACE else CUSTOM_WATCHFACE, customWatchface.serialize())
+ aapsLogger.debug(LTag.WEAR, "Stored Custom Watchface ${customWatchface.name} ${isdefault}: $customWatchface")
+ }
+
+ fun setDefaultWatchface() {
+ readCustomWatchface(true)?.let {store(it)}
+ aapsLogger.debug(LTag.WEAR, "Custom Watchface reset to default")
+ }
+
fun joinSet(set: Set, separator: String?): String {
val sb = StringBuilder()
var i = 0
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt b/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt
new file mode 100644
index 0000000000..f01f5de785
--- /dev/null
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/CustomWatchface.kt
@@ -0,0 +1,398 @@
+@file:Suppress("DEPRECATION")
+
+package info.nightscout.androidaps.watchfaces
+
+import android.app.ActionBar.LayoutParams
+import android.content.Context
+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.support.wearable.watchface.WatchFaceStyle
+import android.util.TypedValue
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.forEach
+import androidx.viewbinding.ViewBinding
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.databinding.ActivityCustomBinding
+import info.nightscout.androidaps.watchfaces.utils.BaseWatchFace
+import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
+import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataMap
+import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey
+import info.nightscout.rx.weardata.DrawableData
+import info.nightscout.rx.weardata.DrawableFormat
+import info.nightscout.rx.weardata.EventData
+import info.nightscout.shared.extensions.toVisibility
+import org.joda.time.TimeOfDay
+import org.json.JSONObject
+import java.io.ByteArrayOutputStream
+import javax.inject.Inject
+
+class CustomWatchface : BaseWatchFace() {
+
+ @Inject lateinit var context: Context
+ private lateinit var binding: ActivityCustomBinding
+ private var zoomFactor = 1.0
+ private val displaySize = Point()
+ private val TEMPLATE_RESOLUTION = 400
+ private var lowBatColor = Color.RED
+ private var bgColor = Color.WHITE
+
+ val CUSTOM_VERSION = "v0.1"
+
+ @Suppress("DEPRECATION")
+ override fun inflateLayout(inflater: LayoutInflater): ViewBinding {
+ binding = ActivityCustomBinding.inflate(inflater)
+ setDefaultColors()
+ persistence.store(defaultWatchface(), true)
+ (context.getSystemService(WINDOW_SERVICE) as WindowManager).defaultDisplay.getSize(displaySize)
+ zoomFactor = (displaySize.x).toDouble() / TEMPLATE_RESOLUTION.toDouble()
+ return binding
+ }
+
+ override fun getWatchFaceStyle(): WatchFaceStyle {
+ return WatchFaceStyle.Builder(this)
+ .setAcceptsTapEvents(true)
+ .setHideNotificationIndicator(false)
+ .setShowUnreadCountIndicator(true)
+ .build()
+ }
+
+ override fun setColorDark() {
+ setWatchfaceStyle()
+ //@ColorInt val batteryOkColor = ContextCompat.getColor(this, R.color.dark_midColor)
+ binding.mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.dark_background))
+ binding.sgv.setTextColor(bgColor)
+ binding.direction.setTextColor(bgColor)
+
+ if (ageLevel != 1)
+ binding.timestamp.setTextColor(ContextCompat.getColor(this, R.color.dark_TimestampOld))
+ if (status.batteryLevel != 1)
+ binding.uploaderBattery.setTextColor(lowBatColor)
+ when (loopLevel) {
+ -1 -> binding.loop.setBackgroundResource(R.drawable.loop_grey_25)
+ 1 -> binding.loop.setBackgroundResource(R.drawable.loop_green_25)
+ else -> binding.loop.setBackgroundResource(R.drawable.loop_red_25)
+ }
+
+ basalBackgroundColor = ContextCompat.getColor(this, R.color.basal_dark)
+ basalCenterColor = ContextCompat.getColor(this, R.color.basal_light)
+
+ // rotate the second hand.
+ binding.secondHand.rotation = TimeOfDay().secondOfMinute * 6f
+ // rotate the minute hand.
+ binding.minuteHand.rotation = TimeOfDay().minuteOfHour * 6f
+ // rotate the hour hand.
+ binding.hourHand.rotation = TimeOfDay().hourOfDay * 30f + TimeOfDay().minuteOfHour * 0.5f
+
+ setupCharts()
+ }
+
+ override fun setColorBright() {
+ setColorDark()
+ }
+
+ override fun setColorLowRes() {
+ setColorDark()
+ }
+
+ override fun setSecond() {
+ binding.time.text = "${dateUtil.hourString()}:${dateUtil.minuteString()}" + if (enableSecond) ":${dateUtil.secondString()}" else ""
+ binding.second.text = dateUtil.secondString()
+ // rotate the second hand.
+ binding.secondHand.rotation = TimeOfDay().secondOfMinute * 6f
+ //aapsLogger.debug("XXXXXX Setsecond calles:")
+ }
+
+ private fun setWatchfaceStyle() {
+ bgColor = when (singleBg.sgvLevel) {
+ 1L -> highColor
+ 0L -> midColor
+ -1L -> lowColor
+ else -> midColor
+ }
+ val customWatchface = persistence.readCustomWatchface() ?: persistence.readCustomWatchface(true)
+ //aapsLogger.debug("XXXXX + setWatchfaceStyle Json ${customWatchface?.json}")
+ customWatchface?.let { customWatchface ->
+ val json = JSONObject(customWatchface.json)
+ val drawableDataMap = customWatchface.drawableDataMap
+ 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)
+ midColor = if (json.has("midColor")) Color.parseColor(json.getString("midColor")) else ContextCompat.getColor(this, R.color.inrange)
+ lowColor = if (json.has("lowColor")) Color.parseColor(json.getString("lowColor")) else ContextCompat.getColor(this, R.color.low)
+ lowBatColor = if (json.has("lowBatColor")) Color.parseColor(json.getString("lowBatColor")) else ContextCompat.getColor(this, R.color.dark_uploaderBatteryEmpty)
+ 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)
+ 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")
+ binding.mainLayout.forEach { view ->
+ //aapsLogger.debug("XXXXXX view:" + view.tag.toString())
+ view.tag?.let { tag ->
+ if (json.has(tag.toString())) {
+ var viewjson = json.getJSONObject(tag.toString())
+ //aapsLogger.debug("XXXXXX \"" + tag.toString() + "\": " + viewjson.toString(4))
+ var wrapContent = LayoutParams.WRAP_CONTENT
+ 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
+ var params = FrameLayout.LayoutParams(width, height)
+ params.topMargin = if (viewjson.has("topmargin")) (viewjson.getInt("topmargin") * zoomFactor).toInt() else 0
+ params.leftMargin = if (viewjson.has("leftmargin")) (viewjson.getInt("leftmargin") * zoomFactor).toInt() else 0
+ view.setLayoutParams(params)
+ view.visibility = if (viewjson.has("visibility")) setVisibility(viewjson.getString("visibility")) else View.GONE
+
+ if (view is TextView) {
+ view.rotation = if (viewjson.has("rotation")) viewjson.getInt("rotation").toFloat() else 0F
+ view.setTextSize(TypedValue.COMPLEX_UNIT_PX, ((if (viewjson.has("textsize")) viewjson.getInt("textsize") else 22) * zoomFactor).toFloat())
+ view.gravity = setGravity(if (viewjson.has("gravity")) viewjson.getString("gravity") else "center")
+ view.setTypeface(
+ setFont(if (viewjson.has("font")) viewjson.getString("font") else "sans-serif"),
+ Style.fromKey( viewjson.getString("fontStyle")).typeface
+ )
+ if (viewjson.has("fontColor"))
+ view.setTextColor(getColor(viewjson.getString("fontColor")))
+ }
+
+ if (view is ImageView) {
+ view.clearColorFilter()
+ drawableDataMap[CustomWatchfaceDrawableDataKey.fromKey(tag.toString())]?.toDrawable(resources)?.also {
+ if (viewjson.has("color"))
+ it.colorFilter = changeDrawableColor(getColor(viewjson.getString("color")))
+ else
+ it.clearColorFilter()
+ view.setImageDrawable(it)
+ } ?: apply {
+ view.setImageDrawable(CustomWatchfaceDrawableDataKey.fromKey(tag.toString()).icon?.let { context.getDrawable(it) })
+ if (viewjson.has("color"))
+ view.setColorFilter(getColor(viewjson.getString("color")))
+ else
+ view.clearColorFilter()
+ }
+ }
+ }
+ }
+ }
+ binding.second.visibility= ((binding.second.visibility==View.VISIBLE) && enableSecond).toVisibility()
+ binding.secondHand.visibility= ((binding.secondHand.visibility==View.VISIBLE) && enableSecond).toVisibility()
+ }
+ }
+
+ private fun defaultWatchface(): EventData.ActionSetCustomWatchface {
+ val metadata = JSONObject()
+ .put(CustomWatchfaceMetadataKey.CWF_NAME.key, getString(info.nightscout.shared.R.string.wear_default_watchface))
+ .put(CustomWatchfaceMetadataKey.CWF_AUTHOR.key, "Philoul")
+ .put(CustomWatchfaceMetadataKey.CWF_CREATED_AT.key, dateUtil.dateString(dateUtil.now()))
+ .put(CustomWatchfaceMetadataKey.CWF_VERSION.key, CUSTOM_VERSION)
+ val json = JSONObject()
+ .put("metadata", metadata)
+ .put("highColor", String.format("#%06X", 0xFFFFFF and highColor))
+ .put("midColor", String.format("#%06X", 0xFFFFFF and midColor))
+ .put("lowColor", String.format("#%06X", 0xFFFFFF and lowColor))
+ .put("lowBatColor", String.format("#%06X", 0xFFFFFF and lowBatColor))
+ .put("carbColor", String.format("#%06X", 0xFFFFFF and carbColor))
+ .put("gridColor", String.format("#%06X", 0xFFFFFF and Color.WHITE))
+ .put("pointSize",2)
+ .put("enableSecond", true)
+
+ binding.mainLayout.forEach { view ->
+ val params = view.layoutParams as FrameLayout.LayoutParams
+ if (view is TextView) {
+ json.put(
+ view.tag.toString(),
+ JSONObject()
+ .put("width", (params.width / zoomFactor).toInt())
+ .put("height", (params.height / zoomFactor).toInt())
+ .put("topmargin", (params.topMargin / zoomFactor).toInt())
+ .put("leftmargin", (params.leftMargin / zoomFactor).toInt())
+ .put("rotation", view.rotation.toInt())
+ .put("visibility", getVisibility(view.visibility))
+ .put("textsize", view.textSize.toInt())
+ .put("gravity", getGravity(view.gravity))
+ .put("font", getFont(view.typeface))
+ .put("fontStyle", Style.fromTypeface(view.typeface.style).key)
+ .put("fontColor", String.format("#%06X", 0xFFFFFF and view.currentTextColor))
+ )
+ }
+ if (view is ImageView) {
+ //view.backgroundTintList =
+ json.put(
+ view.tag.toString(),
+ JSONObject()
+ .put("width", (params.width / zoomFactor).toInt())
+ .put("height", (params.height / zoomFactor).toInt())
+ .put("topmargin", (params.topMargin / zoomFactor).toInt())
+ .put("leftmargin", (params.leftMargin / zoomFactor).toInt())
+ .put("visibility", getVisibility(view.visibility))
+ )
+ }
+ if (view is lecho.lib.hellocharts.view.LineChartView) {
+ json.put(
+ view.tag.toString(),
+ JSONObject()
+ .put("width", (params.width / zoomFactor).toInt())
+ .put("height", (params.height / zoomFactor).toInt())
+ .put("topmargin", (params.topMargin / zoomFactor).toInt())
+ .put("leftmargin", (params.leftMargin / zoomFactor).toInt())
+ .put("visibility", getVisibility(view.visibility))
+ )
+ }
+ }
+ val drawableDatas: CustomWatchfaceDrawableDataMap = mutableMapOf()
+ getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let {
+ val drawableDataMap = DrawableData(it,DrawableFormat.PNG)
+ drawableDatas[CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE] = drawableDataMap
+ }
+ return EventData.ActionSetCustomWatchface(getString(info.nightscout.shared.R.string.wear_default_watchface),json.toString(4),drawableDatas)
+ }
+
+ private fun setDefaultColors() {
+ highColor = Color.parseColor("#FFFF00")
+ midColor = Color.parseColor("#00FF00")
+ lowColor = Color.parseColor("#FF0000")
+ carbColor = ContextCompat.getColor(this, R.color.carbs)
+ lowBatColor = ContextCompat.getColor(this, R.color.dark_uploaderBatteryEmpty)
+ gridColor = Color.WHITE
+ }
+
+ private fun setVisibility(visibility: String): Int = when (visibility) {
+ "visible" -> View.VISIBLE
+ "invisible" -> View.INVISIBLE
+ "gone" -> View.GONE
+ else -> View.GONE
+ }
+
+ private fun getVisibility(visibility: Int): String = when (visibility) {
+ View.VISIBLE -> "visible"
+ View.INVISIBLE -> "invisible"
+ View.GONE -> "gone"
+ else -> "gone"
+ }
+
+ private fun setGravity(gravity: String): Int = when (gravity) {
+ "center" -> Gravity.CENTER
+ "left" -> Gravity.LEFT
+ "right" -> Gravity.RIGHT
+ else -> Gravity.CENTER
+ }
+
+ private fun getGravity(gravity: Int): String = when (gravity) {
+ Gravity.CENTER -> "center"
+ Gravity.LEFT -> "left"
+ Gravity.RIGHT -> "right"
+ else -> "center"
+ }
+
+ private fun setFont(font: String): Typeface = when (font) {
+ "sans-serif" -> Typeface.SANS_SERIF
+ "default" -> Typeface.DEFAULT
+ "default-bold" -> Typeface.DEFAULT_BOLD
+ "monospace" -> Typeface.MONOSPACE
+ "serif" -> Typeface.SERIF
+ "roboto-condensed-bold" -> ResourcesCompat.getFont(context, R.font.roboto_condensed_bold)!!
+ "roboto-condensed-light" -> ResourcesCompat.getFont(context, R.font.roboto_condensed_light)!!
+ "roboto-condensed-regular" -> ResourcesCompat.getFont(context, R.font.roboto_condensed_regular)!!
+ "roboto-slab-light" -> ResourcesCompat.getFont(context, R.font.roboto_slab_light)!!
+ else -> Typeface.DEFAULT
+ }
+
+ private fun getFont(font: Typeface): String = when (font) {
+ Typeface.SANS_SERIF -> "sans-serif"
+ Typeface.DEFAULT -> "default"
+ Typeface.DEFAULT_BOLD -> "default-bold"
+ Typeface.MONOSPACE -> "monospace"
+ Typeface.SERIF -> "serif"
+ ResourcesCompat.getFont(context, R.font.roboto_condensed_bold)!! -> "roboto-condensed-bold"
+ ResourcesCompat.getFont(context, R.font.roboto_condensed_light)!! -> "roboto-condensed-light"
+ ResourcesCompat.getFont(context, R.font.roboto_condensed_regular)!! -> "roboto-condensed-regular"
+ ResourcesCompat.getFont(context, R.font.roboto_slab_light)!! -> "roboto-slab-light"
+ else -> "default"
+ }
+
+ enum class Style(val key: String, val typeface: Int) {
+ NORMAL("normal", Typeface.NORMAL),
+ BOLD("bold", Typeface.BOLD),
+ BOLD_ITALIC("bold-italic", Typeface.BOLD_ITALIC),
+ ITALIC("italic", Typeface.ITALIC);
+ companion object {
+ private val keyToEnumMap = HashMap()
+ private val typefaceToEnumMap = HashMap()
+ init {
+ for (value in values()) keyToEnumMap[value.key] = value
+ for (value in values()) typefaceToEnumMap[value.typeface] = value
+ }
+ fun fromKey(key: String?): Style =
+ if (keyToEnumMap.containsKey(key)) {
+ keyToEnumMap[key] ?:NORMAL
+ } else {
+ NORMAL
+ }
+ fun fromTypeface(typeface: Int?): Style =
+ if (typefaceToEnumMap.containsKey(typeface)) {
+ typefaceToEnumMap[typeface] ?:NORMAL
+ } else {
+ NORMAL
+ }
+ }
+ }
+ fun getResourceByteArray(resourceId: Int): ByteArray? {
+ val inputStream = resources.openRawResource(resourceId)
+ val byteArrayOutputStream = ByteArrayOutputStream()
+
+ try {
+ val buffer = ByteArray(1024)
+ var count: Int
+ while (inputStream.read(buffer).also { count = it } != -1) {
+ byteArrayOutputStream.write(buffer, 0, count)
+ }
+ byteArrayOutputStream.close()
+ inputStream.close()
+
+ return byteArrayOutputStream.toByteArray()
+ } catch (e: Exception) {
+ }
+ return null
+ }
+
+ private fun changeDrawableColor(color: Int): ColorFilter {
+ val colorMatrix = ColorMatrix()
+ colorMatrix.setSaturation(0f) // 0 désature l'image, 1 la laisse inchangée.
+
+ // Modifier la teinte de couleur (couleur de fond)
+ colorMatrix.postConcat(
+ ColorMatrix(
+ floatArrayOf(
+ Color.red(color) / 255f, 0f, 0f, 0f, 0f,
+ 0f, Color.green(color) / 255f, 0f, 0f, 0f,
+ 0f, 0f, Color.blue(color) / 255f, 0f, 0f,
+ 0f, 0f, 0f, Color.alpha(color) / 255f, 0f
+ )
+ )
+ )
+
+ // Appliquer la matrice de couleur au ColorFilter
+ return ColorMatrixColorFilter(colorMatrix)
+
+ // Appliquer le ColorFilter au Drawable
+ //drawable.colorFilter = colorFilter
+ //return drawable
+ }
+
+ private fun getColor(color: String): Int {
+ if (color == "bgColor")
+ return bgColor
+ else
+ return Color.parseColor(color)
+ }
+
+}
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/BaseWatchFace.kt b/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/BaseWatchFace.kt
index a1cc53eab9..4a7fd3a5cf 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/BaseWatchFace.kt
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/BaseWatchFace.kt
@@ -36,6 +36,7 @@ import info.nightscout.shared.extensions.toVisibilityKeepSpace
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import kotlin.math.floor
@@ -78,12 +79,15 @@ abstract class BaseWatchFace : WatchFace() {
var gridColor = Color.WHITE
var basalBackgroundColor = Color.BLUE
var basalCenterColor = Color.BLUE
+ var carbColor = Color.GREEN
private var bolusColor = Color.MAGENTA
private var lowResMode = false
private var layoutSet = false
var bIsRound = false
var dividerMatchesBg = false
var pointSize = 2
+ var enableSecond = false
+ var updateSecond: Disposable? = null
// Tapping times
private var sgvTapTime: Long = 0
@@ -245,12 +249,13 @@ abstract class BaseWatchFace : WatchFace() {
override fun onDestroy() {
disposable.clear()
+ updateSecond?.dispose()
simpleUi.onDestroy()
super.onDestroy()
}
override fun getInteractiveModeUpdateRate(): Long {
- return 60 * 1000L // Only call onTimeChanged every 60 seconds
+ return if (enableSecond) 1000L else 60 * 1000L // Only call onTimeChanged every 60 seconds
}
override fun onDraw(canvas: Canvas) {
@@ -271,6 +276,8 @@ abstract class BaseWatchFace : WatchFace() {
missedReadingAlert()
checkVibrateHourly(oldTime, newTime)
if (!simpleUi.isEnabled(currentWatchMode)) setDataFields()
+ } else if (layoutSet && !simpleUi.isEnabled(currentWatchMode) && enableSecond && newTime.hasSecondChanged(oldTime)) {
+ setSecond()
}
}
@@ -349,9 +356,10 @@ abstract class BaseWatchFace : WatchFace() {
}
private fun setDateAndTime() {
- binding.time?.text = if(binding.timePeriod == null) dateUtil.timeString() else dateUtil.hourString() + ":" + dateUtil.minuteString()
+ binding.time?.text = if(binding.timePeriod == null) dateUtil.timeString() else dateUtil.hourString() + ":" + dateUtil.minuteString() + if (enableSecond) ":" + dateUtil.secondString() else ""
binding.hour?.text = dateUtil.hourString()
binding.minute?.text = dateUtil.minuteString()
+ binding.second?.text = dateUtil.secondString()
binding.dateTime?.visibility = sp.getBoolean(R.string.key_show_date, false).toVisibility()
binding.dayName?.text = dateUtil.dayNameString()
binding.day?.text = dateUtil.dayString()
@@ -360,7 +368,12 @@ abstract class BaseWatchFace : WatchFace() {
binding.timePeriod?.text = dateUtil.amPm()
}
- private fun setColor() {
+ open fun setSecond() {
+ binding.time?.text = if(binding.timePeriod == null) dateUtil.timeString() else dateUtil.hourString() + ":" + dateUtil.minuteString() + if (enableSecond) ":" + dateUtil.secondString() else ""
+ binding.second?.text = dateUtil.secondString()
+ }
+
+ fun setColor() {
dividerMatchesBg = sp.getBoolean(R.string.key_match_divider, false)
when {
lowResMode -> setColorLowRes()
@@ -382,6 +395,14 @@ abstract class BaseWatchFace : WatchFace() {
if (simpleUi.isEnabled(currentWatchMode)) simpleUi.setAntiAlias(currentWatchMode)
else setDataFields()
invalidate()
+ /*
+ if (enableSecond)
+ if (updateSecond == null)
+ updateSecond = aapsSchedulers.io.schedulePeriodicallyDirect(
+ ::setSecond, 1000L, 1000L, TimeUnit.MILLISECONDS)
+ else
+ updateSecond?.dispose()
+ */
}
private fun isLowRes(watchMode: WatchMode): Boolean {
@@ -409,12 +430,12 @@ abstract class BaseWatchFace : WatchFace() {
if (lowResMode)
BgGraphBuilder(
sp, dateUtil, graphData.entries, treatmentData.predictions, treatmentData.temps, treatmentData.basals, treatmentData.boluses, pointSize,
- midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe
+ midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, carbColor, timeframe
)
else
BgGraphBuilder(
sp, dateUtil, graphData.entries, treatmentData.predictions, treatmentData.temps, treatmentData.basals, treatmentData.boluses,
- pointSize, highColor, lowColor, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe
+ pointSize, highColor, lowColor, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, carbColor, timeframe
)
binding.chart?.lineChartData = bgGraphBuilder.lineData()
binding.chart?.isViewportCalculationEnabled = true
diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/WatchfaceViewAdapter.kt b/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/WatchfaceViewAdapter.kt
index 6a2f78d954..61333ad1e1 100644
--- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/WatchfaceViewAdapter.kt
+++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/utils/WatchfaceViewAdapter.kt
@@ -6,6 +6,7 @@ import info.nightscout.androidaps.databinding.ActivityHome2Binding
import info.nightscout.androidaps.databinding.ActivityHomeBinding
import info.nightscout.androidaps.databinding.ActivityBigchartBinding
import info.nightscout.androidaps.databinding.ActivityCockpitBinding
+import info.nightscout.androidaps.databinding.ActivityCustomBinding
import info.nightscout.androidaps.databinding.ActivityDigitalstyleBinding
import info.nightscout.androidaps.databinding.ActivityNochartBinding
import info.nightscout.androidaps.databinding.ActivitySteampunkBinding
@@ -22,11 +23,12 @@ class WatchfaceViewAdapter(
cp: ActivityCockpitBinding? = null,
ds: ActivityDigitalstyleBinding? = null,
nC: ActivityNochartBinding? = null,
- sP: ActivitySteampunkBinding? = null
+ sP: ActivitySteampunkBinding? = null,
+ cU: ActivityCustomBinding? = null
) {
init {
- if (aL == null && a2 == null && aa == null && bC == null && cp == null && ds == null && nC == null && sP == null) {
+ if (aL == null && a2 == null && aa == null && bC == null && cp == null && ds == null && nC == null && sP == null && cU == null) {
throw IllegalArgumentException("Require at least on Binding parameter")
}
}
@@ -34,39 +36,40 @@ class WatchfaceViewAdapter(
private val errorMessage = "Missing require View Binding parameter"
// Required attributes
val mainLayout =
- aL?.mainLayout ?: a2?.mainLayout ?: aa?.mainLayout ?: bC?.mainLayout ?: bC?.mainLayout ?: cp?.mainLayout ?: ds?.mainLayout ?: nC?.mainLayout ?: sP?.mainLayout
+ aL?.mainLayout ?: a2?.mainLayout ?: aa?.mainLayout ?: bC?.mainLayout ?: bC?.mainLayout ?: cp?.mainLayout ?: ds?.mainLayout ?: nC?.mainLayout ?: sP?.mainLayout ?: cU?.mainLayout
?: throw IllegalArgumentException(errorMessage)
val timestamp =
- aL?.timestamp ?: a2?.timestamp ?: aa?.timestamp ?: bC?.timestamp ?: bC?.timestamp ?: cp?.timestamp ?: ds?.timestamp ?: nC?.timestamp ?: sP?.timestamp
+ aL?.timestamp ?: a2?.timestamp ?: aa?.timestamp ?: bC?.timestamp ?: bC?.timestamp ?: cp?.timestamp ?: ds?.timestamp ?: nC?.timestamp ?: sP?.timestamp ?: cU?.timestamp
?: throw IllegalArgumentException(errorMessage)
val root =
- aL?.root ?: a2?.root ?: aa?.root ?: bC?.root ?: bC?.root ?: cp?.root ?: ds?.root ?: nC?.root ?: sP?.root
+ aL?.root ?: a2?.root ?: aa?.root ?: bC?.root ?: bC?.root ?: cp?.root ?: ds?.root ?: nC?.root ?: sP?.root ?: cU?.root
?: throw IllegalArgumentException(errorMessage)
// Optional attributes
- val sgv = aL?.sgv ?: a2?.sgv ?: aa?.sgv ?: bC?.sgv ?: bC?.sgv ?: cp?.sgv ?: ds?.sgv ?: nC?.sgv
- val direction = aL?.direction ?: a2?.direction ?: aa?.direction ?: cp?.direction ?: ds?.direction
- val loop = a2?.loop ?: cp?.loop ?: sP?.loop
- val delta = aL?.delta ?: a2?.delta ?: aa?.delta ?: bC?.delta ?: bC?.delta ?: cp?.delta ?: ds?.delta ?: nC?.delta
- val avgDelta = a2?.avgDelta ?: bC?.avgDelta ?: bC?.avgDelta ?: cp?.avgDelta ?: ds?.avgDelta ?: nC?.avgDelta
- val uploaderBattery = aL?.uploaderBattery ?: a2?.uploaderBattery ?: aa?.uploaderBattery ?: cp?.uploaderBattery ?: ds?.uploaderBattery ?: sP?.uploaderBattery
- val rigBattery = a2?.rigBattery ?: cp?.rigBattery ?: ds?.rigBattery ?: sP?.rigBattery
- val basalRate = a2?.basalRate ?: cp?.basalRate ?: ds?.basalRate ?: sP?.basalRate
- val bgi = a2?.bgi ?: ds?.bgi
- val AAPSv2 = a2?.AAPSv2 ?: cp?.AAPSv2 ?: ds?.AAPSv2 ?: sP?.AAPSv2
- val cob1 = a2?.cob1 ?: ds?.cob1
- val cob2 = a2?.cob2 ?: cp?.cob2 ?: ds?.cob2 ?: sP?.cob2
- val time = aL?.time ?: a2?.time ?: aa?.time ?: bC?.time ?: bC?.time ?: cp?.time ?: nC?.time
- val minute = ds?.minute
- val hour = ds?.hour
- val day = a2?.day ?: ds?.day
- val month = a2?.month ?: ds?.month
- val iob1 = a2?.iob1 ?: ds?.iob1
- val iob2 = a2?.iob2 ?: cp?.iob2 ?: ds?.iob2 ?: sP?.iob2
- val chart = a2?.chart ?: aa?.chart ?: bC?.chart ?: bC?.chart ?: ds?.chart ?: sP?.chart
+ val sgv = aL?.sgv ?: a2?.sgv ?: aa?.sgv ?: bC?.sgv ?: bC?.sgv ?: cp?.sgv ?: ds?.sgv ?: nC?.sgv ?: cU?.sgv
+ val direction = aL?.direction ?: a2?.direction ?: aa?.direction ?: cp?.direction ?: ds?.direction ?: cU?.direction
+ val loop = a2?.loop ?: cp?.loop ?: sP?.loop ?: cU?.loop
+ val delta = aL?.delta ?: a2?.delta ?: aa?.delta ?: bC?.delta ?: bC?.delta ?: cp?.delta ?: ds?.delta ?: nC?.delta ?: cU?.delta
+ val avgDelta = a2?.avgDelta ?: bC?.avgDelta ?: bC?.avgDelta ?: cp?.avgDelta ?: ds?.avgDelta ?: nC?.avgDelta ?: cU?.avgDelta
+ val uploaderBattery = aL?.uploaderBattery ?: a2?.uploaderBattery ?: aa?.uploaderBattery ?: cp?.uploaderBattery ?: ds?.uploaderBattery ?: sP?.uploaderBattery ?: cU?.uploaderBattery
+ val rigBattery = a2?.rigBattery ?: cp?.rigBattery ?: ds?.rigBattery ?: sP?.rigBattery ?: cU?.rigBattery
+ val basalRate = a2?.basalRate ?: cp?.basalRate ?: ds?.basalRate ?: sP?.basalRate ?: cU?.basalRate
+ val bgi = a2?.bgi ?: ds?.bgi ?: cU?.bgi
+ val AAPSv2 = a2?.AAPSv2 ?: cp?.AAPSv2 ?: ds?.AAPSv2 ?: sP?.AAPSv2 ?: cU?.AAPSv2
+ val cob1 = a2?.cob1 ?: ds?.cob1 ?: cU?.cob1
+ val cob2 = a2?.cob2 ?: cp?.cob2 ?: ds?.cob2 ?: sP?.cob2 ?: cU?.cob2
+ val time = aL?.time ?: a2?.time ?: aa?.time ?: bC?.time ?: bC?.time ?: cp?.time ?: nC?.time ?: cU?.time
+ val second = cU?.second
+ val minute = ds?.minute ?: cU?.minute
+ val hour = ds?.hour ?: cU?.hour
+ val day = a2?.day ?: ds?.day ?: cU?.day
+ val month = a2?.month ?: ds?.month ?: cU?.month
+ val iob1 = a2?.iob1 ?: ds?.iob1 ?: cU?.iob1
+ val iob2 = a2?.iob2 ?: cp?.iob2 ?: ds?.iob2 ?: sP?.iob2 ?: cU?.iob2
+ val chart = a2?.chart ?: aa?.chart ?: bC?.chart ?: bC?.chart ?: ds?.chart ?: sP?.chart ?: cU?.chart
val status = aL?.status ?: aa?.status ?: bC?.status ?: bC?.status ?: nC?.status
- val timePeriod = ds?.timePeriod ?: aL?.timePeriod ?: nC?.timePeriod ?: bC?.timePeriod
- val dayName = ds?.dayName
+ val timePeriod = ds?.timePeriod ?: aL?.timePeriod ?: nC?.timePeriod ?: bC?.timePeriod ?: cU?.timePeriod
+ val dayName = ds?.dayName ?: cU?.dayName
val mainMenuTap = ds?.mainMenuTap ?: sP?.mainMenuTap
val chartZoomTap = ds?.chartZoomTap ?: sP?.chartZoomTap
val dateTime = ds?.dateTime ?: a2?.dateTime
@@ -91,6 +94,7 @@ class WatchfaceViewAdapter(
is ActivityDigitalstyleBinding -> WatchfaceViewAdapter(null, null, null, null, null, bindLayout)
is ActivityNochartBinding -> WatchfaceViewAdapter(null, null, null, null, null, null, bindLayout)
is ActivitySteampunkBinding -> WatchfaceViewAdapter(null, null, null, null, null, null, null, bindLayout)
+ is ActivityCustomBinding -> WatchfaceViewAdapter(null, null, null, null, null, null, null, null, bindLayout)
else -> throw IllegalArgumentException("ViewBinding is not implement in WatchfaceViewAdapter")
}
}
diff --git a/wear/src/main/res/layout/activity_custom.xml b/wear/src/main/res/layout/activity_custom.xml
new file mode 100644
index 0000000000..a1172a3c3f
--- /dev/null
+++ b/wear/src/main/res/layout/activity_custom.xml
@@ -0,0 +1,379 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml
index a9f53bd230..b29ad82733 100644
--- a/wear/src/main/res/values/strings.xml
+++ b/wear/src/main/res/values/strings.xml
@@ -11,6 +11,7 @@
AAPS(Cockpit)
AAPS(Steampunk)
AAPS(DigitalStyle)
+ AAPS(Custom)
AAPS(Actions)
AAPS(Temp Target)
AAPS(Quick Wizard)
@@ -143,6 +144,7 @@
Only show time and BG
Vibrate hourly
Show Week number
+ Show seconds
Your style:
no style
minimal style
@@ -208,6 +210,14 @@
complication_tap_action
carbs_button_increment_1
carbs_button_increment_2
+ enable_custom_setting
+ digital_watchface
+ digital_timing
+ analog_watchface
+ show_second
+ highcolor
+ midcolor
+ lowcolor
increment
decrement
H
diff --git a/wear/src/main/res/xml/watch_face_configuration_custom.xml b/wear/src/main/res/xml/watch_face_configuration_custom.xml
new file mode 100644
index 0000000000..ff998be701
--- /dev/null
+++ b/wear/src/main/res/xml/watch_face_configuration_custom.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+