Merge pull request #2650 from Philoul/wear/new_custom_watchface

New Customizable Watchface wip
This commit is contained in:
Milos Kozak 2023-08-15 09:10:11 +02:00 committed by GitHub
commit 7d5fe6a7cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 2746 additions and 79 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 'com.github.tony19:logback-android:2.0.0'
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version" 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" api "org.apache.commons:commons-lang3:$commonslang3_version"
//RxBus //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,5 @@
package info.nightscout.rx.events
import info.nightscout.rx.weardata.CustomWatchfaceData
class EventWearUpdateGui(val customWatchfaceData: CustomWatchfaceData? = null, val exportFile: Boolean = false) : Event()

View file

@ -0,0 +1,241 @@
package info.nightscout.rx.weardata
import android.content.res.Resources
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import info.nightscout.shared.R
import kotlinx.serialization.Serializable
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
val CUSTOM_VERSION = "0.3"
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.SVG -> {
//TODO: include svg to Drawable convertor here
null
}
DrawableFormat.XML -> {
// Always return a null Drawable, even if xml file is a valid xml vector file
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>
@Serializable
data class CustomWatchfaceData(val json: String, var metadata: CustomWatchfaceMetadataMap, val drawableDatas: CustomWatchfaceDrawableDataMap)
enum class CustomWatchfaceMetadataKey(val key: String, @StringRes val label: Int) {
CWF_NAME("name", R.string.metadata_label_watchface_name),
CWF_FILENAME("filename", R.string.metadata_wear_import_filename),
CWF_AUTHOR("author", R.string.metadata_label_watchface_author),
CWF_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
}
}
}
class ZipWatchfaceFormat {
companion object {
const val CUSTOM_WF_EXTENTION = ".zip"
const val CUSTOM_JSON_FILE = "CustomWatchface.json"
fun loadCustomWatchface(cwfFile: File): CustomWatchfaceData? {
var json = JSONObject()
var metadata: CustomWatchfaceMetadataMap = mutableMapOf()
val drawableDatas: CustomWatchfaceDrawableDataMap = mutableMapOf()
try {
val zipInputStream = ZipInputStream(cwfFile.inputStream())
var zipEntry: ZipEntry? = zipInputStream.nextEntry
while (zipEntry != null) {
val entryName = zipEntry.name
val buffer = ByteArray(2048)
val byteArrayOutputStream = ByteArrayOutputStream()
var count = zipInputStream.read(buffer)
while (count != -1) {
byteArrayOutputStream.write(buffer, 0, count)
count = zipInputStream.read(buffer)
}
zipInputStream.closeEntry()
if (entryName == CUSTOM_JSON_FILE) {
val jsonString = byteArrayOutputStream.toByteArray().toString(Charsets.UTF_8)
json = JSONObject(jsonString)
metadata = loadMetadata(json)
metadata[CustomWatchfaceMetadataKey.CWF_FILENAME] = cwfFile.name
} else {
val customWatchfaceDrawableDataKey = CustomWatchfaceDrawableDataKey.fromFileName(entryName)
val drawableFormat = DrawableFormat.fromFileName(entryName)
if (customWatchfaceDrawableDataKey != CustomWatchfaceDrawableDataKey.UNKNOWN && drawableFormat != DrawableFormat.UNKNOWN) {
drawableDatas[customWatchfaceDrawableDataKey] = DrawableData(byteArrayOutputStream.toByteArray(), drawableFormat)
}
}
zipEntry = zipInputStream.nextEntry
}
// Valid CWF file must contains a valid json file with a name within metadata and a custom watchface image
if (metadata.containsKey(CustomWatchfaceMetadataKey.CWF_NAME) && drawableDatas.containsKey(CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE))
return CustomWatchfaceData(json.toString(4), metadata, drawableDatas)
else
return null
} catch (e: Exception) {
return null
}
}
fun saveCustomWatchface(file: File, customWatchface: CustomWatchfaceData) {
try {
val outputStream = FileOutputStream(file)
val zipOutputStream = ZipOutputStream(BufferedOutputStream(outputStream))
// Ajouter le fichier JSON au ZIP
val jsonEntry = ZipEntry(CUSTOM_JSON_FILE)
zipOutputStream.putNextEntry(jsonEntry)
zipOutputStream.write(customWatchface.json.toByteArray())
zipOutputStream.closeEntry()
// Ajouter les fichiers divers au ZIP
for (drawableData in customWatchface.drawableDatas) {
val fileEntry = ZipEntry("${drawableData.key.fileName}.${drawableData.value.format.extension}")
zipOutputStream.putNextEntry(fileEntry)
zipOutputStream.write(drawableData.value.value)
zipOutputStream.closeEntry()
}
zipOutputStream.close()
outputStream.close()
} catch (_: Exception) {
}
}
fun loadMetadata(contents: JSONObject): CustomWatchfaceMetadataMap {
val metadata: CustomWatchfaceMetadataMap = mutableMapOf()
if (contents.has("metadata")) {
val meta = contents.getJSONObject("metadata")
for (key in meta.keys()) {
val metaKey = CustomWatchfaceMetadataKey.fromKey(key)
if (metaKey != null) {
metadata[metaKey] = meta.getString(key)
}
}
}
return metadata
}
}
}

View file

@ -1,8 +1,10 @@
package info.nightscout.rx.weardata package info.nightscout.rx.weardata
import info.nightscout.rx.events.Event import info.nightscout.rx.events.Event
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.Objects import java.util.Objects
@ -13,13 +15,20 @@ sealed class EventData : Event() {
fun serialize() = Json.encodeToString(serializer(), this) fun serialize() = Json.encodeToString(serializer(), this)
@ExperimentalSerializationApi
fun serializeByte() = ProtoBuf.encodeToByteArray(serializer(), this)
companion object { companion object {
fun deserialize(json: String) = try { fun deserialize(json: String) = try {
Json.decodeFromString(serializer(), json) Json.decodeFromString(serializer(), json)
} catch (ignored: Exception) { } catch (ignored: Exception) {
Error(System.currentTimeMillis()) Error(System.currentTimeMillis())
} }
@ExperimentalSerializationApi
fun deserializeByte(byteArray: ByteArray) = try {
ProtoBuf.decodeFromByteArray(serializer(), byteArray)
} catch (ignored: Exception) {
Error(System.currentTimeMillis())
}
} }
// Mobile <- Wear // Mobile <- Wear
@ -142,6 +151,12 @@ sealed class EventData : Event() {
@Serializable @Serializable
data class CancelNotification(val timeStamp: Long) : EventData() data class CancelNotification(val timeStamp: Long) : EventData()
@Serializable
data class ActionGetCustomWatchface(
val customWatchface: ActionSetCustomWatchface,
val exportFile: Boolean = false
) : EventData()
@Serializable @Serializable
data class ActionPing(val timeStamp: Long) : EventData() data class ActionPing(val timeStamp: Long) : EventData()
@ -267,6 +282,16 @@ sealed class EventData : Event() {
val validTo: Int val validTo: Int
) : EventData() ) : EventData()
} }
@Serializable
data class ActionSetCustomWatchface(
val customWatchfaceData: CustomWatchfaceData
) : EventData()
@Serializable
data class ActionrequestCustomWatchface(val exportFile: Boolean) : EventData()
@Serializable
data class ActionrequestSetDefaultWatchface(val timeStamp: Long) : EventData()
@Serializable @Serializable
data class ActionProfileSwitchOpenActivity(val timeShift: Int, val percentage: Int) : EventData() 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)) 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(): String = minuteString(now())
fun minuteString(mills: Long): String = fun minuteString(mills: Long): String =
DateTime(mills).toString(DateTimeFormat.forPattern("mm")) 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="disconnecting">Disconnecting</string>
<string name="waiting_for_disconnection">Waiting for disconnection</string> <string name="waiting_for_disconnection">Waiting for disconnection</string>
<!-- Custom Watchface -->
<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_wear_import_filename">File name: %1$s</string>
<string name="metadata_label_watchface_version">Watchface version: %1$s</string>
<string name="wear_default_watchface">Default Watchface</string>
</resources> </resources>

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="48dp"
android:height="24dp" android:height="48dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path

View file

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

View file

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

View file

