Compare commits

...

51 Commits
v1.2 ... v1.4

Author SHA1 Message Date
e41adff62d Update README.md
Added credit
2023-11-21 20:35:27 +00:00
ae403e724c Update README.md
Added description of v1.4
2023-11-21 20:31:32 +00:00
7fd90fe85d Apply automatic changes 2023-11-21 18:24:57 +00:00
3fadbb5757 Update translate.yml 2023-11-21 18:13:47 +00:00
d5b3b6a2c5 Merge pull request #15 from Someone0nEarth/lean_ui
Icons for Menu Types
2023-11-21 18:08:56 +00:00
4cc4aef300 Update HomeAssistantIconMenuItem.mc
Removed comment
2023-11-21 18:04:46 +00:00
71c1ccc229 Code review comment
Removed debug
Fixed manifest App ID for release
2023-11-21 17:58:18 +00:00
ce90d9d47f Single HomeAssistantService for all Taps
1. Amended code for a single HomeAssistantService for all 'tap's
2. Removed now redundant GET request for taps without a service now that having a service is enforced.
3. Determined that migrating API code from 'toggle's to the HomeAssistantService is awkward due to the close coupling with other methods in the class.
2023-11-20 21:32:35 +00:00
bcdcfdc66c Set showing icons for types as default 2023-11-19 17:04:22 +01:00
440980a96f Aligned naming of menu item types 2023-11-19 13:24:42 +01:00
ab995db5ba Removed redundant code from spike 2023-11-19 13:24:37 +01:00
604294ae0b Corrected log strings 2023-11-19 13:11:51 +01:00
5f8ffdce5e Changed color of tap and menu icon 2023-11-19 13:11:51 +01:00
eab2af72f8 Added app setting for left - right menu alignment 2023-11-19 13:11:51 +01:00
7bd3486724 Refactored icon submenu creation 2023-11-19 13:11:51 +01:00
d93aa78686 Refactored MenuItems creation 2023-11-19 13:11:48 +01:00
081a41737f Bugfix 2023-11-19 13:07:07 +01:00
2c09ab71a8 Lean UI spike use correct icons for menu and tap 2023-11-19 13:07:07 +01:00
8764969537 Leaner UI spike 2023-11-19 13:07:03 +01:00
a4db5e5699 Merge pull request #14 from house-of-abbey/10-separate-phone-connection-test-from-connection-available-test
10 separate phone connection test from connection available test
2023-11-18 16:02:08 +00:00
d6c6657ade Apply automatic changes 2023-11-18 16:01:19 +00:00
b90e6c7130 Connection reporting improvements and update resumption
Separation of phone connectivity and Internet access tests for finer grained feedback to the user.
Updates no longer stop on absence of Internet connectivity.
2023-11-18 15:52:02 +00:00
61311336da Update README.md 2023-11-18 07:28:50 +00:00
2742090b68 Merge pull request #12 from house-of-abbey/11-impose-the-requirement-for-a-service-field-for-taps
Tap menu items without a service tag will be ignored.
2023-11-17 18:01:03 +00:00
4b320086ee Tap menu items without a service tag will be ignored.
The JSON schema checker is relied on to highlight this error.
2023-11-17 17:51:01 +00:00
6b8892aee8 Update README.md
Added release notes for version 1.3.
2023-11-16 09:19:36 +00:00
fce353dc38 Apply automatic changes 2023-11-15 07:51:24 +00:00
647f92e9c6 2023-11-15 07:49:33 +00:00
c2bd0dfa40 Apply automatic changes 2023-11-14 23:18:35 +00:00
22f9fa6901 Rename resources-deu/corrections.xml to resources-deu/strings/corrections.xml 2023-11-14 23:09:59 +00:00
cf7be0bcd9 Create corrections.xml 2023-11-14 23:09:15 +00:00
6a2a70ba0b Merge pull request #8 from Someone0nEarth/correcting_german_translation 2023-11-14 23:05:59 +00:00
f58fe2d26d Apply automatic changes 2023-11-14 23:00:33 +00:00
812b601f86 Update translate.yml 2023-11-14 22:48:02 +00:00
b726b8b6ac Update translate.yml 2023-11-14 22:31:52 +00:00
abfddd22e2 Update translate.yml 2023-11-14 22:29:39 +00:00
5794d67058 Update translate.yml 2023-11-14 22:27:58 +00:00
a6b7c27bf8 Update translate.yml 2023-11-14 22:24:52 +00:00
c57d6c18cf Added French corrections to corrections.xml 2023-11-14 22:23:31 +00:00
ae346da3f2 Merge pull request #3 from Patrick44fr/patch-1 2023-11-14 22:16:08 +00:00
ff47e2c76c Merge branch 'main' into patch-1 2023-11-14 22:14:53 +00:00
f9efb7b9b8 Update translate.yml 2023-11-14 22:10:15 +00:00
f5e68da346 Merge pull request #6 from house-of-abbey/5-dont-overwrite-corrections-for-strings-xml 2023-11-14 22:09:14 +00:00
e05799bbbc Change to a seperate corrections file 2023-11-14 22:07:02 +00:00
34bead8b67 Merge pull request #7 from house-of-abbey/2-tap-or-toggle-dont-work-on-venu-2
Changes required for 2-tap-or-toggle-dont-work-on-venu-2
2023-11-14 21:32:30 +00:00
33bc12d779 Update HomeAssistantMenuItem.mc
Review comments. Vibrate on tap, visual confirmation on receipt of response.
2023-11-14 21:14:32 +00:00
28a2616021 Correct German translation 2023-11-14 19:14:22 +01:00
2ccd2bfbff Changes required for 2-tap-or-toggle-dont-work-on-venu-2
Made the 'service' field for taps mandatory.
This makes scripts work for both the emulator and real devices.
Provide a response for automations started with a tap, and now they can be used for both tap and toggle.
2023-11-14 08:35:38 +00:00
c57324f7ad Create translate.yml 2023-11-13 23:00:57 +00:00
3c7d9e8b41 Updated translate script to use corrected attr 2023-11-13 22:46:10 +00:00
ba8812a6ab Update strings.xml 2023-11-11 19:43:21 +01:00
93 changed files with 868 additions and 267 deletions

35
.github/workflows/translate.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Translate
on:
workflow_dispatch:
# push:
# paths:
# - "resources/strings/strings.xml"
# - "translate.py"
# - "resources-*/strings/corrections.xml"
jobs:
translate:
runs-on: ubuntu-latest
permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4.7.1
- run: |
pip install beautifulsoup4
pip install deep-translator
pip install lxml
- run: python translate.py
# Commit all changed files back to the repository
- uses: stefanzweifel/git-auto-commit-action@v5
with:
push_options: '--force'

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ bin/
export/
**/Thumbs.db
settings.txt
export.cmd

View File

@ -2,10 +2,6 @@
"folders": [
{
"path": "."
},
{
"name": "abc",
"path": "../blah/abc"
}
],
"settings": {}

View File

