Add Custom Watchface

This commit is contained in:
Philoul 2023-08-04 14:59:08 +02:00
parent 1500381e14
commit b259425361
63 changed files with 2619 additions and 66 deletions

9
_docs/icons/HourHand.svg Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Hour_hand">
<path fill="#FFFFFF" d="M11.999,4.906c-0.104,0-0.188,0.084-0.188,0.188V12c0,0.104,0.084,0.188,0.188,0.188
c0.104,0,0.188-0.084,0.188-0.188V5.094C12.186,4.99,12.102,4.906,11.999,4.906z M11.999,12.094c-0.047,0-0.086-0.038-0.086-0.086
s0.039-0.086,0.086-0.086c0.047,0,0.086,0.038,0.086,0.086S12.046,12.094,11.999,12.094z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 613 B

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Minute_hend">
<path fill="#FFFFFF" d="M11.999,1.406c-0.104,0-0.188,0.084-0.188,0.188V12c0,0.104,0.084,0.188,0.188,0.188
c0.104,0,0.188-0.084,0.188-0.188V1.594C12.186,1.49,12.102,1.406,11.999,1.406z M11.999,12.094c-0.047,0-0.086-0.038-0.086-0.086
s0.039-0.086,0.086-0.086c0.047,0,0.086,0.038,0.086,0.086S12.046,12.094,11.999,12.094z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 615 B

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Second_hand">
<path fill="#FF1313" d="M12.198,11.462v-0.237c0-0.077-0.05-0.131-0.114-0.164V0.508c0-0.047-0.038-0.086-0.086-0.086
c-0.047,0-0.086,0.038-0.086,0.086v10.553c-0.063,0.033-0.114,0.087-0.114,0.164v0.238c-0.219,0.082-0.376,0.29-0.376,0.537
s0.157,0.455,0.376,0.537v0.92c0,0.11,0.089,0.2,0.2,0.2s0.2-0.089,0.2-0.2v-0.919c0.221-0.081,0.381-0.289,0.381-0.538
S12.419,11.543,12.198,11.462z M11.999,12.094c-0.047,0-0.086-0.038-0.086-0.086s0.039-0.086,0.086-0.086
c0.047,0,0.086,0.038,0.086,0.086S12.046,12.094,11.999,12.094z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 816 B

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Simplified_dial">
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="11.999" y1="0.008" x2="11.999" y2="2.708"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="11.999" y1="21.307" x2="11.999" y2="24.008"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="23.999" y1="12" x2="21.298" y2="12"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="2.699" y1="12" x2="-0.001" y2="12"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="5.999" y1="1.616" x2="7.349" y2="3.954"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="16.648" y1="20.061" x2="17.999" y2="22.4"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="22.387" y1="6.001" x2="20.048" y2="7.351"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="3.941" y1="16.651" x2="1.602" y2="18.001"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="1.606" y1="6.008" x2="3.945" y2="7.358"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="20.052" y1="16.658" x2="22.391" y2="18.008"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="17.992" y1="1.612" x2="16.642" y2="3.95"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="7.342" y1="20.057" x2="5.992" y2="22.396"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Background">
<rect x="0.103" width="24" height="24"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 430 B

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Detailed_dial">
<g id="Minutes">
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="10.744" y1="0.074" x2="10.885" y2="1.416"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="13.113" y1="22.599" x2="13.254" y2="23.942"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="9.504" y1="0.27" x2="9.784" y2="1.591"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="14.214" y1="22.425" x2="14.495" y2="23.745"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="8.29" y1="0.595" x2="8.708" y2="1.88"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="15.291" y1="22.136" x2="15.708" y2="23.42"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="7.118" y1="1.046" x2="7.667" y2="2.279"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="16.332" y1="21.737" x2="16.881" y2="22.97"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="4.945" y1="2.3" x2="5.739" y2="3.392"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="18.26" y1="20.623" x2="19.053" y2="21.716"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="3.969" y1="3.091" x2="4.873" y2="4.094"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="19.126" y1="19.922" x2="20.029" y2="20.925"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="3.081" y1="3.979" x2="4.084" y2="4.882"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="19.914" y1="19.133" x2="20.917" y2="20.037"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="2.291" y1="4.955" x2="3.383" y2="5.749"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="20.616" y1="18.267" x2="21.708" y2="19.061"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="1.036" y1="7.128" x2="2.27" y2="7.677"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="21.729" y1="16.339" x2="22.962" y2="16.888"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="0.586" y1="8.3" x2="1.871" y2="8.718"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="22.128" y1="15.298" x2="23.412" y2="15.715"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="0.261" y1="9.514" x2="1.582" y2="9.794"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="22.416" y1="14.221" x2="23.737" y2="14.502"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="0.065" y1="10.754" x2="1.408" y2="10.895"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="22.591" y1="13.12" x2="23.934" y2="13.261"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="0.065" y1="13.263" x2="1.408" y2="13.122"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="22.591" y1="10.894" x2="23.933" y2="10.753"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="0.262" y1="14.503" x2="1.582" y2="14.223"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="22.416" y1="9.793" x2="23.737" y2="9.512"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="0.587" y1="15.717" x2="1.871" y2="15.299"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="22.128" y1="8.716" x2="23.412" y2="8.299"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="1.037" y1="16.889" x2="2.271" y2="16.34"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="21.728" y1="7.676" x2="22.962" y2="7.126"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="2.291" y1="19.062" x2="3.384" y2="18.268"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="20.615" y1="5.747" x2="21.707" y2="4.954"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="3.082" y1="20.038" x2="4.085" y2="19.134"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="19.913" y1="4.881" x2="20.917" y2="3.978"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="3.97" y1="20.926" x2="4.874" y2="19.923"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="19.125" y1="4.093" x2="20.028" y2="3.09"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="4.946" y1="21.716" x2="5.74" y2="20.624"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="18.258" y1="3.392" x2="19.052" y2="2.299"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="7.119" y1="22.971" x2="7.668" y2="21.737"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="16.33" y1="2.278" x2="16.879" y2="1.045"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="8.292" y1="23.421" x2="8.709" y2="22.137"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="15.29" y1="1.879" x2="15.707" y2="0.595"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="9.505" y1="23.746" x2="9.786" y2="22.425"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="14.213" y1="1.591" x2="14.494" y2="0.27"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="10.746" y1="23.942" x2="10.887" y2="22.599"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="13.112" y1="1.416" x2="13.253" y2="0.073"/>
</g>
<g id="Hours">
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="11.999" y1="0.008" x2="11.999" y2="2.708"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="11.999" y1="21.307" x2="11.999" y2="24.008"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="23.999" y1="12" x2="21.298" y2="12"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="2.699" y1="12" x2="-0.001" y2="12"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="5.999" y1="1.616" x2="7.349" y2="3.954"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="16.648" y1="20.061" x2="17.999" y2="22.4"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="22.387" y1="6.001" x2="20.048" y2="7.351"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="3.941" y1="16.651" x2="1.602" y2="18.001"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="1.606" y1="6.008" x2="3.945" y2="7.358"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="20.052" y1="16.658" x2="22.391" y2="18.008"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="17.992" y1="1.612" x2="16.642" y2="3.95"/>
<line fill="#FFFFFF" stroke="#FFFFFF" stroke-width="0.1417" stroke-miterlimit="10" x1="7.342" y1="20.057" x2="5.992" y2="22.396"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="ExportCustom">
<g>
<path fill="#AAAAAA" d="M1.885,11.953c0,2.578,2.09,4.668,4.668,4.668s4.668-2.09,4.668-4.668c0-2.578-2.09-4.668-4.668-4.668
S1.885,9.375,1.885,11.953z M3.093,11.953c0-1.911,1.549-3.46,3.46-3.46s3.46,1.549,3.46,3.46c0,1.911-1.549,3.46-3.46,3.46
S3.093,13.864,3.093,11.953z"/>
<polygon fill="#AAAAAA" points="4.273,4.976 8.833,4.976 9.43,8.314 3.676,8.314 "/>
<polygon fill="#AAAAAA" points="4.273,19.024 8.833,19.024 9.43,15.686 3.676,15.686 "/>
</g>
<g>
<rect x="12.915" y="6.067" fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linejoin="round" stroke-miterlimit="10" width="9.405" height="11.865"/>
<g>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="21.172" y1="7.23" x2="14.064" y2="7.23"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="21.172" y1="8.592" x2="14.064" y2="8.592"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="21.172" y1="11.318" x2="14.064" y2="11.318"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="21.172" y1="9.955" x2="14.064" y2="9.955"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="21.172" y1="12.68" x2="14.064" y2="12.68"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="21.172" y1="14.043" x2="14.064" y2="14.043"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="21.172" y1="16.768" x2="14.064" y2="16.768"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="21.172" y1="15.406" x2="14.064" y2="15.406"/>
</g>
</g>
<polygon fill="#6AE86D" points="19.351,11.953 14.376,9.823 14.376,11.078 6.48,11.078 6.48,12.828 14.376,12.828 14.376,14.084
"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="LoadCustom">
<g>
<path fill="#AAAAAA" d="M17.653,7.286c-2.578,0-4.668,2.09-4.668,4.668c0,2.578,2.09,4.668,4.668,4.668s4.668-2.09,4.668-4.668
C22.321,9.375,20.231,7.286,17.653,7.286z M17.653,15.414c-1.911,0-3.46-1.549-3.46-3.46c0-1.911,1.549-3.46,3.46-3.46
s3.46,1.549,3.46,3.46C21.113,13.864,19.564,15.414,17.653,15.414z"/>
<polygon fill="#AAAAAA" points="19.932,4.976 15.373,4.976 14.776,8.314 20.53,8.314 "/>
<polygon fill="#AAAAAA" points="19.932,19.024 15.373,19.024 14.776,15.686 20.53,15.686 "/>
</g>
<g>
<rect x="1.885" y="6.067" fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linejoin="round" stroke-miterlimit="10" width="9.405" height="11.865"/>
<g>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.034" y1="7.23" x2="10.142" y2="7.23"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.034" y1="8.592" x2="10.142" y2="8.592"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.034" y1="11.318" x2="10.142" y2="11.318"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.034" y1="9.955" x2="10.142" y2="9.955"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.034" y1="12.68" x2="10.142" y2="12.68"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.034" y1="14.043" x2="10.142" y2="14.043"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.034" y1="16.768" x2="10.142" y2="16.768"/>
<line fill="none" stroke="#FFFFFF" stroke-width="0.4819" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.034" y1="15.406" x2="10.142" y2="15.406"/>
</g>
</g>
<polygon fill="#6AE86D" points="17.726,11.953 12.751,9.823 12.751,11.078 4.855,11.078 4.855,12.828 12.751,12.828
12.751,14.084 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="SendCustom">
<g>
<path fill="#AAAAAA" d="M16.168,7.286c-2.578,0-4.668,2.09-4.668,4.668c0,2.578,2.09,4.668,4.668,4.668s4.668-2.09,4.668-4.668
C20.836,9.375,18.746,7.286,16.168,7.286z M16.168,15.414c-1.911,0-3.46-1.549-3.46-3.46c0-1.911,1.549-3.46,3.46-3.46
s3.46,1.549,3.46,3.46C19.628,13.864,18.079,15.414,16.168,15.414z"/>
<polygon fill="#AAAAAA" points="18.448,4.976 13.888,4.976 13.291,8.314 19.045,8.314 "/>
<polygon fill="#AAAAAA" points="18.448,19.024 13.888,19.024 13.291,15.686 19.045,15.686 "/>
</g>
<polygon fill="#6AE86D" points="16.241,11.953 11.266,9.823 11.266,11.078 3.37,11.078 3.37,12.828 11.266,12.828 11.266,14.084
"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="SetDefault">
<g>
<path fill="#AAAAAA" d="M12.103,7.286c-2.578,0-4.668,2.09-4.668,4.668c0,2.578,2.09,4.668,4.668,4.668s4.668-2.09,4.668-4.668
C16.771,9.375,14.681,7.286,12.103,7.286z M12.103,15.414c-1.911,0-3.46-1.549-3.46-3.46c0-1.911,1.549-3.46,3.46-3.46
s3.46,1.549,3.46,3.46C15.563,13.864,14.014,15.414,12.103,15.414z"/>
<polygon fill="#AAAAAA" points="14.383,4.976 9.823,4.976 9.226,8.314 14.98,8.314 "/>
<polygon fill="#AAAAAA" points="14.383,19.024 9.823,19.024 9.226,15.686 14.98,15.686 "/>
</g>
<path fill="#FF1313" d="M13.768,11.999l1.886-1.886c0.081-0.081,0.081-0.213,0-0.294l-1.372-1.372
c-0.078-0.078-0.216-0.078-0.294,0l-1.886,1.886l-1.886-1.886c-0.078-0.078-0.216-0.078-0.294,0L8.552,9.819
C8.513,9.858,8.491,9.911,8.491,9.966c0,0.055,0.022,0.108,0.061,0.147l1.886,1.886l-1.886,1.886
c-0.039,0.039-0.061,0.092-0.061,0.147c0,0.055,0.022,0.108,0.061,0.147l1.372,1.372c0.041,0.04,0.094,0.061,0.147,0.061
c0.053,0,0.106-0.02,0.147-0.061l1.886-1.886l1.886,1.886c0.04,0.04,0.093,0.061,0.147,0.061c0.053,0,0.106-0.02,0.147-0.061
l1.371-1.371c0.081-0.081,0.081-0.213,0-0.294L13.768,11.999z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -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

