Compare commits

...

67 Commits
v1.1 ... v1.6

Author SHA1 Message Date
44d5bf8e93 Update README.md 2023-12-06 22:56:59 +00:00
fae2241f01 Merge pull request #24 from house-of-abbey/23-prevent-battery-drain-by-quitting-the-app-after-a-timeout
Initial version with "auto quit"
2023-12-06 22:43:42 +00:00
42e89906f6 Review comments 2023-12-06 22:09:22 +00:00
8c0540ee45 Initial version with "auto quit"
Quit the application after a user settable period of time based on a timeout value from the settings.
2023-12-02 19:04:53 +00:00
ea58171b2f Merge pull request #20 from house-of-abbey/19-feature-request-confirm-dialogue 2023-12-01 11:53:17 +00:00
531c3e972e Update config.schema.json
Added missing full stops.
2023-12-01 11:42:11 +00:00
2d07eaa9c6 Update HomeAssistantView.mc
Review comment about a random space.
2023-12-01 11:28:19 +00:00
0e443e33d3 Update config.schema.json
Tidy up of help text
2023-12-01 10:19:49 +00:00
e616a6bd76 Apply automatic changes 2023-12-01 10:10:43 +00:00
239f8a793b Apply automatic changes 2023-12-01 10:08:12 +00:00
fc22ca3497 Apply automatic changes 2023-12-01 10:03:11 +00:00
1df64286f0 Updated README
Also missed schema changes of the previous commit. Added a photo of the confirmation view.
2023-12-01 09:54:23 +00:00
0ab9cb800a Initial solution
For optional confirmation dialogue box.
2023-12-01 09:15:59 +00:00
3fe48be756 Update README.md
Adding some hints to avoid setup errors.
2023-11-30 18:56:42 +00:00
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
3bd5361ad2 API Level reduced
Reduced minimum API Level required from 3.3.0 to 3.1.0 to allow more device “part numbers” to be satisfied.
Fixed an issue with one device's icon size.
2023-11-12 17:36:19 +00:00
d540fb576a Bugfixes
Do not crash on zero items to update
Report unreachable URLs
Verify API URL does not have a trailing slash '/'
Increased HTTP response diagnosis
2023-11-12 16:24:56 +00:00
ba8812a6ab Update strings.xml 2023-11-11 19:43:21 +01:00
101 changed files with 1406 additions and 272 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

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"cSpell.words": [
"usbs",
"Venu"
]
}

View File

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

View File