@ -11,6 +11,7 @@ import info.nightscout.configuration.configBuilder.RunningConfigurationImpl
import info.nightscout.configuration.maintenance.ImportExportPrefsImpl import info.nightscout.configuration.maintenance.ImportExportPrefsImpl
import info.nightscout.configuration.maintenance.MaintenanceFragment import info.nightscout.configuration.maintenance.MaintenanceFragment
import info.nightscout.configuration.maintenance.PrefFileListProviderImpl 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.LogSettingActivity
import info.nightscout.configuration.maintenance.activities.PrefImportListActivity import info.nightscout.configuration.maintenance.activities.PrefImportListActivity
import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat import info.nightscout.configuration.maintenance.formats.EncryptedPrefsFormat
@ -34,6 +35,7 @@ abstract class ConfigurationModule {
@ContributesAndroidInjector abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment @ContributesAndroidInjector abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment
@ContributesAndroidInjector abstract fun contributesCsvExportWorker(): ImportExportPrefsImpl.CsvExportWorker @ContributesAndroidInjector abstract fun contributesCsvExportWorker(): ImportExportPrefsImpl.CsvExportWorker
@ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity @ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
@ContributesAndroidInjector abstract fun contributesCustomWatchfaceImportListActivity(): CustomWatchfaceImportListActivity
@ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat @ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat
@ContributesAndroidInjector abstract fun prefImportListProviderInjector(): PrefFileListProvider @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

@ -55,6 +55,9 @@ import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventDiaconnG8PumpLogReset import info.nightscout.rx.events.EventDiaconnG8PumpLogReset
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey
import info.nightscout.rx.weardata.ZipWatchfaceFormat
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
@ -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: CustomWatchfaceData) {
prefFileList.ensureExportDirExists()
val newFile = prefFileList.newCwfFile(customWatchface.metadata[CustomWatchfaceMetadataKey.CWF_FILENAME] ?:"")
ZipWatchfaceFormat.saveCustomWatchface(newFile, customWatchface)
}
override fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) { override fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) {
askToConfirmImport(activity, importFile) { password -> askToConfirmImport(activity, importFile) { password ->

View file

@ -17,6 +17,8 @@ import info.nightscout.interfaces.maintenance.PrefsMetadataKey
import info.nightscout.interfaces.maintenance.PrefsStatus import info.nightscout.interfaces.maintenance.PrefsStatus
import info.nightscout.interfaces.storage.Storage import info.nightscout.interfaces.storage.Storage
import info.nightscout.interfaces.versionChecker.VersionCheckerUtils import info.nightscout.interfaces.versionChecker.VersionCheckerUtils
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.ZipWatchfaceFormat
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import org.joda.time.DateTime import org.joda.time.DateTime
import org.joda.time.Days import org.joda.time.Days
@ -88,6 +90,20 @@ class PrefFileListProviderImpl @Inject constructor(
return prefFiles return prefFiles
} }
override fun listCustomWatchfaceFiles(): MutableList<CustomWatchfaceData> {
val customWatchfaceFiles = mutableListOf<CustomWatchfaceData>()
// searching dedicated dir, only for new CWF format
exportsPath.walk().filter { it.isFile && it.name.endsWith(ZipWatchfaceFormat.CUSTOM_WF_EXTENTION) }.forEach { file ->
// Here loadCustomWatchface will unzip, check and load CustomWatchface
ZipWatchfaceFormat.loadCustomWatchface(file)?.also { customWatchface ->
customWatchfaceFiles.add(customWatchface)
}
}
return customWatchfaceFiles
}
private fun metadataFor(loadMetadata: Boolean, contents: String): PrefMetadataMap { private fun metadataFor(loadMetadata: Boolean, contents: String): PrefMetadataMap {
if (!loadMetadata) { if (!loadMetadata) {
return mapOf() return mapOf()
@ -128,6 +144,10 @@ class PrefFileListProviderImpl @Inject constructor(
val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss")) val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss"))
return File(exportsPath, timeLocal + "_UserEntry.csv") 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${ZipWatchfaceFormat.CUSTOM_WF_EXTENTION}")
}
// check metadata for known issues, change their status and add info with explanations // check metadata for known issues, change their status and add info with explanations
override fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata> { override fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata> {

View file

@ -0,0 +1,121 @@
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.PrefFileListProvider
import info.nightscout.configuration.databinding.CustomWatchfaceImportListActivityBinding
import info.nightscout.configuration.R
import info.nightscout.configuration.databinding.CustomWatchfaceImportListItemBinding
import info.nightscout.interfaces.versionChecker.VersionCheckerUtils
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventMobileDataToWear
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.CUSTOM_VERSION
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey.*
import info.nightscout.rx.weardata.CustomWatchfaceMetadataMap
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
@Inject lateinit var versionCheckerUtils: VersionCheckerUtils
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<CustomWatchfaceData>) : 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 CustomWatchfaceData
val customWF = EventData.ActionSetCustomWatchface(customWatchfaceFile)
val i = Intent()
setResult(FragmentActivity.RESULT_OK, i)
rxBus.send(EventMobileDataToWear(customWF))
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.drawableDatas[CustomWatchfaceDrawableDataKey
.CUSTOM_WATCHFACE]?.toDrawable(resources)
with(holder.customWatchfaceImportListItemBinding) {
filelistName.text = rh.gs(info.nightscout.shared.R.string.metadata_wear_import_filename, metadata[CWF_FILENAME])
filelistName.tag = customWatchfaceFile
customWatchface.setImageDrawable(drawable)
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] ?:"")
val colorAttr = if (checkCustomVersion(metadata)) info.nightscout.core.ui.R.attr.metadataTextOkColor else info.nightscout.core.ui.R.attr.metadataTextWarningColor
cwfVersion.setTextColor(rh.gac(cwfVersion.context, colorAttr))
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
private fun checkCustomVersion(metadata: CustomWatchfaceMetadataMap): Boolean {
metadata[CWF_VERSION]?.let { version ->
val currentAppVer = versionCheckerUtils.versionDigits(CUSTOM_VERSION)
val metadataVer = versionCheckerUtils.versionDigits(version)
//Only check that Loaded Watchface version is lower or equal to Wear CustomWatchface version
return ((currentAppVer.size >= 2) && (metadataVer.size >= 2) && (currentAppVer[0] >= metadataVer[0]))
}
return false
}
}

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,125 @@
<?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_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,9 @@
<string name="prefdecrypt_issue_wrong_format">Missing encryption configuration, settings format is invalid!</string> <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> <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>
<!-- Permissions --> <!-- Permissions -->
<string name="alert_dialog_storage_permission_text">Please reboot your phone or restart AAPS from the System Settings \notherwise Android APS will not have logging (important to track and verify that the algorithms are working correctly)!</string> <string name="alert_dialog_storage_permission_text">Please reboot your phone or restart AAPS from the System Settings \notherwise Android APS will not have logging (important to track and verify that the algorithms are working correctly)!</string>

View file

@ -51,6 +51,15 @@
android:pathPrefix="@string/path_rx_bridge" android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" /> android:scheme="wear" />
</intent-filter> </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> </service>
</application> </application>

View file

@ -5,13 +5,24 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy 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.databinding.WearFragmentBinding
import info.nightscout.plugins.general.wear.events.EventWearUpdateGui
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventMobileDataToWear
import info.nightscout.rx.events.EventMobileToWear import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.CustomWatchfaceDrawableDataKey
import info.nightscout.rx.weardata.CustomWatchfaceMetadataKey
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.EventData
import info.nightscout.shared.extensions.toVisibility
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
@ -24,9 +35,12 @@ class WearFragment : DaggerFragment() {
@Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var dateUtil: DateUtil @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 var _binding: WearFragmentBinding? = null
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
@ -43,6 +57,20 @@ class WearFragment : DaggerFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.resend.setOnClickListener { rxBus.send(EventData.ActionResendData("WearFragment")) } binding.resend.setOnClickListener { rxBus.send(EventData.ActionResendData("WearFragment")) }
binding.openSettings.setOnClickListener { rxBus.send(EventMobileToWear(EventData.OpenSettings(dateUtil.now()))) } binding.openSettings.setOnClickListener { rxBus.send(EventMobileToWear(EventData.OpenSettings(dateUtil.now()))) }
binding.loadCustom.setOnClickListener {
importExportPrefs.verifyStoragePermissions(this) {
importExportPrefs.importCustomWatchface(this)
}
}
binding.defaultCustom.setOnClickListener {
rxBus.send(EventMobileToWear(EventData.ActionrequestSetDefaultWatchface(dateUtil.now())))
updateGui()
}
binding.exportCustom.setOnClickListener {
wearPlugin.savedCustomWatchface?.let { importExportPrefs.exportCustomWatchface(it) }
?: apply { rxBus.send(EventMobileToWear(EventData.ActionrequestCustomWatchface(true)))}
}
} }
override fun onResume() { override fun onResume() {
@ -50,7 +78,15 @@ class WearFragment : DaggerFragment() {
disposable += rxBus disposable += rxBus
.toObservable(EventWearUpdateGui::class.java) .toObservable(EventWearUpdateGui::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException) .subscribe({
it.customWatchfaceData?.let { loadCustom(it) }
if (it.exportFile)
ToastUtils.okToast(activity, rh.gs(R.string.wear_new_custom_watchface_exported))
updateGui()
}, fabricPrivacy::logException)
if (wearPlugin.savedCustomWatchface == null)
rxBus.send(EventMobileToWear(EventData.ActionrequestCustomWatchface(false)))
//EventMobileDataToWear
updateGui() updateGui()
} }
@ -67,6 +103,18 @@ class WearFragment : DaggerFragment() {
private fun updateGui() { private fun updateGui() {
_binding ?: return _binding ?: return
wearPlugin.savedCustomWatchface?.let {
binding.customName.text = rh.gs(R.string.wear_custom_watchface, it.metadata[CustomWatchfaceMetadataKey.CWF_NAME])
binding.coverChart.setImageDrawable(it.drawableDatas[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.coverChart.setImageDrawable(null)
}
binding.connectedDevice.text = wearPlugin.connectedDevice binding.connectedDevice.text = wearPlugin.connectedDevice
binding.customWatchfaceLayout.visibility = (wearPlugin.connectedDevice != rh.gs(R.string.no_watch_connected)).toVisibility()
}
private fun loadCustom(cwf: CustomWatchfaceData) {
wearPlugin.savedCustomWatchface = cwf
} }
} }

View file

@ -17,7 +17,9 @@ import info.nightscout.rx.events.EventLoopUpdateGui
import info.nightscout.rx.events.EventMobileToWear import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.events.EventOverviewBolusProgress import info.nightscout.rx.events.EventOverviewBolusProgress
import info.nightscout.rx.events.EventPreferenceChange import info.nightscout.rx.events.EventPreferenceChange
import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.weardata.CustomWatchfaceData
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.EventData
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
@ -54,6 +56,7 @@ class WearPlugin @Inject constructor(
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
var connectedDevice = "---" var connectedDevice = "---"
var savedCustomWatchface: CustomWatchfaceData? = null
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
@ -89,6 +92,10 @@ class WearPlugin @Inject constructor(
.toObservable(EventLoopUpdateGui::class.java) .toObservable(EventLoopUpdateGui::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ dataHandlerMobile.resendData("EventLoopUpdateGui") }, fabricPrivacy::logException) .subscribe({ dataHandlerMobile.resendData("EventLoopUpdateGui") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventWearUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ it.customWatchfaceData?.let { cwf -> savedCustomWatchface = cwf } }, fabricPrivacy::logException)
} }
override fun onStop() { override fun onStop() {

View file

@ -1,5 +0,0 @@
package info.nightscout.plugins.general.wear.events
import info.nightscout.rx.events.Event
class EventWearUpdateGui : Event()

View file

@ -41,6 +41,7 @@ import info.nightscout.interfaces.iob.GlucoseStatusProvider
import info.nightscout.interfaces.iob.InMemoryGlucoseValue import info.nightscout.interfaces.iob.InMemoryGlucoseValue
import info.nightscout.interfaces.iob.IobCobCalculator import info.nightscout.interfaces.iob.IobCobCalculator
import info.nightscout.interfaces.logging.UserEntryLogger import info.nightscout.interfaces.logging.UserEntryLogger
import info.nightscout.interfaces.maintenance.ImportExportPrefs
import info.nightscout.interfaces.nsclient.ProcessedDeviceStatusData import info.nightscout.interfaces.nsclient.ProcessedDeviceStatusData
import info.nightscout.interfaces.plugin.ActivePlugin import info.nightscout.interfaces.plugin.ActivePlugin
import info.nightscout.interfaces.plugin.PluginBase import info.nightscout.interfaces.plugin.PluginBase
@ -59,6 +60,7 @@ import info.nightscout.plugins.R
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventMobileToWear import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.events.EventWearUpdateGui
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
import info.nightscout.rx.weardata.EventData import info.nightscout.rx.weardata.EventData
@ -107,7 +109,8 @@ class DataHandlerMobile @Inject constructor(
private val commandQueue: CommandQueue, private val commandQueue: CommandQueue,
private val fabricPrivacy: FabricPrivacy, private val fabricPrivacy: FabricPrivacy,
private val uiInteraction: UiInteraction, private val uiInteraction: UiInteraction,
private val persistenceLayer: PersistenceLayer private val persistenceLayer: PersistenceLayer,
private val importExportPrefs: ImportExportPrefs
) { ) {
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
@ -314,6 +317,13 @@ class DataHandlerMobile @Inject constructor(
.toObservable(EventData.ActionHeartRate::class.java) .toObservable(EventData.ActionHeartRate::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ handleHeartRate(it) }, fabricPrivacy::logException) .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() { private fun handleTddStatus() {
@ -1247,4 +1257,15 @@ class DataHandlerMobile @Inject constructor(
device = actionHeartRate.device) device = actionHeartRate.device)
repository.runTransaction(InsertOrUpdateHeartRateTransaction(hr)).blockingAwait() 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.customWatchfaceData.json}")
rxBus.send(EventWearUpdateGui(customWatchface.customWatchfaceData, command.exportFile))
if (command.exportFile)
importExportPrefs.exportCustomWatchface(customWatchface.customWatchfaceData)
}
} }

View file

@ -26,9 +26,10 @@ import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.receivers.ReceiverStatusStore import info.nightscout.interfaces.receivers.ReceiverStatusStore
import info.nightscout.plugins.R import info.nightscout.plugins.R
import info.nightscout.plugins.general.wear.WearPlugin 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.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventMobileDataToWear
import info.nightscout.rx.events.EventMobileToWear import info.nightscout.rx.events.EventMobileToWear
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
@ -44,6 +45,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await import kotlinx.coroutines.tasks.await
import kotlinx.serialization.ExperimentalSerializationApi
import javax.inject.Inject import javax.inject.Inject
class DataLayerListenerServiceMobile : WearableListenerService() { class DataLayerListenerServiceMobile : WearableListenerService() {
@ -80,7 +82,8 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private val rxPath get() = getString(info.nightscout.shared.R.string.path_rx_bridge) private val rxPath get() = getString(info.nightscout.shared.R.string.path_rx_bridge)
private val rxDataPath get() = getString(info.nightscout.shared.R.string.path_rx_data_bridge)
@ExperimentalSerializationApi
override fun onCreate() { override fun onCreate() {
AndroidInjection.inject(this) AndroidInjection.inject(this)
super.onCreate() super.onCreate()
@ -90,6 +93,10 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
.toObservable(EventMobileToWear::class.java) .toObservable(EventMobileToWear::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe { sendMessage(rxPath, it.payload.serialize()) } .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) { override fun onCapabilityChanged(p0: CapabilityInfo) {
@ -125,7 +132,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
} }
super.onDataChanged(dataEvents) super.onDataChanged(dataEvents)
} }
@ExperimentalSerializationApi
override fun onMessageReceived(messageEvent: MessageEvent) { override fun onMessageReceived(messageEvent: MessageEvent) {
super.onMessageReceived(messageEvent) super.onMessageReceived(messageEvent)
@ -136,6 +143,11 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
val command = EventData.deserialize(String(messageEvent.data)) val command = EventData.deserialize(String(messageEvent.data))
rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId }) 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 +176,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
private fun pickBestNodeId(nodes: Set<Node>): Node? = private fun pickBestNodeId(nodes: Set<Node>): Node? =
nodes.firstOrNull { it.isNearby } ?: nodes.firstOrNull() nodes.firstOrNull { it.isNearby } ?: nodes.firstOrNull()
@Suppress("unused") //@Suppress("unused")
private fun sendData(path: String, vararg params: DataMap) { private fun sendData(path: String, vararg params: DataMap) {
if (wearPlugin.isEnabled()) { if (wearPlugin.isEnabled()) {
scope.launch { scope.launch {
@ -201,7 +213,6 @@ class DataLayerListenerServiceMobile : WearableListenerService() {
} }
} }
@Suppress("unused")
private fun sendMessage(path: String, data: ByteArray) { private fun sendMessage(path: String, data: ByteArray) {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path") aapsLogger.debug(LTag.WEAR, "sendMessage: $path")
transcriptionNodeId?.also { nodeId -> 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,199 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="2dp" android:paddingTop="2dp"
tools:context="info.nightscout.plugins.general.wear.WearFragment"> tools:context="info.nightscout.plugins.general.wear.WearFragment">
<TextView <com.google.android.material.card.MaterialCardView
android:id="@+id/connected_device" android:id="@+id/log"
style="@style/Widget.MaterialComponents.CardView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal" android:layout_marginStart="4dp"
android:paddingTop="10dp" android:layout_marginEnd="4dp"
android:paddingBottom="10dp" android:layout_marginTop="4dp"
android:text="@string/no_watch_connected" app:cardCornerRadius="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" app:contentPadding="2dp"
tools:ignore="HardcodedText" /> app:cardElevation="2dp"
app:cardUseCompatPadding="false"
android:layout_gravity="center">
<com.google.android.material.button.MaterialButton <TextView
android:id="@+id/resend" android:id="@+id/connected_device"
style="@style/GrayButton" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal"
android:drawableTop="@drawable/ic_refresh" android:paddingTop="10dp"
android:paddingLeft="0dp" android:paddingBottom="10dp"
android:paddingRight="0dp" android:text="@string/no_watch_connected"
android:text="@string/resend_all_data" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?attr/treatmentButton" /> tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton <androidx.gridlayout.widget.GridLayout
android:id="@+id/open_settings" android:layout_width="match_parent"
style="@style/GrayButton" 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>
<LinearLayout
android:id="@+id/custom_watchface_Layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:drawableTop="@drawable/ic_settings" android:orientation="vertical">
android:paddingLeft="0dp"
android:paddingRight="0dp" <com.google.android.material.card.MaterialCardView
android:text="@string/open_settings_on_wear" style="@style/Widget.MaterialComponents.CardView"
android:textColor="?attr/treatmentButton" /> 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">
<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="20dp"
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_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="0"
app:layout_column="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_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="0"
app:layout_column="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_columnWeight="1"
app:layout_gravity="fill"
app:layout_row="1"
app:layout_column="0" />
</androidx.gridlayout.widget.GridLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/custom_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
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>
</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_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_summary">Show the predictions on the watchface.</string>
<string name="wear_predictions_title">Predictions</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_exported">Custom watchface exported</string>
<string name="resend_all_data">Resend All Data</string> <string name="resend_all_data">Resend All Data</string>
<string name="open_settings_on_wear">Open Settings on Wear</string> <string name="open_settings_on_wear">Open Settings on Wear</string>

View file

@ -241,6 +241,31 @@
</intent-filter> </intent-filter>
</service> </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 <service
android:name=".comm.DataLayerListenerServiceWear" android:name=".comm.DataLayerListenerServiceWear"
android:exported="true"> android:exported="true">
@ -267,6 +292,15 @@
android:pathPrefix="@string/path_rx_bridge" android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" /> android:scheme="wear" />
</intent-filter> </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>
<service <service
@ -612,6 +646,7 @@
<action android:name="watch_face_configuration_bigchart" /> <action android:name="watch_face_configuration_bigchart" />
<action android:name="watch_face_configuration_circle" /> <action android:name="watch_face_configuration_circle" />
<action android:name="watch_face_configuration_cockpit" /> <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_digitalstyle" />
<action android:name="watch_face_configuration_home" /> <action android:name="watch_face_configuration_home" />
<action android:name="watch_face_configuration_home2" /> <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.androidaps.tile.TempTargetTileService
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventWearDataToMobile
import info.nightscout.rx.events.EventWearToMobile import info.nightscout.rx.events.EventWearToMobile
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
@ -179,6 +180,35 @@ class DataHandlerWear @Inject constructor(
TileService.getUpdater(context).requestUpdate(QuickWizardTileService::class.java) 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)
persistence.readCustomWatchface()?.let {
rxBus.send(EventWearDataToMobile(EventData.ActionGetCustomWatchface(it, false)))
}
}
disposable += rxBus
.toObservable(EventData.ActionrequestSetDefaultWatchface::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "Set Default Watchface received from ${it.sourceNodeId}")
persistence.setDefaultWatchface()
persistence.readCustomWatchface()?.let {
rxBus.send(EventWearDataToMobile(EventData.ActionGetCustomWatchface(it, false)))
}
}
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) { 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.androidaps.interaction.utils.WearUtil
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventWearDataToMobile
import info.nightscout.rx.events.EventWearToMobile import info.nightscout.rx.events.EventWearToMobile
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
@ -21,6 +22,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.tasks.await import kotlinx.coroutines.tasks.await
import kotlinx.serialization.ExperimentalSerializationApi
import javax.inject.Inject import javax.inject.Inject
class DataLayerListenerServiceWear : WearableListenerService() { class DataLayerListenerServiceWear : WearableListenerService() {
@ -43,7 +45,8 @@ class DataLayerListenerServiceWear : WearableListenerService() {
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private val rxPath get() = getString(info.nightscout.shared.R.string.path_rx_bridge) private val rxPath get() = getString(info.nightscout.shared.R.string.path_rx_bridge)
private val rxDataPath get() = getString(info.nightscout.shared.R.string.path_rx_data_bridge)
@ExperimentalSerializationApi
override fun onCreate() { override fun onCreate() {
AndroidInjection.inject(this) AndroidInjection.inject(this)
super.onCreate() super.onCreate()
@ -54,6 +57,12 @@ class DataLayerListenerServiceWear : WearableListenerService() {
.subscribe { .subscribe {
sendMessage(rxPath, it.payload.serialize()) 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) { override fun onCapabilityChanged(p0: CapabilityInfo) {
@ -87,7 +96,7 @@ class DataLayerListenerServiceWear : WearableListenerService() {
} }
super.onDataChanged(dataEvents) super.onDataChanged(dataEvents)
} }
@ExperimentalSerializationApi
override fun onMessageReceived(messageEvent: MessageEvent) { override fun onMessageReceived(messageEvent: MessageEvent) {
super.onMessageReceived(messageEvent) super.onMessageReceived(messageEvent)
@ -100,6 +109,14 @@ class DataLayerListenerServiceWear : WearableListenerService() {
transcriptionNodeId = messageEvent.sourceNodeId transcriptionNodeId = messageEvent.sourceNodeId
aapsLogger.debug(LTag.WEAR, "Updated node: $transcriptionNodeId") 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 contributesBIGChart(): BigChartWatchface
@ContributesAndroidInjector abstract fun contributesNOChart(): NoChartWatchface @ContributesAndroidInjector abstract fun contributesNOChart(): NoChartWatchface
@ContributesAndroidInjector abstract fun contributesCircleWatchface(): CircleWatchface @ContributesAndroidInjector abstract fun contributesCircleWatchface(): CircleWatchface
@ContributesAndroidInjector abstract fun contributesCustomWatchface(): CustomWatchface
@ContributesAndroidInjector abstract fun contributesTileBase(): TileBase @ContributesAndroidInjector abstract fun contributesTileBase(): TileBase
@ContributesAndroidInjector abstract fun contributesQuickWizardTileService(): QuickWizardTileService @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_STALE_REPORTED = "staleReported"
const val KEY_DATA_UPDATED = "data_updated_at" 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 { fun getString(key: String, defaultValue: String): String {
@ -130,6 +132,23 @@ open class Persistence @Inject constructor(
return null 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) { fun store(singleBg: SingleBg) {
putString(BG_DATA_PERSISTENCE_KEY, singleBg.serialize()) putString(BG_DATA_PERSISTENCE_KEY, singleBg.serialize())
aapsLogger.debug(LTag.WEAR, "Stored BG data: $singleBg") 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") 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.customWatchfaceData} ${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 { fun joinSet(set: Set<String>, separator: String?): String {
val sb = StringBuilder() val sb = StringBuilder()
var i = 0 var i = 0

View file

@ -0,0 +1,495 @@
@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.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
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.CUSTOM_VERSION
import info.nightscout.rx.weardata.CustomWatchfaceData
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.rx.weardata.ZipWatchfaceFormat
import info.nightscout.shared.extensions.toVisibility
import info.nightscout.shared.extensions.toVisibilityKeepSpace
import info.nightscout.shared.sharedPreferences.SP
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
@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 setDataFields() {
super.setDataFields()
binding.direction2.setImageDrawable(resources.getDrawable(TrendArrow.fromSymbol(singleBg.slopeArrow).icon))
}
override fun setColorDark() {
setWatchfaceStyle()
binding.mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.dark_background))
binding.sgv.setTextColor(bgColor)
binding.direction.setTextColor(bgColor)
binding.direction2.colorFilter = changeDrawableColor(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 (showSecond) ":${dateUtil.secondString()}" else ""
binding.second.text = dateUtil.secondString()
// rotate the second hand.
binding.secondHand.rotation = TimeOfDay().secondOfMinute * 6f
//aapsLogger.debug("XXXXX SetSecond $watchModeString")
}
override fun updateSecondVisibility() {
binding.second.visibility = showSecond.toVisibility()
binding.secondHand.visibility = showSecond.toVisibility()
}
private fun setWatchfaceStyle() {
val customWatchface = persistence.readCustomWatchface() ?: persistence.readCustomWatchface(true)
customWatchface?.let {
try {
val json = JSONObject(it.customWatchfaceData.json)
val drawableDataMap = it.customWatchfaceData.drawableDatas
enableSecond = (if (json.has("enableSecond")) json.getBoolean("enableSecond") else false) && sp.getBoolean(R.string.key_show_seconds, true)
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
bgColor = when (singleBg.sgvLevel) {
1L -> highColor
0L -> midColor
-1L -> lowColor
else -> midColor
}
binding.mainLayout.forEach { view ->
CustomViews.fromId(view.id)?.let { id ->
if (json.has(id.key)) {
var viewjson = json.getJSONObject(id.key)
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"), id.visibility(sp)) 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"),
setStyle(if (viewjson.has("fontStyle")) viewjson.getString("fontStyle") else "normal")
)
if (viewjson.has("fontColor"))
view.setTextColor(getColor(viewjson.getString("fontColor")))
if (viewjson.has("textvalue"))
view.text = viewjson.getString("textvalue")
}
if (view is ImageView) {
view.clearColorFilter()
drawableDataMap[CustomWatchfaceDrawableDataKey.fromKey(id.key)]?.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(id.key).icon?.let { context.getDrawable(it) })
if (viewjson.has("color"))
view.setColorFilter(getColor(viewjson.getString("color")))
else
view.clearColorFilter()
}
}
} else {
view.visibility = View.GONE
if (view is TextView) {
view.text = ""
}
}
}
}
updateSecondVisibility()
} catch (e:Exception) {
persistence.store(defaultWatchface(), false) // relaod correct values to avoid crash of watchface
}
}
}
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_FILENAME.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
CustomViews.fromId(view.id)?.let {
if (view is TextView) {
json.put(
it.key,
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", getStyle(view.typeface.style))
.put("fontColor", String.format("#%06X", 0xFFFFFF and view.currentTextColor))
)
}
if (view is ImageView) {
//view.backgroundTintList =
json.put(
it.key,
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(
it.key,
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 metadataMap = ZipWatchfaceFormat.loadMetadata(json)
val drawableDataMap: CustomWatchfaceDrawableDataMap = mutableMapOf()
getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let {
val drawableData = DrawableData(it,DrawableFormat.PNG)
drawableDataMap[CustomWatchfaceDrawableDataKey.CUSTOM_WATCHFACE] = drawableData
}
return EventData.ActionSetCustomWatchface(CustomWatchfaceData(json.toString(4), metadataMap, drawableDataMap))
}
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, pref: Boolean = true): Int = when (visibility) {
"visible" -> pref.toVisibility()
"invisible" -> pref.toVisibilityKeepSpace()
"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"
}
private fun setStyle(style: String): Int = when (style) {
"normal" -> Typeface.NORMAL
"bold" -> Typeface.BOLD
"bold-italic" -> Typeface.BOLD_ITALIC
"italic" -> Typeface.ITALIC
else -> Typeface.NORMAL
}
private fun getStyle(style: Int): String = when (style) {
Typeface.NORMAL -> "normal"
Typeface.BOLD -> "bold"
Typeface.BOLD_ITALIC -> "bold-italic"
Typeface.ITALIC -> "italic"
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)
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
)
)
)
return ColorMatrixColorFilter(colorMatrix)
}
private fun getColor(color: String): Int {
if (color == "bgColor")
return bgColor
else
return try {
Color.parseColor(color)
} catch (e: Exception) {
Color.GRAY
}
}
enum class CustomViews(val key: String, @IdRes val id: Int, @StringRes val pref: Int?) {
BACKGROUND(CustomWatchfaceDrawableDataKey.BACKGROUND.key, R.id.background, null),
CHART("chart", R.id.chart, null),
COVER_CHART(CustomWatchfaceDrawableDataKey.COVERCHART.key, R.id.cover_chart, null),
FREETEXT1("freetext1", R.id.freetext1, null),
FREETEXT2("freetext2", R.id.freetext2, null),
IOB1("iob1", R.id.iob1, R.string.key_show_iob),
IOB2("iob2", R.id.iob2, R.string.key_show_iob),
COB1("cob1", R.id.cob1, R.string.key_show_cob),
COB2("cob2", R.id.cob2, R.string.key_show_cob),
DELTA("delta", R.id.delta, R.string.key_show_delta),
AVG_DELTA("avg_delta", R.id.avg_delta, R.string.key_show_avg_delta),
UPLOADER_BATTERY("uploader_battery", R.id.uploader_battery, R.string.key_show_uploader_battery),
RIG_BATTERY("rig_battery", R.id.rig_battery, R.string.key_show_rig_battery),
BASALRATE("basalRate", R.id.basalRate, R.string.key_show_temp_basal),
BGI("bgi", R.id.bgi, null),
TIME("time", R.id.time, null),
HOUR("hour", R.id.hour, null),
MINUTE("minute", R.id.minute, null),
SECOND("second", R.id.second, R.string.key_show_seconds),
TIMEPERIOD("timePeriod", R.id.timePeriod, null),
DAY_NAME("day_name", R.id.day_name, null),
DAY("day", R.id.day, null),
MONTH("month", R.id.month, null),
LOOP("loop", R.id.loop, R.string.key_show_external_status),
DIRECTION("direction", R.id.direction, R.string.key_show_direction),
DIRECTION2("direction2", R.id.direction2, R.string.key_show_direction),
TIMESTAMP("timestamp", R.id.timestamp, R.string.key_show_ago),
SGV("sgv", R.id.sgv, R.string.key_show_bg),
COVER_PLATE(CustomWatchfaceDrawableDataKey.COVERPLATE.key, R.id.cover_plate, null),
HOUR_HABD(CustomWatchfaceDrawableDataKey.HOURHAND.key, R.id.hour_hand, null),
MINUTE_HAND(CustomWatchfaceDrawableDataKey.MINUTEHAND.key, R.id.minute_hand, null),
SECOND_HAND(CustomWatchfaceDrawableDataKey.SECONDHAND.key, R.id.second_hand, R.string.key_show_seconds);
companion object {
private val keyToEnumMap = HashMap<String, CustomViews>()
private val idToEnumMap = HashMap<Int, CustomViews>()
init {
for (value in values()) keyToEnumMap[value.key] = value
for (value in values()) idToEnumMap[value.id] = value
}
fun fromKey(key: String): CustomViews? =
if (keyToEnumMap.containsKey(key)) {
keyToEnumMap[key]
} else {
null
}
fun fromId(id: Int): CustomViews? =
if (idToEnumMap.containsKey(id)) {
idToEnumMap[id]
} else {
null
}
}
fun visibility(sp: SP): Boolean = this.pref?.let { sp.getBoolean(it, true) }
?: true
}
enum class TrendArrow(val text: String, val symbol: String,@DrawableRes val icon: Int) {
NONE("NONE", "??", R.drawable.ic_invalid),
TRIPLE_UP("TripleUp", "X", R.drawable.ic_invalid),
DOUBLE_UP("DoubleUp", "\u21c8", R.drawable.ic_doubleup),
SINGLE_UP("SingleUp", "\u2191", R.drawable.ic_singleup),
FORTY_FIVE_UP("FortyFiveUp", "\u2197", R.drawable.ic_fortyfiveup),
FLAT("Flat", "\u2192", R.drawable.ic_flat),
FORTY_FIVE_DOWN("FortyFiveDown", "\u2198",R.drawable.ic_fortyfivedown),
SINGLE_DOWN("SingleDown", "\u2193", R.drawable.ic_singledown),
DOUBLE_DOWN("DoubleDown", "\u21ca", R.drawable.ic_doubledown),
TRIPLE_DOWN("TripleDown", "X",R.drawable.ic_invalid)
;
companion object {
fun fromSymbol(direction: String?) =
values().firstOrNull { it.symbol == direction } ?: NONE
}
}
}

View file

@ -78,12 +78,16 @@ abstract class BaseWatchFace : WatchFace() {
var gridColor = Color.WHITE var gridColor = Color.WHITE
var basalBackgroundColor = Color.BLUE var basalBackgroundColor = Color.BLUE
var basalCenterColor = Color.BLUE var basalCenterColor = Color.BLUE
var carbColor = Color.GREEN
private var bolusColor = Color.MAGENTA private var bolusColor = Color.MAGENTA
private var lowResMode = false private var lowResMode = false
private var layoutSet = false private var layoutSet = false
var bIsRound = false var bIsRound = false
var dividerMatchesBg = false var dividerMatchesBg = false
var pointSize = 2 var pointSize = 2
var enableSecond = false
val showSecond: Boolean
get() = enableSecond && currentWatchMode == WatchMode.INTERACTIVE
// Tapping times // Tapping times
private var sgvTapTime: Long = 0 private var sgvTapTime: Long = 0
@ -250,7 +254,7 @@ abstract class BaseWatchFace : WatchFace() {
} }
override fun getInteractiveModeUpdateRate(): Long { override fun getInteractiveModeUpdateRate(): Long {
return 60 * 1000L // Only call onTimeChanged every 60 seconds return if (showSecond) 1000L else 60 * 1000L // Only call onTimeChanged every 60 seconds
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
@ -271,6 +275,8 @@ abstract class BaseWatchFace : WatchFace() {
missedReadingAlert() missedReadingAlert()
checkVibrateHourly(oldTime, newTime) checkVibrateHourly(oldTime, newTime)
if (!simpleUi.isEnabled(currentWatchMode)) setDataFields() if (!simpleUi.isEnabled(currentWatchMode)) setDataFields()
} else if (layoutSet && !simpleUi.isEnabled(currentWatchMode) && showSecond && newTime.hasSecondChanged(oldTime)) {
setSecond()
} }
} }
@ -358,9 +364,20 @@ abstract class BaseWatchFace : WatchFace() {
binding.month?.text = dateUtil.monthString() binding.month?.text = dateUtil.monthString()
binding.timePeriod?.visibility = android.text.format.DateFormat.is24HourFormat(this).not().toVisibility() binding.timePeriod?.visibility = android.text.format.DateFormat.is24HourFormat(this).not().toVisibility()
binding.timePeriod?.text = dateUtil.amPm() binding.timePeriod?.text = dateUtil.amPm()
if (showSecond)
setSecond()
} }
private fun setColor() { open fun setSecond() {
binding.time?.text = if(binding.timePeriod == null) dateUtil.timeString() else dateUtil.hourString() + ":" + dateUtil.minuteString() + if (showSecond) ":" + dateUtil.secondString() else ""
binding.second?.text = dateUtil.secondString()
}
open fun updateSecondVisibility() {
binding.second?.visibility = showSecond.toVisibility()
}
fun setColor() {
dividerMatchesBg = sp.getBoolean(R.string.key_match_divider, false) dividerMatchesBg = sp.getBoolean(R.string.key_match_divider, false)
when { when {
lowResMode -> setColorLowRes() lowResMode -> setColorLowRes()
@ -378,9 +395,12 @@ abstract class BaseWatchFace : WatchFace() {
} }
override fun onWatchModeChanged(watchMode: WatchMode) { override fun onWatchModeChanged(watchMode: WatchMode) {
updateSecondVisibility() // will show second if enabledSecond and Interactive mode, hide in other situation
setSecond() // will remove second from main date and time if not in Interactive mode
lowResMode = isLowRes(watchMode) lowResMode = isLowRes(watchMode)
if (simpleUi.isEnabled(currentWatchMode)) simpleUi.setAntiAlias(currentWatchMode) if (simpleUi.isEnabled(currentWatchMode)) simpleUi.setAntiAlias(currentWatchMode)
else setDataFields() else
setDataFields()
invalidate() invalidate()
} }
@ -409,12 +429,12 @@ abstract class BaseWatchFace : WatchFace() {
if (lowResMode) if (lowResMode)
BgGraphBuilder( BgGraphBuilder(
sp, dateUtil, graphData.entries, treatmentData.predictions, treatmentData.temps, treatmentData.basals, treatmentData.boluses, pointSize, 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 else
BgGraphBuilder( BgGraphBuilder(
sp, dateUtil, graphData.entries, treatmentData.predictions, treatmentData.temps, treatmentData.basals, treatmentData.boluses, 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?.lineChartData = bgGraphBuilder.lineData()
binding.chart?.isViewportCalculationEnabled = true 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.ActivityHomeBinding
import info.nightscout.androidaps.databinding.ActivityBigchartBinding import info.nightscout.androidaps.databinding.ActivityBigchartBinding
import info.nightscout.androidaps.databinding.ActivityCockpitBinding import info.nightscout.androidaps.databinding.ActivityCockpitBinding
import info.nightscout.androidaps.databinding.ActivityCustomBinding
import info.nightscout.androidaps.databinding.ActivityDigitalstyleBinding import info.nightscout.androidaps.databinding.ActivityDigitalstyleBinding
import info.nightscout.androidaps.databinding.ActivityNochartBinding import info.nightscout.androidaps.databinding.ActivityNochartBinding
import info.nightscout.androidaps.databinding.ActivitySteampunkBinding import info.nightscout.androidaps.databinding.ActivitySteampunkBinding
@ -22,11 +23,12 @@ class WatchfaceViewAdapter(
cp: ActivityCockpitBinding? = null, cp: ActivityCockpitBinding? = null,
ds: ActivityDigitalstyleBinding? = null, ds: ActivityDigitalstyleBinding? = null,
nC: ActivityNochartBinding? = null, nC: ActivityNochartBinding? = null,
sP: ActivitySteampunkBinding? = null sP: ActivitySteampunkBinding? = null,
cU: ActivityCustomBinding? = null
) { ) {
init { 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") throw IllegalArgumentException("Require at least on Binding parameter")
} }
} }
@ -34,39 +36,40 @@ class WatchfaceViewAdapter(
private val errorMessage = "Missing require View Binding parameter" private val errorMessage = "Missing require View Binding parameter"
// Required attributes // Required attributes
val mainLayout = 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) ?: throw IllegalArgumentException(errorMessage)
val timestamp = 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) ?: throw IllegalArgumentException(errorMessage)
val root = 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) ?: throw IllegalArgumentException(errorMessage)
// Optional attributes // Optional attributes
val sgv = aL?.sgv ?: a2?.sgv ?: aa?.sgv ?: bC?.sgv ?: bC?.sgv ?: cp?.sgv ?: ds?.sgv ?: nC?.sgv 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 val direction = aL?.direction ?: a2?.direction ?: aa?.direction ?: cp?.direction ?: ds?.direction ?: cU?.direction
val loop = a2?.loop ?: cp?.loop ?: sP?.loop 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 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 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 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 val rigBattery = a2?.rigBattery ?: cp?.rigBattery ?: ds?.rigBattery ?: sP?.rigBattery ?: cU?.rigBattery
val basalRate = a2?.basalRate ?: cp?.basalRate ?: ds?.basalRate ?: sP?.basalRate val basalRate = a2?.basalRate ?: cp?.basalRate ?: ds?.basalRate ?: sP?.basalRate ?: cU?.basalRate
val bgi = a2?.bgi ?: ds?.bgi val bgi = a2?.bgi ?: ds?.bgi ?: cU?.bgi
val AAPSv2 = a2?.AAPSv2 ?: cp?.AAPSv2 ?: ds?.AAPSv2 ?: sP?.AAPSv2 val AAPSv2 = a2?.AAPSv2 ?: cp?.AAPSv2 ?: ds?.AAPSv2 ?: sP?.AAPSv2 ?: cU?.AAPSv2
val cob1 = a2?.cob1 ?: ds?.cob1 val cob1 = a2?.cob1 ?: ds?.cob1 ?: cU?.cob1
val cob2 = a2?.cob2 ?: cp?.cob2 ?: ds?.cob2 ?: sP?.cob2 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 val time = aL?.time ?: a2?.time ?: aa?.time ?: bC?.time ?: bC?.time ?: cp?.time ?: nC?.time ?: cU?.time
val minute = ds?.minute val second = cU?.second
val hour = ds?.hour val minute = ds?.minute ?: cU?.minute
val day = a2?.day ?: ds?.day val hour = ds?.hour ?: cU?.hour
val month = a2?.month ?: ds?.month val day = a2?.day ?: ds?.day ?: cU?.day
val iob1 = a2?.iob1 ?: ds?.iob1 val month = a2?.month ?: ds?.month ?: cU?.month
val iob2 = a2?.iob2 ?: cp?.iob2 ?: ds?.iob2 ?: sP?.iob2 val iob1 = a2?.iob1 ?: ds?.iob1 ?: cU?.iob1
val chart = a2?.chart ?: aa?.chart ?: bC?.chart ?: bC?.chart ?: ds?.chart ?: sP?.chart 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 status = aL?.status ?: aa?.status ?: bC?.status ?: bC?.status ?: nC?.status
val timePeriod = ds?.timePeriod ?: aL?.timePeriod ?: nC?.timePeriod ?: bC?.timePeriod val timePeriod = ds?.timePeriod ?: aL?.timePeriod ?: nC?.timePeriod ?: bC?.timePeriod ?: cU?.timePeriod
val dayName = ds?.dayName val dayName = ds?.dayName ?: cU?.dayName
val mainMenuTap = ds?.mainMenuTap ?: sP?.mainMenuTap val mainMenuTap = ds?.mainMenuTap ?: sP?.mainMenuTap
val chartZoomTap = ds?.chartZoomTap ?: sP?.chartZoomTap val chartZoomTap = ds?.chartZoomTap ?: sP?.chartZoomTap
val dateTime = ds?.dateTime ?: a2?.dateTime val dateTime = ds?.dateTime ?: a2?.dateTime
@ -91,6 +94,7 @@ class WatchfaceViewAdapter(
is ActivityDigitalstyleBinding -> WatchfaceViewAdapter(null, null, null, null, null, bindLayout) is ActivityDigitalstyleBinding -> WatchfaceViewAdapter(null, null, null, null, null, bindLayout)
is ActivityNochartBinding -> WatchfaceViewAdapter(null, 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 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") else -> throw IllegalArgumentException("ViewBinding is not implement in WatchfaceViewAdapter")
} }
} }

View file

@ -0,0 +1,12 @@
<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="M2.702,16.281c1.952,1.81 4.279,4.076 5.31,5.654v0.002c0,0 0.001,-0.001 0.001,-0.001c0,0 0.001,0.001 0.001,0.001v-0.002c1.032,-1.578 3.359,-3.844 5.31,-5.654l-1.325,-1.821c0,0 -1.578,1.408 -2.934,2.728V2.063H6.961l0,15.125c-1.356,-1.319 -2.934,-2.728 -2.934,-2.728L2.702,16.281z"
android:fillColor="@color/white"/>
<path
android:pathData="M10.676,16.281c1.952,1.81 4.279,4.076 5.31,5.654v0.002c0,0 0.001,-0.001 0.001,-0.001c0,0 0.001,0.001 0.001,0.001v-0.002c1.032,-1.578 3.359,-3.844 5.31,-5.654l-1.325,-1.821c0,0 -1.578,1.408 -2.934,2.728V2.063h-2.104v15.125c-1.356,-1.319 -2.934,-2.728 -2.934,-2.728L10.676,16.281z"
android:fillColor="@color/white"/>
</vector>

View file

@ -0,0 +1,12 @@
<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="M2.702,7.719c1.952,-1.81 4.279,-4.076 5.31,-5.654V2.063c0,0 0.001,0.001 0.001,0.001c0,0 0.001,-0.001 0.001,-0.001v0.002c1.032,1.578 3.359,3.844 5.31,5.654L11.999,9.54c0,0 -1.578,-1.408 -2.934,-2.728v15.125H6.961l0,-15.125C5.605,8.132 4.027,9.54 4.027,9.54L2.702,7.719z"
android:fillColor="@color/white"/>
<path
android:pathData="M10.676,7.719c1.952,-1.81 4.279,-4.076 5.31,-5.654V2.063c0,0 0.001,0.001 0.001,0.001c0,0 0.001,-0.001 0.001,-0.001v0.002c1.032,1.578 3.359,3.844 5.31,5.654L19.973,9.54c0,0 -1.578,-1.408 -2.934,-2.728v15.125h-2.104V6.813c-1.356,1.319 -2.934,2.728 -2.934,2.728L10.676,7.719z"
android:fillColor="@color/white"/>
</vector>

View file

@ -0,0 +1,9 @@
<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="M16.281,17.311c1.81,-1.952 4.076,-4.279 5.654,-5.31h0.002c0,0 -0.001,-0.001 -0.001,-0.001c0,0 0.001,-0.001 0.001,-0.001h-0.002c-1.578,-1.032 -3.844,-3.359 -5.654,-5.31L14.46,8.014c0,0 1.408,1.578 2.728,2.934H2.063v2.104h15.125c-1.319,1.356 -2.728,2.934 -2.728,2.934L16.281,17.311z"
android:fillColor="@color/white"/>
</vector>

View file

@ -0,0 +1,9 @@
<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="M11.272,18.783c2.66,-0.1 5.908,-0.143 7.753,0.243l0.001,0.001c0,0 0,-0.001 0,-0.001c0,0 0.001,0 0.001,0l-0.001,-0.001c-0.386,-1.845 -0.343,-5.093 -0.243,-7.753l-2.225,-0.351c0,0 -0.12,2.111 -0.146,4.003L5.717,4.229L4.229,5.717l10.695,10.695c-1.892,0.026 -4.003,0.146 -4.003,0.146L11.272,18.783z"
android:fillColor="@color/white"/>
</vector>

View file

@ -0,0 +1,9 @@
<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="M18.783,12.728c-0.1,-2.66 -0.143,-5.908 0.243,-7.753l0.001,-0.001c0,0 -0.001,0 -0.001,0c0,0 0,-0.001 0,-0.001l-0.001,0.001c-1.845,0.386 -5.093,0.343 -7.753,0.243l-0.351,2.225c0,0 2.111,0.12 4.003,0.146L4.229,18.283l1.488,1.488L16.412,9.076c0.026,1.892 0.146,4.003 0.146,4.003L18.783,12.728z"
android:fillColor="@color/white"/>
</vector>

View file

@ -0,0 +1,18 @@
<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="M17.35,19.561m-1.147,0a1.147,1.147 0,1 1,2.294 0a1.147,1.147 0,1 1,-2.294 0"
android:fillColor="@color/white"/>
<path
android:pathData="M17.301,16.804c-0.459,0 -0.83,-0.372 -0.83,-0.831c0,-2.985 1.391,-4.027 2.51,-4.864c0.937,-0.702 1.614,-1.209 1.614,-2.849c0,-2.262 -2.173,-2.965 -2.964,-2.965c-1.705,0 -3.083,1.022 -3.778,2.804c-0.167,0.427 -0.65,0.64 -1.075,0.471c-0.427,-0.167 -0.638,-0.648 -0.471,-1.075c0.944,-2.417 2.935,-3.86 5.325,-3.86c1.865,0 4.626,1.47 4.626,4.626c0,2.472 -1.264,3.418 -2.28,4.178c-1.031,0.772 -1.844,1.38 -1.844,3.535C18.131,16.432 17.76,16.804 17.301,16.804z"
android:fillColor="@color/white"/>
<path
android:pathData="M6.846,19.561m-1.147,0a1.147,1.147 0,1 1,2.294 0a1.147,1.147 0,1 1,-2.294 0"
android:fillColor="@color/white"/>
<path
android:pathData="M6.797,16.804c-0.459,0 -0.83,-0.372 -0.83,-0.831c0,-2.985 1.391,-4.027 2.51,-4.864c0.937,-0.702 1.614,-1.209 1.614,-2.849c0,-2.262 -2.173,-2.965 -2.964,-2.965c-1.705,0 -3.083,1.022 -3.778,2.804c-0.167,0.427 -0.65,0.64 -1.075,0.471C1.845,8.403 1.635,7.922 1.801,7.495c0.944,-2.417 2.935,-3.86 5.325,-3.86c1.865,0 4.626,1.47 4.626,4.626c0,2.472 -1.264,3.418 -2.28,4.178c-1.031,0.772 -1.844,1.38 -1.844,3.535C7.627,16.432 7.256,16.804 6.797,16.804z"
android:fillColor="@color/white"/>
</vector>

View file

@ -0,0 +1,9 @@
<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="M6.689,16.281c1.952,1.81 4.279,4.076 5.31,5.654v0.002c0,0 0.001,-0.001 0.001,-0.001c0,0 0.001,0.001 0.001,0.001v-0.002c1.032,-1.578 3.359,-3.844 5.31,-5.654l-1.325,-1.821c0,0 -1.578,1.408 -2.934,2.728V2.063h-2.104v15.125c-1.356,-1.319 -2.934,-2.728 -2.934,-2.728L6.689,16.281z"
android:fillColor="@color/white"/>
</vector>

View file

@ -0,0 +1,9 @@
<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="M17.102,7.719c-1.952,-1.81 -4.279,-4.076 -5.31,-5.654V2.063c0,0 -0.001,0.001 -0.001,0.001c0,0 -0.001,-0.001 -0.001,-0.001v0.002c-1.032,1.578 -3.359,3.844 -5.31,5.654L7.805,9.54c0,0 1.578,-1.408 2.934,-2.728v15.125h2.104V6.813c1.356,1.319 2.934,2.728 2.934,2.728L17.102,7.719z"
android:fillColor="@color/white"/>
</vector>

View file

@ -0,0 +1,391 @@
<?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: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:layout_width="400px"
android:layout_height="170px"
android:layout_marginTop="230px"
android:layout_marginBottom="0px" />
<ImageView
android:id="@+id/cover_chart"
android:layout_width="400px"
android:layout_height="400px"
android:layout_marginTop="0px"
android:layout_marginLeft="0px"
android:visibility="gone"
android:orientation="vertical" />
<TextView
android:id="@+id/freetext1"
android:layout_width="0px"
android:layout_height="0px"
android:layout_marginTop="0px"
android:layout_marginLeft="0px"
android:textSize="21px"
android:gravity="center"
android:visibility="gone"
android:textColor="@color/light_grey" />
<TextView
android:id="@+id/freetext2"
android:layout_width="0px"
android:layout_height="0px"
android:layout_marginTop="0px"
android:layout_marginLeft="0px"
android:textSize="21px"
android:gravity="center"
android:visibility="gone"
android:textColor="@color/light_grey" />
<TextView
android:id="@+id/iob1"
android:layout_width="130px"
android:layout_height="33px"
android:layout_marginTop="168px"
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:layout_width="130px"
android:layout_height="33px"
android:layout_marginTop="196px"
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:layout_width="120px"
android:layout_height="33px"
android:layout_marginTop="168px"
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:layout_width="120px"
android:layout_height="33px"
android:layout_marginTop="196px"
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:layout_width="59px"
android:layout_height="32px"
android:layout_marginTop="133px"
android:layout_marginLeft="15px"
android:gravity="center"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="+/-" />
<TextView
android:id="@+id/avg_delta"
android:layout_width="59px"
android:layout_height="32px"
android:layout_marginTop="133px"
android:layout_marginLeft="74px"
android:gravity="center"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="@string/abbreviation_average" />
<TextView
android:id="@+id/uploader_battery"
android:layout_width="60px"
android:layout_height="32px"
android:gravity="center"
android:layout_marginTop="133px"
android:layout_marginLeft="129px"
android:textSize="23px"
android:fontFamily="@font/roboto_condensed_bold"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="100%" />
<TextView
android:id="@+id/rig_battery"
android:layout_width="60px"
android:layout_height="32px"
android:gravity="center"
android:layout_marginTop="133px"
android:layout_marginLeft="189px"
android:textSize="23px"
android:fontFamily="@font/roboto_condensed_bold"
android:textStyle="bold"
android:textColor="@color/light_grey"
android:visibility="visible"
tools:text="100%" />
<TextView
android:id="@+id/basalRate"
android:layout_width="91px"
android:layout_height="32px"
android:layout_marginTop="133px"
android:layout_marginLeft="249px"
android:gravity="center"
android:fontFamily="@font/roboto_condensed_bold"
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:layout_width="60px"
android:layout_height="32px"
android:layout_marginTop="133px"
android:layout_marginLeft="340px"
android:gravity="center"
android:textSize="23px"
android:textStyle="bold"
android:textColor="@color/light_grey"
android:visibility="visible"
tools:text="bgi" />
<TextView
android:id="@+id/time"
android:layout_width="0px"
android:layout_height="0px"
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:layout_width="0px"
android:layout_height="0px"
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:layout_width="0px"
android:layout_height="0px"
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:layout_width="0px"
android:layout_height="0px"
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:layout_width="0px"
android:layout_height="0px"
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:layout_width="56px"
android:layout_height="36px"
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:layout_width="56px"
android:layout_height="36px"
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:layout_width="50px"
android:layout_height="36px"
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:layout_width="50px"
android:layout_height="50px"
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:layout_width="52px"
android:layout_height="52px"
android:layout_marginTop="26px"
android:layout_marginLeft="291px"
android:visibility="gone"
android:gravity="left"
android:textSize="39px"
android:textStyle="bold"
android:textColor="@color/light_grey"
tools:text="--" />
<ImageView
android:id="@+id/direction2"
android:layout_width="52px"
android:layout_height="52px"
android:layout_marginTop="26px"
android:layout_marginLeft="291px"
android:visibility="visible"
android:src="@drawable/ic_flat"
android:orientation="vertical" />
<TextView
android:id="@+id/timestamp"
android:layout_width="52px"
android:layout_height="34px"
android:gravity="left"
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:layout_width="400px"
android:layout_height="100px"
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: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: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: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: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_cockpit">AAPS(Cockpit)</string>
<string name="label_watchface_steampunk">AAPS(Steampunk)</string> <string name="label_watchface_steampunk">AAPS(Steampunk)</string>
<string name="label_watchface_digital_style">AAPS(DigitalStyle)</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_actions_tile">AAPS(Actions)</string>
<string name="label_temp_target_tile">AAPS(Temp Target)</string> <string name="label_temp_target_tile">AAPS(Temp Target)</string>
<string name="label_quick_wizard_tile">AAPS(Quick Wizard)</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_simplify_ui_sum">Only show time and BG</string>
<string name="pref_vibrate_hourly">Vibrate hourly</string> <string name="pref_vibrate_hourly">Vibrate hourly</string>
<string name="pref_show_weeknumber">Show Week number</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_pref_your_style">Your style:</string>
<string name="digitalstyle_style_none">no style</string> <string name="digitalstyle_style_none">no style</string>
<string name="digitalstyle_style_minimal">minimal 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_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_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_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="increment">increment</string>
<string name="decrement">decrement</string> <string name="decrement">decrement</string>
<string name="first_char_high">H</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>