View file

@ -0,0 +1,5 @@
package info.nightscout.rx.events
import info.nightscout.rx.weardata.EventData
class EventMobileDataToWear(val payload: EventData.ActionSetCustomWatchface) : Event()

View file

@ -0,0 +1,5 @@
package info.nightscout.rx.events
import info.nightscout.rx.weardata.EventData
class EventWearCwfExported(val payload: EventData.ActionSetCustomWatchface): Event()

View file

@ -0,0 +1,5 @@
package info.nightscout.rx.events
import info.nightscout.rx.weardata.EventData
class EventWearDataToMobile(val payload: EventData) : Event()

View file

@ -0,0 +1,3 @@
package info.nightscout.rx.events
class EventWearUpdateGui : Event()

View file

@ -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<String, CustomWatchfaceDrawableDataKey>()
private val fileNameToEnumMap = HashMap<String, CustomWatchfaceDrawableDataKey>()
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<String, DrawableFormat>()
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<CustomWatchfaceDrawableDataKey, DrawableData>
typealias CustomWatchfaceMetadataMap = MutableMap<CustomWatchfaceMetadataKey, String>
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<String, CustomWatchfaceMetadataKey>()
init {
for (value in values()) keyToEnumMap[value.key] = value
}
fun fromKey(key: String): CustomWatchfaceMetadataKey? =
if (keyToEnumMap.containsKey(key)) {
keyToEnumMap[key]
} else {
null
}
}
}