@ -34,7 +34,8 @@ Example schema as shown in the images:
{
"entity": "script.food_on_table",
"name": "Food is Ready!",
"type": "tap"
"type": "tap",
"service" : "script.turn_on"
},
{
"entity": "light.bedside_light_switch",
@ -79,6 +80,12 @@ Example schema as shown in the images:
"name": "Garage Door Check",
"type": "toggle"
},
{
"entity": "automation.turn_off_usb_chargers",
"name": "Turn off USBs",
"type": "tap",
"service" : "automation.trigger"
},
{
"entity": "scene.tv_light",
"name": "TV Lights Scene",
@ -99,6 +106,18 @@ The example above illustrates how to configure:
* Service invocation, e.g. Scene setting, (tap)
* A sub-menu to open (tap)
The example JSON shows an example usage of each of these Home Assistance entity types. Presently, an automation is the only one that can be either a 'tap' or a 'toggle'.
| HA Type | Tap | Toggle |
|------------|:---:|:------:|
| Switch | ❌ | ✅ |
| Light | ❌ | ✅ |
| Automation | ✅ | ✅ |
| Script | ✅ | ❌ |
| Scene | ✅ | ❌ |
NB. All 'tap' items must specify a 'service' tag.
Possible future extensions might include specifying the alternative texts to use instead of "On" and "Off", e.g. "Locked" and "Unlocked" (but wouldn't having locks operated from your watch be a security concern ;-))
The [schema](https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json) is checked by using a URL directly back to this GitHub source repository, so you do not need to install that file. You can just copy & paste your entity names from the YAML configuration files used to configure Home Assistant. With a submenu, there's a difference between "title" and "name". The "name" goes on the menu item, and the "title" at the head of the submenu. If your dashboard definition fails to meet the schema, the application will simply drop items with the wrong field names without warning.
@ -150,3 +169,5 @@ When you change the JSON file defining your dashboard, you must exit the applica
| 1.0 | Initial release for 26 devices. |
| 1.1 | Updated for 54 more devices, 80 in total. Scene support. Added vibrate acknowledgement for tap-based menu items. Falls back to a custom visual confirmation in the absence of 'toast' and vibrate support. Bug fix for large menus needing status updates. |
| 1.2 | Do not crash on zero items to update. Report unreachable URLs. Verify API URL does not have a trailing slash '/'. Increased HTTP response diagnosis. Reduced minimum API Level required from 3.3.0 to 3.1.0 to allow more device "part numbers" to be satisfied. |
| 1.3 | Tap for scripts was working in emulation but not on some phones. Decision is to make the 'service' field in the JSON compulsory for 'tap' menu items. This is a breaking change, but for many might be a fix for something not working correctly. Improve language support, we can now accept language corrections and prevent the automated translation of strings from clobbering manually refined entries. Thank you to two new contributors. |
| 1.4 | New lean user Interface with thanks to [SomeoneOnEarth](https://github.com/Someone0nEarth) for their contribution which is now the default. If you prefer the old style you can still select it in the settings. The provision of a 'service' tag is now not just heavily suggested by the JSON schema, it is enforced in code. With appologies to anyone suffering a breakage as a result. |

View File

@ -26,7 +26,7 @@
"type": { "const": "tap" },
"service": { "$ref": "#/$defs/entity" }
},
"required": ["entity", "name", "type"],
"required": ["entity", "name", "type", "service"],
"additionalProperties": false
},
"menu": {

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">عن</string>
<string id="MenuItemTap">مقبض</string>
<string id="MenuItemMenu">قائمة طعام</string>
<string id="NoPhone">لا يوجد اتصال الهاتف</string>
<string id="NoInternet">لا يوجد اتصال بالإنترنت</string>
<string id="NoResponse">لا توجد استجابة، تحقق من الاتصال بالإنترنت</string>
<string id="NoMenu">خطأ في إحضار القائمة</string>
<string id="NoAPIKey">لا يوجد مفتاح API في إعدادات التطبيق</string>
<string id="NoApiUrl">لا يوجد عنوان URL لواجهة برمجة التطبيقات في إعدادات التطبيق</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Изкл</string>
<string id="MenuItemTap">Докоснете</string>
<string id="MenuItemMenu">Меню</string>
<string id="NoPhone">Няма телефонна връзка</string>
<string id="NoInternet">Няма интернет връзка</string>
<string id="NoResponse">Няма отговор, проверете интернет връзката</string>
<string id="NoMenu">Грешка при извличане на менюто</string>
<string id="NoAPIKey">Няма API ключ в настройките на приложението</string>
<string id="NoApiUrl">Няма URL адрес на API в настройките на приложението</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Vypnuto</string>
<string id="MenuItemTap">Klepněte</string>
<string id="MenuItemMenu">Jídelní lístek</string>
<string id="NoPhone">Žádné telefonní spojení</string>
<string id="NoInternet">Žádné internetové připojení</string>
<string id="NoResponse">Žádná odpověď, zkontrolujte připojení k internetu</string>
<string id="NoMenu">Chyba načítání nabídky</string>
<string id="NoAPIKey">V nastavení aplikace není žádný klíč API</string>
<string id="NoApiUrl">V nastavení aplikace není žádná adresa URL API</string>

View File

@ -24,8 +24,10 @@
<string id="MenuItemOff">Af</string>
<string id="MenuItemTap">Tryk på</string>
<string id="MenuItemMenu">Menu</string>
<string id="NoPhone">Ingen telefonforbindelse</string>
<string id="NoInternet">Ingen internetforbindelse</string>
<string id="NoMenu">Menuhentningsfejl</string>
<string id="NoResponse">Intet svar, tjek internetforbindelse</string>
<string id="NoMenu">Fejl ved menuhentning</string>
<string id="NoAPIKey">Ingen API-nøgle i applikationsindstillingerne</string>
<string id="NoApiUrl">Ingen API-URL i applikationsindstillingerne</string>
<string id="NoConfigUrl">Ingen konfigurations-URL i applikationsindstillingerne</string>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Distributed under MIT Licence
See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
tested on a Venu 2 device. The source code is provided at:
https://github.com/house-of-abbey/GarminHomeAssistant.
P A Abbey & J D Abbey, 14 November 2023
-->
<!--
Corrections for the German language
Korrekturen für die deutsche Sprache
-->
<strings>
<string id="MenuItemTap">Antippen</string>
<string id="MenuItemMenu">Menü</string>
<string id="UnhandledHttpErr">Die HTTP-Anfrage gab folgenden Fehlercode zurück = </string>
</strings>

View File

@ -22,9 +22,11 @@
<string id="AppName">HomeAssistant</string>
<string id="MenuItemOn">An</string>
<string id="MenuItemOff">Aus</string>
<string id="MenuItemTap">Klopfen</string>
<string id="MenuItemMenu">Speisekarte</string>
<string id="MenuItemTap">Antippen</string>
<string id="MenuItemMenu">Menü</string>
<string id="NoPhone">Keine Telefonverbindung</string>
<string id="NoInternet">Keine Internetverbindung</string>
<string id="NoResponse">Keine Antwort, überprüfen Sie die Internetverbindung</string>
<string id="NoMenu">Fehler beim Abrufen des Menüs</string>
<string id="NoAPIKey">Kein API-Schlüssel in den Anwendungseinstellungen</string>
<string id="NoApiUrl">Keine API-URL in den Anwendungseinstellungen</string>
@ -32,6 +34,6 @@
<string id="ApiFlood">API-Aufrufe zu schnell. Bitte verlangsamen Sie Ihre Anfragen.</string>
<string id="ApiUrlNotFound">URL nicht gefunden. Möglicher API-URL-Fehler in den Einstellungen.</string>
<string id="ConfigUrlNotFound">URL nicht gefunden. Möglicher Konfigurations-URL-Fehler in den Einstellungen.</string>
<string id="UnhandledHttpErr">Die HTTP-Anfrage hat den Fehlercode = zurückgegeben</string>
<string id="UnhandledHttpErr">Die HTTP-Anfrage gab folgenden Fehlercode zurück = </string>
<string id="TrailingSlashErr">Die API-URL darf keinen abschließenden Schrägstrich „/“ enthalten.</string>
</strings>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Uit</string>
<string id="MenuItemTap">Kraan</string>
<string id="MenuItemMenu">Menu</string>
<string id="NoPhone">Geen telefoonverbinding</string>
<string id="NoInternet">Geen internet verbinding</string>
<string id="NoResponse">Geen reactie, controleer de internetverbinding</string>
<string id="NoMenu">Fout bij ophalen van menu</string>
<string id="NoAPIKey">Geen API-sleutel in de applicatie-instellingen</string>
<string id="NoApiUrl">Geen API-URL in de applicatie-instellingen</string>

View File

@ -24,12 +24,14 @@
<string id="MenuItemOff">Väljas</string>
<string id="MenuItemTap">Puudutage</string>
<string id="MenuItemMenu">Menüü</string>
<string id="NoPhone">Telefoniühendus puudub</string>
<string id="NoInternet">Interneti-ühendus puudub</string>
<string id="NoResponse">Ei reageeri, kontrollige Interneti-ühendust</string>
<string id="NoMenu">Menüü toomise viga</string>
<string id="NoAPIKey">Rakenduse seadetes pole API-võtit</string>
<string id="NoApiUrl">Rakenduse seadetes pole API URL-i</string>
<string id="NoConfigUrl">Rakenduse seadetes pole konfiguratsiooni URL-i</string>
<string id="ApiFlood">API-kutsed liiga kiired. Palun aeglustage taotluste esitamist.</string>
<string id="ApiFlood">API-kõned liiga kiired. Palun aeglustage oma taotlusi.</string>
<string id="ApiUrlNotFound">URL-i ei leitud. Võimalik API URL-i viga seadetes.</string>
<string id="ConfigUrlNotFound">URL-i ei leitud. Võimalik konfiguratsiooni URL-i viga seadetes.</string>
<string id="UnhandledHttpErr">HTTP päring tagastas veakoodi =</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Vinossa</string>
<string id="MenuItemTap">Napauta</string>
<string id="MenuItemMenu">Valikko</string>
<string id="NoPhone">Ei puhelinyhteyttä</string>
<string id="NoInternet">Ei Internet-yhteyttä</string>
<string id="NoResponse">Ei vastausta, tarkista Internet-yhteys</string>
<string id="NoMenu">Valikkohakuvirhe</string>
<string id="NoAPIKey">Sovellusasetuksissa ei ole API-avainta</string>
<string id="NoApiUrl">Sovellusasetuksissa ei ole API URL-osoitetta</string>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Distributed under MIT Licence
See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
tested on a Venu 2 device. The source code is provided at:
https://github.com/house-of-abbey/GarminHomeAssistant.
P A Abbey & J D Abbey, 14 November 2023
-->
<!--
Corrections for the French language
Corrections pour la langue française
-->
<strings>
<string id="MenuItemOn">Activé</string>
<string id="MenuItemTap">Clic</string>
<string id="ApiFlood">Appels API trop rapide. Veuillez signaler cette erreur avec les détails de l'appareil.</string>
</strings>

View File

@ -20,16 +20,18 @@
<strings>
<string id="AppName">HomeAssistant</string>
<string id="MenuItemOn">Sur</string>
<string id="MenuItemOn">Activé</string>
<string id="MenuItemOff">Désactivé</string>
<string id="MenuItemTap">Robinet</string>
<string id="MenuItemTap">Clic</string>
<string id="MenuItemMenu">Menu</string>
<string id="NoPhone">Pas de connexion téléphonique</string>
<string id="NoInternet">Pas de connexion Internet</string>
<string id="NoResponse">Pas de réponse, vérifiez la connexion Internet</string>
<string id="NoMenu">Erreur de récupération du menu</string>
<string id="NoAPIKey">Pas de clé API dans les paramètres de l'application</string>
<string id="NoApiUrl">Aucune URL API dans les paramètres de l'application</string>
<string id="NoConfigUrl">Aucune URL de configuration dans les paramètres de l'application</string>
<string id="ApiFlood">Appels API trop rapides. Veuillez ralentir vos demandes.</string>
<string id="ApiFlood">Appels API trop rapide. Veuillez signaler cette erreur avec les détails de l'appareil.</string>
<string id="ApiUrlNotFound">URL introuvable. Erreur potentielle d'URL d'API dans les paramètres.</string>
<string id="ConfigUrlNotFound">URL introuvable. Erreur potentielle d'URL de configuration dans les paramètres.</string>
<string id="UnhandledHttpErr">La requête HTTP a renvoyé un code d'erreur =</string>

View File

@ -24,10 +24,12 @@
<string id="MenuItemOff">Μακριά από</string>
<string id="MenuItemTap">Παρακέντηση</string>
<string id="MenuItemMenu">Μενού</string>
<string id="NoPhone">Δεν υπάρχει σύνδεση τηλεφώνου</string>
<string id="NoInternet">Δεν υπάρχει σύνδεση στο διαδίκτυο</string>
<string id="NoResponse">Καμία απάντηση, ελέγξτε τη σύνδεση στο Διαδίκτυο</string>
<string id="NoMenu">Σφάλμα ανάκτησης μενού</string>
<string id="NoAPIKey">Δεν υπάρχει κλειδί API στις ρυθμίσεις της εφαρμογής</string>
<string id="NoApiUrl">Δεν υπάρχει URL API στις ρυθμίσεις της εφαρμογής</string>
<string id="NoApiUrl">Δεν υπάρχει διεύθυνση URL API στις ρυθμίσεις της εφαρμογής</string>
<string id="NoConfigUrl">Δεν υπάρχει διεύθυνση URL διαμόρφωσης στις ρυθμίσεις της εφαρμογής</string>
<string id="ApiFlood">Κλήσεις API πολύ γρήγορες. Παρακαλώ επιβραδύνετε τα αιτήματά σας.</string>
<string id="ApiUrlNotFound">Η διεύθυνση URL δεν βρέθηκε. Πιθανό σφάλμα διεύθυνσης URL API στις ρυθμίσεις.</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">כבוי</string>
<string id="MenuItemTap">בֶּרֶז</string>
<string id="MenuItemMenu">תַפרִיט</string>
<string id="NoPhone">אין חיבור לטלפון</string>
<string id="NoInternet">אין חיבור אינטרנט</string>
<string id="NoResponse">אין תגובה, בדוק חיבור לאינטרנט</string>
<string id="NoMenu">שגיאת אחזור תפריט</string>
<string id="NoAPIKey">אין מפתח API בהגדרות האפליקציה</string>
<string id="NoApiUrl">אין כתובת API בהגדרות האפליקציה</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Isključeno</string>
<string id="MenuItemTap">Dodirnite</string>
<string id="MenuItemMenu">Jelovnik</string>
<string id="NoPhone">Nema telefonske veze</string>
<string id="NoInternet">Nema internetske veze</string>
<string id="NoResponse">Nema odgovora, provjerite internetsku vezu</string>
<string id="NoMenu">Pogreška dohvaćanja izbornika</string>
<string id="NoAPIKey">Nema API ključa u postavkama aplikacije</string>
<string id="NoApiUrl">Nema API URL-a u postavkama aplikacije</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Ki</string>
<string id="MenuItemTap">Koppintson a</string>
<string id="MenuItemMenu">Menü</string>
<string id="NoPhone">Nincs telefonkapcsolat</string>
<string id="NoInternet">Nincs internetkapcsolat</string>
<string id="NoResponse">Nincs válasz, ellenőrizze az internetkapcsolatot</string>
<string id="NoMenu">Menü Lekérési hiba</string>
<string id="NoAPIKey">Nincs API kulcs az alkalmazás beállításaiban</string>
<string id="NoApiUrl">Nincs API URL az alkalmazás beállításai között</string>

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="18" viewBox="0 -960 960 960" width="18" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="18" viewBox="0 -960 960 960" width="18" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="21" viewBox="0 -960 960 960" width="21" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="21" viewBox="0 -960 960 960" width="21" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="24" viewBox="0 -960 960 960" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="24" viewBox="0 -960 960 960" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="26" viewBox="0 -960 960 960" width="26" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="26" viewBox="0 -960 960 960" width="26" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="28" viewBox="0 -960 960 960" width="28" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="28" viewBox="0 -960 960 960" width="28" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="30" viewBox="0 -960 960 960" width="30" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="30" viewBox="0 -960 960 960" width="30" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="32" viewBox="0 -960 960 960" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="32" viewBox="0 -960 960 960" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="38" viewBox="0 -960 960 960" width="38" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="38" viewBox="0 -960 960 960" width="38" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="42" viewBox="0 -960 960 960" width="42" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="42" viewBox="0 -960 960 960" width="42" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="46" viewBox="0 -960 960 960" width="46" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="46" viewBox="0 -960 960 960" width="46" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 606 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 782 B

View File

@ -17,4 +17,6 @@
<drawables>
<bitmap id="ErrorIcon" filename="error.svg"/>
</drawables>
<bitmap id="GroupTypeIcon" filename="group_type.svg"/>
<bitmap id="TapTypeIcon" filename="tap_type.svg"/>
</drawables>

View File

@ -0,0 +1 @@
<svg height="53" viewBox="0 -960 960 960" width="53" xmlns="http://www.w3.org/2000/svg"><path d="M479.858-160Q460-160 446-174.142q-14-14.141-14-34Q432-228 446.142-242q14.141-14 34-14Q500-256 514-241.858q14 14.141 14 34Q528-188 513.858-174q-14.141 14-34 14Zm0-272Q460-432 446-446.142q-14-14.141-14-34Q432-500 446.142-514q14.141-14 34-14Q500-528 514-513.858q14 14.141 14 34Q528-460 513.858-446q-14.141 14-34 14Zm0-272Q460-704 446-718.142q-14-14.141-14-34Q432-772 446.142-786q14.141-14 34-14Q500-800 514-785.858q14 14.141 14 34Q528-732 513.858-718q-14.141 14-34 14Z" fill="darkgrey" stroke="darkgrey"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1 @@
<svg height="53" viewBox="0 -960 960 960" width="53" xmlns="http://www.w3.org/2000/svg"><path d="M445-80q-29 0-56-12t-45-35L127-403l21-23q14-15 34.5-18.5T221-438l99 53v-365q0-12.75 8.675-21.375 8.676-8.625 21.5-8.625 12.825 0 21.325 8.625T380-750v465l-144-77 156 198q10 12 23.76 18 13.76 6 29.24 6h205q38 0 64-26t26-64v-170q0-25.5-17.25-42.75T680-460H460v-60h219.646q50.148 0 85.251 35T800-400v170q0 63-43.5 106.5T650-80H445ZM203-665q-11.074-18.754-17.037-40.492Q180-727.229 180-750.246 180-821 229.725-870.5T350-920q70.55 0 120.275 49.738Q520-820.524 520-749.956q0 22.956-5.963 44.614Q508.074-683.685 497-665l-52-30q7-12 11-26t4-29.478Q460-796 427.882-828q-32.117-32-78-32Q304-860 272-827.917 240-795.833 240-750q0 15 4 29t11 26l-52 30Zm285 335Z" fill="blue" stroke="blue"/></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -24,11 +24,13 @@
<string id="MenuItemOff">Mati</string>
<string id="MenuItemTap">Mengetuk</string>
<string id="MenuItemMenu">Menu</string>
<string id="NoPhone">Tidak ada koneksi Telepon</string>
<string id="NoInternet">Tidak ada koneksi internet</string>
<string id="NoResponse">Tidak Ada Respon, periksa koneksi Internet</string>
<string id="NoMenu">Kesalahan Pengambilan Menu</string>
<string id="NoAPIKey">Tidak ada kunci API di pengaturan aplikasi</string>
<string id="NoApiUrl">Tidak ada URL API di pengaturan aplikasi</string>
<string id="NoConfigUrl">Tidak ada URL konfigurasi di pengaturan aplikasi</string>
<string id="NoConfigUrl">Tidak ada URL konfigurasi dalam pengaturan aplikasi</string>
<string id="ApiFlood">Panggilan API terlalu cepat. Harap memperlambat permintaan Anda.</string>
<string id="ApiUrlNotFound">URL tidak ditemukan. Potensi kesalahan URL API dalam pengaturan.</string>
<string id="ConfigUrlNotFound">URL tidak ditemukan. Potensi kesalahan URL Konfigurasi dalam pengaturan.</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Spento</string>
<string id="MenuItemTap">Rubinetto</string>
<string id="MenuItemMenu">Menù</string>
<string id="NoPhone">Nessuna connessione telefonica</string>
<string id="NoInternet">Nessuna connessione internet</string>
<string id="NoResponse">Nessuna risposta, controlla la connessione Internet</string>
<string id="NoMenu">Errore di recupero del menu</string>
<string id="NoAPIKey">Nessuna chiave API nelle impostazioni dell'applicazione</string>
<string id="NoApiUrl">Nessun URL API nelle impostazioni dell'applicazione</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">オフ</string>
<string id="MenuItemTap">タップ</string>
<string id="MenuItemMenu">メニュー</string>
<string id="NoPhone">電話が接続されていません</string>
<string id="NoInternet">インターネット接続なし</string>
<string id="NoResponse">応答がありません。インターネット接続を確認してください</string>
<string id="NoMenu">メニューフェッチエラー</string>
<string id="NoAPIKey">アプリケーション設定に API キーがありません</string>
<string id="NoApiUrl">アプリケーション設定に API URL がありません</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">끄다</string>
<string id="MenuItemTap">수도꼭지</string>
<string id="MenuItemMenu">메뉴</string>
<string id="NoPhone">전화 연결 없음</string>
<string id="NoInternet">인터넷에 연결되지 않음</string>
<string id="NoResponse">응답이 없습니다. 인터넷 연결을 확인하세요.</string>
<string id="NoMenu">메뉴 가져오기 오류</string>
<string id="NoAPIKey">애플리케이션 설정에 API 키가 없습니다.</string>
<string id="NoApiUrl">애플리케이션 설정에 API URL이 없습니다.</string>
@ -33,5 +35,5 @@
<string id="ApiUrlNotFound">URL을 찾을 수 없습니다. 설정에 잠재적인 API URL 오류가 있습니다.</string>
<string id="ConfigUrlNotFound">URL을 찾을 수 없습니다. 설정에 잠재적인 구성 URL 오류가 있습니다.</string>
<string id="UnhandledHttpErr">HTTP 요청이 오류 코드를 반환했습니다 =</string>
<string id="TrailingSlashErr">API URL에는 후행 슬래시 '/'가 있어서는 안 됩니다.</string>
<string id="TrailingSlashErr">API URL에는 후행 슬래시 '/'가 없어야 합니다.</string>
</strings>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Izslēgts</string>
<string id="MenuItemTap">Krāns</string>
<string id="MenuItemMenu">Izvēlne</string>
<string id="NoPhone">Nav tālruņa savienojuma</string>
<string id="NoInternet">Nav interneta savienojuma</string>
<string id="NoResponse">Nav atbildes, pārbaudiet interneta savienojumu</string>
<string id="NoMenu">Izvēlnes ielādes kļūda</string>
<string id="NoAPIKey">Lietojumprogrammas iestatījumos nav API atslēgas</string>
<string id="NoApiUrl">Lietojumprogrammas iestatījumos nav API URL</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Išjungta</string>
<string id="MenuItemTap">Bakstelėkite</string>
<string id="MenuItemMenu">Meniu</string>
<string id="NoPhone">Nėra telefono ryšio</string>
<string id="NoInternet">Nėra interneto ryšio</string>
<string id="NoResponse">Neatsako, patikrinkite interneto ryšį</string>
<string id="NoMenu">Meniu gavimo klaida</string>
<string id="NoAPIKey">Programos nustatymuose nėra API rakto</string>
<string id="NoApiUrl">Programos nustatymuose nėra API URL</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Av</string>
<string id="MenuItemTap">Trykk på</string>
<string id="MenuItemMenu">Meny</string>
<string id="NoPhone">Ingen telefonforbindelse</string>
<string id="NoInternet">Ingen Internett-tilkobling</string>
<string id="NoResponse">Ingen svar, sjekk Internett-tilkoblingen</string>
<string id="NoMenu">Menyhentingsfeil</string>
<string id="NoAPIKey">Ingen API-nøkkel i applikasjonsinnstillingene</string>
<string id="NoApiUrl">Ingen API-URL i applikasjonsinnstillingene</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Wyłączony</string>
<string id="MenuItemTap">Uzyskiwać</string>
<string id="MenuItemMenu">Menu</string>
<string id="NoPhone">Brak połączenia telefonicznego</string>
<string id="NoInternet">Brak połączenia z internetem</string>
<string id="NoResponse">Brak odpowiedzi, sprawdź połączenie internetowe</string>
<string id="NoMenu">Błąd pobierania menu</string>
<string id="NoAPIKey">Brak klucza API w ustawieniach aplikacji</string>
<string id="NoApiUrl">Brak adresu API w ustawieniach aplikacji</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Desligado</string>
<string id="MenuItemTap">Tocar</string>
<string id="MenuItemMenu">Cardápio</string>
<string id="NoPhone">Sem conexão telefônica</string>
<string id="NoInternet">Sem conexão com a Internet</string>
<string id="NoResponse">Sem resposta, verifique a conexão com a Internet</string>
<string id="NoMenu">Erro ao buscar menu</string>
<string id="NoAPIKey">Nenhuma chave de API nas configurações do aplicativo</string>
<string id="NoApiUrl">Nenhum URL de API nas configurações do aplicativo</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Oprit</string>
<string id="MenuItemTap">Atingeți</string>
<string id="MenuItemMenu">Meniul</string>
<string id="NoPhone">Fără conexiune telefonică</string>
<string id="NoInternet">Fără conexiune internet</string>
<string id="NoResponse">Niciun răspuns, verificați conexiunea la internet</string>
<string id="NoMenu">Eroare de preluare a meniului</string>
<string id="NoAPIKey">Nicio cheie API în setările aplicației</string>
<string id="NoApiUrl">Nicio adresă URL API în setările aplicației</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Vypnuté</string>
<string id="MenuItemTap">Klepnite</string>
<string id="MenuItemMenu">Ponuka</string>
<string id="NoPhone">Žiadne telefónne spojenie</string>
<string id="NoInternet">Žiadne internetové pripojenie</string>
<string id="NoResponse">Žiadna odpoveď, skontrolujte internetové pripojenie</string>
<string id="NoMenu">Chyba načítania ponuky</string>
<string id="NoAPIKey">V nastaveniach aplikácie nie je žiadny kľúč API</string>
<string id="NoApiUrl">V nastaveniach aplikácie nie je žiadna adresa URL rozhrania API</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Izključeno</string>
<string id="MenuItemTap">Tapnite</string>
<string id="MenuItemMenu">meni</string>
<string id="NoPhone">Ni telefonske povezave</string>
<string id="NoInternet">Ni internetne povezave</string>
<string id="NoResponse">Ni odgovora, preverite internetno povezavo</string>
<string id="NoMenu">Napaka pri pridobivanju menija</string>
<string id="NoAPIKey">V nastavitvah aplikacije ni ključa API</string>
<string id="NoApiUrl">V nastavitvah aplikacije ni URL-ja API-ja</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Apagado</string>
<string id="MenuItemTap">Grifo</string>
<string id="MenuItemMenu">Menú</string>
<string id="NoPhone">Sin conexión telefónica</string>
<string id="NoInternet">Sin conexión a Internet</string>
<string id="NoResponse">No hay respuesta, verifique la conexión a Internet</string>
<string id="NoMenu">Error de recuperación del menú</string>
<string id="NoAPIKey">Sin clave API en la configuración de la aplicación</string>
<string id="NoApiUrl">No hay URL de API en la configuración de la aplicación</string>

View File

@ -24,10 +24,12 @@
<string id="MenuItemOff">Av</string>
<string id="MenuItemTap">Knacka</string>
<string id="MenuItemMenu">Meny</string>
<string id="NoPhone">Ingen telefonanslutning</string>
<string id="NoInternet">Ingen internetanslutning</string>
<string id="NoResponse">Inget svar, kontrollera internetanslutningen</string>
<string id="NoMenu">Menyhämtningsfel</string>
<string id="NoAPIKey">Ingen API-nyckel i applikationsinställningarna</string>
<string id="NoApiUrl">Ingen API-URL i programinställningarna</string>
<string id="NoApiUrl">Ingen API-URL i applikationsinställningarna</string>
<string id="NoConfigUrl">Ingen konfigurations-URL i programinställningarna</string>
<string id="ApiFlood">API-anrop för snabba. Vänligen sakta ner dina förfrågningar.</string>
<string id="ApiUrlNotFound">Webbadressen hittades inte. Potentiellt API-URL-fel i inställningarna.</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">ปิด</string>
<string id="MenuItemTap">แตะ</string>
<string id="MenuItemMenu">เมนู</string>
<string id="NoPhone">ไม่มีการเชื่อมต่อโทรศัพท์</string>
<string id="NoInternet">ไม่มีการเชื่อมต่ออินเทอร์เน็ต</string>
<string id="NoResponse">ไม่มีการตอบสนอง ตรวจสอบการเชื่อมต่ออินเทอร์เน็ต</string>
<string id="NoMenu">เมนูดึงข้อมูลผิดพลาด</string>
<string id="NoAPIKey">ไม่มีคีย์ API ในการตั้งค่าแอปพลิเคชัน</string>
<string id="NoApiUrl">ไม่มี URL API ในการตั้งค่าแอปพลิเคชัน</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Kapalı</string>
<string id="MenuItemTap">Musluk</string>
<string id="MenuItemMenu">Menü</string>
<string id="NoPhone">Telefon bağlantısı yok</string>
<string id="NoInternet">İnternet bağlantısı yok</string>
<string id="NoResponse">Yanıt Yok, İnternet bağlantısını kontrol edin</string>
<string id="NoMenu">Menü Alma Hatası</string>
<string id="NoAPIKey">Uygulama ayarlarında API anahtarı yok</string>
<string id="NoApiUrl">Uygulama ayarlarında API URL'si yok</string>
@ -33,5 +35,5 @@
<string id="ApiUrlNotFound">URL bulunamadı. Ayarlarda olası API URL hatası.</string>
<string id="ConfigUrlNotFound">URL bulunamadı. Ayarlarda Olası Yapılandırma URL'si hatası.</string>
<string id="UnhandledHttpErr">HTTP isteği hata kodunu döndürdü =</string>
<string id="TrailingSlashErr">API URL'sinin sonunda '/' eğik çizgi olmamalıdır</string>
<string id="TrailingSlashErr">API URL'sinin sonunda eğik çizgi '/' olmamalıdır</string>
</strings>

View File

@ -15,7 +15,7 @@
<!--
Generated by Google Translate: English to Ukrainian
Створено Google Translate з англійської
Згенеровано Google Translate з англійської
-->
<strings>
@ -24,7 +24,9 @@
<string id="MenuItemOff">Вимкнено</string>
<string id="MenuItemTap">Торкніться</string>
<string id="MenuItemMenu">Меню</string>
<string id="NoPhone">Немає телефонного зв'язку</string>
<string id="NoInternet">Немає підключення до Інтернету</string>
<string id="NoResponse">Немає відповіді, перевірте підключення до Інтернету</string>
<string id="NoMenu">Помилка вибірки меню</string>
<string id="NoAPIKey">У налаштуваннях програми немає ключа API</string>
<string id="NoApiUrl">У налаштуваннях програми немає URL-адреси API</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Tắt</string>
<string id="MenuItemTap">Vỗ nhẹ</string>
<string id="MenuItemMenu">Thực đơn</string>
<string id="NoPhone">Không có kết nối điện thoại</string>
<string id="NoInternet">Không có kết nối Internet</string>
<string id="NoResponse">Không có phản hồi, kiểm tra kết nối Internet</string>
<string id="NoMenu">Lỗi tìm nạp menu</string>
<string id="NoAPIKey">Không có khóa API trong cài đặt ứng dụng</string>
<string id="NoApiUrl">Không có URL API trong cài đặt ứng dụng</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">离开</string>
<string id="MenuItemTap">轻敲</string>
<string id="MenuItemMenu">菜单</string>
<string id="NoPhone">没有电话连接</string>
<string id="NoInternet">没有网络连接</string>
<string id="NoResponse">无响应,请检查互联网连接</string>
<string id="NoMenu">菜单获取错误</string>
<string id="NoAPIKey">应用程序设置中没有 API 密钥</string>
<string id="NoApiUrl">应用程序设置中没有 API URL</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">離開</string>
<string id="MenuItemTap">輕敲</string>
<string id="MenuItemMenu">選單</string>
<string id="NoPhone">沒有電話連接</string>
<string id="NoInternet">沒有網路連線</string>
<string id="NoResponse">無響應,請檢查互聯網連接</string>
<string id="NoMenu">選單取得錯誤</string>
<string id="NoAPIKey">應用程式設定中沒有 API 金鑰</string>
<string id="NoApiUrl">應用程式設定中沒有 API URL</string>

View File

@ -24,7 +24,9 @@
<string id="MenuItemOff">Mati</string>
<string id="MenuItemTap">Ketik</string>
<string id="MenuItemMenu">Menu</string>
<string id="NoPhone">Tiada sambungan Telefon</string>
<string id="NoInternet">Tiada sambungan internet</string>
<string id="NoResponse">Tiada Respons, semak sambungan Internet</string>
<string id="NoMenu">Ralat Pengambilan Menu</string>
<string id="NoAPIKey">Tiada kunci API dalam tetapan aplikasi</string>
<string id="NoApiUrl">Tiada URL API dalam tetapan aplikasi</string>

View File

@ -23,4 +23,8 @@
<!-- Best be a public URL in order to work away from your home LAN and have a trusted HTTPS certificate -->
<property id="config_url" type="string"></property>
<property id="types_representation" type="boolean"></property>
<property id="menu_alignment" type="boolean"></property>
</properties>

View File

@ -42,4 +42,22 @@
type="alphaNumeric"
/>
</setting>
<setting
propertyKey="@Properties.types_representation"
title="Representing types with icons (off) or with labels (on)"
>
<settingConfig
type="boolean"
/>
</setting>
<setting
propertyKey="@Properties.menu_alignment"
title="Left (off) or Right (on) Menu Alignment"
>
<settingConfig
type="boolean"
/>
</setting>
</settings>

View File

@ -18,7 +18,9 @@
<string id="MenuItemOff">Off</string>
<string id="MenuItemTap">Tap</string>
<string id="MenuItemMenu">Menu</string>
<string id="NoPhone">No Phone connection</string>
<string id="NoInternet">No Internet connection</string>
<string id="NoResponse">No Response, check Internet connection</string>
<string id="NoMenu">Menu Fetch Error</string>
<string id="NoAPIKey">No API key in the application settings</string>
<string id="NoApiUrl">No API URL in the application settings</string>

View File

@ -30,12 +30,12 @@ using Toybox.Timer;
const bRadius = 10;
class Alert extends WatchUi.View {
hidden var mTimer;
hidden var mTimeout;
hidden var mText;
hidden var mFont;
hidden var mFgcolor;
hidden var mBgcolor;
private var mTimer;
private var mTimeout;
private var mText;
private var mFont;
private var mFgcolor;
private var mBgcolor;
function initialize(params as Lang.Dictionary) {
View.initialize();

View File

@ -26,14 +26,14 @@ using Toybox.WatchUi;
using Toybox.Communications;
class ErrorView extends ScalableView {
hidden const cSettings as Lang.Dictionary = {
private const cSettings as Lang.Dictionary = {
:errorIconMargin => 7f
};
// Vertical spacing between the top of the face and the error icon
hidden var mErrorIconMargin;
hidden var mText as Lang.String;
hidden var mErrorIcon;
hidden var mTextArea;
private var mErrorIconMargin;
private var mText as Lang.String;
private var mErrorIcon;
private var mTextArea;
function initialize(text as Lang.String) {
ScalableView.initialize();

View File

@ -22,28 +22,31 @@ using Toybox.Application;
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Application.Properties;
using Toybox.Timer;
class HomeAssistantApp extends Application.AppBase {
hidden var mHaMenu;
hidden var strNoApiKey as Lang.String;
hidden var strNoApiUrl as Lang.String;
hidden var strNoConfigUrl as Lang.String;
hidden var strNoInternet as Lang.String;
hidden var strNoMenu as Lang.String;
hidden var strApiFlood as Lang.String;
hidden var strConfigUrlNotFound as Lang.String;
hidden var strUnhandledHttpErr as Lang.String;
hidden var strTrailingSlashErr as Lang.String;
hidden var mItemsToUpdate; // Array initialised by onReturnFetchMenuConfig()
hidden var mNextItemToUpdate = 0; // Index into the above array
private var mHaMenu;
private var strNoApiKey as Lang.String;
private var strNoApiUrl as Lang.String;
private var strNoConfigUrl as Lang.String;
private var strNoPhone as Lang.String;
private var strNoInternet as Lang.String;
private var strNoResponse as Lang.String;
private var strNoMenu as Lang.String;
private var strApiFlood as Lang.String;
private var strConfigUrlNotFound as Lang.String;
private var strUnhandledHttpErr as Lang.String;
private var strTrailingSlashErr as Lang.String;
private var mItemsToUpdate; // Array initialised by onReturnFetchMenuConfig()
private var mNextItemToUpdate = 0; // Index into the above array
function initialize() {
AppBase.initialize();
strNoApiKey = WatchUi.loadResource($.Rez.Strings.NoAPIKey);
strNoApiUrl = WatchUi.loadResource($.Rez.Strings.NoApiUrl);
strNoConfigUrl = WatchUi.loadResource($.Rez.Strings.NoConfigUrl);
strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone);
strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet);
strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse);
strNoMenu = WatchUi.loadResource($.Rez.Strings.NoMenu);
strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood);
strConfigUrlNotFound = WatchUi.loadResource($.Rez.Strings.ConfigUrlNotFound);
@ -64,32 +67,37 @@ class HomeAssistantApp extends Application.AppBase {
if ((Properties.getValue("api_key") as Lang.String).length() == 0) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem execScript(): No API key in the application settings.");
System.println("HomeAssistantApp getInitialView(): No API key in the application settings.");
}
return [new ErrorView(strNoApiKey + "."), new ErrorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else if (api_url.length() == 0) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem execScript(): No API URL in the application settings.");
System.println("HomeAssistantApp getInitialView(): No API URL in the application settings.");
}
return [new ErrorView(strNoApiUrl + "."), new ErrorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else if (api_url.substring(-1, api_url.length()).equals("/")) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem execScript(): API URL must not have a trailing slash '/'.");
System.println("HomeAssistantApp getInitialView(): API URL must not have a trailing slash '/'.");
}
return [new ErrorView(strTrailingSlashErr + "."), new ErrorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else if ((Properties.getValue("config_url") as Lang.String).length() == 0) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem execScript(): No configuration URL in the application settings.");
System.println("HomeAssistantApp getInitialView(): No configuration URL in the application settings.");
}
return [new ErrorView(strNoConfigUrl + "."), new ErrorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else if (System.getDeviceSettings().phoneConnected && System.getDeviceSettings().connectionAvailable) {
fetchMenuConfig();
return [new WatchUi.View(), new WatchUi.BehaviorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else {
} else if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantApp fetchMenuConfig(): No Phone connection, skipping API call.");
}
return [new ErrorView(strNoPhone + "."), new ErrorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantApp fetchMenuConfig(): No Internet connection, skipping API call.");
}
return [new ErrorView(strNoInternet + "."), new ErrorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
} else {
fetchMenuConfig();
return [new WatchUi.View(), new WatchUi.BehaviorDelegate()] as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>;
}
}
@ -100,15 +108,24 @@ class HomeAssistantApp extends Application.AppBase {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: " + responseCode);
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Data: " + data);
}
if (responseCode == Communications.BLE_QUEUE_FULL) {
if (responseCode == Communications.BLE_HOST_TIMEOUT || responseCode == Communications.BLE_CONNECTION_UNAVAILABLE) {
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == Communications.BLE_QUEUE_FULL) {
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
var cw = WatchUi.getCurrentView();
if (!(cw[0] instanceof ErrorView)) {
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
} else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) {
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
WatchUi.pushView(new ErrorView(strNoResponse), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == 404) {
if (Globals.scDebug) {
System.println("HomeAssistantApp onReturnFetchMenuConfig() Response Code: 404, page not found. Check Configuration URL setting.");
@ -138,7 +155,7 @@ class HomeAssistantApp extends Application.AppBase {
function fetchMenuConfig() as Void {
var options = {
:method => Communications.HTTP_REQUEST_METHOD_GET,
:method => Communications.HTTP_REQUEST_METHOD_GET,
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
};
Communications.makeWebRequest(

View File

@ -0,0 +1,58 @@
//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant.
//
// P A Abbey & J D Abbey, 31 October 2023
//
//
// Description:
//
// Menu button that triggers a service.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Graphics;
using Toybox.Application.Properties;
class HomeAssistantIconMenuItem extends WatchUi.IconMenuItem {
private var mHomeAssistantService as HomeAssistantService;
private var mService as Lang.String;
function initialize(
label as Lang.String or Lang.Symbol,
subLabel as Lang.String or Lang.Symbol or Null,
identifier as Lang.Object or Null,
service as Lang.String or Null,
icon as Graphics.BitmapType or WatchUi.Drawable,
options as {
:alignment as WatchUi.MenuItem.Alignment
} or Null,
haService as HomeAssistantService
) {
WatchUi.IconMenuItem.initialize(
label,
subLabel,
identifier,
icon,
options
);
mHomeAssistantService = haService;
mIdentifier = identifier;
mService = service;
}
function callService() as Void {
mHomeAssistantService.call(mIdentifier as Lang.String, mService);
}
}

View File

@ -24,143 +24,33 @@ using Toybox.Graphics;
using Toybox.Application.Properties;
class HomeAssistantMenuItem extends WatchUi.MenuItem {
hidden var mApiKey as Lang.String;
hidden var strNoInternet as Lang.String;
hidden var strApiFlood as Lang.String;
hidden var strApiUrlNotFound as Lang.String;
hidden var strUnhandledHttpErr as Lang.String;
hidden var mService as Lang.String;
private var mHomeAssistantService as HomeAssistantService;
private var mService as Lang.String;
function initialize(
label as Lang.String or Lang.Symbol,
subLabel as Lang.String or Lang.Symbol or Null,
label as Lang.String or Lang.Symbol,
subLabel as Lang.String or Lang.Symbol or Null,
identifier as Lang.Object or Null,
service as Lang.String or Null,
options as {
service as Lang.String or Null,
options as {
:alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
} or Null
} or Null,
haService as HomeAssistantService
) {
strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet);
strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood);
strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound);
strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr);
mApiKey = Properties.getValue("api_key");
mService = service;
WatchUi.MenuItem.initialize(
label,
subLabel,
identifier,
options
);
mHomeAssistantService = haService;
mService = service;
}
// Callback function after completing the POST request to call a script.
//
function onReturnExecScript(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String) as Void {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem onReturnExecScript() Response Code: " + responseCode);
System.println("HomeAssistantMenuItem onReturnExecScript() Response Data: " + data);
}
if (responseCode == Communications.BLE_QUEUE_FULL) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem onReturnExecScript() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
var cw = WatchUi.getCurrentView();
if (!(cw[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
} else if (responseCode == 404) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem onReturnExecScript() Response Code: 404, page not found. Check API URL setting.");
}
WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == 200) {
var d = data as Lang.Array;
for(var i = 0; i < d.size(); i++) {
if ((d[i].get("entity_id") as Lang.String).equals(mIdentifier)) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem onReturnExecScript(): Correct script executed.");
}
if (WatchUi has :showToast) {
WatchUi.showToast(
(d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String,
null
);
}
if (Attention has :vibrate) {
Attention.vibrate([
new Attention.VibeProfile(50, 100), // On for 100ms
new Attention.VibeProfile( 0, 100), // Off for 100ms
new Attention.VibeProfile(50, 100) // On for 100ms
]);
}
if (!(WatchUi has :showToast) && !(Attention has :vibrate)) {
new Alert({
:timeout => Globals.scAlertTimeout,
:font => Graphics.FONT_MEDIUM,
:text => (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String,
:fgcolor => Graphics.COLOR_WHITE,
:bgcolor => Graphics.COLOR_BLACK
}).pushView(WatchUi.SLIDE_IMMEDIATE);
}
}
}
} else {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem onReturnExecScript(): Unhandled HTTP response code = " + responseCode);
}
WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
}
function execScript() as Void {
var options = {
:method => Communications.HTTP_REQUEST_METHOD_POST,
:headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON,
"Authorization" => "Bearer " + mApiKey
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
};
if (System.getDeviceSettings().phoneConnected && System.getDeviceSettings().connectionAvailable) {
// Updated SDK and got a new error
// ERROR: venu: Cannot find symbol ':substring' on type 'PolyType<Null or $.Toybox.Lang.Object>'.
var id = mIdentifier as Lang.String;
if (mService == null) {
var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + id.substring(0, id.find(".")) + "/" + id.substring(id.find(".")+1, id.length());
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem execScript() URL=" + url);
System.println("HomeAssistantMenuItem execScript() mIdentifier=" + mIdentifier);
}
Communications.makeWebRequest(
url,
null,
options,
method(:onReturnExecScript)
);
} else {
var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + mService.substring(0, mService.find(".")) + "/" + mService.substring(mService.find(".")+1, null);
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem execScript() URL=" + url);
System.println("HomeAssistantMenuItem execScript() mIdentifier=" + mIdentifier);
}
Communications.makeWebRequest(
url,
{
"entity_id" => id
},
options,
method(:onReturnExecScript)
);
}
} else {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem execScript(): No Internet connection, skipping API call.");
}
WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
function callService() as Void {
mHomeAssistantService.call(mIdentifier as Lang.String, mService);
}
}

View File

@ -0,0 +1,122 @@
//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant.
//
// P A Abbey & J D Abbey & SomeoneOnEarth, 17 November 2023
//
//
// Description:
//
// MenuItems Factory.
//
//-----------------------------------------------------------------------------------
using Toybox.Application;
using Toybox.Lang;
using Toybox.WatchUi;
class HomeAssistantMenuItemFactory {
private var mMenuItemOptions as Lang.Dictionary;
private var mLabelToggle as Lang.Dictionary;
private var strMenuItemTap as Lang.String;
private var bRepresentTypesWithLabels as Lang.Boolean;
private var mTapTypeIcon as WatchUi.Bitmap;
private var mGroupTypeIcon as WatchUi.Bitmap;
private var mHomeAssistantService as HomeAssistantService;
private static var instance;
private function initialize() {
mLabelToggle = {
:enabled => WatchUi.loadResource($.Rez.Strings.MenuItemOn) as Lang.String,
:disabled => WatchUi.loadResource($.Rez.Strings.MenuItemOff) as Lang.String
};
bRepresentTypesWithLabels = Application.Properties.getValue("types_representation") as Lang.Boolean;
var menuItemAlignment = Application.Properties.getValue("menu_alignment") as Lang.Boolean;
if(menuItemAlignment){
mMenuItemOptions = {
:alignment => WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_RIGHT
};
} else {
mMenuItemOptions = {
:alignment => WatchUi.MenuItem.MENU_ITEM_LABEL_ALIGN_LEFT
};
}
strMenuItemTap = WatchUi.loadResource($.Rez.Strings.MenuItemTap);
mTapTypeIcon = new WatchUi.Bitmap({
:rezId => $.Rez.Drawables.TapTypeIcon,
:locX => WatchUi.LAYOUT_HALIGN_CENTER,
:locY => WatchUi.LAYOUT_VALIGN_CENTER
});
mGroupTypeIcon = new WatchUi.Bitmap({
:rezId => $.Rez.Drawables.GroupTypeIcon,
:locX => WatchUi.LAYOUT_HALIGN_CENTER,
:locY => WatchUi.LAYOUT_VALIGN_CENTER
});
mHomeAssistantService = new HomeAssistantService();
}
static function create() as HomeAssistantMenuItemFactory {
if (instance == null) {
instance = new HomeAssistantMenuItemFactory();
}
return instance;
}
function toggle(label as Lang.String or Lang.Symbol, identifier as Lang.Object or Null) as WatchUi.MenuItem {
var subLabel = null;
if (bRepresentTypesWithLabels == true){
subLabel=mLabelToggle;
}
return new HomeAssistantToggleMenuItem(
label,
subLabel,
identifier,
false,
mMenuItemOptions
);
}
function tap(label as Lang.String or Lang.Symbol, identifier as Lang.Object or Null, service as Lang.String or Null) as WatchUi.MenuItem {
if (bRepresentTypesWithLabels) {
return new HomeAssistantMenuItem(
label,
strMenuItemTap,
identifier,
service,
mMenuItemOptions,
mHomeAssistantService
);
} else {
return new HomeAssistantIconMenuItem(
label,
null,
identifier,
service,
mTapTypeIcon,
mMenuItemOptions,
mHomeAssistantService
);
}
}
function group(definition as Lang.Dictionary) as WatchUi.MenuItem {
if (bRepresentTypesWithLabels) {
return new HomeAssistantViewMenuItem(definition);
} else {
return new HomeAssistantViewIconMenuItem(definition, mGroupTypeIcon, mMenuItemOptions);
}
}
}

View File

@ -0,0 +1,150 @@
//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant.
//
// P A Abbey & J D Abbey & SomeoneOnEarth, 19 November 2023
//
//
// Description:
//
// Calling a Home Assistant Service.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Graphics;
using Toybox.Application.Properties;
class HomeAssistantService {
private var mApiKey as Lang.String;
private var strNoPhone as Lang.String;
private var strNoInternet as Lang.String;
private var strNoResponse as Lang.String;
private var strApiFlood as Lang.String;
private var strApiUrlNotFound as Lang.String;
private var strUnhandledHttpErr as Lang.String;
function initialize() {
strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone);
strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet);
strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse);
strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood);
strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound);
strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr);
mApiKey = Properties.getValue("api_key");
}
// Callback function after completing the POST request to call a service.
//
function onReturnCall(responseCode as Lang.Number, data as Null or Lang.Dictionary or Lang.String, context as Lang.Object) as Void {
var identifier = context as Lang.String;
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: " + responseCode);
System.println("HomeAssistantService onReturnCall() Response Data: " + data);
}
if (responseCode == Communications.BLE_HOST_TIMEOUT || responseCode == Communications.BLE_CONNECTION_UNAVAILABLE) {
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == Communications.BLE_QUEUE_FULL) {
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
} else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) {
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
WatchUi.pushView(new ErrorView(strNoResponse), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == 404) {
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall() Response Code: 404, page not found. Check API URL setting.");
}
WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == 200) {
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall(): Service executed.");
}
var d = data as Lang.Array;
var toast = "Executed";
for(var i = 0; i < d.size(); i++) {
if ((d[i].get("entity_id") as Lang.String).equals(identifier)) {
toast = (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String;
}
}
if (WatchUi has :showToast) {
WatchUi.showToast(toast, null);
} else {
new Alert({
:timeout => Globals.scAlertTimeout,
:font => Graphics.FONT_MEDIUM,
:text => toast,
:fgcolor => Graphics.COLOR_WHITE,
:bgcolor => Graphics.COLOR_BLACK
}).pushView(WatchUi.SLIDE_IMMEDIATE);
}
} else {
if (Globals.scDebug) {
System.println("HomeAssistantService onReturnCall(): Unhandled HTTP response code = " + responseCode);
}
WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
}
function call(identifier as Lang.String, service as Lang.String) as Void {
var options = {
:method => Communications.HTTP_REQUEST_METHOD_POST,
:headers => {
"Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON,
"Authorization" => "Bearer " + mApiKey
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
:context => identifier
};
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantService call(): No Phone connection, skipping API call.");
}
WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantService call(): No Internet connection, skipping API call.");
}
WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else {
var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + service.substring(0, service.find(".")) + "/" + service.substring(service.find(".")+1, null);
if (Globals.scDebug) {
System.println("HomeAssistantService call() URL=" + url);
System.println("HomeAssistantService call() service=" + service);
}
Communications.makeWebRequest(
url,
{
"entity_id" => identifier
},
options,
method(:onReturnCall)
);
if (Attention has :vibrate) {
Attention.vibrate([
new Attention.VibeProfile(50, 100), // On for 100ms
new Attention.VibeProfile( 0, 100), // Off for 100ms
new Attention.VibeProfile(50, 100) // On for 100ms
]);
}
}
}
}