@ -4,8 +4,7 @@
A Garmin application to provide a "dashboard" to control your devices via [Home Assistant](https://www.home-assistant.io/). The application will never be as fully fledged as a Home Assistant dashboard, so it is designed to be good enough for the simple and essential things. Those things that can be activated via an on/off toggle or a tap. That should cover lights, switches, and anything requiring a single press such as an automation. For anything more complicated, e.g. thermostat, it would always be quicker and simpler to reach for your phone or tablet... or the device's own remote control!
The application is designed around a simple scrollable menu where menu items have been extended to interface with the [Home Assistant API](https://developers.home-assistant.io/docs/api/rest/), e.g. to get the status of switches or lights for display on the toggle menu item. It is possible to nest menus, so there is a menu item to open a sub-menu. This can be
arbitrarily deep and nested in the format of a tree of items, although you need to consider if reaching for your phone becomes quicker to select the device what you want to control.
The application is designed around a simple scrollable menu where menu items have been extended to interface with the [Home Assistant API](https://developers.home-assistant.io/docs/api/rest/), e.g. to get the status of switches or lights for display on the toggle menu item. It is possible to nest menus, so there is a menu item to open a sub-menu. This can be arbitrarily deep and nested in the format of a tree of items, although you need to consider if reaching for your phone becomes quicker to select the device what you want to control.
It is important to note that your Home Assistant instance will need to be accessible via HTTPS with public SSL or all requests from the Garmin will not work. This cannot be a self-signed certificate, it must be a public certificate (You can get one for free from [Let's Encrypt](https://letsencrypt.org/) or you can pay for [Home Assistant cloud](https://www.nabucasa.com/)).
@ -35,7 +34,11 @@ Example schema as shown in the images:
{
"entity": "script.food_on_table",
"name": "Food is Ready!",
"type": "tap"
"type": "tap",
"tap_action": {
"service": "script.turn_on",
"confirm": true
}
},
{
"entity": "light.bedside_light_switch",
@ -80,11 +83,21 @@ 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",
"tap_action": {
"service": "automation.trigger"
}
},
{
"entity": "scene.tv_light",
"name": "TV Lights Scene",
"type": "tap",
"service": "scene.turn_on"
"tap_action": {
"service": "scene.turn_on"
}
}
]
}
@ -100,10 +113,63 @@ 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.
### Old depricated format
Version 1.5 brought in a change to the JSON schema so the follow old format remains useable but is no longer favoured. The schema now marks it as 'depracated' to nudge people over.
```json
{
"entity": "scene.tv_light",
"name": "TV Lights Scene",
"type": "tap",
"service": "scene.turn_on"
}
```
The above should be replaced by the following:
```json
{
"entity": "scene.tv_light",
"name": "TV Lights Scene",
"type": "tap",
"tap_action": {
"service": "scene.turn_on"
}
}
```
This allows the `confirm` field to be accommodated in the `tap_action` along side the `service` tag, and follows the Home Assistant YAML format more closely.
## Editing the JSON file
You have options. The first is what we use.
1. **Best!** Use the [Studio Code Server](https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863) addon for Home Assistant. You can then edit your JSON file in place.
2. Locally installed VSCode, or if not installed
3. try the on-line version at https://vscode.dev/
Paste in your JSON (and change the file type to JSON if not saving), it will then verify your file format and schema for you, highlighting any errors for you to fix.
A failure to get the file format right tends to mean that the response to the application errors with `INVALID_HTTP_BODY_IN_NETWORK_RESPONSE` (code of -400). This means the response did not contain JSON, it was probably an error message in plain text that could not be parsed by the Connect IQ API call. See [Toybox.Communications](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html) for the list of error code you might be present with on your device.
Make sure you can browse to the URL of your JSON file in a standard web browser to make sure it is accessible.
## API Key Creation
Having created your JSON definition for your dashboard, you need to create an API key for your personal account on Home Assistant.
@ -112,12 +178,14 @@ Having created your JSON definition for your dashboard, you need to create an AP
Having created that token, before you dismiss the dialogue box with the value you will never see again, copy it somewhere safe. You need to paste this into the Garmin Application's settings.
**Please, please, please!** Copy and paste this API key, do not retype as it will be wrong.
## Settings
<img src="images/GarminHomeAssistantSettings.png" width="400" title="Application Settings"/>
1. Paste your API key you've just created into the top field.
2. Add the URL for your Home Assistant API, e.g. `https://<homeassistant>/api/`.
2. Add the URL for your Home Assistant API, e.g. `https://<homeassistant>/api`. (No trailing slash `/`` character as one gets appended when creating the URL and you do not want two.)
3. Add the URL of your JSON file, e.g. `https://<homeassistant>/local/garmin/<something>.json`.
You should now have a working application on your watch and be able to operate your Home Assistant devices for as long as your watch is within Bluetooth range of your phone.
@ -150,3 +218,8 @@ 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 apologies to anyone suffering a breakage as a result. |
| 1.5 | <img src="images/confirmation_view.jpg" width="200" title="Confirmation View" style="float:right"/> Added an optional confirmation dialogue view to prevent accidental execution of actions on mistaken tap. This also brings a change in the JSON schema to allow an optional field to specify that the confirmation should be used for a menu item. As we are now maturing and adding features we have decided to mitigate breaking changes to the JSON schema by being more careful to adopt the Home Assistant schema (noting there is a 1:1 mapping between YAML and JSON). This change does deprecate the top level `service` tag in favour of `tag_action` containing multiple fields including `service` & `confirm`. Users should migrate to the new format for the new functionality, but the timescale for actual deprecation are long and undecided. |
| 1.6 | Added a user configurable 'timeout' in seconds so that when no action is taken the application automatically closes, stopping the continuous polling for changes of status and hence saving the drain on the battery. This can be disabled with timeout=0. |

View File

@ -12,8 +12,12 @@
"type": "object",
"properties": {
"entity": { "$ref": "#/$defs/entity" },
"name": { "type": "string" },
"type": { "const": "toggle" }
"name": { "title": "Your familiar name", "type": "string" },
"type": {
"title": "Menu item type",
"description": "One of 'tap, 'toggle' or 'group'.",
"const": "toggle"
}
},
"required": ["entity", "name", "type"],
"additionalProperties": false
@ -22,20 +26,37 @@
"type": "object",
"properties": {
"entity": { "$ref": "#/$defs/entity" },
"name": { "type": "string" },
"type": { "const": "tap" },
"service": { "$ref": "#/$defs/entity" }
"name": { "title": "Your familiar name", "type": "string" },
"type": {
"title": "Menu item type",
"description": "One of 'tap, 'toggle' or 'group'.",
"const": "tap"
},
"service": {
"$ref": "#/$defs/entity",
"deprecated": true,
"title": "Schema change:",
"description": "Use 'tap_action' instead to mirror Home Assistant."
},
"tap_action": { "$ref": "#/$defs/action" }
},
"required": ["entity", "name", "type"],
"oneOf": [
{ "required": ["entity", "name", "type", "service"] },
{ "required": ["entity", "name", "type", "tap_action"] }
],
"additionalProperties": false
},
"menu": {
"type": "object",
"properties": {
"entity": { "$ref": "#/$defs/entity" },
"name": { "type": "string" },
"name": { "title": "Your familiar name", "type": "string" },
"title": { "type": "string" },
"type": { "const": "group" },
"type": {
"title": "Menu item type",
"description": "One of 'tap, 'toggle' or 'group'.",
"const": "group"
},
"items": { "$ref": "#/$defs/items" }
},
"required": ["entity", "name", "title", "type", "items"],
@ -52,8 +73,23 @@
}
},
"entity": {
"title": "Home Assistant entity name",
"type": "string",
"pattern": "^[^.]+\\.[^.]+$"
},
"action": {
"title": "Action",
"description": "'confirm' field is optional.",
"type": "object",
"properties": {
"service": { "$ref": "#/$defs/entity" },
"confirm": {
"type": "boolean",
"default": false,
"description": "Confirm the action before execution as a precaution."
}
},
"required": ["service"]
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -21,7 +21,7 @@
Use "Monkey C: Edit Application" from the Visual Studio Code command palette
to update the application attributes.
-->
<iq:application id="40131e87-31ff-454b-a8e2-92276ee399d6" type="watch-app" name="@Strings.AppName" entry="HomeAssistantApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="3.3.0">
<iq:application id="98c36259-498a-4458-9cef-74a273ad2bc3" type="watch-app" name="@Strings.AppName" entry="HomeAssistantApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="3.1.0">
<!--
Use the following from the Visual Studio Code comand palette to edit
the build targets:
@ -33,7 +33,7 @@
<!--
Device Information & References:
* https://developer.garmin.com/connect-iq/compatible-devices/
* https://developer.garmin.com/connect-iq/reference-guides/devices-reference/#epix%E2%84%A2progen251mmtactix%C2%AE7%E2%80%93amolededition
* https://developer.garmin.com/connect-iq/reference-guides/devices-reference/
-->
<!-- Screen Size 390x390 launcher icon size 70x70 -->
@ -124,6 +124,7 @@
<iq:product id="instinct2s"/>
<!-- Screen Size 176x176 launcher icon size 62x62 -->
<iq:product id="instinct2x"/>
<!-- Screen Size 176x176 launcher icon size 26x26 -->
<iq:product id="instinctcrossover"/>
<!-- Screen Size 218x218 launcher icon size 30x30 -->
<iq:product id="legacyherocaptainmarvel"/>

View File

@ -18,8 +18,9 @@
project.manifest = manifest.xml
# Device Reference
# https://developer.garmin.com/connect-iq/reference-guides/devices-reference/
# Device References
# * https://developer.garmin.com/connect-iq/compatible-devices/
# * https://developer.garmin.com/connect-iq/reference-guides/devices-reference/
#
# Widget launcher icon, multiple resolutions
# https://forums.garmin.com/developer/connect-iq/f/discussion/255433/widget-launcher-icon-multiple-resolutions/1563305
@ -121,7 +122,8 @@ instinct2.resourcePath = $(instinct2.resourcePath);resources-launcher-62-62;reso
instinct2s.resourcePath = $(instinct2s.resourcePath);resources-launcher-54-54;resources-icons-18
# Screen Size 176x176 launcher icon size 62x62
instinct2x.resourcePath = $(instinct2x.resourcePath);resources-launcher-62-62;resources-icons-21
instinctcrossover.resourcePath = $(instinctcrossover.resourcePath);resources-launcher-62-62;resources-icons-21
# Screen Size 176x176 launcher icon size 26x26
instinctcrossover.resourcePath = $(instinctcrossover.resourcePath);resources-launcher-26-26;resources-icons-21
# Screen Size 218x218 launcher icon size 30x30
legacyherocaptainmarvel.resourcePath = $(legacyherocaptainmarvel.resourcePath);resources-launcher-30-30;resources-icons-26
# Screen Size 260x260 launcher icon size 35x35

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">عن</string>
<string id="MenuItemTap">مقبض</string>
<string id="MenuItemMenu">قائمة طعام</string>
<string id="Confirm">بالتأكيد؟</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>
<string id="NoConfigUrl">لا يوجد عنوان URL للتكوين في إعدادات التطبيق</string>
<string id="ApiFlood">مكالمات API سريعة جدًا. يرجى إبطاء طلباتك.</string>
<string id="ApiUrlNotFound">لم يتم العثور على عنوان URL. خطأ محتمل في عنوان URL لواجهة برمجة التطبيقات في الإعدادات.</string>
<string id="ConfigUrlNotFound">لم يتم العثور على عنوان URL. خطأ محتمل في عنوان URL للتكوين في الإعدادات.</string>
<string id="UnhandledHttpErr">قام طلب HTTP بإرجاع رمز الخطأ =</string>
<string id="TrailingSlashErr">يجب ألا يحتوي عنوان URL لواجهة برمجة التطبيقات على شرطة مائلة لاحقة '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Изкл</string>
<string id="MenuItemTap">Докоснете</string>
<string id="MenuItemMenu">Меню</string>
<string id="Confirm">Сигурен?</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="NoConfigUrl">Няма конфигурационен URL адрес в настройките на приложението</string>
<string id="ApiFlood">Извикванията на API са твърде бързи. Моля, забавете вашите заявки.</string>
<string id="ApiUrlNotFound">URL не е намерен. Потенциална грешка в URL адреса на API в настройките.</string>
<string id="ConfigUrlNotFound">URL не е намерен. Потенциална грешка в URL адреса на конфигурацията в настройките.</string>
<string id="UnhandledHttpErr">HTTP заявката върна код на грешка =</string>
<string id="TrailingSlashErr">URL адресът на API не трябва да има наклонена черта '/' в края</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Vypnuto</string>
<string id="MenuItemTap">Klepněte</string>
<string id="MenuItemMenu">Jídelní lístek</string>
<string id="Confirm">Tak určitě?</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>
<string id="NoConfigUrl">V nastavení aplikace není žádná konfigurační URL</string>
<string id="ApiFlood">Příliš rychlá volání API. Zpomalte prosím své požadavky.</string>
<string id="ApiUrlNotFound">Adresa URL nenalezena. Potenciální chyba URL API v nastavení.</string>
<string id="ConfigUrlNotFound">Adresa URL nenalezena. Potenciální chyba konfigurační adresy URL v nastavení.</string>
<string id="UnhandledHttpErr">Požadavek HTTP vrátil kód chyby =</string>
<string id="TrailingSlashErr">Adresa URL rozhraní API nesmí mít koncové lomítko „/“</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Af</string>
<string id="MenuItemTap">Tryk på</string>
<string id="MenuItemMenu">Menu</string>
<string id="Confirm">Jo da?</string>
<string id="NoPhone">Ingen telefonforbindelse</string>
<string id="NoInternet">Ingen internetforbindelse</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>
<string id="ApiFlood">API-kald for hurtigt. Sænk venligst dine anmodninger.</string>
<string id="ApiUrlNotFound">URL ikke fundet. Potentiel API URL-fejl i indstillinger.</string>
<string id="ConfigUrlNotFound">URL ikke fundet. Potentiel konfigurations-URL-fejl i indstillinger.</string>
<string id="UnhandledHttpErr">HTTP-anmodning returnerede fejlkode =</string>
<string id="TrailingSlashErr">API URL må ikke have en efterfølgende skråstreg '/'</string>
</strings>

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,12 +22,19 @@
<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="Confirm">Sicher?</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>
<string id="NoConfigUrl">Keine Konfigurations-URL in den Anwendungseinstellungen</string>
<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 gab folgenden Fehlercode zurück = </string>
<string id="TrailingSlashErr">Die API-URL darf keinen abschließenden Schrägstrich „/“ enthalten.</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Uit</string>
<string id="MenuItemTap">Kraan</string>
<string id="MenuItemMenu">Menu</string>
<string id="Confirm">Zeker?</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>
<string id="NoConfigUrl">Geen configuratie-URL in de applicatie-instellingen</string>
<string id="ApiFlood">API-aanroepen te snel. Vertraag uw verzoeken.</string>
<string id="ApiUrlNotFound">URL niet gevonden. Mogelijke API-URL-fout in instellingen.</string>
<string id="ConfigUrlNotFound">URL niet gevonden. Mogelijke configuratie-URL-fout in de instellingen.</string>
<string id="UnhandledHttpErr">HTTP-verzoek retourneerde foutcode =</string>
<string id="TrailingSlashErr">API-URL mag geen afsluitende slash '/' bevatten</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Väljas</string>
<string id="MenuItemTap">Puudutage</string>
<string id="MenuItemMenu">Menüü</string>
<string id="Confirm">Muidugi?</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-kutsed 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>
<string id="TrailingSlashErr">API URL-i lõpus ei tohi olla kaldkriipsu „/”</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Vinossa</string>
<string id="MenuItemTap">Napauta</string>
<string id="MenuItemMenu">Valikko</string>
<string id="Confirm">Varma?</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>
<string id="NoConfigUrl">Sovelluksen asetuksissa ei ole konfigurointi-URL-osoitetta</string>
<string id="ApiFlood">API-kutsut liian nopeita. Hidasta pyyntöjäsi.</string>
<string id="ApiUrlNotFound">URL-osoitetta ei löydy. Mahdollinen API URL -virhe asetuksissa.</string>
<string id="ConfigUrlNotFound">URL-osoitetta ei löydy. Mahdollinen konfigurointi-URL-virhe asetuksissa.</string>
<string id="UnhandledHttpErr">HTTP-pyyntö palautti virhekoodin =</string>
<string id="TrailingSlashErr">API-URL-osoitteessa ei saa olla perässä olevaa kauttaviivaa '/'</string>
</strings>

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,14 +20,21 @@
<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="Confirm">Bien sûr?</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>
<string id="TrailingSlashErr">L'URL de l'API ne doit pas comporter de barre oblique finale '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Μακριά από</string>
<string id="MenuItemTap">Παρακέντηση</string>
<string id="MenuItemMenu">Μενού</string>
<string id="Confirm">Σίγουρος?</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="NoConfigUrl">Δεν υπάρχει διεύθυνση URL διαμόρφωσης στις ρυθμίσεις της εφαρμογής</string>
<string id="ApiFlood">Κλήσεις API πολύ γρήγορες. Παρακαλώ επιβραδύνετε τα αιτήματά σας.</string>
<string id="ApiUrlNotFound">Η διεύθυνση URL δεν βρέθηκε. Πιθανό σφάλμα διεύθυνσης URL API στις ρυθμίσεις.</string>
<string id="ConfigUrlNotFound">Η διεύθυνση URL δεν βρέθηκε. Πιθανό σφάλμα διεύθυνσης URL διαμόρφωσης στις ρυθμίσεις.</string>
<string id="UnhandledHttpErr">Το αίτημα HTTP επέστρεψε κωδικό σφάλματος =</string>
<string id="TrailingSlashErr">Η διεύθυνση URL του API δεν πρέπει να έχει τελική κάθετο "/"</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">כבוי</string>
<string id="MenuItemTap">בֶּרֶז</string>
<string id="MenuItemMenu">תַפרִיט</string>
<string id="Confirm">בטוח?</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>
<string id="NoConfigUrl">אין כתובת אתר תצורה בהגדרות האפליקציה</string>
<string id="ApiFlood">קריאות API מהירות מדי. נא להאט את הבקשות שלך.</string>
<string id="ApiUrlNotFound">כתובת האתר לא נמצאה. שגיאה פוטנציאלית של כתובת ה-API בהגדרות.</string>
<string id="ConfigUrlNotFound">כתובת האתר לא נמצאה. שגיאת כתובת אתר פוטנציאלית של תצורה בהגדרות.</string>
<string id="UnhandledHttpErr">בקשת HTTP החזירה קוד שגיאה =</string>
<string id="TrailingSlashErr">כתובת ה-API לא חייבת לכלול לוכסן אחורי '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Isključeno</string>
<string id="MenuItemTap">Dodirnite</string>
<string id="MenuItemMenu">Jelovnik</string>
<string id="Confirm">Naravno?</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>
<string id="NoConfigUrl">Nema konfiguracijskog URL-a u postavkama aplikacije</string>
<string id="ApiFlood">API pozivi su prebrzi. Molimo usporite svoje zahtjeve.</string>
<string id="ApiUrlNotFound">URL nije pronađen. Potencijalna pogreška API URL-a u postavkama.</string>
<string id="ConfigUrlNotFound">URL nije pronađen. Potencijalna pogreška URL-a konfiguracije u postavkama.</string>
<string id="UnhandledHttpErr">HTTP zahtjev vratio je kod greške =</string>
<string id="TrailingSlashErr">API URL ne smije imati kosu crtu na kraju '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Ki</string>
<string id="MenuItemTap">Koppintson a</string>
<string id="MenuItemMenu">Menü</string>
<string id="Confirm">Biztos?</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>
<string id="NoConfigUrl">Nincs konfigurációs URL az alkalmazás beállításai között</string>
<string id="ApiFlood">Az API-hívások túl gyorsak. Kérjük, lassítsa a kérések teljesítését.</string>
<string id="ApiUrlNotFound">Az URL nem található. Lehetséges API URL hiba a beállításokban.</string>
<string id="ConfigUrlNotFound">Az URL nem található. Lehetséges konfigurációs URL hiba a beállításokban.</string>
<string id="UnhandledHttpErr">A HTTP-kérés = hibakódot adott vissza</string>
<string id="TrailingSlashErr">Az API URL-ben nem lehet perjel a „/”</string>
</strings>

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,10 +24,17 @@
<string id="MenuItemOff">Mati</string>
<string id="MenuItemTap">Mengetuk</string>
<string id="MenuItemMenu">Menu</string>
<string id="Confirm">Tentu?</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>
<string id="UnhandledHttpErr">Permintaan HTTP mengembalikan kode kesalahan =</string>
<string id="TrailingSlashErr">URL API tidak boleh memiliki garis miring '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Spento</string>
<string id="MenuItemTap">Rubinetto</string>
<string id="MenuItemMenu">Menù</string>
<string id="Confirm">Sicuro?</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>
<string id="NoConfigUrl">Nessun URL di configurazione nelle impostazioni dell'applicazione</string>
<string id="ApiFlood">Chiamate API troppo rapide. Per favore rallenta le tue richieste.</string>
<string id="ApiUrlNotFound">URL non trovato. Potenziale errore URL API nelle impostazioni.</string>
<string id="ConfigUrlNotFound">URL non trovato. Potenziale errore dell'URL di configurazione nelle impostazioni.</string>
<string id="UnhandledHttpErr">La richiesta HTTP ha restituito il codice di errore =</string>
<string id="TrailingSlashErr">L'URL dell'API non deve avere una barra finale "/"</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">オフ</string>
<string id="MenuItemTap">タップ</string>
<string id="MenuItemMenu">メニュー</string>
<string id="Confirm">もちろん?</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>
<string id="NoConfigUrl">アプリケーション設定に構成 URL がありません</string>
<string id="ApiFlood">API 呼び出しが速すぎます。リクエストは遅くしてください。</string>
<string id="ApiUrlNotFound">URLが見つかりません。設定における API URL エラーの可能性があります。</string>
<string id="ConfigUrlNotFound">URLが見つかりません。設定内の構成 URL エラーの可能性があります。</string>
<string id="UnhandledHttpErr">HTTP リクエストがエラー コードを返しました =</string>
<string id="TrailingSlashErr">API URL の末尾にスラッシュ「/」を含めることはできません</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">끄다</string>
<string id="MenuItemTap">수도꼭지</string>
<string id="MenuItemMenu">메뉴</string>
<string id="Confirm">확신하는?</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>
<string id="NoConfigUrl">애플리케이션 설정에 구성 URL이 없습니다.</string>
<string id="ApiFlood">API 호출이 너무 빠릅니다. 요청 속도를 늦추시기 바랍니다.</string>
<string id="ApiUrlNotFound">URL을 찾을 수 없습니다. 설정에 잠재적인 API URL 오류가 있습니다.</string>
<string id="ConfigUrlNotFound">URL을 찾을 수 없습니다. 설정에 잠재적인 구성 URL 오류가 있습니다.</string>
<string id="UnhandledHttpErr">HTTP 요청이 오류 코드를 반환했습니다 =</string>
<string id="TrailingSlashErr">API URL에는 후행 슬래시 '/'가 없어야 합니다.</string>
</strings>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Izslēgts</string>
<string id="MenuItemTap">Krāns</string>
<string id="MenuItemMenu">Izvēlne</string>
<string id="Confirm">Protams?</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>
<string id="NoConfigUrl">Lietojumprogrammas iestatījumos nav konfigurācijas URL</string>
<string id="ApiFlood">API izsaukumi ir pārāk ātri. Lūdzu, palēniniet pieprasījumu izpildi.</string>
<string id="ApiUrlNotFound">URL nav atrasts. Iespējama API URL kļūda iestatījumos.</string>
<string id="ConfigUrlNotFound">URL nav atrasts. Iespējama konfigurācijas URL kļūda iestatījumos.</string>
<string id="UnhandledHttpErr">HTTP pieprasījums atgrieza kļūdas kodu =</string>
<string id="TrailingSlashErr">API URL beigās nedrīkst būt slīpsvītra “/”</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Išjungta</string>
<string id="MenuItemTap">Bakstelėkite</string>
<string id="MenuItemMenu">Meniu</string>
<string id="Confirm">Žinoma?</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>
<string id="NoConfigUrl">Programos nustatymuose nėra konfigūracijos URL</string>
<string id="ApiFlood">API skambučiai per greiti. Sulėtinkite prašymų vykdymą.</string>
<string id="ApiUrlNotFound">URL nerastas. Galima API URL klaida nustatymuose.</string>
<string id="ConfigUrlNotFound">URL nerastas. Galima konfigūracijos URL klaida nustatymuose.</string>
<string id="UnhandledHttpErr">HTTP užklausa grąžino klaidos kodą =</string>
<string id="TrailingSlashErr">API URL pabaigoje negali būti pasvirojo brūkšnio „/“</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Av</string>
<string id="MenuItemTap">Trykk på</string>
<string id="MenuItemMenu">Meny</string>
<string id="Confirm">Sikker?</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>
<string id="NoConfigUrl">Ingen konfigurasjons-URL i applikasjonsinnstillingene</string>
<string id="ApiFlood">API-kall for raske. Vennligst senke forespørslene dine.</string>
<string id="ApiUrlNotFound">Finner ikke URL. Potensiell API URL-feil i innstillingene.</string>
<string id="ConfigUrlNotFound">Finner ikke URL. Potensiell konfigurasjons-URL-feil i innstillingene.</string>
<string id="UnhandledHttpErr">HTTP-forespørsel returnerte feilkode =</string>
<string id="TrailingSlashErr">API URL må ikke ha en etterfølgende skråstrek '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Wyłączony</string>
<string id="MenuItemTap">Uzyskiwać</string>
<string id="MenuItemMenu">Menu</string>
<string id="Confirm">Jasne?</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>
<string id="NoConfigUrl">Brak adresu URL konfiguracji w ustawieniach aplikacji</string>
<string id="ApiFlood">Wywołania API są zbyt szybkie. Proszę spowolnić swoje żądania.</string>
<string id="ApiUrlNotFound">Nie znaleziono adresu URL. Potencjalny błąd adresu URL interfejsu API w ustawieniach.</string>
<string id="ConfigUrlNotFound">Nie znaleziono adresu URL. Potencjalny błąd adresu URL konfiguracji w ustawieniach.</string>
<string id="UnhandledHttpErr">Żądanie HTTP zwróciło kod błędu =</string>
<string id="TrailingSlashErr">Adres URL interfejsu API nie może zawierać końcowego ukośnika „/”</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Desligado</string>
<string id="MenuItemTap">Tocar</string>
<string id="MenuItemMenu">Cardápio</string>
<string id="Confirm">Claro?</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>
<string id="NoConfigUrl">Nenhum URL de configuração nas configurações do aplicativo</string>
<string id="ApiFlood">Chamadas de API muito rápidas. Por favor, diminua a velocidade de seus pedidos.</string>
<string id="ApiFlood">Chamadas de API muito rápidas. Por favor, desacelere seus pedidos.</string>
<string id="ApiUrlNotFound">URL não encontrado. Possível erro de URL da API nas configurações.</string>
<string id="ConfigUrlNotFound">URL não encontrado. Possível erro de URL de configuração nas configurações.</string>
<string id="UnhandledHttpErr">Solicitação HTTP retornou código de erro =</string>
<string id="TrailingSlashErr">O URL da API não deve ter uma barra final '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Oprit</string>
<string id="MenuItemTap">Atingeți</string>
<string id="MenuItemMenu">Meniul</string>
<string id="Confirm">Sigur?</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>
<string id="NoConfigUrl">Nicio adresă URL de configurare în setările aplicației</string>
<string id="ApiFlood">Apeluri API prea rapide. Vă rugăm să vă încetiniți solicitările.</string>
<string id="ApiUrlNotFound">Adresa URL nu a fost găsită. Potențială eroare URL API în setări.</string>
<string id="ConfigUrlNotFound">Adresa URL nu a fost găsită. Potențială eroare URL de configurare în setări.</string>
<string id="UnhandledHttpErr">Solicitarea HTTP a returnat codul de eroare =</string>
<string id="TrailingSlashErr">Adresa URL API nu trebuie să aibă o bară oblică „/”</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Vypnuté</string>
<string id="MenuItemTap">Klepnite</string>
<string id="MenuItemMenu">Ponuka</string>
<string id="Confirm">Samozrejme?</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>
<string id="NoConfigUrl">V nastaveniach aplikácie nie je žiadna konfiguračná URL</string>
<string id="ApiFlood">Volania API sú príliš rýchle. Spomaľte svoje požiadavky.</string>
<string id="ApiUrlNotFound">Adresa URL sa nenašla. Potenciálna chyba webovej adresy rozhrania API v nastaveniach.</string>
<string id="ConfigUrlNotFound">Adresa URL sa nenašla. Potenciálna chyba konfiguračnej adresy URL v nastaveniach.</string>
<string id="UnhandledHttpErr">Požiadavka HTTP vrátila kód chyby =</string>
<string id="TrailingSlashErr">Adresa URL rozhrania API nesmie obsahovať koncovú lomku „/“</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Izključeno</string>
<string id="MenuItemTap">Tapnite</string>
<string id="MenuItemMenu">meni</string>
<string id="Confirm">Seveda?</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>
<string id="NoConfigUrl">V nastavitvah aplikacije ni konfiguracijskega URL-ja</string>
<string id="ApiFlood">API klici so prehitri. Prosim, upočasnite svoje zahteve.</string>
<string id="ApiUrlNotFound">URL-ja ni bilo mogoče najti. Morebitna napaka URL-ja API-ja v nastavitvah.</string>
<string id="ConfigUrlNotFound">URL-ja ni bilo mogoče najti. Morebitna napaka URL-ja konfiguracije v nastavitvah.</string>
<string id="UnhandledHttpErr">Zahteva HTTP je vrnila kodo napake =</string>
<string id="TrailingSlashErr">URL API-ja ne sme imeti končne poševnice '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Apagado</string>
<string id="MenuItemTap">Grifo</string>
<string id="MenuItemMenu">Menú</string>
<string id="Confirm">¿Seguro?</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>
<string id="NoConfigUrl">No hay URL de configuración en la configuración de la aplicación.</string>
<string id="ApiFlood">Llamadas API demasiado rápidas. Por favor, ralentice sus solicitudes.</string>
<string id="ApiUrlNotFound">URL no encontrada. Posible error de URL de API en la configuración.</string>
<string id="ConfigUrlNotFound">URL no encontrada. Posible error de URL de configuración en la configuración.</string>
<string id="UnhandledHttpErr">La solicitud HTTP devolvió el código de error =</string>
<string id="TrailingSlashErr">La URL de API no debe tener una barra diagonal '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Av</string>
<string id="MenuItemTap">Knacka</string>
<string id="MenuItemMenu">Meny</string>
<string id="Confirm">Säker?</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 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>
<string id="ConfigUrlNotFound">Webbadressen hittades inte. Potentiellt konfigurations-URL-fel i inställningarna.</string>
<string id="UnhandledHttpErr">HTTP-begäran returnerade felkod =</string>
<string id="TrailingSlashErr">API-URL får inte ha ett snedstreck '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">ปิด</string>
<string id="MenuItemTap">แตะ</string>
<string id="MenuItemMenu">เมนู</string>
<string id="Confirm">แน่นอน?</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="NoConfigUrl">ไม่มี URL การกำหนดค่าในการตั้งค่าแอปพลิเคชัน</string>
<string id="ApiFlood">การเรียก API เร็วเกินไป กรุณาชะลอคำขอของคุณ</string>
<string id="ApiUrlNotFound">ไม่พบ URL ข้อผิดพลาด URL API ที่อาจเกิดขึ้นในการตั้งค่า</string>
<string id="ConfigUrlNotFound">ไม่พบ URL ข้อผิดพลาด URL การกำหนดค่าที่อาจเกิดขึ้นในการตั้งค่า</string>
<string id="UnhandledHttpErr">คำขอ HTTP ส่งคืนรหัสข้อผิดพลาด =</string>
<string id="TrailingSlashErr">URL ของ API ต้องไม่มีเครื่องหมายทับต่อท้าย '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Kapalı</string>
<string id="MenuItemTap">Musluk</string>
<string id="MenuItemMenu">Menü</string>
<string id="Confirm">Elbette?</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>
<string id="NoConfigUrl">Uygulama ayarlarında yapılandırma URL'si yok</string>
<string id="ApiFlood">API çağrıları çok hızlı. Lütfen isteklerinizi yavaşlatın.</string>
<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>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Вимкнено</string>
<string id="MenuItemTap">Торкніться</string>
<string id="MenuItemMenu">Меню</string>
<string id="Confirm">Зрозуміло?</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="NoConfigUrl">У налаштуваннях програми немає URL-адреси конфігурації</string>
<string id="ApiFlood">Надто швидкі виклики API. Будь ласка, сповільніть свої запити.</string>
<string id="ApiUrlNotFound">URL не знайдено. Потенційна помилка URL-адреси API в налаштуваннях.</string>
<string id="ConfigUrlNotFound">URL не знайдено. Потенційна помилка URL-адреси конфігурації в налаштуваннях.</string>
<string id="UnhandledHttpErr">Запит HTTP повернув код помилки =</string>
<string id="TrailingSlashErr">URL-адреса API не повинна містити косу риску '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Tắt</string>
<string id="MenuItemTap">Vỗ nhẹ</string>
<string id="MenuItemMenu">Thực đơn</string>
<string id="Confirm">Chắc chắ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>
<string id="NoConfigUrl">Không có URL cấu hình trong cài đặt ứng dụng</string>
<string id="ApiFlood">Cuộc gọi API quá nhanh. Hãy làm chậm yêu cầu của bạn.</string>
<string id="ApiUrlNotFound">Không tìm thấy URL. Lỗi URL API tiềm ẩn trong cài đặt.</string>
<string id="ConfigUrlNotFound">Không tìm thấy URL. Lỗi URL cấu hình tiềm ẩn trong cài đặt.</string>
<string id="UnhandledHttpErr">Yêu cầu HTTP trả về mã lỗi =</string>
<string id="TrailingSlashErr">URL API không được có dấu gạch chéo ở cuối '/'</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">离开</string>
<string id="MenuItemTap">轻敲</string>
<string id="MenuItemMenu">菜单</string>
<string id="Confirm">当然?</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>
<string id="NoConfigUrl">应用程序设置中没有配置 URL</string>
<string id="ApiFlood">API 调用速度太快。请放慢您的请求。</string>
<string id="ApiUrlNotFound">找不到网址。设置中可能存在 API URL 错误。</string>
<string id="ConfigUrlNotFound">找不到网址。设置中可能存在配置 URL 错误。</string>
<string id="UnhandledHttpErr">HTTP请求返回错误码=</string>
<string id="TrailingSlashErr">API URL 不得有尾部斜杠“/”</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">離開</string>
<string id="MenuItemTap">輕敲</string>
<string id="MenuItemMenu">選單</string>
<string id="Confirm">當然?</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>
<string id="NoConfigUrl">應用程式設定中沒有配置 URL</string>
<string id="ApiFlood">API 呼叫速度太快。請放慢您的請求。</string>
<string id="ApiUrlNotFound">找不到網址。設定中可能存在 API URL 錯誤。</string>
<string id="ConfigUrlNotFound">找不到網址。設定中可能存在配置 URL 錯誤。</string>
<string id="UnhandledHttpErr">HTTP請求回傳錯誤碼=</string>
<string id="TrailingSlashErr">API URL 不得有尾部斜線“/”</string>
</strings>

View File

@ -24,10 +24,17 @@
<string id="MenuItemOff">Mati</string>
<string id="MenuItemTap">Ketik</string>
<string id="MenuItemMenu">Menu</string>
<string id="Confirm">pasti?</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>
<string id="NoConfigUrl">Tiada URL konfigurasi dalam tetapan aplikasi</string>
<string id="ApiFlood">Panggilan API terlalu pantas. Sila perlahankan permintaan anda.</string>
<string id="ApiUrlNotFound">URL tidak ditemui. Ralat URL API yang berpotensi dalam tetapan.</string>
<string id="ConfigUrlNotFound">URL tidak ditemui. Ralat URL Konfigurasi Potensi dalam tetapan.</string>
<string id="UnhandledHttpErr">Permintaan HTTP mengembalikan kod ralat =</string>
<string id="TrailingSlashErr">URL API tidak boleh mempunyai garis miring '/'</string>
</strings>

View File

@ -23,4 +23,15 @@
<!-- 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>
<!--
Application timeout in seconds, except 0 for no timeout (default). After this amount of elapsed time
with no activity, exit the application.
-->
<property id="app_timeout" type="number">0</property>
<property id="types_representation" type="boolean"></property>
<property id="menu_alignment" type="boolean"></property>
</properties>

View File

@ -42,4 +42,33 @@
type="alphaNumeric"
/>
</setting>
<setting
propertyKey="@Properties.app_timeout"
title="Timeout in seconds. Exit the application after this period of inactivity to save the device battery."
>
<settingConfig
type="numeric"
min="0"
/>
</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,10 +18,17 @@
<string id="MenuItemOff">Off</string>
<string id="MenuItemTap">Tap</string>
<string id="MenuItemMenu">Menu</string>
<string id="Confirm">Sure?</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>
<string id="NoConfigUrl">No configuration URL in the application settings</string>
<string id="ApiFlood">API calls too rapid. Please slow down your requests.</string>
<string id="ApiUrlNotFound">URL not found. Potential API URL error in settings.</string>
<string id="ConfigUrlNotFound">URL not found. Potential Configuration URL error in settings.</string>
<string id="UnhandledHttpErr">HTTP request returned error code = </string>
<string id="TrailingSlashErr">API URL must not have a trailing slash '/'</string>
</strings>

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();
@ -132,11 +132,13 @@ class AlertDelegate extends WatchUi.InputDelegate {
function onKey(evt) {
mView.dismiss();
getApp().getQuitTimer().reset();
return true;
}
function onTap(evt) {
mView.dismiss();
getApp().getQuitTimer().reset();
return true;
}
}

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();
@ -82,6 +82,7 @@ class ErrorDelegate extends WatchUi.BehaviorDelegate {
WatchUi.BehaviorDelegate.initialize();
}
function onBack() {
getApp().getQuitTimer().reset();
WatchUi.popView(WatchUi.SLIDE_DOWN);
return true;
}

View File

@ -22,61 +22,87 @@ 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 mItemsToUpdate; // Array initialised by onReturnFetchMenuConfig()
hidden var mNextItemToUpdate = 0; // Index into the above array
private var mHaMenu;
private var quitTimer as QuitTimer;
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);
strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet);
strNoMenu = WatchUi.loadResource($.Rez.Strings.NoMenu);
strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood);
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);
strUnhandledHttpErr = WatchUi.loadResource($.Rez.Strings.UnhandledHttpErr);
strTrailingSlashErr = WatchUi.loadResource($.Rez.Strings.TrailingSlashErr);
quitTimer = new QuitTimer();
}
// onStart() is called on application start up
function onStart(state as Lang.Dictionary?) as Void {
quitTimer.begin();
}
// onStop() is called when your application is exiting
function onStop(state as Lang.Dictionary?) as Void {}
function onStop(state as Lang.Dictionary?) as Void {
quitTimer.stop();
}
// Return the initial view of your application here
function getInitialView() as Lang.Array<WatchUi.Views or WatchUi.InputDelegates>? {
var api_url = Properties.getValue("api_url") as Lang.String;
if ((Properties.getValue("api_key") as Lang.String).length() == 0) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem Note - 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 ((Properties.getValue("api_url") as Lang.String).length() == 0) {
} else if (api_url.length() == 0) {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem Note - 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("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 Note - 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 Note - fetchMenuConfig(): No Internet connection, skipping API call.");
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>;
}
}
@ -87,38 +113,54 @@ 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.");
}
WatchUi.pushView(new ErrorView(strConfigUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == 200) {
mHaMenu = new HomeAssistantView(data, null);
WatchUi.switchToView(mHaMenu, new HomeAssistantViewDelegate(), WatchUi.SLIDE_IMMEDIATE);
mItemsToUpdate = mHaMenu.getItemsToUpdate();
// Start the continuous update process that continues for as long as the application is running.
// The chain of functions from 'updateNextMenuItem()' calls 'updateNextMenuItem()' on completion.
updateNextMenuItem();
} else if (responseCode == -300) {
if (mItemsToUpdate.size() > 0) {
updateNextMenuItem();
}
} else if (responseCode == Communications.NETWORK_REQUEST_TIMED_OUT) {
if (Globals.scDebug) {
System.println("HomeAssistantApp Note - onReturnFetchMenuConfig(): Network request timeout.");
System.println("HomeAssistantApp onReturnFetchMenuConfig(): Network request timeout.");
}
WatchUi.pushView(new ErrorView(strNoMenu + ". " + strNoInternet + "?"), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else {
if (Globals.scDebug) {
System.println("HomeAssistantApp Note - onReturnFetchMenuConfig(): Configuration not found or potential validation issue.");
System.println("HomeAssistantApp onReturnFetchMenuConfig(): Unhandled HTTP response code = " + responseCode);
}
WatchUi.pushView(new ErrorView(strNoMenu + " code=" + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP);
WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
}
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(
@ -137,6 +179,10 @@ class HomeAssistantApp extends Application.AppBase {
mNextItemToUpdate = (mNextItemToUpdate + 1) % itu.size();
}
function getQuitTimer() as QuitTimer{
return quitTimer;
}
}
function getApp() as HomeAssistantApp {

View File

@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------------
//
// 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 confirmation dialogue view.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
// Required for callback method definition
typedef Method as Toybox.Lang.Method;
using Toybox.WatchUi;
class HomeAssistantConfirmation extends WatchUi.Confirmation {
function initialize() {
WatchUi.Confirmation.initialize(WatchUi.loadResource($.Rez.Strings.Confirm));
}
}
class HomeAssistantConfirmationDelegate extends WatchUi.ConfirmationDelegate {
private var confirmMethod;
function initialize(callback as Method() as Void) {
WatchUi.ConfirmationDelegate.initialize();
confirmMethod = callback;
}
function onResponse(response) as Lang.Boolean {
getApp().getQuitTimer().reset();
if (response == WatchUi.CONFIRM_YES) {
confirmMethod.invoke();
}
return true;
}
}

View File

@ -0,0 +1,72 @@
//-----------------------------------------------------------------------------------
//
// 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;
class HomeAssistantIconMenuItem extends WatchUi.IconMenuItem {
private var mHomeAssistantService as HomeAssistantService;
private var mService as Lang.String;
private var mConfirm as Lang.Boolean;
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,
confirm as Lang.Boolean,
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;
mConfirm = confirm;
}
function callService() as Void {
if (mConfirm) {
WatchUi.pushView(
new HomeAssistantConfirmation(),
new HomeAssistantConfirmationDelegate(method(:onConfirm)),
WatchUi.SLIDE_IMMEDIATE
);
} else {
onConfirm();
}
}
function onConfirm() as Void {
mHomeAssistantService.call(mIdentifier as Lang.String, mService);
}
}

View File

@ -21,131 +21,50 @@
using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Graphics;
using Toybox.Application.Properties;
class HomeAssistantMenuItem extends WatchUi.MenuItem {
hidden var mApiKey = Properties.getValue("api_key");
hidden var strNoInternet as Lang.String;
hidden var strApiFlood as Lang.String;
hidden var mService as Lang.String;
private var mHomeAssistantService as HomeAssistantService;
private var mService as Lang.String;
private var mConfirm as Lang.Boolean;
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,
confirm as Lang.Boolean,
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);
mService = service;
WatchUi.MenuItem.initialize(
label,
subLabel,
identifier,
options
);
mHomeAssistantService = haService;
mService = service;
mConfirm = confirm;
}
// 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 == 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 Note - 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);
}
}
}
}
}
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)
);
}
function callService() as Void {
if (mConfirm) {
WatchUi.pushView(
new HomeAssistantConfirmation(),
new HomeAssistantConfirmationDelegate(method(:onConfirm)),
WatchUi.SLIDE_IMMEDIATE
);
} else {
if (Globals.scDebug) {
System.println("HomeAssistantMenuItem Note - execScript(): No Internet connection, skipping API call.");
}
WatchUi.pushView(new ErrorView(strNoInternet + "."), new ErrorDelegate(), WatchUi.SLIDE_UP);
onConfirm();
}
}
function onConfirm() as Void {
mHomeAssistantService.call(mIdentifier as Lang.String, mService);
}
}