View file

@ -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()

View file

@ -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"))

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M0.103,0h24v24h-24z"/>
</vector>

View file

@ -0,0 +1,183 @@
<vector android:height="400dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00000000"
android:pathData="M10.744,0.074L10.885,1.416"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M13.113,22.599L13.254,23.942"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M9.504,0.27L9.784,1.591"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M14.214,22.425L14.495,23.745"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M8.29,0.595L8.708,1.88"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M15.291,22.136L15.708,23.42"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M7.118,1.046L7.667,2.279"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M16.332,21.737L16.881,22.97"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M4.945,2.3L5.739,3.392"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M18.26,20.623L19.053,21.716"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M3.969,3.091L4.873,4.094"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M19.126,19.922L20.029,20.925"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M3.081,3.979L4.084,4.882"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M19.914,19.133L20.917,20.037"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M2.291,4.955L3.383,5.749"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M20.616,18.267L21.708,19.061"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M1.036,7.128L2.27,7.677"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M21.729,16.339L22.962,16.888"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M0.586,8.3L1.871,8.718"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M22.128,15.298L23.412,15.715"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M0.261,9.514L1.582,9.794"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M22.416,14.221L23.737,14.502"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M0.065,10.754L1.408,10.895"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M22.591,13.12L23.934,13.261"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M0.065,13.263L1.408,13.122"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M22.591,10.894L23.933,10.753"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M0.262,14.503L1.582,14.223"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M22.416,9.793L23.737,9.512"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M0.587,15.717L1.871,15.299"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M22.128,8.716L23.412,8.299"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M1.037,16.889L2.271,16.34"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M21.728,7.676L22.962,7.126"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M2.291,19.062L3.384,18.268"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M20.615,5.747L21.707,4.954"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M3.082,20.038L4.085,19.134"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M19.913,4.881L20.917,3.978"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M3.97,20.926L4.874,19.923"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M19.125,4.093L20.028,3.09"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M4.946,21.716L5.74,20.624"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M18.258,3.392L19.052,2.299"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M7.119,22.971L7.668,21.737"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M16.33,2.278L16.879,1.045"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M8.292,23.421L8.709,22.137"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M15.29,1.879L15.707,0.595"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M9.505,23.746L9.786,22.425"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M14.213,1.591L14.494,0.27"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M10.746,23.942L10.887,22.599"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#00000000"
android:pathData="M13.112,1.416L13.253,0.073"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M11.999,0.008L11.999,2.708"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M11.999,21.307L11.999,24.008"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M23.999,12L21.298,12"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M2.699,12L-0.001,12"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M5.999,1.616L7.349,3.954"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M16.648,20.061L17.999,22.4"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M22.387,6.001L20.048,7.351"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M3.941,16.651L1.602,18.001"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M1.606,6.008L3.945,7.358"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M20.052,16.658L22.391,18.008"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M17.992,1.612L16.642,3.95"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M7.342,20.057L5.992,22.396"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="400dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M11.999,4.906c-0.104,0 -0.188,0.084 -0.188,0.188V12c0,0.104 0.084,0.188 0.188,0.188c0.104,0 0.188,-0.084 0.188,-0.188V5.094C12.186,4.99 12.102,4.906 11.999,4.906zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="400dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M11.999,1.406c-0.104,0 -0.188,0.084 -0.188,0.188V12c0,0.104 0.084,0.188 0.188,0.188c0.104,0 0.188,-0.084 0.188,-0.188V1.594C12.186,1.49 12.102,1.406 11.999,1.406zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="400dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF1313" android:pathData="M12.198,11.462v-0.237c0,-0.077 -0.05,-0.131 -0.114,-0.164V0.508c0,-0.047 -0.038,-0.086 -0.086,-0.086c-0.047,0 -0.086,0.038 -0.086,0.086v10.553c-0.063,0.033 -0.114,0.087 -0.114,0.164v0.238c-0.219,0.082 -0.376,0.29 -0.376,0.537s0.157,0.455 0.376,0.537v0.92c0,0.11 0.089,0.2 0.2,0.2s0.2,-0.089 0.2,-0.2v-0.919c0.221,-0.081 0.381,-0.289 0.381,-0.538S12.419,11.543 12.198,11.462zM11.999,12.094c-0.047,0 -0.086,-0.038 -0.086,-0.086s0.039,-0.086 0.086,-0.086c0.047,0 0.086,0.038 0.086,0.086S12.046,12.094 11.999,12.094z"/>
</vector>