View File

@ -22,28 +22,33 @@ using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Graphics;
using Toybox.Application.Properties;
using Toybox.Timer;
class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
hidden var mApiKey as Lang.String;
hidden var strNoInternet as Lang.String;
hidden var strApiFlood as Lang.String;
hidden var strApiUrlNotFound as Lang.String;
hidden var strUnhandledHttpErr as Lang.String;
private var mApiKey as Lang.String;
private var strNoPhone as Lang.String;
private var strNoInternet as Lang.String;
private var strNoResponse as Lang.String;
private var strApiFlood as Lang.String;
private var strApiUrlNotFound as Lang.String;
private var strUnhandledHttpErr as Lang.String;
function initialize(
label as Lang.String or Lang.Symbol,
subLabel as Lang.String or Lang.Symbol or {
label as Lang.String or Lang.Symbol,
subLabel as Lang.String or Lang.Symbol or {
:enabled as Lang.String or Lang.Symbol or Null,
:disabled as Lang.String or Lang.Symbol or Null
} or Null,
identifier,
enabled as Lang.Boolean,
options as {
enabled as Lang.Boolean,
options as {
:alignment as WatchUi.MenuItem.Alignment,
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
:icon as Graphics.BitmapType or WatchUi.Drawable or Lang.Symbol
} or Null
) {
strNoPhone = WatchUi.loadResource($.Rez.Strings.NoPhone);
strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet);
strNoResponse = WatchUi.loadResource($.Rez.Strings.NoResponse);
strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood);
strApiUrlNotFound = WatchUi.loadResource($.Rez.Strings.ApiUrlNotFound);
strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr);
@ -70,26 +75,41 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: " + responseCode);
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Data: " + data);
}
if (responseCode == Communications.BLE_QUEUE_FULL) {
// Provide the ability to terminate updating chain of calls for a permanent network error.
var keepUpdating = true;
if (responseCode == Communications.BLE_HOST_TIMEOUT || responseCode == Communications.BLE_CONNECTION_UNAVAILABLE) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
} else if (responseCode == Communications.BLE_QUEUE_FULL) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
var cw = WatchUi.getCurrentView();
if (!(cw[0] instanceof ErrorView)) {
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
getApp().updateNextMenuItem();
} else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strNoResponse), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
} else if (responseCode == 404) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState() Response Code: 404, page not found. Check API URL setting.");
}
var cw = WatchUi.getCurrentView();
if (!(cw[0] instanceof ErrorView)) {
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
keepUpdating = false;
} else if (responseCode == 200) {
var state = data.get("state") as Lang.String;
if (Globals.scDebug) {
@ -99,13 +119,18 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String);
}
setUiToggle(state);
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
getApp().updateNextMenuItem();
} else {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode);
}
WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP);
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
}
if (keepUpdating) {
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
getApp().updateNextMenuItem();
}
}
@ -117,7 +142,24 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
};
if (System.getDeviceSettings().phoneConnected && System.getDeviceSettings().connectionAvailable) {
var keepUpdating = true;
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
}
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
if (!(WatchUi.getCurrentView()[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
} else {
var url = Properties.getValue("api_url") + "/states/" + mIdentifier;
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState() URL=" + url);
@ -128,11 +170,17 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
options,
method(:onReturnGetState)
);
} else {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
// The update is called by onReturnGetState() instead
keepUpdating = false;
}
// On temporary failure, keep the updating going.
if (keepUpdating) {
// Need to avoid an infinite loop where the pushed ErrorView does not appear before getState() is called again
// and the call stack overflows. So continue the call chain from somewhere asynchronous.
var myTimer = new Timer.Timer();
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
myTimer.start(getApp().method(:updateNextMenuItem), 500, false);
System.println("HomeAssistantToggleMenuItem getState(): Updated failed " + mIdentifier);
}
}
@ -143,20 +191,26 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: " + responseCode);
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Data: " + data);
}
if (responseCode == Communications.BLE_QUEUE_FULL) {
if (responseCode == Communications.BLE_HOST_TIMEOUT || responseCode == Communications.BLE_CONNECTION_UNAVAILABLE) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_HOST_TIMEOUT or BLE_CONNECTION_UNAVAILABLE, Bluetooth connection severed.");
}
WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == Communications.BLE_QUEUE_FULL) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: BLE_QUEUE_FULL, API calls too rapid.");
}
WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: NETWORK_REQUEST_TIMED_OUT, check Internet connection.");
}
WatchUi.pushView(new ErrorView(strNoResponse), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == 404) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState() Response Code: 404, page not found. Check API URL setting.");
}
var cw = WatchUi.getCurrentView();
if (!(cw[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == 200) {
var state;
var d = data as Lang.Array;
@ -186,7 +240,21 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
},
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
};
if (System.getDeviceSettings().phoneConnected && System.getDeviceSettings().connectionAvailable) {
if (! System.getDeviceSettings().phoneConnected) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Phone connection, skipping API call.");
}
// Toggle the UI back
setEnabled(!isEnabled());
WatchUi.pushView(new ErrorView(strNoPhone + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (! System.getDeviceSettings().connectionAvailable) {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem getState(): No Internet connection, skipping API call.");
}
// Toggle the UI back
setEnabled(!isEnabled());
WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else {
// Updated SDK and got a new error
// ERROR: venu: Cannot find symbol ':substring' on type 'PolyType<Null or $.Toybox.Lang.Object>'.
var id = mIdentifier as Lang.String;
@ -208,11 +276,6 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
options,
method(:onReturnSetState)
);
} else {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem setState(): No Internet connection, skipping API call.");
}
WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
}

View File

@ -18,15 +18,15 @@
//
//-----------------------------------------------------------------------------------
using Toybox.Application;
using Toybox.Lang;
using Toybox.Graphics;
using Toybox.WatchUi;
class HomeAssistantView extends WatchUi.Menu2 {
hidden var strMenuItemTap as Lang.String;
// List of items that need to have their status updated periodically
hidden var mListToggleItems = [];
hidden var mListMenuItems = [];
private var mListToggleItems = [];
private var mListMenuItems = [];
function initialize(
definition as Lang.Dictionary,
@ -36,11 +36,6 @@ class HomeAssistantView extends WatchUi.Menu2 {
:theme as WatchUi.MenuTheme or Null
} or Null
) {
strMenuItemTap = WatchUi.loadResource($.Rez.Strings.MenuItemTap);
var toggle_obj = {
:enabled => WatchUi.loadResource($.Rez.Strings.MenuItemOn) as Lang.String,
:disabled => WatchUi.loadResource($.Rez.Strings.MenuItemOff) as Lang.String
};
if (options == null) {
options = {
@ -59,27 +54,13 @@ class HomeAssistantView extends WatchUi.Menu2 {
var service = items[i].get("service") as Lang.String or Null;
if (type != null && name != null && entity != null) {
if (type.equals("toggle")) {
var item = new HomeAssistantToggleMenuItem(
name,
toggle_obj,
entity,
false,
null
);
var item = HomeAssistantMenuItemFactory.create().toggle(name, entity);
addItem(item);
mListToggleItems.add(item);
} else if (type.equals("tap")) {
addItem(
new HomeAssistantMenuItem(
name,
strMenuItemTap,
entity,
service,
null
)
);
} else if (type.equals("tap") && service != null) {
addItem( HomeAssistantMenuItemFactory.create().tap(name, entity, service));
} else if (type.equals("group")) {
var item = new HomeAssistantViewMenuItem(items[i]);
var item = HomeAssistantMenuItemFactory.create().group(items[i]);
addItem(item);
mListMenuItems.add(item);
}
@ -89,10 +70,15 @@ class HomeAssistantView extends WatchUi.Menu2 {
function getItemsToUpdate() as Lang.Array<HomeAssistantToggleMenuItem> {
var fullList = [];
var lmi = mListMenuItems as Lang.Array<HomeAssistantViewMenuItem>;
for(var i = 0; i < lmi.size(); i++) {
fullList.addAll(lmi[i].getMenuView().getItemsToUpdate());
var lmi = mListMenuItems as Lang.Array<WatchUi.MenuItem>;
for(var i = 0; i < mListMenuItems.size(); i++) {
var item = lmi[i];
if (item instanceof HomeAssistantViewMenuItem || item instanceof HomeAssistantViewIconMenuItem) {
fullList.addAll(item.getMenuView().getItemsToUpdate());
}
}
return fullList.addAll(mListToggleItems);
}
@ -124,7 +110,13 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
if (Globals.scDebug) {
System.println(haItem.getLabel() + " " + haItem.getId());
}
haItem.execScript();
haItem.callService();
} else if (item instanceof HomeAssistantIconMenuItem) {
var haItem = item as HomeAssistantIconMenuItem;
if (Globals.scDebug) {
System.println(haItem.getLabel() + " " + haItem.getId());
}
haItem.callService();
} else if (item instanceof HomeAssistantViewMenuItem) {
var haMenuItem = item as HomeAssistantViewMenuItem;
if (Globals.scDebug) {
@ -132,6 +124,13 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
}
// No delegate state to be amended, so re-use 'self'.
WatchUi.pushView(haMenuItem.getMenuView(), self, WatchUi.SLIDE_LEFT);
} else if (item instanceof HomeAssistantViewIconMenuItem) {
var haMenuItem = item as HomeAssistantViewIconMenuItem;
if (Globals.scDebug) {
System.println("IconMenu: " + haMenuItem.getLabel() + " " + haMenuItem.getId());
}
// No delegate state to be amended, so re-use 'self'.
WatchUi.pushView(haMenuItem.getMenuView(), self, WatchUi.SLIDE_LEFT);
} else {
if (Globals.scDebug) {
System.println(item.getLabel() + " " + item.getId());
@ -143,4 +142,4 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
WatchUi.popView(WatchUi.SLIDE_RIGHT);
}
}
}

View File

@ -0,0 +1,48 @@
//-----------------------------------------------------------------------------------
//
// Distributed under MIT Licence
// See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
//
//-----------------------------------------------------------------------------------
//
// GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
// tested on a Venu 2 device. The source code is provided at:
// https://github.com/house-of-abbey/GarminHomeAssistant.
//
// P A Abbey & J D Abbey & SomeoneOnEarth, 16 November 2023
//
//
// Description:
//
// Menu button with an icon that opens a sub-menu.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.WatchUi;
class HomeAssistantViewIconMenuItem extends WatchUi.IconMenuItem {
private var mMenu as HomeAssistantView;
function initialize(definition as Lang.Dictionary, icon as WatchUi.Drawable, options as {
:alignment as WatchUi.MenuItem.Alignment
} or Null) {
var label = definition.get("name") as Lang.String;
var identifier = definition.get("entity") as Lang.String;
WatchUi.IconMenuItem.initialize(
label,
null,
identifier,
icon,
options
);
mMenu = new HomeAssistantView(definition, null);
}
function getMenuView() as HomeAssistantView {
return mMenu;
}
}

View File

@ -22,7 +22,7 @@ using Toybox.Lang;
using Toybox.WatchUi;
class HomeAssistantViewMenuItem extends WatchUi.MenuItem {
hidden var mMenu as HomeAssistantView;
private var mMenu as HomeAssistantView;
function initialize(definition as Lang.Dictionary) {
// definitions.get(...) are Strings here as they have been checked by HomeAssistantView first

View File

@ -23,7 +23,7 @@ using Toybox.WatchUi;
using Toybox.Math;
class ScalableView extends WatchUi.View {
hidden var mScreenWidth;
private var mScreenWidth;
function initialize() {
View.initialize();

View File

@ -18,7 +18,7 @@
# language using Google Translate.
#
# Python installation:
# pip install BeautifulSoup
# pip install beautifulsoup4
# pip install deep-translator
# NB. For XML formatting:
# pip install lxml
@ -81,12 +81,18 @@ languages: list[tuple[str, str, str]] = [
langLength = len(languages)
exceptionIds: list[str] = ["AppName", "AppVersionTitle"]
titleIds: list[str] = ["setMode", "tapIcon"]
titleIds: list[str] = []
i = 1
with open("./resources/strings/strings.xml") as f:
with open("./resources/strings/strings.xml", "r") as f:
c = f.read().replace('\r', '')
for l in languages:
os.makedirs(f"./resources-{l[0]}/strings/", exist_ok=True)
try:
with open(f"./resources-{l[0]}/strings/corrections.xml", "r") as r:
curr = BeautifulSoup(r.read().replace('\r', ''), features="xml")
except FileNotFoundError:
curr = BeautifulSoup("", features=["xml"])
print(f"{i} of {langLength}: Translating English to {l[2]}")
soup = BeautifulSoup(c, features="xml")
soup.find(name="strings").insert_before("\n\n")
@ -99,7 +105,12 @@ with open("./resources/strings/strings.xml") as f:
for s in soup.find(name="strings").findAll(name="string"):
s.insert_before(" ")
if s["id"] not in exceptionIds:
if s["id"] in exceptionIds: continue
s_curr = curr.find(name="string", attrs={ "id": s["id"] })
if s_curr:
s.string = s_curr.string
else:
a = GoogleTranslator(source='en', target=l[1]).translate(s.string)
if s["id"] in titleIds:
s.string = a.title()
@ -108,8 +119,8 @@ with open("./resources/strings/strings.xml") as f:
for s in soup.find(name="strings").findAll(text=lambda text:isinstance(text, Comment)):
s.insert_before(" ")
s.replace_with(Comment(" " + GoogleTranslator(source='en', target=l[1]).translate(s) + " "))
#print(str(soup))
os.makedirs(f"./resources-{l[0]}/strings/", exist_ok=True)
with open(f"./resources-{l[0]}/strings/strings.xml", "wb") as w:
w.write(soup.encode("utf-8"))
i += 1