View File

@ -0,0 +1,129 @@
//-----------------------------------------------------------------------------------
//
// 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,
confirm as Lang.Boolean
) as WatchUi.MenuItem {
if (bRepresentTypesWithLabels) {
return new HomeAssistantMenuItem(
label,
strMenuItemTap,
identifier,
service,
confirm,
mMenuItemOptions,
mHomeAssistantService
);
} else {
return new HomeAssistantIconMenuItem(
label,
null,
identifier,
service,
confirm,
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,29 +22,38 @@ using Toybox.Lang;
using Toybox.WatchUi;
using Toybox.Graphics;
using Toybox.Application.Properties;
using Toybox.Timer;
class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
hidden var mApiKey = Properties.getValue("api_key");
hidden var strNoInternet as Lang.String;
hidden var strApiFlood 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
) {
strNoInternet = WatchUi.loadResource($.Rez.Strings.NoInternet);
strApiFlood = WatchUi.loadResource($.Rez.Strings.ApiFlood);
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");
WatchUi.ToggleMenuItem.initialize(label, subLabel, identifier, enabled, options);
mApiKey = Properties.getValue("api_key");
}
private function setUiToggle(state as Null or Lang.String) as Void {
@ -66,15 +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);
}
} 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.");
}
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) {
@ -84,9 +119,19 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
setLabel((data.get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String);
}
setUiToggle(state);
} else {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnGetState(): Unhandled HTTP response code = " + responseCode);
}
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();
}
// Now this feels very "closely coupled" to the application, but it is the most reliable method instead of using a timer.
getApp().updateNextMenuItem();
}
function getState() as Void {
@ -97,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);
@ -108,11 +170,17 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
options,
method(:onReturnGetState)
);
} else {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem Note - 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);
}
}
@ -123,15 +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.");
}
var cw = WatchUi.getCurrentView();
if (!(cw[0] instanceof ErrorView)) {
// Avoid pushing multiple ErrorViews
WatchUi.pushView(new ErrorView(strApiFlood), new ErrorDelegate(), WatchUi.SLIDE_UP);
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.");
}
WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP);
} else if (responseCode == 200) {
var state;
var d = data as Lang.Array;
@ -144,6 +223,11 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
setUiToggle(state);
}
}
} else {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem onReturnSetState(): Unhandled HTTP response code = " + responseCode);
}
WatchUi.pushView(new ErrorView(strUnhandledHttpErr + responseCode ), new ErrorDelegate(), WatchUi.SLIDE_UP);
}
}
@ -156,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;
@ -178,11 +276,6 @@ class HomeAssistantToggleMenuItem extends WatchUi.ToggleMenuItem {
options,
method(:onReturnSetState)
);
} else {
if (Globals.scDebug) {
System.println("HomeAssistantToggleMenuItem Note - 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 = {
@ -53,33 +48,28 @@ class HomeAssistantView extends WatchUi.Menu2 {
var items = definition.get("items") as Lang.Dictionary;
for(var i = 0; i < items.size(); i++) {
var type = items[i].get("type") as Lang.String or Null;
var name = items[i].get("name") as Lang.String or Null;
var entity = items[i].get("entity") as Lang.String or Null;
var service = items[i].get("service") as Lang.String or Null;
var type = items[i].get("type") as Lang.String or Null;
var name = items[i].get("name") as Lang.String or Null;
var entity = items[i].get("entity") as Lang.String or Null;
var tap_action = items[i].get("tap_action") as Lang.Dictionary or Null;
var service = items[i].get("service") as Lang.String or Null;
var confirm = false as Lang.Boolean;
if (tap_action != null) {
service = tap_action.get("service");
confirm = tap_action.get("confirm");
if (confirm == null) {
confirm = false;
}
}
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, confirm));
} 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 +79,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);
}
@ -112,7 +107,23 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
Menu2InputDelegate.initialize();
}
function onBack() {
getApp().getQuitTimer().reset();
WatchUi.popView(WatchUi.SLIDE_RIGHT);
}
// Only for CheckboxMenu
function onDone() {
getApp().getQuitTimer().reset();
}
// Only for CustomMenu
function onFooter() {
getApp().getQuitTimer().reset();
}
function onSelect(item as WatchUi.MenuItem) as Void {
getApp().getQuitTimer().reset();
if (item instanceof HomeAssistantToggleMenuItem) {
var haToggleItem = item as HomeAssistantToggleMenuItem;
if (Globals.scDebug) {
@ -124,7 +135,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 +149,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());
@ -139,8 +163,9 @@ class HomeAssistantViewDelegate extends WatchUi.Menu2InputDelegate {
}
}
function onBack() {
WatchUi.popView(WatchUi.SLIDE_RIGHT);
// Only for CustomMenu
function onTitle() {
getApp().getQuitTimer().reset();
}
}
}

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

56
source/QuitTimer.mc Normal file
View File

@ -0,0 +1,56 @@
//-----------------------------------------------------------------------------------
//
// 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.
//
// J D Abbey & P A Abbey, 28 December 2022
//
//
// Description:
//
// Quit the application after a period of inactivity in order to save the battery.
//
//-----------------------------------------------------------------------------------
using Toybox.Lang;
using Toybox.Timer;
using Toybox.Application.Properties;
using Toybox.WatchUi;
class QuitTimer extends Timer.Timer {
private var api_timeout;
function initialize() {
Timer.Timer.initialize();
// Timer needs delay in milliseconds.
api_timeout = (Properties.getValue("app_timeout") as Lang.Number) * 1000;
}
function exitApp() as Void {
if (Globals.scDebug) {
System.println("QuitTimer exitApp(): Exiting");
}
// This will exit the system cleanly from any point within an app.
System.exit();
}
function begin() {
if (api_timeout > 0) {
start(method(:exitApp), api_timeout, false);
}
}
function reset() {
if (Globals.scDebug) {
System.println("QuitTimer reset(): Restarted quit timer");
}
stop();
begin();
}
}

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

Some files were not shown because too many files have changed in this diff Show More