View file

@ -0,0 +1,39 @@
<vector android:height="400dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF"
android:pathData="M11.999,0.008L11.999,2.708"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M11.999,21.307L11.999,24.008"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M23.999,12L21.298,12"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M2.699,12L-0.001,12"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M5.999,1.616L7.349,3.954"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M16.648,20.061L17.999,22.4"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M22.387,6.001L20.048,7.351"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M3.941,16.651L1.602,18.001"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M1.606,6.008L3.945,7.358"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M20.052,16.658L22.391,18.008"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M17.992,1.612L16.642,3.95"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
<path android:fillColor="#FFFFFF"
android:pathData="M7.342,20.057L5.992,22.396"
android:strokeColor="#FFFFFF" android:strokeWidth="0.1417"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View file

@ -39,4 +39,12 @@
<string name="disconnecting">Disconnecting</string>
<string name="waiting_for_disconnection">Waiting for disconnection</string>
<!-- 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_author">Author: %1$s</string>
<string name="metadata_label_watchface_name">Name: %1$s</string>
<string name="metadata_label_watchface_version">Watchface version: %1$s</string>
<string name="wear_default_watchface">Default Watchface</string>
</resources>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="path_rx_bridge" translatable="false">/rx_bridge</string>
<string name="path_rx_data_bridge" translatable="false">/rx_data_bridge</string>
</resources>

View file

@ -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
)

View file

@ -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)

View file

@ -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<PrefsFile>
fun listCustomWatchfaceFiles(): MutableList<CustomWatchfaceFile>
fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata>
fun formatExportedAgo(utcTime: String): String
}

View file

@ -18,6 +18,10 @@
android:name=".maintenance.activities.PrefImportListActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name=".maintenance.activities.CustomWatchfaceImportListActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name="info.nightscout.configuration.activities.SingleFragmentActivity"
android:exported="false"

View file

@ -8,6 +8,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.configuration.R
import info.nightscout.configuration.maintenance.CustomWatchfaceFileContract
import info.nightscout.configuration.maintenance.PrefsFileContract
import info.nightscout.core.ui.dialogs.OKDialog
import info.nightscout.core.ui.locale.LocaleHelper
@ -55,6 +56,8 @@ open class DaggerAppCompatActivityWithResult : DaggerAppCompatActivity() {
}
}
val callForCustomWatchfaceFile = registerForActivityResult(CustomWatchfaceFileContract()) { }
val callForBatteryOptimization = registerForActivityResult(OptimizationPermissionContract()) {
updateButtons()
}

View file

@ -11,8 +11,10 @@ import info.nightscout.configuration.configBuilder.RunningConfigurationImpl
import info.nightscout.configuration.maintenance.ImportExportPrefsImpl
import info.nightscout.configuration.maintenance.MaintenanceFragment
import info.nightscout.configuration.maintenance.PrefFileListProviderImpl
import info.nightscout.configuration.maintenance.activities.CustomWatchfaceImportListActivity
import info.nightscout.configuration.maintenance.activities.LogSettingActivity
import info.nightscout.configuration.maintenance.activities.PrefImportListActivity
import info.nightscout.configuration.maintenance.formats.ZipCustomWatchfaceFormat
import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat
import info.nightscout.interfaces.AndroidPermission
import info.nightscout.interfaces.ConfigBuilder
@ -34,6 +36,8 @@ abstract class ConfigurationModule {
@ContributesAndroidInjector abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment
@ContributesAndroidInjector abstract fun contributesCsvExportWorker(): ImportExportPrefsImpl.CsvExportWorker
@ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
@ContributesAndroidInjector abstract fun contributesCustomWatchfaceImportListActivity(): CustomWatchfaceImportListActivity
@ContributesAndroidInjector abstract fun contributesZipCustomWatchfaceFormat(): ZipCustomWatchfaceFormat
@ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat
@ContributesAndroidInjector abstract fun prefImportListProviderInjector(): PrefFileListProvider

View file

@ -0,0 +1,24 @@
package info.nightscout.configuration.maintenance
import android.content.Context
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
import androidx.fragment.app.FragmentActivity
class CustomWatchfaceFileContract: ActivityResultContract<Void?, Unit?>() {
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)
}
}

View file

@ -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 ->

View file

@ -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<Config>,
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<CustomWatchfaceFile> {
val customWatchfaceFiles = mutableListOf<CustomWatchfaceFile>()
// 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<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata> {

View file

@ -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<CustomWatchfaceFile>) : RecyclerView.Adapter<RecyclerViewAdapter.PrefFileViewHolder>() {
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)
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,19 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="4dp"
android:orientation="vertical"
tools:context="info.nightscout.configuration.maintenance.activities.CustomWatchfaceImportListActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadeScrollbars="true"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical">
</androidx.recyclerview.widget.RecyclerView>
</FrameLayout>

View file

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/careportal_cardview"
style="@style/Widget.MaterialComponents.CardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:gravity="center_vertical"
android:layout_marginBottom="3dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/custom_watchface"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="1dp"
android:src="@drawable/watchface_custom"
android:contentDescription="@string/a11y_file" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical"
tools:ignore="UseCompoundDrawables">
<TextView
android:id="@+id/custom_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="4dp"
android:ellipsize="none"
android:maxLines="2"
android:paddingStart="0dp"
android:paddingEnd="10dp"
android:scrollHorizontally="false"
android:text="Default Custom Watchface"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/importListFileNameColor"
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
android:id="@+id/filelist_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:ellipsize="none"
android:paddingStart="0dp"
android:paddingEnd="10dp"
android:scrollHorizontally="false"
android:text="Filename"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/importListFileNameColor"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="0dp"
android:ellipsize="none"
android:paddingStart="0dp"
android:paddingEnd="10dp"
android:scrollHorizontally="false"
android:text="Author: Name"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/importListFileNameColor"
android:textSize="11sp"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/created_at"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="0dp"
android:paddingStart="0dp"
android:paddingEnd="10dp"
android:text="created at: lqkjdshflqkdjhflqdskfhlqdsf"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/importListFileNameColor"
android:textSize="11sp" />
<TextView
android:id="@+id/cwf_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="0dp"
android:paddingStart="0dp"
android:paddingEnd="10dp"
android:text="CWF version:"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?attr/importListFileNameColor"
android:textSize="11sp" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -164,6 +164,11 @@
<string name="prefdecrypt_issue_wrong_format">Missing encryption configuration, settings format is invalid!</string>
<string name="prefdecrypt_issue_wrong_algorithm">Unsupported or not specified encryption algorithm!</string>
<!-- Custom Watchface -->
<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 -->
<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

@ -51,6 +51,15 @@
android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" />
</intent-filter>
<intent-filter>
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data
android:host="*"
android:pathPrefix="@string/path_rx_data_bridge"
android:scheme="wear" />
</intent-filter>
</service>
</application>

View file

@ -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
}
}

View file

@ -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()

View file

@ -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
}
}

View file

@ -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>): 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 ->

View file

@ -0,0 +1,80 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M1.885,11.953c0,2.578 2.09,4.668 4.668,4.668s4.668,-2.09 4.668,-4.668c0,-2.578 -2.09,-4.668 -4.668,-4.668S1.885,9.375 1.885,11.953zM3.093,11.953c0,-1.911 1.549,-3.46 3.46,-3.46s3.46,1.549 3.46,3.46c0,1.911 -1.549,3.46 -3.46,3.46S3.093,13.864 3.093,11.953z"
android:fillColor="#AAAAAA"/>
<path
android:pathData="M4.273,4.976l4.56,0l0.597,3.338l-5.754,0z"
android:fillColor="#AAAAAA"/>
<path
android:pathData="M4.273,19.024l4.56,0l0.597,-3.338l-5.754,0z"
android:fillColor="#AAAAAA"/>
<path
android:pathData="M12.915,6.067h9.405v11.865h-9.405z"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"/>
<path
android:pathData="M21.172,7.23L14.064,7.23"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M21.172,8.592L14.064,8.592"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M21.172,11.318L14.064,11.318"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M21.172,9.955L14.064,9.955"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M21.172,12.68L14.064,12.68"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M21.172,14.043L14.064,14.043"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M21.172,16.768L14.064,16.768"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M21.172,15.406L14.064,15.406"
android:strokeLineJoin="round"
android:strokeWidth="0.4819"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"/>
<path
android:pathData="M19.351,11.953l-4.975,-2.13l0,1.255l-7.896,0l0,1.75l7.896,0l0,1.256z"
android:fillColor="#6AE86D"/>
</vector>

View file

@ -0,0 +1,42 @@
<vector android:height="48dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#AAAAAA" android:pathData="M17.653,7.286c-2.578,0 -4.668,2.09 -4.668,4.668c0,2.578 2.09,4.668 4.668,4.668s4.668,-2.09 4.668,-4.668C22.321,9.375 20.231,7.286 17.653,7.286zM17.653,15.414c-1.911,0 -3.46,-1.549 -3.46,-3.46c0,-1.911 1.549,-3.46 3.46,-3.46s3.46,1.549 3.46,3.46C21.113,13.864 19.564,15.414 17.653,15.414z"/>
<path android:fillColor="#AAAAAA" android:pathData="M19.932,4.976l-4.559,0l-0.597,3.338l5.754,0z"/>
<path android:fillColor="#AAAAAA" android:pathData="M19.932,19.024l-4.559,0l-0.597,-3.338l5.754,0z"/>
<path android:fillColor="#00000000"
android:pathData="M1.885,6.067h9.405v11.865h-9.405z"
android:strokeColor="#FFFFFF" android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#00000000"
android:pathData="M3.034,7.23L10.142,7.23"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#00000000"
android:pathData="M3.034,8.592L10.142,8.592"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#00000000"
android:pathData="M3.034,11.318L10.142,11.318"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#00000000"
android:pathData="M3.034,9.955L10.142,9.955"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#00000000"
android:pathData="M3.034,12.68L10.142,12.68"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#00000000"
android:pathData="M3.034,14.043L10.142,14.043"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#00000000"
android:pathData="M3.034,16.768L10.142,16.768"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#00000000"
android:pathData="M3.034,15.406L10.142,15.406"
android:strokeColor="#FFFFFF" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="0.4819"/>
<path android:fillColor="#6AE86D" android:pathData="M17.726,11.953l-4.975,-2.13l0,1.255l-7.896,0l0,1.75l7.896,0l0,1.256z"/>
</vector>

View file

@ -0,0 +1,7 @@
<vector android:height="48dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#AAAAAA" android:pathData="M16.168,7.286c-2.578,0 -4.668,2.09 -4.668,4.668c0,2.578 2.09,4.668 4.668,4.668s4.668,-2.09 4.668,-4.668C20.836,9.375 18.746,7.286 16.168,7.286zM16.168,15.414c-1.911,0 -3.46,-1.549 -3.46,-3.46c0,-1.911 1.549,-3.46 3.46,-3.46s3.46,1.549 3.46,3.46C19.628,13.864 18.079,15.414 16.168,15.414z"/>
<path android:fillColor="#AAAAAA" android:pathData="M18.448,4.976l-4.56,0l-0.597,3.338l5.754,0z"/>
<path android:fillColor="#AAAAAA" android:pathData="M18.448,19.024l-4.56,0l-0.597,-3.338l5.754,0z"/>
<path android:fillColor="#6AE86D" android:pathData="M16.241,11.953l-4.975,-2.13l0,1.255l-7.896,0l0,1.75l7.896,0l0,1.256z"/>
</vector>

View file

@ -0,0 +1,7 @@
<vector android:height="48dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#AAAAAA" android:pathData="M12.103,7.286c-2.578,0 -4.668,2.09 -4.668,4.668c0,2.578 2.09,4.668 4.668,4.668s4.668,-2.09 4.668,-4.668C16.771,9.375 14.681,7.286 12.103,7.286zM12.103,15.414c-1.911,0 -3.46,-1.549 -3.46,-3.46c0,-1.911 1.549,-3.46 3.46,-3.46s3.46,1.549 3.46,3.46C15.563,13.864 14.014,15.414 12.103,15.414z"/>
<path android:fillColor="#AAAAAA" android:pathData="M14.383,4.976l-4.56,0l-0.597,3.338l5.754,0z"/>
<path android:fillColor="#AAAAAA" android:pathData="M14.383,19.024l-4.56,0l-0.597,-3.338l5.754,0z"/>
<path android:fillColor="#FF1313" android:pathData="M13.768,11.999l1.886,-1.886c0.081,-0.081 0.081,-0.213 0,-0.294l-1.372,-1.372c-0.078,-0.078 -0.216,-0.078 -0.294,0l-1.886,1.886l-1.886,-1.886c-0.078,-0.078 -0.216,-0.078 -0.294,0L8.552,9.819C8.513,9.858 8.491,9.911 8.491,9.966c0,0.055 0.022,0.108 0.061,0.147l1.886,1.886l-1.886,1.886c-0.039,0.039 -0.061,0.092 -0.061,0.147c0,0.055 0.022,0.108 0.061,0.147l1.372,1.372c0.041,0.04 0.094,0.061 0.147,0.061c0.053,0 0.106,-0.02 0.147,-0.061l1.886,-1.886l1.886,1.886c0.04,0.04 0.093,0.061 0.147,0.061c0.053,0 0.106,-0.02 0.147,-0.061l1.371,-1.371c0.081,-0.081 0.081,-0.213 0,-0.294L13.768,11.999z"/>
</vector>

View file

@ -1,42 +1,201 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="2dp"
tools:context="info.nightscout.plugins.general.wear.WearFragment">
<TextView
android:id="@+id/connected_device"
<com.google.android.material.card.MaterialCardView
android:id="@+id/log"
style="@style/Widget.MaterialComponents.CardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="@string/no_watch_connected"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:ignore="HardcodedText" />
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">
<com.google.android.material.button.MaterialButton
android:id="@+id/resend"
style="@style/GrayButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_refresh"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/resend_all_data"
android:textColor="?attr/treatmentButton" />
<TextView
android:id="@+id/connected_device"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="@string/no_watch_connected"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/open_settings"
style="@style/GrayButton"
<androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dip"
app:columnCount="2">
<com.google.android.material.button.MaterialButton
android:id="@+id/resend"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:drawableTop="@drawable/ic_refresh"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/resend_all_data"
android:textColor="?attr/treatmentButton"
app:layout_gravity="fill"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_row="0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/open_settings"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:drawableTop="@drawable/ic_settings"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/open_settings_on_wear"
android:textColor="?attr/treatmentButton"
app:layout_gravity="fill"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_row="0" />
</androidx.gridlayout.widget.GridLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/custom_watchface"
style="@style/Widget.MaterialComponents.CardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_settings"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/open_settings_on_wear"
android:textColor="?attr/treatmentButton" />
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">
<LinearLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/custom_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:layout_marginBottom="0dp"
android:text="@string/wear_custom_watchface" />
<androidx.gridlayout.widget.GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:padding="10dip"
app:columnCount="2">
<info.nightscout.core.ui.elements.SingleClickButton
android:id="@+id/load_custom"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/load_custom"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/wear_load_watchface"
android:textSize="11sp"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="0" />
<info.nightscout.core.ui.elements.SingleClickButton
android:id="@+id/send_custom"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/send_custom"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/wear_send_watchface"
android:textSize="11sp"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="0" />
<info.nightscout.core.ui.elements.SingleClickButton
android:id="@+id/export_custom"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/export_custom"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/wear_export_watchface"
android:textSize="11sp"
android:visibility="visible"
app:layout_column="0"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="1" />
<info.nightscout.core.ui.elements.SingleClickButton
android:id="@+id/default_custom"
style="@style/GrayButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableTop="@drawable/set_default"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/wear_default_watchface"
android:textSize="11sp"
android:visibility="visible"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="1" />
</androidx.gridlayout.widget.GridLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/custom_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="20dp"
android:orientation="vertical">
<ImageView
android:id="@+id/cover_chart"
android:tag="cover_chart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/watchface_custom"
android:visibility="visible"
android:orientation="vertical" />
</LinearLayout>
</LinearLayout>

View file

@ -361,6 +361,11 @@
<string name="wear_notifysmb_summary">Show SMB on the watch like a standard bolus.</string>
<string name="wear_predictions_summary">Show the predictions on the watchface.</string>
<string name="wear_predictions_title">Predictions</string>
<string name="wear_custom_watchface">Custom Watchface: %1$s</string>
<string name="wear_load_watchface">Load Watchface</string>
<string name="wear_send_watchface">Send Watchface</string>
<string name="wear_export_watchface">Export Watchface</string>
<string name="wear_new_custom_watchface_received">New watchface received from watch</string>
<string name="resend_all_data">Resend All Data</string>
<string name="open_settings_on_wear">Open Settings on Wear</string>

View file

@ -241,6 +241,31 @@
</intent-filter>
</service>
<service
android:name=".watchfaces.CustomWatchface"
android:allowEmbedded="true"
android:exported="false"
android:label="@string/label_watchface_custom"
android:permission="android.permission.BIND_WALLPAPER">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
<meta-data
android:name="com.google.android.wearable.watchface.preview"
android:resource="@drawable/watchface_custom" />
<meta-data
android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
android:value="watch_face_configuration_custom" />
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
</intent-filter>
</service>
<service
android:name=".comm.DataLayerListenerServiceWear"
android:exported="true">
@ -267,6 +292,15 @@
android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data
android:host="*"
android:pathPrefix="@string/path_rx_data_bridge"
android:scheme="wear" />
</intent-filter>
</service>
<service
@ -612,6 +646,7 @@
<action android:name="watch_face_configuration_bigchart" />
<action android:name="watch_face_configuration_circle" />
<action android:name="watch_face_configuration_cockpit" />
<action android:name="watch_face_configuration_custom" />
<action android:name="watch_face_configuration_digitalstyle" />
<action android:name="watch_face_configuration_home" />
<action android:name="watch_face_configuration_home2" />

View file

@ -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) {

View file

@ -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")
}
}
}

View file

@ -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

View file

@ -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<String>, separator: String?): String {
val sb = StringBuilder()
var i = 0

View file

@ -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<String, Style>()
private val typefaceToEnumMap = HashMap<Int, Style>()
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)
}
}

View file

@ -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

View file

@ -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")
}
}

View file

@ -0,0 +1,379 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".watchfaces.CustomWatchface">
<ImageView
android:id="@+id/background"
android:tag="background"
android:layout_width="400px"
android:layout_height="400px"
android:layout_marginTop="0px"
android:layout_marginLeft="0px"
android:visibility="visible"
android:src="@drawable/background"
android:orientation="vertical" />
<lecho.lib.hellocharts.view.LineChartView
android:id="@+id/chart"
android:tag="chart"
android:layout_width="400px"
android:layout_height="170px"
android:layout_marginTop="230px"
android:layout_marginBottom="0px" />
<ImageView
android:id="@+id/cover_chart"
android:tag="cover_chart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="0px"
android:layout_marginLeft="0px"
android:visibility="gone"
android:orientation="vertical" />
<TextView
android:id="@+id/iob1"
android:tag="iob1"
android:layout_width="130px"
android:layout_height="wrap_content"
android:layout_marginTop="172px"
android:layout_marginLeft="270px"
android:textSize="21px"
android:gravity="center"
android:textColor="@color/light_grey"
tools:text="@string/activity_IOB" />
<TextView
android:id="@+id/iob2"
android:tag="iob2"
android:layout_width="130px"
android:layout_height="wrap_content"
android:layout_marginTop="198px"
android:layout_marginLeft="270px"
android:textSize="21px"
android:textStyle="bold"
android:gravity="center"
android:textColor="@color/light_grey"
tools:text="@string/no_iob_u" />
<TextView
android:id="@+id/cob1"
android:tag="cob1"
android:layout_width="130px"
android:layout_height="wrap_content"
android:layout_marginTop="172px"
android:layout_marginLeft="0px"
android:gravity="center"
android:text="@string/activity_carb"
android:textColor="@color/light_grey"
android:textSize="21px" />
<TextView
android:id="@+id/cob2"
android:tag="cob2"
android:layout_width="130px"
android:layout_height="wrap_content"
android:layout_marginTop="198px"
android:layout_marginLeft="0px"
android:gravity="center"
android:text="@string/no_cob_g"
android:textSize="21px"
android:textColor="@color/light_grey"
android:textStyle="bold" />
<TextView
android:id="@+id/delta"
android:tag="delta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="133px"
android:layout_marginLeft="15px"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="+/-" />
<TextView
android:id="@+id/avg_delta"
android:tag="avg_delta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="133px"
android:layout_marginLeft="75px"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="@string/abbreviation_average" />
<TextView
android:id="@+id/uploader_battery"
android:tag="uploader_battery"
android:layout_width="60px"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="133px"
android:layout_marginLeft="129px"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="100%" />
<TextView
android:id="@+id/rig_battery"
android:tag="rig_battery"
android:layout_width="60px"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="133px"
android:layout_marginLeft="189px"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
android:visibility="visible"
tools:text="100%" />
<TextView
android:id="@+id/basalRate"
android:tag="basalRate"
android:layout_width="91px"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="133px"
android:layout_marginLeft="249px"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="@string/no_tmp_basal_u_h" />
<TextView
android:id="@+id/bgi"
android:tag="bgi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="133px"
android:layout_marginLeft="340px"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
android:visibility="visible"
tools:text="bgi" />
<TextView
android:id="@+id/time"
android:tag="time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="151px"
android:gravity="center"
android:fontFamily="@font/roboto_condensed_bold"
android:textSize="70px"
android:textStyle="bold"
android:textColor="@color/light_grey"
android:visibility="gone"
tools:text="22:00" />
<!-- hour -->
<TextView
android:id="@+id/hour"
android:tag="hour"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="151px"
android:layout_marginLeft="119px"
android:fontFamily="@font/roboto_condensed_bold"
android:textAllCaps="true"
android:textSize="70px"
android:textStyle="bold"
android:textColor="@color/light_grey"
android:visibility="gone"
tools:text="20" />
<!-- minute -->
<TextView
android:id="@+id/minute"
android:tag="minute"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="158px"
android:layout_marginLeft="210px"
android:fontFamily="@font/roboto_condensed_bold"
android:textColor="@color/light_grey"
android:textSize="46px"
android:visibility="gone"
tools:text="00" />
<!-- minute -->
<TextView
android:id="@+id/second"
android:tag="second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="158px"
android:layout_marginLeft="210px"
android:fontFamily="@font/roboto_condensed_bold"
android:textColor="@color/light_grey"
android:textSize="46px"
android:visibility="gone"
tools:text="00" />
<!-- 12h period AM / PM -->
<TextView
android:id="@+id/timePeriod"
android:tag="timePeriod"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="210px"
android:layout_marginTop="205px"
android:fontFamily="@font/roboto_condensed_bold"
android:textSize="17px"
android:textStyle="bold"
android:textColor="@color/light_grey"
android:visibility="gone"
tools:text="AM" />
<TextView
android:id="@+id/day_name"
android:tag="day_name"
android:layout_width="56px"
android:layout_height="wrap_content"
android:layout_marginTop="172px"
android:layout_marginLeft="120px"
android:textAllCaps="true"
android:gravity="center"
android:textSize="24px"
android:textStyle="bold"
android:textColor="@color/white"
android:visibility="visible"
tools:text="DDD." />
<TextView
android:id="@+id/day"
android:tag="day"
android:layout_width="56px"
android:layout_height="wrap_content"
android:layout_marginTop="198px"
android:layout_marginLeft="120px"
android:gravity="center"
android:textStyle="bold"
android:textSize="24px"
android:textColor="@color/white"
tools:text="day" />
<TextView
android:id="@+id/month"
android:tag="month"
android:layout_width="50px"
android:layout_height="wrap_content"
android:layout_marginTop="180px"
android:layout_marginLeft="220px"
android:gravity="center"
android:textStyle="bold"
android:textSize="24px"
android:textColor="@color/white"
tools:text="févr." />
<TextView
android:id="@+id/loop"
android:tag="loop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="61px"
android:layout_marginLeft="68px"
android:background="@drawable/loop_grey_25"
android:gravity="center"
android:textSize="24px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="--'" />
<TextView
android:id="@+id/direction"
android:tag="direction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="26px"
android:layout_marginLeft="291px"
android:textSize="39px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="--" />
<TextView
android:id="@+id/timestamp"
android:tag="timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="79px"
android:layout_marginLeft="291px"
android:textSize="25px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="--'" />
<TextView
android:id="@+id/sgv"
android:tag="sgv"
android:layout_width="400px"
android:layout_height="wrap_content"
android:gravity="center"
android:lines="1"
android:layout_marginTop="26px"
android:layout_marginLeft="0px"
android:fontFamily="@font/roboto_condensed_bold"
android:textSize="74px"
android:textColor="@color/light_grey"
tools:text="200" />
<ImageView
android:id="@+id/cover_plate"
android:tag="cover_plate"
android:layout_width="400px"
android:layout_height="400px"
android:orientation="vertical"
android:visibility="visible"
android:src="@drawable/simplified_dial" />
<ImageView
android:id="@+id/hour_hand"
android:tag="hour_hand"
android:layout_width="400px"
android:layout_height="400px"
android:orientation="vertical"
android:rotation="20"
android:src="@drawable/hour_hand" />
<ImageView
android:id="@+id/minute_hand"
android:tag="minute_hand"
android:layout_width="400px"
android:layout_height="400px"
android:orientation="vertical"
android:rotation="40"
android:src="@drawable/minute_hand" />
<ImageView
android:id="@+id/second_hand"
android:tag="second_hand"
android:layout_width="400px"
android:layout_height="400px"
android:orientation="vertical"
android:rotation="60"
android:src="@drawable/second_hand"
tools:ignore="UseAppTint" />
<!-- FLAGs -->
<View
android:id="@+id/AAPSv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
</FrameLayout>

View file

@ -11,6 +11,7 @@
<string name="label_watchface_cockpit">AAPS(Cockpit)</string>
<string name="label_watchface_steampunk">AAPS(Steampunk)</string>
<string name="label_watchface_digital_style">AAPS(DigitalStyle)</string>
<string name="label_watchface_custom">AAPS(Custom)</string>
<string name="label_actions_tile">AAPS(Actions)</string>
<string name="label_temp_target_tile">AAPS(Temp Target)</string>
<string name="label_quick_wizard_tile">AAPS(Quick Wizard)</string>
@ -143,6 +144,7 @@
<string name="pref_simplify_ui_sum">Only show time and BG</string>
<string name="pref_vibrate_hourly">Vibrate hourly</string>
<string name="pref_show_weeknumber">Show Week number</string>
<string name="custom_pref_show_seconds">Show seconds</string>
<string name="digitalstyle_pref_your_style">Your style:</string>
<string name="digitalstyle_style_none">no style</string>
<string name="digitalstyle_style_minimal">minimal style</string>
@ -208,6 +210,14 @@
<string name="key_complication_tap_action" translatable="false">complication_tap_action</string>
<string name="key_carbs_button_increment_1" translatable="false">carbs_button_increment_1</string>
<string name="key_carbs_button_increment_2" translatable="false">carbs_button_increment_2</string>
<string name="key_enable_custom_setting" translatable="false">enable_custom_setting</string>
<string name="key_digital_watchface" translatable="false">digital_watchface</string>
<string name="key_digital_timing" translatable="false">digital_timing</string>
<string name="key_analog_watchface" translatable="false">analog_watchface</string>
<string name="key_show_seconds" translatable="false">show_second</string>
<string name="key_high_color" translatable="false">highcolor</string>
<string name="key_mid_color" translatable="false">midcolor</string>
<string name="key_low_color" translatable="false">lowcolor</string>
<string name="increment">increment</string>
<string name="decrement">decrement</string>
<string name="first_char_high">H</string>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/key_show_seconds"
android:title="@string/custom_pref_show_seconds"
app:wear_iconOff="@drawable/settings_off"
app:wear_iconOn="@drawable/settings_on" />
<CheckBoxPreference
android:defaultValue="false"
android:key="@string/key_vibrate_hourly"
android:title="@string/pref_vibrate_hourly"
app:wear_iconOff="@drawable/settings_off"
app:wear_iconOn="@drawable/settings_on" />
<ListPreference
android:defaultValue="off"
android:entries="@array/watchface_simplify_ui_name"
android:entryValues="@array/watchface_simplify_ui_values"
android:key="@string/key_simplify_ui"
android:summary="@string/pref_simplify_ui_sum"
android:title="@string/pref_simplify_ui" />
</PreferenceScreen>