diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 097a0800c3..e383015b66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,10 @@ env: HOMEBREW_NO_INSTALL_CLEANUP: TRUE BUNDLE_PATH: vendor/bundle +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest diff --git a/.swiftformat b/.swiftformat index f0006d0269..3e13ecd632 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,6 +1,6 @@ --swiftversion 5.3 ---exclude vendor,Pods,**/Swiftgen/**,**/Resources/**,fastlane +--exclude vendor,Pods,**/Swiftgen/**,**/Resources/**,fastlane,**/Assets/** --wraparguments before-first --wrapparameters before-first diff --git a/Configuration/Entitlements/activate_special_entitlements.sh b/Configuration/Entitlements/activate_special_entitlements.sh index e15f68ee37..330e5bf31a 100755 --- a/Configuration/Entitlements/activate_special_entitlements.sh +++ b/Configuration/Entitlements/activate_special_entitlements.sh @@ -19,6 +19,16 @@ else echo "warning: Push provider disabled" fi +if [[ $TARGET_NAME = "App" ]]; then + if [[ $CI && $CONFIGURATION != "Release" ]]; then + echo "warning: THREAD_NETWORK_CREDENTIALS disabled for CI" + elif [[ ${ENABLE_THREAD_NETWORK_CREDENTIALS} -eq 1 ]]; then + /usr/libexec/PlistBuddy -c "add com.apple.developer.networking.manage-thread-network-credentials bool true" "$ENTITLEMENTS_FILE" + else + echo "warning: THREAD_NETWORK_CREDENTIALS disabled" + fi +fi + if [[ $TARGET_NAME = "App" ]]; then if [[ $CI && $CONFIGURATION != "Release" ]]; then echo "warning: Device name disabled for CI" diff --git a/Configuration/HomeAssistant.xcconfig b/Configuration/HomeAssistant.xcconfig index 3590394e8b..0eeda91314 100644 --- a/Configuration/HomeAssistant.xcconfig +++ b/Configuration/HomeAssistant.xcconfig @@ -6,6 +6,7 @@ BUNDLE_ID_PREFIX = io.robbie ENABLE_CRITICAL_ALERTS_QMQYCKL255 = 1 ENABLE_PUSH_PROVIDER_QMQYCKL255 = 1 ENABLE_DEVICE_NAME_QMQYCKL255 = 1 +ENABLE_THREAD_NETWORK_CREDENTIALS_QMQYCKL255 = 1 // cascades down PRODUCT_BUNDLE_IDENTIFIER = ${BUNDLE_ID_PREFIX}.HomeAssistant${BUNDLE_ID_SUFFIX}${PROVISIONING_SUFFIX} @@ -28,6 +29,7 @@ CODE_SIGN_STYLE_DEFAULT = Automatic ENABLE_CRITICAL_ALERTS[sdk=iphoneos*] = $(ENABLE_CRITICAL_ALERTS_$(DEVELOPMENT_TEAM)) ENABLE_PUSH_PROVIDER[sdk=iphoneos*] = $(ENABLE_PUSH_PROVIDER_$(DEVELOPMENT_TEAM)) ENABLE_DEVICE_NAME[sdk=iphoneos*] = $(ENABLE_DEVICE_NAME_$(DEVELOPMENT_TEAM)) +ENABLE_THREAD_NETWORK_CREDENTIALS[sdk=iphoneos*] = $(ENABLE_THREAD_NETWORK_CREDENTIALS_$(DEVELOPMENT_TEAM)) // We mutate the entitlements at build time to support other development teams CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES diff --git a/Gemfile.lock b/Gemfile.lock index 406602d483..508b115038 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,21 +16,21 @@ GEM artifactory (3.0.15) ast (2.4.2) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.618.0) - aws-sdk-core (3.132.0) + aws-eventstream (1.3.0) + aws-partitions (1.859.0) + aws-sdk-core (3.188.0) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.58.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (1.73.0) + aws-sdk-core (~> 3, >= 3.188.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-s3 (1.140.0) + aws-sdk-core (~> 3, >= 3.188.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.1) + aws-sigv4 (~> 1.6) + aws-sigv4 (1.7.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -81,17 +81,16 @@ GEM highline (~> 2.0.0) concurrent-ruby (1.2.2) declarative (0.0.20) - digest-crc (0.6.4) + digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) + domain_name (0.6.20231109) dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.92.4) - faraday (1.10.1) + excon (0.105.0) + faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -119,8 +118,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.209.0) + fastimage (2.2.7) + fastlane (2.217.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -141,10 +140,11 @@ GEM google-apis-playcustomapp_v1 (~> 0.1) google-cloud-storage (~> 1.31) highline (~> 2.0) + http-cookie (~> 1.0.5) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) @@ -152,7 +152,7 @@ GEM security (= 0.1.3) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) + terminal-table (~> 3) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) @@ -169,9 +169,9 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.25.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-core (0.7.0) + google-apis-androidpublisher_v3 (0.53.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.2) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -180,30 +180,29 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.13.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-playcustomapp_v1 (0.10.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-storage_v1 (0.17.0) - google-apis-core (>= 0.7, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.29.0) + google-apis-core (>= 0.11.0, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.2.0) - google-cloud-storage (1.38.0) + google-cloud-errors (1.3.1) + google-cloud-storage (1.45.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.17.0) + google-apis-storage_v1 (~> 0.29.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.2.0) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) @@ -213,16 +212,15 @@ GEM httpclient (2.8.3) i18n (1.14.1) concurrent-ruby (~> 1.0) - jmespath (1.6.1) + jmespath (1.6.2) json (2.6.3) - jwt (2.4.1) - memoist (0.16.2) - mini_magick (4.11.0) - mini_mime (1.1.2) + jwt (2.7.1) + mini_magick (4.12.0) + mini_mime (1.1.5) minitest (5.20.0) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.0.0) + multipart-post (2.3.0) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) @@ -232,10 +230,10 @@ GEM parallel (1.22.1) parser (3.1.2.1) ast (~> 2.4.1) - plist (3.6.0) + plist (3.7.0) public_suffix (4.0.7) rainbow (3.1.1) - rake (13.0.6) + rake (13.1.0) redcarpet (3.5.1) regexp_parser (2.5.0) representable (3.2.0) @@ -262,16 +260,17 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.17.0) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.8) + simctl (1.6.10) CFPropertyList naturally terminal-notifier (2.0.0) - terminal-table (1.6.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) @@ -282,11 +281,8 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) unicode-display_width (2.2.0) - webrick (1.7.0) + webrick (1.8.1) word_wrap (1.0.0) xcodeproj (1.23.0) CFPropertyList (>= 2.3.3, < 4.0) diff --git a/HomeAssistant.xcodeproj/project.pbxproj b/HomeAssistant.xcodeproj/project.pbxproj index 66188063b4..60c8b6e7bb 100644 --- a/HomeAssistant.xcodeproj/project.pbxproj +++ b/HomeAssistant.xcodeproj/project.pbxproj @@ -516,6 +516,26 @@ 42BDF44B2A8EA5AB00562183 /* SecurityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42BDF44A2A8EA5AB00562183 /* SecurityViewController.swift */; }; 42BDF4582A93FC2A00562183 /* BiometricsAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42BDF44D2A8EAD2700562183 /* BiometricsAuthenticationService.swift */; }; 42E9ADE42B064AF0003772BC /* BiometricsAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E9ADE32B064AF0003772BC /* BiometricsAuthenticationViewController.swift */; }; + 420B10042B1CF6D800D383D8 /* SharedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 420B10032B1CF6D800D383D8 /* SharedAssets.xcassets */; }; + 420B10092B1D12DD00D383D8 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 420B10082B1D129B00D383D8 /* Colors.xcassets */; }; + 420B100C2B1D204400D383D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 420B100B2B1D204400D383D8 /* Assets.xcassets */; }; + 424A7F462B188946008C8DF3 /* WidgetBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 424A7F452B188946008C8DF3 /* WidgetBackground.swift */; }; + 424A7F482B188BF3008C8DF3 /* WidgetContentMargin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 424A7F472B188BF3008C8DF3 /* WidgetContentMargin.swift */; }; + 426740A92B17391000C1DD73 /* Data+Hexadecimal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 426740A72B17390A00C1DD73 /* Data+Hexadecimal.swift */; }; + 42CA28BB2B1028330093B31A /* SimulatorThreadClientService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CA28BA2B1028330093B31A /* SimulatorThreadClientService.swift */; }; + 42DD84132B14ACAB00936F16 /* Color+ColorAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42DD84122B14ACAB00936F16 /* Color+ColorAsset.swift */; }; + 42DD84162B14D7AC00936F16 /* WebViewExternalBusMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42DD84142B14D68C00936F16 /* WebViewExternalBusMessage.swift */; }; + 42DD84192B14D83B00936F16 /* WebViewExternalBusMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42DD84182B14D83B00936F16 /* WebViewExternalBusMessageTests.swift */; }; + 42F5CAB92B10AD9800409816 /* ThreadCredentialsSharingViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F5CAB82B10AD9800409816 /* ThreadCredentialsSharingViewModelTests.swift */; }; + 42F5CABC2B10AE1A00409816 /* ServerFixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F5CABB2B10AE1A00409816 /* ServerFixture.swift */; }; + 42F5CAE52B10CDC600409816 /* HACornerRadius.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CA28AD2B101D4D0093B31A /* HACornerRadius.swift */; }; + 42F5CAE72B10CDC900409816 /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CA28AF2B101D6B0093B31A /* CardView.swift */; }; + 42F5CAE82B10CDC900409816 /* HAButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CA28B52B1022680093B31A /* HAButton.swift */; }; + 42F5CAE92B10CED600409816 /* ThreadCredentialsSharingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CA28B32B101E560093B31A /* ThreadCredentialsSharingViewModel.swift */; }; + 42F5CAEA2B10CED600409816 /* ThreadClientService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CA28B72B10279D0093B31A /* ThreadClientService.swift */; }; + 42F5CAEB2B10CED600409816 /* ThreadCredentialsSharing+build.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F5CADF2B10CD2D00409816 /* ThreadCredentialsSharing+build.swift */; }; + 42F5CAEC2B10CED600409816 /* ThreadCredentialsSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CA28A62B1012DE0093B31A /* ThreadCredentialsSharingView.swift */; }; + 42F5CAED2B10CF3A00409816 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B15042273188300635D5C /* Assets.swift */; }; 491E98FF25D543560077BBE3 /* LogbookEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491E98FE25D543560077BBE3 /* LogbookEntry.swift */; }; 491E990025D543560077BBE3 /* LogbookEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491E98FE25D543560077BBE3 /* LogbookEntry.swift */; }; 539AA1653F4BCDB61FE7C696 /* Pods_iOS_Shared_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 213EF66D14F92AF8BF2E9E98 /* Pods_iOS_Shared_iOS.framework */; }; @@ -726,12 +746,10 @@ B64E756823F3BFF200472C04 /* caribbean-green@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B64E754023F3BFF200472C04 /* caribbean-green@3x.png */; }; B655E915227FE88A00CFDC94 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B60247FE1FBD343000998205 /* InfoPlist.strings */; }; B657A8EA1CA646EB00121384 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B657A8E91CA646EB00121384 /* AppDelegate.swift */; }; - B657A8F31CA646EB00121384 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B657A8F21CA646EB00121384 /* Assets.xcassets */; }; B657A8F61CA646EB00121384 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B657A8F41CA646EB00121384 /* LaunchScreen.storyboard */; }; B657A90C1CA646EB00121384 /* HomeAssistantUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B657A90B1CA646EB00121384 /* HomeAssistantUITests.swift */; }; B658AA7E2250B2A000C9BFE3 /* MobileAppUpdateRegistrationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B658AA7C2250B25D00C9BFE3 /* MobileAppUpdateRegistrationRequest.swift */; }; B658AA7F2250B2A100C9BFE3 /* MobileAppUpdateRegistrationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B658AA7C2250B25D00C9BFE3 /* MobileAppUpdateRegistrationRequest.swift */; }; - B65B15052273188300635D5C /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B15042273188300635D5C /* Assets.swift */; }; B65C0B522282BA13007E057B /* NotificationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65C0B512282BA13007E057B /* NotificationSettingsViewController.swift */; }; B6617EED1CFE79AD004DEE6D /* NSURL+QueryDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6617EEC1CFE79AD004DEE6D /* NSURL+QueryDictionary.swift */; }; B661FB68226B961400E541DD /* WebSocketBridge.js in Resources */ = {isa = PBXBuildFile; fileRef = B661FB67226B961400E541DD /* WebSocketBridge.js */; }; @@ -1575,6 +1593,35 @@ 42BDF44A2A8EA5AB00562183 /* SecurityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityViewController.swift; sourceTree = ""; }; 42BDF44D2A8EAD2700562183 /* BiometricsAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricsAuthenticationService.swift; sourceTree = ""; }; 42E9ADE32B064AF0003772BC /* BiometricsAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricsAuthenticationViewController.swift; sourceTree = ""; }; + 420B10032B1CF6D800D383D8 /* SharedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SharedAssets.xcassets; sourceTree = ""; }; + 420B10082B1D129B00D383D8 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; + 420B100B2B1D204400D383D8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 422894BF2B03B76200C1DAFB /* ThreadNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ThreadNetwork.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.2.sdk/System/Library/Frameworks/ThreadNetwork.framework; sourceTree = DEVELOPER_DIR; }; + 424A7F452B188946008C8DF3 /* WidgetBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBackground.swift; sourceTree = ""; }; + 424A7F472B188BF3008C8DF3 /* WidgetContentMargin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetContentMargin.swift; sourceTree = ""; }; + 426740A72B17390A00C1DD73 /* Data+Hexadecimal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Hexadecimal.swift"; sourceTree = ""; }; + 42805A132B0226050095414C /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Base; path = Base.lproj/AppIntentVocabulary.plist; sourceTree = ""; }; + 42CA28A62B1012DE0093B31A /* ThreadCredentialsSharingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadCredentialsSharingView.swift; sourceTree = ""; }; + 42CA28AD2B101D4D0093B31A /* HACornerRadius.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HACornerRadius.swift; sourceTree = ""; }; + 42CA28AF2B101D6B0093B31A /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = ""; }; + 42CA28B32B101E560093B31A /* ThreadCredentialsSharingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadCredentialsSharingViewModel.swift; sourceTree = ""; }; + 42CA28B52B1022680093B31A /* HAButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAButton.swift; sourceTree = ""; }; + 42CA28B72B10279D0093B31A /* ThreadClientService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadClientService.swift; sourceTree = ""; }; + 42CA28BA2B1028330093B31A /* SimulatorThreadClientService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorThreadClientService.swift; sourceTree = ""; }; + 42DD84122B14ACAB00936F16 /* Color+ColorAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+ColorAsset.swift"; sourceTree = ""; }; + 42DD84142B14D68C00936F16 /* WebViewExternalBusMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewExternalBusMessage.swift; sourceTree = ""; }; + 42DD84182B14D83B00936F16 /* WebViewExternalBusMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewExternalBusMessageTests.swift; sourceTree = ""; }; + 42DD84322B15DC2F00936F16 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Intents.strings; sourceTree = ""; }; + 42DD84332B15DC2F00936F16 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Frontend.strings; sourceTree = ""; }; + 42DD84342B15DC2F00936F16 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoPlist.strings; sourceTree = ""; }; + 42DD84352B15DC2F00936F16 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; + 42DD84362B15DC3F00936F16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Intents.strings; sourceTree = ""; }; + 42DD84372B15DC3F00936F16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Frontend.strings; sourceTree = ""; }; + 42DD84382B15DC3F00936F16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/InfoPlist.strings; sourceTree = ""; }; + 42DD84392B15DC3F00936F16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; + 42F5CAB82B10AD9800409816 /* ThreadCredentialsSharingViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadCredentialsSharingViewModelTests.swift; sourceTree = ""; }; + 42F5CABB2B10AE1A00409816 /* ServerFixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerFixture.swift; sourceTree = ""; }; + 42F5CADF2B10CD2D00409816 /* ThreadCredentialsSharing+build.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreadCredentialsSharing+build.swift"; sourceTree = ""; }; 479C2CCB032E2A0ECDE45B87 /* Pods-Tests-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests-App/Pods-Tests-App.debug.xcconfig"; sourceTree = ""; }; 491E98FE25D543560077BBE3 /* LogbookEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogbookEntry.swift; sourceTree = ""; }; 553A33E097387AA44265DB13 /* Pods-iOS-App-metadata.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "Pods-iOS-App-metadata.plist"; path = "Pods/Pods-iOS-App-metadata.plist"; sourceTree = ""; }; @@ -1859,7 +1906,6 @@ B655E927227FF19E00CFDC94 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Intents.strings; sourceTree = ""; }; B657A8E61CA646EB00121384 /* Home Assistant Δ.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Home Assistant Δ.app"; sourceTree = BUILT_PRODUCTS_DIR; }; B657A8E91CA646EB00121384 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - B657A8F21CA646EB00121384 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B657A8F51CA646EB00121384 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B657A8F71CA646EB00121384 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B657A8FC1CA646EB00121384 /* HomeAssistant-Tests-App.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "HomeAssistant-Tests-App.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2312,6 +2358,8 @@ 115560E027010D8400A8F818 /* WidgetBasicContainerView.swift */, 1165705527018C4E003906A7 /* WidgetEmptyView.swift */, 115560E227010DAB00A8F818 /* WidgetBasicView.swift */, + 424A7F452B188946008C8DF3 /* WidgetBackground.swift */, + 424A7F472B188BF3008C8DF3 /* WidgetContentMargin.swift */, ); path = Common; sourceTree = ""; @@ -2578,11 +2626,13 @@ 11AD2E2A2528FDB700FBC437 /* WebView */ = { isa = PBXGroup; children = ( + 42DD84172B14D83400936F16 /* Tests */, 11DE822F24FAE66F00E636B8 /* UIWindow+Additions.swift */, 113FB1122515A065000AC680 /* ScaleFactorMutator.swift */, 11DE822D24FAC51000E636B8 /* IncomingURLHandler.swift */, B64BB3A71E9C6551001E8B46 /* WebViewController.swift */, 11EFCDD224F5F39100314D85 /* WebViewWindowController.swift */, + 42DD84142B14D68C00936F16 /* WebViewExternalBusMessage.swift */, ); path = WebView; sourceTree = ""; @@ -2904,6 +2954,7 @@ 29278BB24639BA945D3D86B4 /* Frameworks */ = { isa = PBXGroup; children = ( + 422894BF2B03B76200C1DAFB /* ThreadNetwork.framework */, 11B63B0E297A19DC00D908ED /* MatterSupport.framework */, 1112EA92271B78690038BBFC /* UserNotifications.framework */, 117318AC25199E220013E010 /* Foundation.framework */, @@ -2952,6 +3003,93 @@ 42E9ADE32B064AF0003772BC /* BiometricsAuthenticationViewController.swift */, ); path = LocalAuthentication; + 426740A42B17348700C1DD73 /* Assets */ = { + isa = PBXGroup; + children = ( + B65B15042273188300635D5C /* Assets.swift */, + 420B10032B1CF6D800D383D8 /* SharedAssets.xcassets */, + 420B10082B1D129B00D383D8 /* Colors.xcassets */, + ); + path = Assets; + sourceTree = ""; + }; + 426740A62B1738F900C1DD73 /* Extensions */ = { + isa = PBXGroup; + children = ( + 426740A72B17390A00C1DD73 /* Data+Hexadecimal.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 42CA28A52B1012B00093B31A /* ThreadCredentialsSharing */ = { + isa = PBXGroup; + children = ( + 42F5CAB72B10AD8C00409816 /* Tests */, + 42CA28B92B1028250093B31A /* Mocks */, + 42CA28A62B1012DE0093B31A /* ThreadCredentialsSharingView.swift */, + 42CA28B32B101E560093B31A /* ThreadCredentialsSharingViewModel.swift */, + 42CA28B72B10279D0093B31A /* ThreadClientService.swift */, + 42F5CADF2B10CD2D00409816 /* ThreadCredentialsSharing+build.swift */, + ); + path = ThreadCredentialsSharing; + sourceTree = ""; + }; + 42CA28AC2B101D320093B31A /* DesignSystem */ = { + isa = PBXGroup; + children = ( + 42CA28B12B101D9C0093B31A /* Components */, + ); + path = DesignSystem; + sourceTree = ""; + }; + 42CA28B12B101D9C0093B31A /* Components */ = { + isa = PBXGroup; + children = ( + 42CA28AF2B101D6B0093B31A /* CardView.swift */, + 42CA28B52B1022680093B31A /* HAButton.swift */, + ); + path = Components; + sourceTree = ""; + }; + 42CA28B22B101DA70093B31A /* Utilities */ = { + isa = PBXGroup; + children = ( + 42CA28AD2B101D4D0093B31A /* HACornerRadius.swift */, + 42DD84122B14ACAB00936F16 /* Color+ColorAsset.swift */, + ); + path = Utilities; + sourceTree = ""; + }; + 42CA28B92B1028250093B31A /* Mocks */ = { + isa = PBXGroup; + children = ( + 42CA28BA2B1028330093B31A /* SimulatorThreadClientService.swift */, + ); + path = Mocks; + sourceTree = ""; + }; + 42DD84172B14D83400936F16 /* Tests */ = { + isa = PBXGroup; + children = ( + 42DD84182B14D83B00936F16 /* WebViewExternalBusMessageTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; + 42F5CAB72B10AD8C00409816 /* Tests */ = { + isa = PBXGroup; + children = ( + 42F5CAB82B10AD9800409816 /* ThreadCredentialsSharingViewModelTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; + 42F5CABA2B10AE0C00409816 /* Fixtures */ = { + isa = PBXGroup; + children = ( + 42F5CABB2B10AE1A00409816 /* ServerFixture.swift */, + ); + path = Fixtures; sourceTree = ""; }; 9C4E5E20229D97FA0044C8EC /* Configuration */ = { @@ -3261,6 +3399,7 @@ 11EFCDD424F5FA7E00314D85 /* Scenes */, B661FB6B226BCC8500E541DD /* Settings */, 11B7ECD9274DA521009AD634 /* Servers */, + 42CA28A52B1012B00093B31A /* ThreadCredentialsSharing */, B679B1FA1E1F3D020071D366 /* Utilities */, 11AD2E2A2528FDB700FBC437 /* WebView */, 11A71C6924A463EE00D9565F /* ZoneManager */, @@ -3453,9 +3592,8 @@ B69933961E232AF50054453D /* Resources */ = { isa = PBXGroup; children = ( + 420B100B2B1D204400D383D8 /* Assets.xcassets */, B672AB022215057600175465 /* Alternate Icons */, - B65B15042273188300635D5C /* Assets.swift */, - B657A8F21CA646EB00121384 /* Assets.xcassets */, B672AB552216B07500175465 /* gallery.ckcomplication */, B658AA7622506DAF00C9BFE3 /* GoogleService-Info-Beta.plist */, B658AA7222506AD400C9BFE3 /* GoogleService-Info-Debug.plist */, @@ -3571,6 +3709,7 @@ D014EEA82128E192008EA6F5 /* ConnectionInfo.swift */, B66D6B1F2227A2EA009D8B90 /* WatchHelpers.swift */, D0BE440B2104224A00C74314 /* Authentication */, + 42F5CABA2B10AE0C00409816 /* Fixtures */, B672333A225DB66A0031D629 /* WebSocket */, B6E91C212232482A0014CB8D /* Webhook */, D0B25BD0213218B000678C2C /* Requests */, @@ -3595,16 +3734,20 @@ isa = PBXGroup; children = ( 42BDF44C2A8EAD1900562183 /* LocalAuthentication */, + 426740A42B17348700C1DD73 /* Assets */, + 42CA28AC2B101D320093B31A /* DesignSystem */, 11B38EE0275C545C00205C7B /* Intents */, D014EEAA212928EC008EA6F5 /* API */, D0FF79C920D7787F0034574D /* ClientEvents */, D00302BC20D4BEC0004C2CA9 /* Environment */, D0FF79D020D87CF60034574D /* Common */, + 426740A62B1738F900C1DD73 /* Extensions */, 11F855D124DF6C7A0018013E /* Iconic */, D0B25BC72130C9BB00678C2C /* Location */, D0EEF325214DF30D00D1D360 /* Notifications */, D0EEF31C214DDD3800D1D360 /* Resources */, D0C884782122A64500CCB501 /* Settings */, + 42CA28B22B101DA70093B31A /* Utilities */, D03D891920E0A85300D4F28D /* Shared.h */, ); path = Shared; @@ -4437,6 +4580,8 @@ "pt-BR", ml, hu, + he, + et, ); mainGroup = B657A8DD1CA646EB00121384; productRefGroup = B657A8E71CA646EB00121384 /* Products */; @@ -4551,6 +4696,7 @@ B64E756723F3BFF200472C04 /* black@2x.png in Resources */, B606168E1D1F117700249C11 /* US-EN-Alexa-Motion-In-Front-Yard.wav in Resources */, B60616261D1F117700249C11 /* US-EN-Morgan-Freeman-Motion-In-Garage.wav in Resources */, + 420B100C2B1D204400D383D8 /* Assets.xcassets in Resources */, B60616941D1F117800249C11 /* US-EN-Alexa-Smoke-Detected-In-Garage.wav in Resources */, B606163F1D1F117700249C11 /* US-EN-Morgan-Freeman-Turning-Off-The-Bar-Lights.wav in Resources */, B64E754B23F3BFF200472C04 /* fire-orange@2x.png in Resources */, @@ -4589,7 +4735,6 @@ B60616831D1F117700249C11 /* US-EN-Alexa-Garage-Door-Opened.wav in Resources */, B60616401D1F117700249C11 /* US-EN-Morgan-Freeman-Turning-Off-The-Chandelier.wav in Resources */, B60616981D1F117800249C11 /* US-EN-Alexa-Water-Detected-In-Basement.wav in Resources */, - B657A8F31CA646EB00121384 /* Assets.xcassets in Resources */, B60616B21D1F117800249C11 /* US-EN-Daisy-Garage-Door-Open.wav in Resources */, B60616171D1F117700249C11 /* US-EN-Morgan-Freeman-Friend-Is-Arriving.wav in Resources */, B606160C1D1F117700249C11 /* US-EN-Morgan-Freeman-Back-Door-Opened.wav in Resources */, @@ -4807,6 +4952,8 @@ 1165705A2702A3BA003906A7 /* Frontend.strings in Resources */, B63D28BD215D9E3600F3B907 /* Localizable.strings in Resources */, 11F855D624DF6C7A0018013E /* MaterialDesignIcons.ttf in Resources */, + 420B10092B1D12DD00D383D8 /* Colors.xcassets in Resources */, + 420B10042B1CF6D800D383D8 /* SharedAssets.xcassets in Resources */, 12D447D93F82395EF40487B5 /* Pods-iOS-Shared-iOS-metadata.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5349,10 +5496,12 @@ buildActionMask = 2147483647; files = ( 110E694424E77125004AA96D /* WidgetActionsProvider.swift in Sources */, + 424A7F482B188BF3008C8DF3 /* WidgetContentMargin.swift in Sources */, 115560E127010D8400A8F818 /* WidgetBasicContainerView.swift in Sources */, 115560EE27012F7300A8F818 /* WidgetOpenPage.swift in Sources */, 115560E327010DAB00A8F818 /* WidgetBasicView.swift in Sources */, 1171507024DFCDE60065E874 /* Widgets.swift in Sources */, + 424A7F462B188946008C8DF3 /* WidgetBackground.swift in Sources */, 115560F227012FE100A8F818 /* WidgetOpenPageProvider.swift in Sources */, 1165705627018C4E003906A7 /* WidgetEmptyView.swift in Sources */, 1171508124DFCEC50065E874 /* WidgetActions.swift in Sources */, @@ -5422,16 +5571,19 @@ 11195F6B267EFB1F003DF674 /* NotificationManagerLocalPushInterface.swift in Sources */, B6022213226DAC9D00E8DBFE /* ScaledFont.swift in Sources */, 42BDF44B2A8EA5AB00562183 /* SecurityViewController.swift in Sources */, + 42F5CAEC2B10CED600409816 /* ThreadCredentialsSharingView.swift in Sources */, 1112AE9B25F71775007A541A /* LocationHistoryListViewController.swift in Sources */, B68EDD03215F0E2900DD6B28 /* NotificationCategoryConfigurator.swift in Sources */, D0FF79D220D87D200034574D /* ClientEventTableViewController.swift in Sources */, 117D8A0824A9347F00580913 /* UIColor+CSSRGB.swift in Sources */, + 42F5CAE92B10CED600409816 /* ThreadCredentialsSharingViewModel.swift in Sources */, 11F3D74C2495377B00C05BBA /* SensorListViewController.swift in Sources */, B6617EED1CFE79AD004DEE6D /* NSURL+QueryDictionary.swift in Sources */, B626AAF11D8F972800A0D225 /* SettingsDetailViewController.swift in Sources */, 1127381C2622B6F300F5E312 /* DebugSettingsViewController.swift in Sources */, 11DE823024FAE66F00E636B8 /* UIWindow+Additions.swift in Sources */, 11DA6B4F2713912F008ADFAF /* OnboardingPermissionViewController.swift in Sources */, + 42CA28BB2B1028330093B31A /* SimulatorThreadClientService.swift in Sources */, 1127383C2625512600F5E312 /* ButtonRowWithLoading.swift in Sources */, 11195F71267EFE2C003DF674 /* NotificationManagerLocalPushInterfaceUnsupported.swift in Sources */, 1130F57E253A2ED500F371BE /* ComplicationFamilySelectViewController.swift in Sources */, @@ -5489,6 +5641,7 @@ D0C88462211ED16300CCB501 /* OnboardingAuth.swift in Sources */, 11EFCDDC24F6065F00314D85 /* AboutSceneDelegate.swift in Sources */, B6D8A32A2271455300FA765D /* OnboardingErrorViewController.swift in Sources */, + 42F5CAEA2B10CED600409816 /* ThreadClientService.swift in Sources */, B648AE252275918F006972AF /* Segues.swift in Sources */, 11ADB13E24C29E6900FF5EB2 /* ZoneManagerRegionFilter.swift in Sources */, 11DE822E24FAC51100E636B8 /* IncomingURLHandler.swift in Sources */, @@ -5505,8 +5658,8 @@ B605C891226E9DAC00EF46DD /* Permissions.swift in Sources */, 1169B7AD25AA76E30035F2AE /* MaterialDesignIcons+Eureka.swift in Sources */, 11F55ECD25D3A364003977AC /* NotificationRateLimitViewController.swift in Sources */, + 42F5CAEB2B10CED600409816 /* ThreadCredentialsSharing+build.swift in Sources */, 117EBC32261D398B00F5334A /* ZoneManagerAccuracyFuzzer.swift in Sources */, - B65B15052273188300635D5C /* Assets.swift in Sources */, 113FB1132515A065000AC680 /* ScaleFactorMutator.swift in Sources */, 1185DFB3271FF53800ED7D9A /* OnboardingAuthStepSensors.swift in Sources */, 11108D632634C8FE009DAB0F /* LearnMoreButtonRow.swift in Sources */, @@ -5528,10 +5681,12 @@ 1185DFAE271FF53800ED7D9A /* OnboardingAuthStepConfig.swift in Sources */, B661FB7A226C197900E541DD /* OnboardingManualURLViewController.swift in Sources */, 119A827C252A3C4700D7000D /* NFCNDEFPayload+Additions.swift in Sources */, + 42DD84162B14D7AC00936F16 /* WebViewExternalBusMessage.swift in Sources */, 11EFCDD324F5F39100314D85 /* WebViewWindowController.swift in Sources */, 11EFCDE024F60E5900314D85 /* BasicSceneDelegate.swift in Sources */, 11A71C6F24A4644A00D9565F /* ZoneManagerIgnoreReason.swift in Sources */, 1101568324D770B2009424C9 /* iOSTagManager.swift in Sources */, + 42F5CABC2B10AE1A00409816 /* ServerFixture.swift in Sources */, 11B1FFC524CCD72F00F9BCB2 /* VoiceShortcutRow.swift in Sources */, 1168BF33271809C600DD4D15 /* OnboardingAuthError.swift in Sources */, B661FB6F226BCCAD00E541DD /* ConnectionSettingsViewController.swift in Sources */, @@ -5543,6 +5698,7 @@ buildActionMask = 2147483647; files = ( 11A71C7624A5028200D9565F /* ZoneManagerEvent.test.swift in Sources */, + 42DD84192B14D83B00936F16 /* WebViewExternalBusMessageTests.swift in Sources */, 116D3A4627252C3200EF5D21 /* OnboardingAuthStepConfig.test.swift in Sources */, 11C95E3628BC20EA00171F1C /* OnboardingAuthLoginViewController.test.swift in Sources */, 117D8A0A24A9381F00580913 /* UIColor+CSSRGB.test.swift in Sources */, @@ -5553,6 +5709,7 @@ 11A71C9124A598AB00D9565F /* ZoneManagerProcessor.test.swift in Sources */, 11ED439827265B9C00B5FD45 /* OnboardingAuthStepNotify.test.swift in Sources */, 11EFD3C327264306000AF78B /* UIAlertAction+Additions.swift in Sources */, + 42F5CAB92B10AD9800409816 /* ThreadCredentialsSharingViewModelTests.swift in Sources */, 11A71C8F24A5946B00D9565F /* FakeCLLocationManager.swift in Sources */, 11EF62DA24C3687D00BABB64 /* ZoneManagerRegionFilter.test.swift in Sources */, 11A71C8724A5074E00D9565F /* ZoneManager.test.swift in Sources */, @@ -5798,6 +5955,8 @@ 118BDA8825A6DBBA00731016 /* FrontmostAppSensor.swift in Sources */, 11EE9B4624C4E01500404AF8 /* SharedPlist.swift in Sources */, 1110836824AFEFA60027A67A /* Promise+WebhookJson.swift in Sources */, + 42F5CAE72B10CDC900409816 /* CardView.swift in Sources */, + 42DD84132B14ACAB00936F16 /* Color+ColorAsset.swift in Sources */, 115560E827011E3300A8F818 /* HAPanel.swift in Sources */, 11C9E43B2505B04E00492A88 /* HACoreAudioObjectSystem.swift in Sources */, D0C88464211F33CE00CCB501 /* TokenManager.swift in Sources */, @@ -5846,6 +6005,7 @@ 11169BC8262BE460005EF90A /* UNNotificationContent+Additions.swift in Sources */, 11AF4D14249C7E09006C74C0 /* ActivitySensor.swift in Sources */, 11B38EE9275C54A200205C7B /* GetCameraImageIntentHandler.swift in Sources */, + 426740A92B17391000C1DD73 /* Data+Hexadecimal.swift in Sources */, D0EEF306214DD3CF00D1D360 /* ObjectMapperTransformers.swift in Sources */, 113A8D49283C7B1700B9DA32 /* PeriodicUpdateManager.swift in Sources */, 11AF4D22249C924B006C74C0 /* GeocoderSensor.swift in Sources */, @@ -5876,6 +6036,7 @@ 111D295624F30E2400C8A7D1 /* Updater.swift in Sources */, 11B38EE5275C54A200205C7B /* SendLocationIntentHandler.swift in Sources */, 1120C57F274638330046C38B /* PerServerContainer.swift in Sources */, + 42F5CAE82B10CDC900409816 /* HAButton.swift in Sources */, B672334D225DE1490031D629 /* SubscribeEvents.swift in Sources */, 1168BF302718070400DD4D15 /* NSMutableAttributedString+Additions.swift in Sources */, 1104FC9125322C1800B8BE34 /* Dictionary+Additions.swift in Sources */, @@ -5898,6 +6059,7 @@ 11C4628224B053A800031902 /* WebhookResponseUpdateSensors.swift in Sources */, 11F3847B24FB27FC00CB0D74 /* DeviceWrapperBatteryObserver.swift in Sources */, B6B74CB82283983300D58A68 /* WatchComplication.swift in Sources */, + 42F5CAED2B10CF3A00409816 /* Assets.swift in Sources */, 11F855DC24DF6C7A0018013E /* IconImageView.swift in Sources */, 11A48D7D24CA7E4E0021BDD9 /* NotificationCategory.swift in Sources */, 1121CD4927128A970071C2AA /* UIView+StackView.swift in Sources */, @@ -5918,6 +6080,7 @@ D0BE440A2104224600C74314 /* TokenInfo.swift in Sources */, 119EC3C724D5119300617D51 /* MobileAppConfig.swift in Sources */, B658AA7E2250B2A000C9BFE3 /* MobileAppUpdateRegistrationRequest.swift in Sources */, + 42F5CAE52B10CDC600409816 /* HACornerRadius.swift in Sources */, 1182620724F9C492000795C6 /* HACoreMediaObjectCamera.swift in Sources */, B6221F6622266FA000502A30 /* WebhookRequest.swift in Sources */, 1104FD05253292CD00B8BE34 /* Guarantee+Additions.swift in Sources */, @@ -6192,6 +6355,8 @@ 11B154A327ABAADA00462185 /* pt-BR */, 1128FF38297E5F7D00BAAFD9 /* ml */, 11EFB44C29D89FD100CE4B05 /* hu */, + 42DD84332B15DC2F00936F16 /* he */, + 42DD84372B15DC3F00936F16 /* et */, ); name = Frontend.strings; sourceTree = ""; @@ -6239,6 +6404,8 @@ 11B154A427ABAADA00462185 /* pt-BR */, 1128FF39297E5F7D00BAAFD9 /* ml */, 11EFB44D29D89FD100CE4B05 /* hu */, + 42DD84342B15DC2F00936F16 /* he */, + 42DD84382B15DC3F00936F16 /* et */, ); name = InfoPlist.strings; sourceTree = ""; @@ -6278,6 +6445,8 @@ 11B154A227ABAADA00462185 /* pt-BR */, 1128FF37297E5F7D00BAAFD9 /* ml */, 11EFB44B29D89FD100CE4B05 /* hu */, + 42DD84322B15DC2F00936F16 /* he */, + 42DD84362B15DC3F00936F16 /* et */, ); name = Intents.intentdefinition; sourceTree = ""; @@ -6333,6 +6502,8 @@ 11B154A527ABAADA00462185 /* pt-BR */, 1128FF3A297E5F7D00BAAFD9 /* ml */, 11EFB44E29D89FD100CE4B05 /* hu */, + 42DD84352B15DC2F00936F16 /* he */, + 42DD84392B15DC3F00936F16 /* et */, ); name = Localizable.strings; path = .; @@ -6728,6 +6899,7 @@ "-framework", "\"UIKit\"", "$(inherited)", + "-fprofile-instr-generate", ); PRODUCT_MODULE_NAME = HomeAssistant; PRODUCT_NAME = "Home Assistant β"; @@ -6942,6 +7114,7 @@ "-framework", "\"UIKit\"", "$(inherited)", + "-fprofile-instr-generate", ); PRODUCT_MODULE_NAME = HomeAssistant; PRODUCT_NAME = "Home Assistant Δ"; @@ -6983,6 +7156,7 @@ "-framework", "\"UIKit\"", "$(inherited)", + "-fprofile-instr-generate", ); PRODUCT_MODULE_NAME = HomeAssistant; PRODUCT_NAME = "Home Assistant"; diff --git a/HomeAssistant.xcodeproj/xcshareddata/xcschemes/App-Debug.xcscheme b/HomeAssistant.xcodeproj/xcshareddata/xcschemes/App-Debug.xcscheme index d89a7ad3b4..6d053cd02c 100644 --- a/HomeAssistant.xcodeproj/xcshareddata/xcschemes/App-Debug.xcscheme +++ b/HomeAssistant.xcodeproj/xcshareddata/xcschemes/App-Debug.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift index edc1510bff..a0ab616e5d 100644 --- a/Sources/App/AppDelegate.swift +++ b/Sources/App/AppDelegate.swift @@ -423,7 +423,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - func checkForAlerts() { + private func checkForAlerts() { firstly { Current.serverAlerter.check(dueToUserInteraction: false) }.done { [sceneManager] alert in @@ -478,7 +478,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - func setupWatchCommunicator() { + private func setupWatchCommunicator() { Current.servers.add(observer: self) // This directly mutates the data structure for observations to avoid race conditions. @@ -558,7 +558,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ = Communicator.shared } - func setupiOS12Features() { + private func setupiOS12Features() { // Tell the system we have a app notification settings screen and want critical alerts // This is effectively a migration @@ -581,7 +581,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { }) } - func setupFirebase() { + private func setupFirebase() { let optionsFile: String = { switch Current.appConfiguration { case .Beta: return "GoogleService-Info-Beta" @@ -599,7 +599,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { notificationManager.setupFirebase() } - func setupModels() { + private func setupModels() { // Force Realm migration to happen now _ = Realm.live() @@ -610,7 +610,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { WidgetOpenPageIntent.setupObserver() } - func setupMenus() { + private func setupMenus() { NotificationCenter.default.addObserver( self, selector: #selector(menuRelatedSettingDidChange(_:)), diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index c9e5019648..11e3593e70 100644 --- a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,176 +1,74 @@ { - "images": [ + "images" : [ { - "filename": "release-40x40.png", - "idiom": "iphone", - "scale": "2x", - "size": "20x20" + "filename" : "release-1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" }, { - "filename": "release-60x60.png", - "idiom": "iphone", - "scale": "3x", - "size": "20x20" + "filename" : "16-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" }, { - "filename": "release-58x58.png", - "idiom": "iphone", - "scale": "2x", - "size": "29x29" + "filename" : "32-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" }, { - "filename": "release-87x87.png", - "idiom": "iphone", - "scale": "3x", - "size": "29x29" + "filename" : "32-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" }, { - "filename": "release-80x80.png", - "idiom": "iphone", - "scale": "2x", - "size": "40x40" + "filename" : "64-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" }, { - "filename": "release-120x120.png", - "idiom": "iphone", - "scale": "3x", - "size": "40x40" + "filename" : "128-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" }, { - "filename": "release-120x120.png", - "idiom": "iphone", - "scale": "2x", - "size": "60x60" + "filename" : "256-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" }, { - "filename": "release-180x180.png", - "idiom": "iphone", - "scale": "3x", - "size": "60x60" + "filename" : "256-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" }, { - "filename": "release-20x20.png", - "idiom": "ipad", - "scale": "1x", - "size": "20x20" + "filename" : "512-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" }, { - "filename": "release-40x40.png", - "idiom": "ipad", - "scale": "2x", - "size": "20x20" + "filename" : "512-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" }, { - "filename": "release-29x29.png", - "idiom": "ipad", - "scale": "1x", - "size": "29x29" - }, - { - "filename": "release-58x58.png", - "idiom": "ipad", - "scale": "2x", - "size": "29x29" - }, - { - "filename": "release-40x40.png", - "idiom": "ipad", - "scale": "1x", - "size": "40x40" - }, - { - "filename": "release-80x80.png", - "idiom": "ipad", - "scale": "2x", - "size": "40x40" - }, - { - "filename": "release-76x76.png", - "idiom": "ipad", - "scale": "1x", - "size": "76x76" - }, - { - "filename": "release-152x152.png", - "idiom": "ipad", - "scale": "2x", - "size": "76x76" - }, - { - "filename": "release-167x167.png", - "idiom": "ipad", - "scale": "2x", - "size": "83.5x83.5" - }, - { - "filename": "release-1024x1024.png", - "idiom": "ios-marketing", - "scale": "1x", - "size": "1024x1024" - }, - { - "filename": "16-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "16x16" - }, - { - "filename": "32-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "16x16" - }, - { - "filename": "32-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "32x32" - }, - { - "filename": "64-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "32x32" - }, - { - "filename": "128-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "128x128" - }, - { - "filename": "256-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "128x128" - }, - { - "filename": "256-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "256x256" - }, - { - "filename": "512-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "256x256" - }, - { - "filename": "512-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "512x512" - }, - { - "filename": "1024-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "512x512" + "filename" : "1024-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" } ], - "info": { - "author": "xcode", - "version": 1 + "info" : { + "author" : "xcode", + "version" : 1 } } diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-120x120.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-120x120.png deleted file mode 100644 index 09129b862e..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-120x120.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-152x152.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-152x152.png deleted file mode 100644 index 69286aa911..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-152x152.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-167x167.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-167x167.png deleted file mode 100644 index ec1dee19dd..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-167x167.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-180x180.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-180x180.png deleted file mode 100644 index 3af8e9addf..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-180x180.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-20x20.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-20x20.png deleted file mode 100644 index c75e16ae13..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-20x20.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-29x29.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-29x29.png deleted file mode 100644 index 3e7e5876e4..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-29x29.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-40x40.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-40x40.png deleted file mode 100644 index 549528e583..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-40x40.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-58x58.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-58x58.png deleted file mode 100644 index 43d151999e..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-58x58.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-60x60.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-60x60.png deleted file mode 100644 index 4b5ac5d823..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-60x60.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-76x76.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-76x76.png deleted file mode 100644 index fe4e9ec962..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-76x76.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-80x80.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-80x80.png deleted file mode 100644 index f6285f3ccd..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-80x80.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-87x87.png b/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-87x87.png deleted file mode 100644 index 998a59fc91..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.appiconset/release-87x87.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/Contents.json b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/Contents.json index 0279282d81..9bca5e376e 100644 --- a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/Contents.json +++ b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/Contents.json @@ -1,176 +1,74 @@ { - "images": [ + "images" : [ { - "filename": "beta-40x40.png", - "idiom": "iphone", - "scale": "2x", - "size": "20x20" + "filename" : "beta-1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" }, { - "filename": "beta-60x60.png", - "idiom": "iphone", - "scale": "3x", - "size": "20x20" + "filename" : "16-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" }, { - "filename": "beta-58x58.png", - "idiom": "iphone", - "scale": "2x", - "size": "29x29" + "filename" : "32-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" }, { - "filename": "beta-87x87.png", - "idiom": "iphone", - "scale": "3x", - "size": "29x29" + "filename" : "32-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" }, { - "filename": "beta-80x80.png", - "idiom": "iphone", - "scale": "2x", - "size": "40x40" + "filename" : "64-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" }, { - "filename": "beta-120x120.png", - "idiom": "iphone", - "scale": "3x", - "size": "40x40" + "filename" : "128-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" }, { - "filename": "beta-120x120.png", - "idiom": "iphone", - "scale": "2x", - "size": "60x60" + "filename" : "256-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" }, { - "filename": "beta-180x180.png", - "idiom": "iphone", - "scale": "3x", - "size": "60x60" + "filename" : "256-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" }, { - "filename": "beta-20x20.png", - "idiom": "ipad", - "scale": "1x", - "size": "20x20" + "filename" : "512-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" }, { - "filename": "beta-40x40.png", - "idiom": "ipad", - "scale": "2x", - "size": "20x20" + "filename" : "512-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" }, { - "filename": "beta-29x29.png", - "idiom": "ipad", - "scale": "1x", - "size": "29x29" - }, - { - "filename": "beta-58x58.png", - "idiom": "ipad", - "scale": "2x", - "size": "29x29" - }, - { - "filename": "beta-40x40.png", - "idiom": "ipad", - "scale": "1x", - "size": "40x40" - }, - { - "filename": "beta-80x80.png", - "idiom": "ipad", - "scale": "2x", - "size": "40x40" - }, - { - "filename": "beta-76x76.png", - "idiom": "ipad", - "scale": "1x", - "size": "76x76" - }, - { - "filename": "beta-152x152.png", - "idiom": "ipad", - "scale": "2x", - "size": "76x76" - }, - { - "filename": "beta-167x167.png", - "idiom": "ipad", - "scale": "2x", - "size": "83.5x83.5" - }, - { - "filename": "beta-1024x1024.png", - "idiom": "ios-marketing", - "scale": "1x", - "size": "1024x1024" - }, - { - "filename": "16-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "16x16" - }, - { - "filename": "32-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "16x16" - }, - { - "filename": "32-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "32x32" - }, - { - "filename": "64-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "32x32" - }, - { - "filename": "128-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "128x128" - }, - { - "filename": "256-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "128x128" - }, - { - "filename": "256-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "256x256" - }, - { - "filename": "512-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "256x256" - }, - { - "filename": "512-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "512x512" - }, - { - "filename": "1024-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "512x512" + "filename" : "1024-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" } ], - "info": { - "author": "xcode", - "version": 1 + "info" : { + "author" : "xcode", + "version" : 1 } } diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-120x120.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-120x120.png deleted file mode 100644 index a70ac4304a..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-120x120.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-152x152.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-152x152.png deleted file mode 100644 index 74d97db50a..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-152x152.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-167x167.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-167x167.png deleted file mode 100644 index c602f48723..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-167x167.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-180x180.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-180x180.png deleted file mode 100644 index b083b624c4..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-180x180.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-20x20.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-20x20.png deleted file mode 100644 index 60884e0417..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-20x20.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-29x29.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-29x29.png deleted file mode 100644 index 7e5a5cc2fd..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-29x29.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-40x40.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-40x40.png deleted file mode 100644 index 7d0321fe3f..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-40x40.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-58x58.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-58x58.png deleted file mode 100644 index 79c0a3a516..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-58x58.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-60x60.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-60x60.png deleted file mode 100644 index 40dfc64c4e..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-60x60.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-76x76.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-76x76.png deleted file mode 100644 index f658df42bc..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-76x76.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-80x80.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-80x80.png deleted file mode 100644 index 893f298938..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-80x80.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-87x87.png b/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-87x87.png deleted file mode 100644 index dad846b9cf..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.beta.appiconset/beta-87x87.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/Contents.json b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/Contents.json index 439cefbeaa..9bebc6ffbc 100644 --- a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/Contents.json +++ b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/Contents.json @@ -1,176 +1,74 @@ { - "images": [ + "images" : [ { - "filename": "dev-40x40.png", - "idiom": "iphone", - "scale": "2x", - "size": "20x20" + "filename" : "dev-1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" }, { - "filename": "dev-60x60.png", - "idiom": "iphone", - "scale": "3x", - "size": "20x20" + "filename" : "16-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" }, { - "filename": "dev-58x58.png", - "idiom": "iphone", - "scale": "2x", - "size": "29x29" + "filename" : "32-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" }, { - "filename": "dev-87x87.png", - "idiom": "iphone", - "scale": "3x", - "size": "29x29" + "filename" : "32-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" }, { - "filename": "dev-80x80.png", - "idiom": "iphone", - "scale": "2x", - "size": "40x40" + "filename" : "64-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" }, { - "filename": "dev-120x120.png", - "idiom": "iphone", - "scale": "3x", - "size": "40x40" + "filename" : "128-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" }, { - "filename": "dev-120x120.png", - "idiom": "iphone", - "scale": "2x", - "size": "60x60" + "filename" : "256-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" }, { - "filename": "dev-180x180.png", - "idiom": "iphone", - "scale": "3x", - "size": "60x60" + "filename" : "256-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" }, { - "filename": "dev-20x20.png", - "idiom": "ipad", - "scale": "1x", - "size": "20x20" + "filename" : "512-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" }, { - "filename": "dev-40x40.png", - "idiom": "ipad", - "scale": "2x", - "size": "20x20" + "filename" : "512-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" }, { - "filename": "dev-29x29.png", - "idiom": "ipad", - "scale": "1x", - "size": "29x29" - }, - { - "filename": "dev-58x58.png", - "idiom": "ipad", - "scale": "2x", - "size": "29x29" - }, - { - "filename": "dev-40x40.png", - "idiom": "ipad", - "scale": "1x", - "size": "40x40" - }, - { - "filename": "dev-80x80.png", - "idiom": "ipad", - "scale": "2x", - "size": "40x40" - }, - { - "filename": "dev-76x76.png", - "idiom": "ipad", - "scale": "1x", - "size": "76x76" - }, - { - "filename": "dev-152x152.png", - "idiom": "ipad", - "scale": "2x", - "size": "76x76" - }, - { - "filename": "dev-167x167.png", - "idiom": "ipad", - "scale": "2x", - "size": "83.5x83.5" - }, - { - "filename": "dev-1024x1024.png", - "idiom": "ios-marketing", - "scale": "1x", - "size": "1024x1024" - }, - { - "filename": "16-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "16x16" - }, - { - "filename": "32-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "16x16" - }, - { - "filename": "32-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "32x32" - }, - { - "filename": "64-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "32x32" - }, - { - "filename": "128-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "128x128" - }, - { - "filename": "256-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "128x128" - }, - { - "filename": "256-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "256x256" - }, - { - "filename": "512-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "256x256" - }, - { - "filename": "512-mac.png", - "idiom": "mac", - "scale": "1x", - "size": "512x512" - }, - { - "filename": "1024-mac.png", - "idiom": "mac", - "scale": "2x", - "size": "512x512" + "filename" : "1024-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" } ], - "info": { - "author": "xcode", - "version": 1 + "info" : { + "author" : "xcode", + "version" : 1 } } diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-120x120.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-120x120.png deleted file mode 100644 index 33c41dd9cf..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-120x120.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-152x152.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-152x152.png deleted file mode 100644 index 47871a2a87..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-152x152.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-167x167.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-167x167.png deleted file mode 100644 index 1dcfad7b97..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-167x167.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-180x180.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-180x180.png deleted file mode 100644 index ba3e1878d4..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-180x180.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-20x20.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-20x20.png deleted file mode 100644 index 813ca86bc0..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-20x20.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-29x29.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-29x29.png deleted file mode 100644 index d2723e42ed..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-29x29.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-40x40.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-40x40.png deleted file mode 100644 index aabab88be2..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-40x40.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-58x58.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-58x58.png deleted file mode 100644 index b75d192fab..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-58x58.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-60x60.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-60x60.png deleted file mode 100644 index 1fe04b9e09..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-60x60.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-76x76.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-76x76.png deleted file mode 100644 index 4b96f7ed38..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-76x76.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-80x80.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-80x80.png deleted file mode 100644 index 9ba71eebe1..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-80x80.png and /dev/null differ diff --git a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-87x87.png b/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-87x87.png deleted file mode 100644 index f51bfa2e4e..0000000000 Binary files a/Sources/App/Resources/Assets.xcassets/AppIcon.dev.appiconset/dev-87x87.png and /dev/null differ diff --git a/Sources/App/Resources/en.lproj/Localizable.strings b/Sources/App/Resources/en.lproj/Localizable.strings index c5f42707ab..4035e507ad 100644 --- a/Sources/App/Resources/en.lproj/Localizable.strings +++ b/Sources/App/Resources/en.lproj/Localizable.strings @@ -324,6 +324,7 @@ Home Assistant is free and open source home automation software with a focus on "settings.developer.crashlytics_test.non_fatal.notification.body" = "When you press OK, a non-fatal error will be sent to Crashlytics. It may take up to 5 minutes to appear in the console."; "settings.developer.crashlytics_test.non_fatal.notification.title" = "About to submit a non-fatal error"; "settings.developer.crashlytics_test.non_fatal.title" = "Test Crashlytics Non-Fatal Error"; +"settings.developer.mock_thread_credentials_sharing.title" = "Simulator Thread Credentials Sharing"; "settings.developer.debug_strings.title" = "Debug strings"; "settings.developer.export_log_files.title" = "Export log files"; "settings.developer.footer" = "Don't use these if you don't know what you are doing!"; @@ -774,3 +775,8 @@ Home Assistant is free and open source home automation software with a focus on "widgets.open_page.not_configured" = "No Pages Available"; "widgets.open_page.title" = "Open Page"; "yes_label" = "Yes"; +"thread.credentials.network_name_title" = "Network Name"; +"thread.credentials.network_key_title" = "Network Key"; +"thread.credentials.border_agent_id_title" = "Border Agent ID"; +"thread.credentials.share_credentials_button_title" = "Share credential with Home Assistant"; +"thread.credentials.screen_title" = "Thread Credentials"; diff --git a/Sources/App/Resources/et.lproj/Frontend.strings b/Sources/App/Resources/et.lproj/Frontend.strings new file mode 100644 index 0000000000..6366458a75 --- /dev/null +++ b/Sources/App/Resources/et.lproj/Frontend.strings @@ -0,0 +1,12 @@ +"panel::calendar" = "Calendar"; +"panel::config" = "Settings"; +"panel::developer_tools" = "Developer Tools"; +"panel::energy" = "Energy"; +"panel::history" = "History"; +"panel::logbook" = "Logbook"; +"panel::mailbox" = "Mailbox"; +"panel::map" = "Map"; +"panel::media_browser" = "Media"; +"panel::profile" = "Profile"; +"panel::shopping_list" = "Shopping List"; +"panel::states" = "Overview"; \ No newline at end of file diff --git a/Sources/App/Resources/et.lproj/InfoPlist.strings b/Sources/App/Resources/et.lproj/InfoPlist.strings new file mode 100644 index 0000000000..30de817afc --- /dev/null +++ b/Sources/App/Resources/et.lproj/InfoPlist.strings @@ -0,0 +1,12 @@ +"NSLocationAlwaysAndWhenInUseUsageDescription" = "We suggest selecting \"Always Allow\" for the best location experience. Selecting \"Only While Using the App\" will disable iBeacons, geofences, background location updates and accurate reporting."; +"NSLocationAlwaysUsageDescription" = "We always need access to your location for features like iBeacons, geofences, background location updates and accurate reporting."; +"NSLocationWhenInUseUsageDescription" = "Only allowing location access while app is in use will disable iBeacons, geofences, background location updates and accurate reporting."; +"NSMotionUsageDescription" = "Motion is used to improve location updates with current motion type, as well as provide basic pedometer data."; +"NSSiriUsageDescription" = "We use Siri to allow created shortcuts to interact with the app."; +"NSPhotoLibraryAddUsageDescription" = "Photo Library access is needed to allow saving photos from the web view."; +"NSPhotoLibraryUsageDescription" = "Photo Library access is needed to allow saving photos from the web view."; +"NSLocalNetworkUsageDescription" = "Locate and communicate with your Home Assistant instance."; +"NFCReaderUsageDescription" = "Reading and writing NFC tags allows you to trigger events."; +"SEND_LOCATION_APP_SHORTCUT_TITLE" = "Send Location"; +"NSCameraUsageDescription" = "Take photos and send them to your Home Assistant server."; +"NSCrossWebsiteTrackingUsageDescription" = "Optionally enable cross-website tracking if your configuration requires it."; diff --git a/Sources/App/Resources/et.lproj/Intents.strings b/Sources/App/Resources/et.lproj/Intents.strings new file mode 100644 index 0000000000..ed6a093e9c --- /dev/null +++ b/Sources/App/Resources/et.lproj/Intents.strings @@ -0,0 +1,292 @@ +"0aqrcy" = "Failed: ${error}"; + +"2KWKqM" = "Failed: Home Assistant is not reachable"; + +"2LIxe4" = "Failed: ${error}"; + +"2b0Uo4" = "Service"; + +"3jKamg" = "{{ now() }}"; + +"4fFx9y" = "Updated"; + +"4tpAXn" = "Just to confirm, you wanted ‘${server}’?"; + +"5ZzwZD" = "Failed: Home Assistant is not currently reachable"; + +"5bVvn0" = "Failed to update location: ${error}"; + +"637QjD" = "Error"; + +"6ozAdN" = "Camera Image"; + +"6qSzZG" = "There are ${count} options matching ‘${server}’."; + +"7boBzr" = "Call ${service} with data"; + +"8BMgur" = "Done"; + +"8skKro" = "Which actions?"; + +"A1pokw" = "Ask for Assistance"; + +"B28HAk" = "Assist"; + +"B4bXyc" = "Done"; + +"BafuI1" = "Language"; + +"BwZv3u" = "Which server?"; + +"CmlkE4" = "shortcut_event"; + +"DFWJB8" = "Open ${page}"; + +"DdnZyl" = "Got the latest still from ${cameraID}"; + +"DhCruz" = "Assist Result"; + +"DqfwpO" = "Server"; + +"DsasVx" = "Error"; + +"EA0zLv" = "There are ${count} options matching ‘${server}’."; + +"EWqRDj" = "Failed: ${error}"; + +"FBQiVD" = "Call a service on the Home Assistant instance"; + +"FZ9qkT" = "Render Template"; + +"FtQqoU" = "Location updated"; + +"GiSjQE" = "Failed: ${error}"; + +"HCM8Oe" = "Fire ${eventName} with data"; + +"HSLcG7" = "Event Name"; + +"HoH8wg" = "Actions"; + +"J3qct4" = "Server"; + +"JBMp1T" = "Just to confirm, you wanted ‘${server}’?"; + +"K4xrvF" = "Error"; + +"KHH48D" = "Failed: Home Assistant is not currently reachable"; + +"KJS2jk" = "Failed: ${error}"; + +"KM7mXC" = "Action"; + +"KSmKl0" = "Called ${domain}.${service}"; + +"KzOHYA" = "Failed: ${error}"; + +"LVEY3e" = "Camera ID"; + +"Lgymq2" = "Which service?"; + +"LsEMMg" = "Icon"; + +"MTPojJ" = "What is the event name?"; + +"MwEpGz" = "Done"; + +"N9iJpK" = "Get Camera Image"; + +"NUF0Pa" = "${result}"; + +"NdG9Jz" = "Renders the Home Assistant template"; + +"NsbUTz" = "Camera Entity"; + +"Q2Qp4a" = "Action"; + +"QCGKRz" = "Which language?"; + +"QVD2lM" = "Service Data"; + +"QmGpOH" = "Error"; + +"RCQAlr" = "Get image of ${cameraID}"; + +"RMQY3r" = "Send location to Home Assistant"; + +"RsOw3P" = "Successfully rendered template"; + +"T06Fka" = "Open a page in the frontend"; + +"U4u9Xz" = "There are ${count} options matching ‘${server}’."; + +"ULka1b" = "Actions"; + +"VP2XpM" = "Event Data"; + +"Wk7m4V" = "Server"; + +"X02YQ2" = "${action}"; + +"X4A8Cc" = "Service Domain"; + +"Xq7UuD" = "Render the provided template"; + +"YNQXwq" = "Error"; + +"YxAau3" = "There are ${count} options matching ‘${server}’."; + +"ZApPxB" = "Service Name"; + +"ZRPVYO" = "Open a page in the frontend"; + +"ZT9Mld" = "Event Name"; + +"ZmUm9Q" = "What template would you like to render?"; + +"Zoqsph" = "Page"; + +"aXZJt9" = "Just to confirm, you wanted ‘${server}’?"; + +"am5G4A" = "Just to confirm, you wanted ‘${server}’?"; + +"cK9jdK" = "Failed: ${error}"; + +"cLmTme" = "Action"; + +"cuflz3" = "Failed: Home Assistant is not currently reachable"; + +"d65H6l" = "Assist with \"${text}\""; + +"dFHPYK" = "Performed ${action}"; + +"eObn2i" = "Page"; + +"f1KX0Q" = "View and run actions"; + +"fkQXDn" = "There are ${count} options matching ‘${action}’."; + +"flUgtx" = "Which action?"; + +"fnz838" = "Sensors updated"; + +"foI0Fv" = "Failed: Home Assistant is not currently reachable"; + +"ftQHJw" = "Which server?"; + +"fuRWMi" = "Send ${location} to Home Assistant"; + +"g8g7Af" = "Perform the action"; + +"gI3mme" = "What service data do you want to send?"; + +"gePUyy" = "Location"; + +"glRCfJ" = "Failed: Home Assistant is not currently reachable"; + +"gv7Gg6" = "Location"; + +"h1xQ53" = "Update Sensors"; + +"hKNPaD" = "Server"; + +"hLFNfv" = "Failed: ${error}"; + +"hXWHln" = "Server"; + +"hfoDeC" = "Intent Language"; + +"hhPmPc" = "JSON"; + +"hsrlTY" = "Get a single still frame from a camera"; + +"iMvUhs" = "Open Page"; + +"jWGtvO" = "There are ${count} options matching ‘${server}’."; + +"jtqxOg" = "Just to confirm, you wanted ‘${action}’?"; + +"kGB23u" = "Perform Action"; + +"l2coEc" = "Which server?"; + +"lGqSSG" = "Failed to update sensors."; + +"lNmaCR" = "Template"; + +"lxHlyQ" = "Fire Event"; + +"mAibJP" = "Failed: Home Assistant is not currently reachable"; + +"mJ6CrP" = "Failed to send ${eventName}: ${error}"; + +"mZnRHS" = "${eventName} fired"; + +"mi6B62" = "What data do you want to send with the event?"; + +"ni0OSe" = "Just to confirm, you wanted ‘${server}’?"; + +"ns9RRU" = "Open Page"; + +"nzqmFA" = "Done"; + +"oHn0vD" = "Pages"; + +"oIWFyR" = "Failed: ${error}"; + +"onq5yE" = "Failed to call service with error: ${error}"; + +"pQhTjo" = "Failed: Home Assistant is not currently reachable"; + +"pjQR7t" = "Update Sensors"; + +"qYpzmu" = "Which pages?"; + +"qayMH6" = "Call Service"; + +"qzje4k" = "Failed: ${error}"; + +"r32M7N" = "Failed: ${error}"; + +"r6LXFe" = "Failed"; + +"rFSNpu" = "Done"; + +"rOlW1x" = "What location do you want to send?"; + +"rU3vhT" = "Server Identifier"; + +"sNFoWe" = "Which camera?"; + +"sQJAJS" = "Error"; + +"sfDGvk" = "Rendered Template"; + +"txfcnn" = "Text"; + +"tycImk" = "Performs an action defined in the app"; + +"uqeIcc" = "Assist with \"${text}\""; + +"v7TSD5" = "Send Location"; + +"vAIAF2" = "Send a sensor update to Home Assistant"; + +"vebd70" = "Server"; + +"wfPQQQ" = "Fire an event to the Home Assistant event bus"; + +"xN51te" = "Result"; + +"xfXG6b" = "Which server?"; + +"xhwpj4" = "Done"; + +"ypnPyU" = "Perform ${action}"; + +"yyjTxO" = "Error"; + +"zIiPij" = "${result}"; + +"zjdBxm" = "Which page?"; + diff --git a/Sources/App/Resources/et.lproj/Localizable.strings b/Sources/App/Resources/et.lproj/Localizable.strings new file mode 100644 index 0000000000..bfd02e9599 --- /dev/null +++ b/Sources/App/Resources/et.lproj/Localizable.strings @@ -0,0 +1,771 @@ +"about.acknowledgements.title" = "Acknowledgements"; +"about.beta.title" = "Join Beta"; +"about.chat.title" = "Chat"; +"about.documentation.title" = "Documentation"; +"about.easter_egg.message" = "i love you"; +"about.easter_egg.title" = "You found me!"; +"about.forums.title" = "Forums"; +"about.github.title" = "GitHub"; +"about.github_issue_tracker.title" = "GitHub Issue Tracker"; +"about.help_localize.title" = "Help localize the app!"; +"about.home_assistant_on_facebook.title" = "Home Assistant on Facebook"; +"about.home_assistant_on_twitter.title" = "Home Assistant on Twitter"; +"about.logo.app_title" = "Home Assistant Companion"; +"about.logo.tagline" = "Awaken Your Home"; +"about.review.title" = "Leave a review"; +"about.title" = "About"; +"about.website.title" = "Website"; +"actions_configurator.rows.background_color.title" = "Background Color"; +"actions_configurator.rows.icon.title" = "Icon"; +"actions_configurator.rows.icon_color.title" = "Icon Color"; +"actions_configurator.rows.name.title" = "Name"; +"actions_configurator.rows.text.title" = "Text"; +"actions_configurator.rows.text_color.title" = "Text Color"; +"actions_configurator.title" = "New Action"; +"actions_configurator.trigger_example.share" = "Share Contents"; +"actions_configurator.trigger_example.title" = "Example Trigger"; +"actions_configurator.visual_section.scene_defined" = "The appearance of this action is controlled by the scene configuration."; +"actions_configurator.visual_section.scene_hint_footer" = "You can also change these by customizing the Scene attributes: %@"; +"actions_configurator.visual_section.server_defined" = "The appearance of this action is controlled by the server configuration."; +"addButtonLabel" = "Add"; +"alerts.alert.ok" = "OK"; +"alerts.auth_required.message" = "The server has rejected your credentials, and you must sign in again to continue."; +"alerts.auth_required.title" = "You must sign in to continue"; +"alerts.confirm.cancel" = "Cancel"; +"alerts.confirm.ok" = "OK"; +"alerts.deprecations.notification_category.message" = "You must migrate to actions defined in the notification itself before %1$@."; +"alerts.deprecations.notification_category.title" = "Notification Categories are deprecated"; +"alerts.open_url_from_deep_link.message" = "Open URL (%@) from deep link?"; +"alerts.open_url_from_notification.message" = "Open URL (%@) found in notification?"; +"alerts.open_url_from_notification.title" = "Open URL?"; +"alerts.prompt.cancel" = "Cancel"; +"alerts.prompt.ok" = "OK"; +"always_open_label" = "Always Open"; +"cancel_label" = "Cancel"; +"cl_error.description.deferred_accuracy_too_low" = "Deferred mode is not supported for the requested accuracy."; +"cl_error.description.deferred_canceled" = "The request for deferred updates was canceled by your app or by the location manager."; +"cl_error.description.deferred_distance_filtered" = "Deferred mode does not support distance filters."; +"cl_error.description.deferred_failed" = "The location manager did not enter deferred mode for an unknown reason."; +"cl_error.description.deferred_not_updating_location" = "The manager did not enter deferred mode since updates were already disabled/paused."; +"cl_error.description.denied" = "Access to the location service was denied by the user."; +"cl_error.description.geocode_canceled" = "The geocode request was canceled."; +"cl_error.description.geocode_found_no_result" = "The geocode request yielded no result."; +"cl_error.description.geocode_found_partial_result" = "The geocode request yielded a partial result."; +"cl_error.description.heading_failure" = "The heading could not be determined."; +"cl_error.description.location_unknown" = "The location manager was unable to obtain a location value right now."; +"cl_error.description.network" = "The network was unavailable or a network error occurred."; +"cl_error.description.ranging_failure" = "A general ranging error occurred."; +"cl_error.description.ranging_unavailable" = "Ranging is disabled."; +"cl_error.description.region_monitoring_denied" = "Access to the region monitoring service was denied by the user."; +"cl_error.description.region_monitoring_failure" = "A registered region cannot be monitored."; +"cl_error.description.region_monitoring_response_delayed" = "Core Location will deliver events but they may be delayed."; +"cl_error.description.region_monitoring_setup_delayed" = "Core Location could not initialize the region monitoring feature immediately."; +"cl_error.description.unknown" = "Unknown Core Location error"; +"client_events.event_type.location_update" = "Location Update"; +"client_events.event_type.networkRequest" = "Network Request"; +"client_events.event_type.notification" = "Notification"; +"client_events.event_type.notification.title" = "Received a Push Notification: %@"; +"client_events.event_type.service_call" = "Service Call"; +"client_events.event_type.unknown" = "Unknown"; +"client_events.view.clear" = "Clear"; +"client_events.view.clear_confirm.message" = "This cannot be undone."; +"client_events.view.clear_confirm.title" = "Are you sure you want to clear all events?"; +"continue_label" = "Continue"; +"copy_label" = "Copy"; +"database.problem.delete" = "Delete Database & Quit App"; +"database.problem.quit" = "Quit App"; +"database.problem.title" = "Database Error"; +"debug_section_label" = "Debug"; +"delete" = "Delete"; +"done_label" = "Done"; +"error_label" = "Error"; +"extensions.map.location.new" = "New Location"; +"extensions.map.location.original" = "Original Location"; +"extensions.map.payload_missing_homeassistant.message" = "Payload didn't contain a homeassistant dictionary!"; +"extensions.map.value_missing_or_uncastable.latitude.message" = "Latitude wasn't found or couldn't be casted to string!"; +"extensions.map.value_missing_or_uncastable.longitude.message" = "Longitude wasn't found or couldn't be casted to string!"; +"extensions.notification_content.error.no_entity_id" = "No entity_id found in payload!"; +"extensions.notification_content.error.request.auth_failed" = "Authentication failed!"; +"extensions.notification_content.error.request.entity_not_found" = "Entity '%@' not found!"; +"extensions.notification_content.error.request.hls_unavailable" = "HLS stream unavailable"; +"extensions.notification_content.error.request.other" = "Got non-200 status code (%li)"; +"ha_api.api_error.cant_build_url" = "Cant build API URL"; +"ha_api.api_error.invalid_response" = "Received invalid response from Home Assistant"; +"ha_api.api_error.manager_not_available" = "HA API Manager is unavailable"; +"ha_api.api_error.mobile_app_component_not_loaded" = "The mobile_app component is not loaded. Please add it to your configuration, restart Home Assistant, and try again."; +"ha_api.api_error.must_upgrade_home_assistant" = "Your Home Assistant version (%@) is too old, you must upgrade to at least version %@ to use the app."; +"ha_api.api_error.not_configured" = "HA API not configured"; +"ha_api.api_error.unacceptable_status_code" = "Unacceptable status code %1$li."; +"ha_api.api_error.unexpected_type" = "Received response with result of type %1$@ but expected type %2$@."; +"ha_api.api_error.unknown" = "An unknown error occurred."; +"ha_api.api_error.update_not_possible" = "Operation could not be performed."; +"help_label" = "Help"; +"intents.server_required_for_value" = "Select a server before picking this value."; +"location_change_notification.app_shortcut.body" = "Location updated via App Shortcut"; +"location_change_notification.background_fetch.body" = "Current location delivery triggered via background fetch"; +"location_change_notification.beacon_region_enter.body" = "%@ entered via iBeacon"; +"location_change_notification.beacon_region_exit.body" = "%@ exited via iBeacon"; +"location_change_notification.launch.body" = "Location updated via app launch"; +"location_change_notification.manual.body" = "Location update triggered by user"; +"location_change_notification.periodic.body" = "Location updated via periodic update"; +"location_change_notification.push_notification.body" = "Location updated via push notification"; +"location_change_notification.region_enter.body" = "%@ entered"; +"location_change_notification.region_exit.body" = "%@ exited"; +"location_change_notification.signaled.body" = "Location updated via update signal"; +"location_change_notification.significant_location_update.body" = "Significant location change detected"; +"location_change_notification.siri.body" = "Location update triggered by Siri"; +"location_change_notification.title" = "Location change"; +"location_change_notification.unknown.body" = "Location updated via unknown method"; +"location_change_notification.url_scheme.body" = "Location updated via URL Scheme"; +"location_change_notification.visit.body" = "Location updated via Visit"; +"location_change_notification.x_callback_url.body" = "Location updated via X-Callback-URL"; +"menu.actions.configure" = "Configure…"; +"menu.actions.title" = "Actions"; +"menu.application.about" = "About %@"; +"menu.application.preferences" = "Preferences…"; +"menu.file.update_sensors" = "Update Sensors"; +"menu.help.help" = "%@ Help"; +"menu.status_item.quit" = "Quit"; +"menu.status_item.toggle" = "Toggle %1$@"; +"menu.view.reload_page" = "Reload Page"; +"nfc.detail.copy" = "Copy to Pasteboard"; +"nfc.detail.duplicate" = "Create a Duplicate"; +"nfc.detail.example_trigger" = "Example Trigger"; +"nfc.detail.fire" = "Fire Event"; +"nfc.detail.share" = "Share Identifier"; +"nfc.detail.tag_value" = "Tag Identifier"; +"nfc.detail.title" = "NFC Tag"; +"nfc.generic_tag_read" = "Tag Read"; +"nfc.list.description" = "NFC tags written by the app will show a notification when you bring your device near them. Activating the notification will launch the app and fire an event.\ +\ +Tags will work on any device with Home Assistant installed which has hardware support to read them."; +"nfc.list.learn_more" = "Learn More"; +"nfc.list.read_tag" = "Read Tag"; +"nfc.list.title" = "NFC Tags"; +"nfc.list.write_tag" = "Write Tag"; +"nfc.not_available" = "NFC is not available on this device"; +"nfc.read.error.generic_failure" = "Failed to read tag"; +"nfc.read.error.not_home_assistant" = "NFC tag is not a Home Assistant tag"; +"nfc.read.error.tag_invalid" = "NFC tag is invalid"; +"nfc.read.start_message" = "Hold your %@ near an NFC tag"; +"nfc.tag_read" = "NFC Tag Read"; +"nfc.write.error.capacity" = "NFC tag has insufficient capacity: needs %ld but only has %ld"; +"nfc.write.error.invalid_format" = "NFC tag is not NDEF format"; +"nfc.write.error.not_writable" = "NFC tag is read-only"; +"nfc.write.identifier_choice.manual" = "Manual"; +"nfc.write.identifier_choice.message" = "The identifier helps differentiate various tags."; +"nfc.write.identifier_choice.random" = "Random (Recommended)"; +"nfc.write.identifier_choice.title" = "What kind of tag identifier?"; +"nfc.write.manual_input.title" = "What identifier for the tag?"; +"nfc.write.start_message" = "Hold your %@ near a writable NFC tag"; +"nfc.write.success_message" = "Tag Written!"; +"no_label" = "No"; +"notification_service.failed_to_load" = "Failed to load attachment"; +"notification_service.loading_dynamic_actions" = "Loading Actions…"; +"notification_service.parser.camera.invalid_entity" = "entity_id provided was invalid."; +"notification_service.parser.url.invalid_url" = "The given URL was invalid."; +"notification_service.parser.url.no_url" = "No URL was provided."; +"notifications_configurator.action.rows.authentication_required.footer" = "When the user selects an action with this option, the system prompts the user to unlock the device. After unlocking, Home Assistant will be notified of the selected action."; +"notifications_configurator.action.rows.authentication_required.title" = "Authentication Required"; +"notifications_configurator.action.rows.destructive.footer" = "When enabled, the action button is displayed with special highlighting to indicate that it performs a destructive task."; +"notifications_configurator.action.rows.destructive.title" = "Destructive"; +"notifications_configurator.action.rows.foreground.footer" = "Enabling this will cause the app to launch if it's in the background when tapping a notification"; +"notifications_configurator.action.rows.foreground.title" = "Launch app"; +"notifications_configurator.action.rows.text_input_button_title.title" = "Button Title"; +"notifications_configurator.action.rows.text_input_placeholder.title" = "Placeholder"; +"notifications_configurator.action.rows.title.title" = "Title"; +"notifications_configurator.action.text_input.title" = "Text Input"; +"notifications_configurator.category.example_call.title" = "Example Service Call"; +"notifications_configurator.category.navigation_bar.title" = "Category Configurator"; +"notifications_configurator.category.preview_notification.body" = "This is a test notification for the %@ notification category"; +"notifications_configurator.category.preview_notification.title" = "Test notification"; +"notifications_configurator.category.rows.actions.footer" = "Categories can have a maximum of 10 actions."; +"notifications_configurator.category.rows.actions.header" = "Actions"; +"notifications_configurator.category.rows.category_summary.default" = "%%u notifications in %%@"; +"notifications_configurator.category.rows.category_summary.footer" = "A format string for the summary description used when the system groups the category’s notifications. You can optionally use '%%u' to show the number of notifications in the group and '%%@' to show the summary argument provided in the push payload."; +"notifications_configurator.category.rows.category_summary.header" = "Category Summary"; +"notifications_configurator.category.rows.hidden_preview_placeholder.default" = "%%u notifications"; +"notifications_configurator.category.rows.hidden_preview_placeholder.footer" = "This text is only displayed if you have notification previews hidden. Use '%%u' for the number of messages with the same thread identifier."; +"notifications_configurator.category.rows.hidden_preview_placeholder.header" = "Hidden Preview Placeholder"; +"notifications_configurator.category.rows.name.title" = "Name"; +"notifications_configurator.identifier" = "Identifier"; +"notifications_configurator.new_action.title" = "New Action"; +"notifications_configurator.settings.footer" = "Identifier must contain only letters and underscores and be uppercase. It must be globally unique to the app."; +"notifications_configurator.settings.footer.id_set" = "Identifier can not be changed after creation. You must delete and recreate the action to change the identifier."; +"notifications_configurator.settings.header" = "Settings"; +"off_label" = "Off"; +"ok_label" = "OK"; +"on_label" = "On"; +"onboarding.connect.mac_safari_warning.message" = "Try restarting Safari if the login form does not open."; +"onboarding.connect.mac_safari_warning.title" = "Launching Safari"; +"onboarding.connect.title" = "Connecting to %@"; +"onboarding.connection_error.more_info_button" = "More Info"; +"onboarding.connection_error.title" = "Failed to Connect"; +"onboarding.connection_test_result.authentication_unsupported.description" = "Authentication type is unsupported%@."; +"onboarding.connection_test_result.basic_auth.description" = "HTTP Basic Authentication is unsupported."; +"onboarding.connection_test_result.certificate_error.action_dont_trust" = "Don't Trust"; +"onboarding.connection_test_result.certificate_error.action_trust" = "Trust Certificate"; +"onboarding.connection_test_result.certificate_error.title" = "Failed to connect securely"; +"onboarding.connection_test_result.client_certificate.description" = "Client Certificate Authentication is not supported."; +"onboarding.connection_test_result.error_code" = "Error Code:"; +"onboarding.connection_test_result.local_network_permission.description" = "\"Local Network\" privacy permission may have been denied. You can change this in the system Settings app."; +"onboarding.device_name_check.error.prompt" = "What device name should be used instead?"; +"onboarding.device_name_check.error.rename_action" = "Rename"; +"onboarding.device_name_check.error.title" = "A device already exists with the name '%1$@'"; +"onboarding.manual_setup.connect" = "Connect"; +"onboarding.manual_setup.couldnt_make_url.message" = "The value '%@' was not a valid URL."; +"onboarding.manual_setup.couldnt_make_url.title" = "Could not create a URL"; +"onboarding.manual_setup.description" = "The URL of your Home Assistant server. Make sure it includes the protocol and port."; +"onboarding.manual_setup.no_scheme.message" = "Should we try connecting using http:// or https://?"; +"onboarding.manual_setup.no_scheme.title" = "URL entered without scheme"; +"onboarding.manual_setup.title" = "Enter URL"; +"onboarding.permissions.allow" = "Allow"; +"onboarding.permissions.allowed" = "Done"; +"onboarding.permissions.change_later_note" = "You can change this permission later in Settings"; +"onboarding.permissions.focus.bullet.automations" = "Focus-based automations"; +"onboarding.permissions.focus.bullet.instant" = "Instant updates when status changes"; +"onboarding.permissions.focus.description" = "Allow whether you are in focus mode to be sent to Home Assistant"; +"onboarding.permissions.focus.grant_description" = "Allow focus permission to create sensors for your focus status, also known as do-not-disturb."; +"onboarding.permissions.focus.title" = "Focus"; +"onboarding.permissions.location.bullet.automations" = "Presence-based automations"; +"onboarding.permissions.location.bullet.history" = "Track location history"; +"onboarding.permissions.location.bullet.wifi" = "Internal URL at home"; +"onboarding.permissions.location.description" = "Enable location services to allow presence detection automations."; +"onboarding.permissions.location.grant_description" = "Allow location permission to create a device_tracker for your device."; +"onboarding.permissions.location.title" = "Location"; +"onboarding.permissions.motion.bullet.activity" = "Sensor for current activity type"; +"onboarding.permissions.motion.bullet.distance" = "Sensor for distance moved"; +"onboarding.permissions.motion.bullet.steps" = "Sensor for step counts"; +"onboarding.permissions.motion.description" = "Allow motion activity and pedometer data to be sent to Home Assistant"; +"onboarding.permissions.motion.grant_description" = "Allow motion permission to create sensors for motion and pedometer data."; +"onboarding.permissions.motion.title" = "Motion & Pedometer"; +"onboarding.permissions.notification.bullet.alert" = "Get alerted from notifications"; +"onboarding.permissions.notification.bullet.badge" = "Update app icon badge"; +"onboarding.permissions.notification.bullet.commands" = "Send commands to your device"; +"onboarding.permissions.notification.description" = "Allow push notifications to be sent from your Home Assistant"; +"onboarding.permissions.notification.grant_description" = "Allow notification permission to create a notify service for your device."; +"onboarding.permissions.notification.title" = "Notifications"; +"onboarding.scanning.discovered_announcement" = "Discovered: %@"; +"onboarding.scanning.manual" = "Enter Address Manually"; +"onboarding.scanning.manual_hint" = "Not finding your server?"; +"onboarding.scanning.title" = "Scanning for Servers"; +"onboarding.welcome.description" = "This app connects to your Home Assistant server and allows integrating data about you and your phone.\ +\ +Home Assistant is free and open source home automation software with a focus on local control and privacy."; +"onboarding.welcome.title" = "Welcome to Home Assistant %@!"; +"open_label" = "Open"; +"preview_output" = "Preview Output"; +"requires_version" = "Requires %@ or later."; +"retry_label" = "Retry"; +"sensors.active.setting.time_until_idle" = "Time Until Idle"; +"sensors.geocoded_location.setting.use_zones" = "Use Zone Name"; +"settings.connection_section.activate_server" = "Activate"; +"settings.connection_section.activate_swipe_hint" = "Quickly activate using a three-finger swipe left or right when viewing a server."; +"settings.connection_section.add_server" = "Add Server"; +"settings.connection_section.all_servers" = "All Servers"; +"settings.connection_section.cloud_overrides_external" = "When connecting via Cloud, the External URL will not be used. You do not need to configure one unless you want to disable Cloud."; +"settings.connection_section.connecting_via" = "Connected via"; +"settings.connection_section.delete_server.message" = "Are you sure you wish to delete this server?"; +"settings.connection_section.delete_server.progress" = "Deleting Server…"; +"settings.connection_section.delete_server.title" = "Delete Server"; +"settings.connection_section.details" = "Details"; +"settings.connection_section.errors.cannot_remove_last_url" = "You cannot remove only available URL."; +"settings.connection_section.external_base_url.placeholder" = "https://homeassistant.myhouse.com"; +"settings.connection_section.external_base_url.title" = "External URL"; +"settings.connection_section.header" = "Connection"; +"settings.connection_section.home_assistant_cloud.title" = "Home Assistant Cloud"; +"settings.connection_section.internal_base_url.placeholder" = "e.g. http://homeassistant.local:8123/"; +"settings.connection_section.internal_base_url.title" = "Internal URL"; +"settings.connection_section.internal_url_hardware_addresses.add_new_ssid" = "Add New Hardware Address"; +"settings.connection_section.internal_url_hardware_addresses.footer" = "Internal URL will be used when the primary network interface has a MAC address matching one of these hardware addresses."; +"settings.connection_section.internal_url_hardware_addresses.header" = "Hardware Addresses"; +"settings.connection_section.internal_url_hardware_addresses.invalid" = "Hardware addresses must look like aa:bb:cc:dd:ee:ff"; +"settings.connection_section.internal_url_ssids.add_new_ssid" = "Add new SSID"; +"settings.connection_section.internal_url_ssids.footer" = "Internal URL will be used when connected to listed SSIDs"; +"settings.connection_section.internal_url_ssids.header" = "SSIDs"; +"settings.connection_section.internal_url_ssids.placeholder" = "MyFunnyNetworkName"; +"settings.connection_section.local_push_description" = "Directly connect to the Home Assistant server for push notifications when on internal SSIDs."; +"settings.connection_section.location_send_type.setting.exact" = "Exact"; +"settings.connection_section.location_send_type.setting.never" = "Never"; +"settings.connection_section.location_send_type.setting.zone_only" = "Zone Name Only"; +"settings.connection_section.location_send_type.title" = "Location Sent"; +"settings.connection_section.logged_in_as" = "Logged in as"; +"settings.connection_section.remote_ui_url.title" = "Remote UI URL"; +"settings.connection_section.sensor_send_type.setting.all" = "All"; +"settings.connection_section.sensor_send_type.setting.none" = "None"; +"settings.connection_section.sensor_send_type.title" = "Sensors Sent"; +"settings.connection_section.servers" = "Servers"; +"settings.connection_section.ssid_permission_and_accuracy_message" = "Accessing SSIDs in the background requires 'Always' location permission and 'Full' location accuracy. Tap here to change your settings."; +"settings.connection_section.ssid_permission_message" = "Accessing SSIDs in the background requires 'Always' location permission. Tap here to change your settings."; +"settings.connection_section.validate_error.edit_url" = "Edit URL"; +"settings.connection_section.validate_error.title" = "Error Saving URL"; +"settings.connection_section.validate_error.use_anyway" = "Use Anyway"; +"settings.connection_section.websocket.status.authenticating" = "Authenticating"; +"settings.connection_section.websocket.status.connected" = "Connected"; +"settings.connection_section.websocket.status.connecting" = "Connecting"; +"settings.connection_section.websocket.status.disconnected.error" = "Error: %1$@"; +"settings.connection_section.websocket.status.disconnected.next_retry" = "Next Retry: %1$@"; +"settings.connection_section.websocket.status.disconnected.retry_count" = "Retry Count: %1$li"; +"settings.connection_section.websocket.status.disconnected.title" = "Disconnected"; +"settings.connection_section.websocket.title" = "WebSocket"; +"settings.debugging.title" = "Debugging"; +"settings.details_section.location_settings_row.title" = "Location"; +"settings.details_section.notification_settings_row.title" = "Notifications"; +"settings.details_section.watch_row.title" = "Apple Watch"; +"settings.developer.annoying_background_notifications.title" = "Annoying Background Info"; +"settings.developer.camera_notification.notification.body" = "Expand this to show the camera content extension"; +"settings.developer.camera_notification.title" = "Show camera notification content extension"; +"settings.developer.copy_realm.alert.message" = "Copied Realm from %@ to %@"; +"settings.developer.copy_realm.alert.title" = "Copied Realm"; +"settings.developer.copy_realm.title" = "Copy Realm from app group to Documents"; +"settings.developer.crashlytics_test.fatal.notification.body" = "NOTE: This will not work if the debugger is connected! When you press OK, the app will crash. You must then re-open the app and wait up to 5 minutes for the crash to appear in the console"; +"settings.developer.crashlytics_test.fatal.notification.title" = "About to crash"; +"settings.developer.crashlytics_test.fatal.title" = "Test Crashlytics Fatal Error"; +"settings.developer.crashlytics_test.non_fatal.notification.body" = "When you press OK, a non-fatal error will be sent to Crashlytics. It may take up to 5 minutes to appear in the console."; +"settings.developer.crashlytics_test.non_fatal.notification.title" = "About to submit a non-fatal error"; +"settings.developer.crashlytics_test.non_fatal.title" = "Test Crashlytics Non-Fatal Error"; +"settings.developer.debug_strings.title" = "Debug strings"; +"settings.developer.export_log_files.title" = "Export log files"; +"settings.developer.footer" = "Don't use these if you don't know what you are doing!"; +"settings.developer.header" = "Developer"; +"settings.developer.map_notification.notification.body" = "Expand this to show the map content extension"; +"settings.developer.map_notification.title" = "Show map notification content extension"; +"settings.developer.show_log_files.title" = "Show log files in Finder"; +"settings.developer.sync_watch_context.title" = "Sync Watch Context"; +"settings.event_log.title" = "Event Log"; +"settings.location_history.detail.explanation" = "The purple circle is your location and its accuracy. Blue circles are your zones. You are inside a zone if the purple circle overlaps a blue circle. Orange circles are additional regions used for sub-100 m zones."; +"settings.location_history.empty" = "No Location History"; +"settings.location_history.title" = "Location History"; +"settings.navigation_bar.about_button.title" = "About"; +"settings.navigation_bar.title" = "Settings"; +"settings.reset_section.reset_alert.message" = "Your settings will be reset and this device will be unregistered from push notifications as well as removed from your Home Assistant configuration."; +"settings.reset_section.reset_alert.progress_message" = "Resetting…"; +"settings.reset_section.reset_alert.title" = "Reset"; +"settings.reset_section.reset_row.title" = "Reset"; +"settings.reset_section.reset_web_cache.title" = "Reset frontend cache"; +"settings.server_select.page_title" = "Server"; +"settings.server_select.title" = "Server"; +"settings.status_section.header" = "Status"; +"settings.status_section.location_name_row.placeholder" = "My Home Assistant"; +"settings.status_section.location_name_row.title" = "Name"; +"settings.status_section.version_row.placeholder" = "0.92.0"; +"settings.status_section.version_row.title" = "Version"; +"settings.template_edit.title" = "Edit Template"; +"settings_details.actions.actions_synced.empty" = "No Synced Actions"; +"settings_details.actions.actions_synced.footer" = "Actions defined in .yaml are not editable on device."; +"settings_details.actions.actions_synced.footer_no_actions" = "Actions may be also defined in the .yaml configuration."; +"settings_details.actions.actions_synced.header" = "Synced Actions"; +"settings_details.actions.footer" = "Actions are used in the Apple Watch app, App Icon Actions and the Today widget"; +"settings_details.actions.footer_mac" = "Actions are used in the application menu and widgets."; +"settings_details.actions.scenes.customize_action" = "Customize"; +"settings_details.actions.scenes.empty" = "No Scenes"; +"settings_details.actions.scenes.footer" = "When enabled, Scenes display alongside actions. When performed, they trigger scene changes."; +"settings_details.actions.scenes.title" = "Scene Actions"; +"settings_details.actions.scenes.select_all" = "Select All"; +"settings_details.actions.title" = "Actions"; +"settings_details.general.app_icon.enum.beta" = "Beta"; +"settings_details.general.app_icon.enum.black" = "Black"; +"settings_details.general.app_icon.enum.blue" = "Blue"; +"settings_details.general.app_icon.enum.caribbean_green" = "Caribbean Green"; +"settings_details.general.app_icon.enum.classic" = "Classic"; +"settings_details.general.app_icon.enum.cornflower_blue" = "Cornflower Blue"; +"settings_details.general.app_icon.enum.crimson" = "Crimson"; +"settings_details.general.app_icon.enum.dev" = "Dev"; +"settings_details.general.app_icon.enum.electric_violet" = "Electric Violet"; +"settings_details.general.app_icon.enum.fire_orange" = "Fire Orange"; +"settings_details.general.app_icon.enum.green" = "Green"; +"settings_details.general.app_icon.enum.old_beta" = "Old Beta"; +"settings_details.general.app_icon.enum.old_dev" = "Old Dev"; +"settings_details.general.app_icon.enum.old_release" = "Old Release"; +"settings_details.general.app_icon.enum.orange" = "Orange"; +"settings_details.general.app_icon.enum.pink" = "Pink"; +"settings_details.general.app_icon.enum.pride_bi" = "Pride: Bi"; +"settings_details.general.app_icon.enum.pride_poc" = "Pride: Progress"; +"settings_details.general.app_icon.enum.pride_rainbow" = "Pride: Rainbow"; +"settings_details.general.app_icon.enum.pride_trans" = "Pride: Trans"; +"settings_details.general.app_icon.enum.pride_non_binary" = "Pride: Non Binary"; +"settings_details.general.app_icon.enum.purple" = "Purple"; +"settings_details.general.app_icon.enum.red" = "Red"; +"settings_details.general.app_icon.enum.release" = "Release"; +"settings_details.general.app_icon.enum.white" = "White"; +"settings_details.general.app_icon.title" = "App Icon"; +"settings_details.general.device_name.title" = "Device Name"; +"settings_details.general.full_screen.title" = "Full Screen"; +"settings_details.general.launch_on_login.title" = "Launch App on Login"; +"settings_details.general.menu_bar_text.title" = "Menu Bar Text"; +"settings_details.general.open_in_browser.chrome" = "Google Chrome"; +"settings_details.general.open_in_browser.default" = "System Default"; +"settings_details.general.open_in_browser.firefox" = "Mozilla Firefox"; +"settings_details.general.open_in_browser.firefoxFocus" = "Mozilla Firefox Focus"; +"settings_details.general.open_in_browser.firefoxKlar" = "Mozilla Firefox Klar"; +"settings_details.general.open_in_browser.safari" = "Apple Safari"; +"settings_details.general.open_in_browser.safari_in_app" = "Apple Safari (in app)"; +"settings_details.general.open_in_browser.title" = "Open Links In"; +"settings_details.general.open_in_private_tab.title" = "Open in Private Tab"; +"settings_details.general.page_zoom.default" = "%@ (Default)"; +"settings_details.general.page_zoom.title" = "Page Zoom"; +"settings_details.general.pinch_to_zoom.title" = "Pinch to Zoom"; +"settings_details.general.restoration.title" = "Remember Last Page"; +"settings_details.general.title" = "General"; +"settings_details.general.visibility.options.dock" = "Dock"; +"settings_details.general.visibility.options.dock_and_menu_bar" = "Dock and Menu Bar"; +"settings_details.general.visibility.options.menu_bar" = "Menu Bar"; +"settings_details.general.visibility.title" = "Show App In…"; +"settings_details.location.background_refresh.disabled" = "Disabled"; +"settings_details.location.background_refresh.enabled" = "Enabled"; +"settings_details.location.background_refresh.title" = "Background Refresh"; +"settings_details.location.location_accuracy.full" = "Full"; +"settings_details.location.location_accuracy.reduced" = "Reduced"; +"settings_details.location.location_accuracy.title" = "Location Accuracy"; +"settings_details.location.location_permission.always" = "Always"; +"settings_details.location.location_permission.needs_request" = "Disabled"; +"settings_details.location.location_permission.never" = "Never"; +"settings_details.location.location_permission.title" = "Location Permission"; +"settings_details.location.location_permission.while_in_use" = "While In Use"; +"settings_details.location.motion_permission.denied" = "Denied"; +"settings_details.location.motion_permission.enabled" = "Enabled"; +"settings_details.location.motion_permission.needs_request" = "Disabled"; +"settings_details.location.motion_permission.title" = "Motion Permission"; +"settings_details.location.notifications.background_fetch.title" = "Background Fetch Notifications"; +"settings_details.location.notifications.beacon_enter.title" = "Enter Zone via iBeacon Notifications"; +"settings_details.location.notifications.beacon_exit.title" = "Exit Zone via iBeacon Notifications"; +"settings_details.location.notifications.enter.title" = "Enter Zone Notifications"; +"settings_details.location.notifications.exit.title" = "Exit Zone Notifications"; +"settings_details.location.notifications.header" = "Location Notifications"; +"settings_details.location.notifications.location_change.title" = "Significant Location Change Notifications"; +"settings_details.location.notifications.push_notification.title" = "Pushed Location Request Notifications"; +"settings_details.location.notifications.url_scheme.title" = "URL Scheme Location Notifications"; +"settings_details.location.notifications.x_callback_url.title" = "X-Callback-URL Location Notifications"; +"settings_details.location.title" = "Location"; +"settings_details.location.update_location" = "Update Location"; +"settings_details.location.updates.background.title" = "Background fetch"; +"settings_details.location.updates.footer" = "Manual location updates can always be triggered"; +"settings_details.location.updates.header" = "Update sources"; +"settings_details.location.updates.notification.title" = "Push notification request"; +"settings_details.location.updates.significant.title" = "Significant location change"; +"settings_details.location.updates.zone.title" = "Zone enter/exit"; +"settings_details.location.zones.beacon.prop_not_set.value" = "Not set"; +"settings_details.location.zones.beacon_major.title" = "iBeacon Major"; +"settings_details.location.zones.beacon_minor.title" = "iBeacon Minor"; +"settings_details.location.zones.beacon_uuid.title" = "iBeacon UUID"; +"settings_details.location.zones.enter_exit_tracked.title" = "Enter/exit tracked"; +"settings_details.location.zones.footer" = "To disable location tracking add track_ios: false to each zones settings or under customize."; +"settings_details.location.zones.location.title" = "Location"; +"settings_details.location.zones.radius.label" = "%li m"; +"settings_details.location.zones.radius.title" = "Radius"; +"settings_details.notifications.badge_section.automatic_setting.description" = "Resets the badge to 0 every time you launch the app."; +"settings_details.notifications.badge_section.automatic_setting.title" = "Automatically"; +"settings_details.notifications.badge_section.button.title" = "Reset Badge"; +"settings_details.notifications.categories.deprecated_note" = "Categories are no longer required for actionable notifications and will be removed in a future release."; +"settings_details.notifications.categories.header" = "Categories"; +"settings_details.notifications.categories_synced.empty" = "No Synced Categories"; +"settings_details.notifications.categories_synced.footer" = "Categories defined in .yaml are not editable on device."; +"settings_details.notifications.categories_synced.footer_no_categories" = "Categories may be also defined in the .yaml configuration."; +"settings_details.notifications.categories_synced.header" = "Synced Categories"; +"settings_details.notifications.info" = "Use the mobile_app notify service to send notifications to your device."; +"settings_details.notifications.local_push.status.available" = "Available (%1$@)"; +"settings_details.notifications.local_push.status.disabled" = "Disabled"; +"settings_details.notifications.local_push.status.establishing" = "Establishing"; +"settings_details.notifications.local_push.status.unavailable" = "Unavailable"; +"settings_details.notifications.local_push.status.unsupported" = "Unsupported"; +"settings_details.notifications.local_push.title" = "Local Push"; +"settings_details.notifications.new_category.title" = "New Category"; +"settings_details.notifications.permission.disabled" = "Denied"; +"settings_details.notifications.permission.enabled" = "Enabled"; +"settings_details.notifications.permission.needs_request" = "Disabled"; +"settings_details.notifications.permission.title" = "Permission"; +"settings_details.notifications.prompt_to_open_urls.title" = "Confirm before opening URL"; +"settings_details.notifications.push_id_section.header" = "Push ID"; +"settings_details.notifications.push_id_section.not_registered" = "Not registered for remote notifications"; +"settings_details.notifications.rate_limits.attempts" = "Attempts"; +"settings_details.notifications.rate_limits.delivered" = "Delivered"; +"settings_details.notifications.rate_limits.errors" = "Errors"; +"settings_details.notifications.rate_limits.footer" = "You are allowed 300 push notifications per 24 hours. Rate limits reset at midnight Universal Coordinated Time (UTC)."; +"settings_details.notifications.rate_limits.footer_with_param" = "You are allowed %u push notifications per 24 hours. Rate limits reset at midnight Universal Coordinated Time (UTC)."; +"settings_details.notifications.rate_limits.header" = "Rate Limits"; +"settings_details.notifications.rate_limits.resets_in" = "Resets In"; +"settings_details.notifications.rate_limits.total" = "Total"; +"settings_details.notifications.sounds.bundled" = "Bundled"; +"settings_details.notifications.sounds.error.cant_build_library_sounds_path" = "Can't build ~/Library/Sounds path: %@"; +"settings_details.notifications.sounds.error.cant_get_directory_contents" = "Can't list directory contents: %@"; +"settings_details.notifications.sounds.error.cant_get_file_sharing_path" = "Can't access file sharing sounds directory: %@"; +"settings_details.notifications.sounds.error.conversion_failed" = "Failed to convert audio to PCM 32 bit 48khz: %@"; +"settings_details.notifications.sounds.error.copy_error" = "Failed to copy file: %@"; +"settings_details.notifications.sounds.error.delete_error" = "Failed to delete file: %@"; +"settings_details.notifications.sounds.footer" = "Built-in, system, or custom sounds can be used with your notifications."; +"settings_details.notifications.sounds.import_custom" = "Import custom sound"; +"settings_details.notifications.sounds.import_file_sharing" = "Import sounds from iTunes File Sharing"; +"settings_details.notifications.sounds.import_mac_instructions" = "Add custom sounds to your Sounds folder to use them in notifications. Use their filename as the sound value in the service call."; +"settings_details.notifications.sounds.import_mac_open_folder" = "Open Folder in Finder"; +"settings_details.notifications.sounds.import_system" = "Import system sounds"; +"settings_details.notifications.sounds.imported" = "Imported"; +"settings_details.notifications.sounds.imported_alert.message" = "%li sounds were imported. Please restart your phone to complete the import."; +"settings_details.notifications.sounds.imported_alert.title" = "Sounds Imported"; +"settings_details.notifications.sounds.system" = "System"; +"settings_details.notifications.sounds.title" = "Sounds"; +"settings_details.notifications.title" = "Notifications"; +"settings_details.privacy.alerts.description" = "Allows checking for important alerts like security vulnerabilities."; +"settings_details.privacy.alerts.title" = "Alerts"; +"settings_details.privacy.analytics.generic_description" = "Allows collection of basic information about your device and interactions with the app. No user identifiable data is shared, including your Home Assistant URLs and tokens. You must restart the app for changes to this setting to take effect."; +"settings_details.privacy.analytics.generic_title" = "Analytics"; +"settings_details.privacy.crash_reporting.description" = "Allows for deeper tracking of crashes and other errors in the app, leading to faster fixes being published. No user identifiable information is sent, other than basic device information. You must restart the app for changes to this setting to take effect."; +"settings_details.privacy.crash_reporting.sentry" = "This feature currently uses Sentry as the report destination."; +"settings_details.privacy.crash_reporting.title" = "Crash Reporting"; +"settings_details.privacy.messaging.description" = "Firebase Cloud Messaging must be enabled for push notifications to function."; +"settings_details.privacy.messaging.title" = "Firebase Cloud Messaging"; +"settings_details.privacy.title" = "Privacy"; +"settings_details.updates.check_for_updates.include_betas" = "Include Beta Releases"; +"settings_details.updates.check_for_updates.title" = "Automatically Check for Updates"; +"settings_details.watch.title" = "Apple Watch"; +"settings_sensors.detail.attributes" = "Attributes"; +"settings_sensors.detail.device_class" = "Device Class"; +"settings_sensors.detail.enabled" = "Enabled"; +"settings_sensors.detail.icon" = "Icon"; +"settings_sensors.detail.state" = "State"; +"settings_sensors.disabled_state_replacement" = "Disabled"; +"settings_sensors.focus_permission.title" = "Focus Permission"; +"settings_sensors.last_updated.footer" = "Last Updated %@"; +"settings_sensors.loading_error.title" = "Failed to load sensors"; +"settings_sensors.periodic_update.description" = "When enabled, these sensors will update with this frequency while the app is open in the foreground."; +"settings_sensors.periodic_update.description_mac" = "When enabled, these sensors will update with this frequency while the app is open. Some sensors will update automatically more often."; +"settings_sensors.periodic_update.off" = "Off"; +"settings_sensors.periodic_update.title" = "Periodic Update"; +"settings_sensors.settings.footer" = "Changes will be applied on the next update."; +"settings_sensors.settings.header" = "Settings"; +"settings_sensors.title" = "Sensors"; +"share_extension.entered_placeholder" = "'entered' in event"; +"share_extension.error.title" = "Couldn't Send"; +"success_label" = "Success"; +"token_error.connection_failed" = "Connection failed."; +"token_error.expired" = "Token is expired."; +"token_error.token_unavailable" = "Token is unavailable."; +"updater.check_for_updates_menu.title" = "Check for Updates…"; +"updater.no_updates_available.on_latest_version" = "You're on the latest version!"; +"updater.no_updates_available.title" = "Check for Updates"; +"updater.update_available.open" = "View '%@'"; +"updater.update_available.title" = "Update Available"; +"url_handler.call_service.confirm.title" = "Call service?"; +"url_handler.call_service.confirm.message" = "Do you want to call the service %@?"; +"url_handler.call_service.error.message" = "An error occurred while attempting to call service %@: %@"; +"url_handler.call_service.success.message" = "Successfully called %@"; +"url_handler.call_service.success.title" = "Called service"; +"url_handler.error.action_not_found" = "Action Not Found"; +"url_handler.fire_event.confirm.title" = "Fire event?"; +"url_handler.fire_event.confirm.message" = "Do you want to fire the event %@?"; +"url_handler.fire_event.error.message" = "An error occurred while attempting to fire event %@: %@"; +"url_handler.fire_event.success.message" = "Successfully fired event %@"; +"url_handler.fire_event.success.title" = "Fired event"; +"url_handler.no_service.message" = "%@ is not a valid route"; +"url_handler.send_location.confirm.title" = "Send location?"; +"url_handler.send_location.confirm.message" = "Do you want to send your location?"; +"url_handler.send_location.error.message" = "An unknown error occurred while attempting to send location: %@"; +"url_handler.send_location.success.message" = "Sent a one shot location"; +"url_handler.send_location.success.title" = "Sent location"; +"url_handler.render_template.confirm.title" = "Render template?"; +"url_handler.render_template.confirm.message" = "Do you want to render %@?"; +"url_handler.x_callback_url.error.eventNameMissing" = "eventName must be defined"; +"url_handler.x_callback_url.error.general" = "A general error occurred"; +"url_handler.x_callback_url.error.serviceMissing" = "service (e.g. homeassistant.turn_on) must be defined"; +"url_handler.x_callback_url.error.templateMissing" = "A renderable template must be defined"; +"username_label" = "Username"; +"watch.configurator.delete.button" = "Delete Complication"; +"watch.configurator.delete.message" = "Are you sure you want to delete this Complication? This cannot be undone."; +"watch.configurator.delete.title" = "Delete Complication?"; +"watch.configurator.list.description" = "Configure a new Complication using the Add button. Once saved, you can choose it on your Apple Watch or in the Watch app."; +"watch.configurator.list.manual_updates.footer" = "Automatic updates occur 4 times per hour. Manual updates can also be done using notifications."; +"watch.configurator.list.manual_updates.manually_update" = "Update Complications"; +"watch.configurator.list.manual_updates.remaining" = "Remaining"; +"watch.configurator.list.manual_updates.state.not_enabled" = "Not Enabled"; +"watch.configurator.list.manual_updates.state.not_installed" = "Not Installed"; +"watch.configurator.list.manual_updates.state.not_paired" = "No Device"; +"watch.configurator.list.manual_updates.title" = "Manual Updates"; +"watch.configurator.new.multiple_complication_info" = "Adding another Complication for the same type as an existing one requires watchOS 7 or newer."; +"watch.configurator.new.title" = "New Complication"; +"watch.configurator.preview_error.not_number" = "Expected a number but got %1$@: '%2$@'"; +"watch.configurator.preview_error.out_of_range" = "Expected a number between 0.0 and 1.0 but got %1$f"; +"watch.configurator.rows.color.title" = "Color"; +"watch.configurator.rows.column_2_alignment.options.leading" = "Leading"; +"watch.configurator.rows.column_2_alignment.options.trailing" = "Trailing"; +"watch.configurator.rows.column_2_alignment.title" = "Column 2 Alignment"; +"watch.configurator.rows.display_name.title" = "Display Name"; +"watch.configurator.rows.gauge.color.title" = "Color"; +"watch.configurator.rows.gauge.gauge_type.options.closed" = "Closed"; +"watch.configurator.rows.gauge.gauge_type.options.open" = "Open"; +"watch.configurator.rows.gauge.gauge_type.title" = "Type"; +"watch.configurator.rows.gauge.style.options.fill" = "Fill"; +"watch.configurator.rows.gauge.style.options.ring" = "Ring"; +"watch.configurator.rows.gauge.style.title" = "Style"; +"watch.configurator.rows.gauge.title" = "Gauge"; +"watch.configurator.rows.icon.choose.title" = "Choose an icon"; +"watch.configurator.rows.icon.color.title" = "Color"; +"watch.configurator.rows.is_public.title" = "Show When Locked"; +"watch.configurator.rows.ring.color.title" = "Color"; +"watch.configurator.rows.ring.ring_type.options.closed" = "Closed"; +"watch.configurator.rows.ring.ring_type.options.open" = "Open"; +"watch.configurator.rows.ring.ring_type.title" = "Type"; +"watch.configurator.rows.ring.value.title" = "Fractional value"; +"watch.configurator.rows.template.selector_title" = "Choose a template"; +"watch.configurator.rows.template.title" = "Template"; +"watch.configurator.sections.gauge.footer" = "The gauge to display in the complication."; +"watch.configurator.sections.gauge.header" = "Gauge"; +"watch.configurator.sections.icon.footer" = "The image to display in the complication."; +"watch.configurator.sections.icon.header" = "Icon"; +"watch.configurator.sections.ring.footer" = "The ring showing progress surrounding the text."; +"watch.configurator.sections.ring.header" = "Ring"; +"watch.labels.complication_group.circular_small.description" = "Use circular small complications to display content in the corners of the Color watch face."; +"watch.labels.complication_group.circular_small.name" = "Circular Small"; +"watch.labels.complication_group.extra_large.description" = "Use the extra large complications to display content on the X-Large watch faces."; +"watch.labels.complication_group.extra_large.name" = "Extra Large"; +"watch.labels.complication_group.graphic.description" = "Use graphic complications to display visually rich content in the Infograph and Infograph Modular clock faces."; +"watch.labels.complication_group.graphic.name" = "Graphic"; +"watch.labels.complication_group.modular.description" = "Use modular small complications to display content in the Modular watch face."; +"watch.labels.complication_group.modular.name" = "Modular"; +"watch.labels.complication_group.utilitarian.description" = "Use the utilitarian complications to display content in the Utility, Motion, Mickey Mouse, and Minnie Mouse watch faces."; +"watch.labels.complication_group.utilitarian.name" = "Utilitarian"; +"watch.labels.complication_group_member.circular_small.description" = "A small circular area used in the Color clock face."; +"watch.labels.complication_group_member.circular_small.name" = "Circular Small"; +"watch.labels.complication_group_member.circular_small.short_name" = "Circular Small"; +"watch.labels.complication_group_member.extra_large.description" = "A large square area used in the X-Large clock face."; +"watch.labels.complication_group_member.extra_large.name" = "Extra Large"; +"watch.labels.complication_group_member.extra_large.short_name" = "Extra Large"; +"watch.labels.complication_group_member.graphic_bezel.description" = "A small square area used in the Modular clock face."; +"watch.labels.complication_group_member.graphic_bezel.name" = "Graphic Bezel"; +"watch.labels.complication_group_member.graphic_bezel.short_name" = "Bezel"; +"watch.labels.complication_group_member.graphic_circular.description" = "A large rectangular area used in the Modular clock face."; +"watch.labels.complication_group_member.graphic_circular.name" = "Graphic Circular"; +"watch.labels.complication_group_member.graphic_circular.short_name" = "Circular"; +"watch.labels.complication_group_member.graphic_corner.description" = "A small square or rectangular area used in the Utility, Mickey, Chronograph, and Simple clock faces."; +"watch.labels.complication_group_member.graphic_corner.name" = "Graphic Corner"; +"watch.labels.complication_group_member.graphic_corner.short_name" = "Corner"; +"watch.labels.complication_group_member.graphic_rectangular.description" = "A small rectangular area used in the in the Photos, Motion, and Timelapse clock faces."; +"watch.labels.complication_group_member.graphic_rectangular.name" = "Graphic Rectangular"; +"watch.labels.complication_group_member.graphic_rectangular.short_name" = "Rectangular"; +"watch.labels.complication_group_member.modular_large.description" = "A large rectangular area that spans the width of the screen in the Utility and Mickey clock faces."; +"watch.labels.complication_group_member.modular_large.name" = "Modular Large"; +"watch.labels.complication_group_member.modular_large.short_name" = "Large"; +"watch.labels.complication_group_member.modular_small.description" = "A curved area that fills the corners in the Infograph clock face."; +"watch.labels.complication_group_member.modular_small.name" = "Modular Small"; +"watch.labels.complication_group_member.modular_small.short_name" = "Small"; +"watch.labels.complication_group_member.utilitarian_large.description" = "A circular area used in the Infograph and Infograph Modular clock faces."; +"watch.labels.complication_group_member.utilitarian_large.name" = "Utilitarian Large"; +"watch.labels.complication_group_member.utilitarian_large.short_name" = "Large"; +"watch.labels.complication_group_member.utilitarian_small.description" = "A circular area with optional curved text placed along the bezel of the Infograph clock face."; +"watch.labels.complication_group_member.utilitarian_small.name" = "Utilitarian Small"; +"watch.labels.complication_group_member.utilitarian_small.short_name" = "Small"; +"watch.labels.complication_group_member.utilitarian_small_flat.description" = "A large rectangular area used in the Infograph Modular clock face."; +"watch.labels.complication_group_member.utilitarian_small_flat.name" = "Utilitarian Small Flat"; +"watch.labels.complication_group_member.utilitarian_small_flat.short_name" = "Small Flat"; +"watch.labels.complication_template.circular_small_ring_image.description" = "A template for displaying a single image surrounded by a configurable progress ring."; +"watch.labels.complication_template.circular_small_ring_text.description" = "A template for displaying a short text string encircled by a configurable progress ring."; +"watch.labels.complication_template.circular_small_simple_image.description" = "A template for displaying a single image."; +"watch.labels.complication_template.circular_small_simple_text.description" = "A template for displaying a short text string."; +"watch.labels.complication_template.circular_small_stack_image.description" = "A template for displaying an image with a line of text below it."; +"watch.labels.complication_template.circular_small_stack_text.description" = "A template for displaying two text strings stacked on top of each other."; +"watch.labels.complication_template.extra_large_columns_text.description" = "A template for displaying two rows and two columns of text."; +"watch.labels.complication_template.extra_large_ring_image.description" = "A template for displaying an image encircled by a configurable progress ring."; +"watch.labels.complication_template.extra_large_ring_text.description" = "A template for displaying text encircled by a configurable progress ring."; +"watch.labels.complication_template.extra_large_simple_image.description" = "A template for displaying an image."; +"watch.labels.complication_template.extra_large_simple_text.description" = "A template for displaying a small amount of text"; +"watch.labels.complication_template.extra_large_stack_image.description" = "A template for displaying a single image with a short line of text below it."; +"watch.labels.complication_template.extra_large_stack_text.description" = "A template for displaying two strings stacked one on top of the other."; +"watch.labels.complication_template.graphic_bezel_circular_text.description" = "A template for displaying a circular complication with text along the bezel."; +"watch.labels.complication_template.graphic_circular_closed_gauge_image.description" = "A template for displaying a full-color circular image and a closed circular gauge."; +"watch.labels.complication_template.graphic_circular_closed_gauge_text.description" = "A template for displaying text inside a closed circular gauge."; +"watch.labels.complication_template.graphic_circular_image.description" = "A template for displaying a full-color circular image."; +"watch.labels.complication_template.graphic_circular_open_gauge_image.description" = "A template for displaying a full-color circular image, an open gauge, and text."; +"watch.labels.complication_template.graphic_circular_open_gauge_range_text.description" = "A template for displaying text inside an open gauge, with leading and trailing text for the gauge."; +"watch.labels.complication_template.graphic_circular_open_gauge_simple_text.description" = "A template for displaying text inside an open gauge, with a single piece of text for the gauge."; +"watch.labels.complication_template.graphic_corner_circular_image.description" = "A template for displaying an image in the clock face’s corner."; +"watch.labels.complication_template.graphic_corner_gauge_image.description" = "A template for displaying an image and a gauge in the clock face’s corner."; +"watch.labels.complication_template.graphic_corner_gauge_text.description" = "A template for displaying text and a gauge in the clock face’s corner."; +"watch.labels.complication_template.graphic_corner_stack_text.description" = "A template for displaying stacked text in the clock face’s corner."; +"watch.labels.complication_template.graphic_corner_text_image.description" = "A template for displaying an image and text in the clock face’s corner."; +"watch.labels.complication_template.graphic_rectangular_large_image.description" = "A template for displaying a large rectangle containing header text and an image."; +"watch.labels.complication_template.graphic_rectangular_standard_body.description" = "A template for displaying a large rectangle containing text."; +"watch.labels.complication_template.graphic_rectangular_text_gauge.description" = "A template for displaying a large rectangle containing text and a gauge."; +"watch.labels.complication_template.modular_large_columns.description" = "A template for displaying multiple columns of data."; +"watch.labels.complication_template.modular_large_standard_body.description" = "A template for displaying a header row and two lines of text"; +"watch.labels.complication_template.modular_large_table.description" = "A template for displaying a header row and columns"; +"watch.labels.complication_template.modular_large_tall_body.description" = "A template for displaying a header row and a tall row of body text."; +"watch.labels.complication_template.modular_small_columns_text.description" = "A template for displaying two rows and two columns of text"; +"watch.labels.complication_template.modular_small_ring_image.description" = "A template for displaying an image encircled by a configurable progress ring"; +"watch.labels.complication_template.modular_small_ring_text.description" = "A template for displaying text encircled by a configurable progress ring"; +"watch.labels.complication_template.modular_small_simple_image.description" = "A template for displaying an image."; +"watch.labels.complication_template.modular_small_simple_text.description" = "A template for displaying a small amount of text."; +"watch.labels.complication_template.modular_small_stack_image.description" = "A template for displaying a single image with a short line of text below it."; +"watch.labels.complication_template.modular_small_stack_text.description" = "A template for displaying two strings stacked one on top of the other."; +"watch.labels.complication_template.style.circular_image" = "Circular Image"; +"watch.labels.complication_template.style.circular_text" = "Circular Text"; +"watch.labels.complication_template.style.closed_gauge_image" = "Closed Gauge Image"; +"watch.labels.complication_template.style.closed_gauge_text" = "Closed Gauge Text"; +"watch.labels.complication_template.style.columns" = "Columns"; +"watch.labels.complication_template.style.columns_text" = "Columns Text"; +"watch.labels.complication_template.style.flat" = "Flat"; +"watch.labels.complication_template.style.gauge_image" = "Gauge Image"; +"watch.labels.complication_template.style.gauge_text" = "Gauge Text"; +"watch.labels.complication_template.style.large_image" = "Large Image"; +"watch.labels.complication_template.style.open_gauge_image" = "Open Gauge Image"; +"watch.labels.complication_template.style.open_gauge_range_text" = "Open Gauge Range Text"; +"watch.labels.complication_template.style.open_gauge_simple_text" = "Open Gauge Simple Text"; +"watch.labels.complication_template.style.ring_image" = "Ring Image"; +"watch.labels.complication_template.style.ring_text" = "Ring Text"; +"watch.labels.complication_template.style.simple_image" = "Simple Image"; +"watch.labels.complication_template.style.simple_text" = "Simple Text"; +"watch.labels.complication_template.style.square" = "Square"; +"watch.labels.complication_template.style.stack_image" = "Stack Image"; +"watch.labels.complication_template.style.stack_text" = "Stack Text"; +"watch.labels.complication_template.style.standard_body" = "Standard Body"; +"watch.labels.complication_template.style.table" = "Table"; +"watch.labels.complication_template.style.tall_body" = "Tall Body"; +"watch.labels.complication_template.style.text_gauge" = "Text Gauge"; +"watch.labels.complication_template.style.text_image" = "Text Image"; +"watch.labels.complication_template.utilitarian_large_flat.description" = "A template for displaying an image and string in a single long line."; +"watch.labels.complication_template.utilitarian_small_flat.description" = "A template for displaying an image and text in a single line."; +"watch.labels.complication_template.utilitarian_small_ring_image.description" = "A template for displaying an image encircled by a configurable progress ring"; +"watch.labels.complication_template.utilitarian_small_ring_text.description" = "A template for displaying text encircled by a configurable progress ring."; +"watch.labels.complication_template.utilitarian_small_square.description" = "A template for displaying a single square image."; +"watch.labels.complication_text_areas.body1.description" = "The main body text to display in the complication."; +"watch.labels.complication_text_areas.body1.label" = "Body 1"; +"watch.labels.complication_text_areas.body2.description" = "The secondary body text to display in the complication."; +"watch.labels.complication_text_areas.body2.label" = "Body 2"; +"watch.labels.complication_text_areas.bottom.description" = "The text to display at the bottom of the gauge."; +"watch.labels.complication_text_areas.bottom.label" = "Bottom"; +"watch.labels.complication_text_areas.center.description" = "The text to display in the complication."; +"watch.labels.complication_text_areas.center.label" = "Center"; +"watch.labels.complication_text_areas.header.description" = "The header text to display in the complication."; +"watch.labels.complication_text_areas.header.label" = "Header"; +"watch.labels.complication_text_areas.inner.description" = "The inner text to display in the complication."; +"watch.labels.complication_text_areas.inner.label" = "Inner"; +"watch.labels.complication_text_areas.inside_ring.description" = "The text to display in the ring of the complication."; +"watch.labels.complication_text_areas.inside_ring.label" = "Inside Ring"; +"watch.labels.complication_text_areas.leading.description" = "The text to display on the leading edge of the gauge."; +"watch.labels.complication_text_areas.leading.label" = "Leading"; +"watch.labels.complication_text_areas.line1.description" = "The text to display on the top line of the complication."; +"watch.labels.complication_text_areas.line1.label" = "Line 1"; +"watch.labels.complication_text_areas.line2.description" = "The text to display on the bottom line of the complication."; +"watch.labels.complication_text_areas.line2.label" = "Line 2"; +"watch.labels.complication_text_areas.outer.description" = "The outer text to display in the complication."; +"watch.labels.complication_text_areas.outer.label" = "Outer"; +"watch.labels.complication_text_areas.row1_column1.description" = "The text to display in the first column of the first row."; +"watch.labels.complication_text_areas.row1_column1.label" = "Row 1, Column 1"; +"watch.labels.complication_text_areas.row1_column2.description" = "The text to display in the second column of the first row."; +"watch.labels.complication_text_areas.row1_column2.label" = "Row 1, Column 2"; +"watch.labels.complication_text_areas.row2_column1.description" = "The text to display in the first column of the second row."; +"watch.labels.complication_text_areas.row2_column1.label" = "Row 2, Column 1"; +"watch.labels.complication_text_areas.row2_column2.description" = "The text to display in the second column of the second row."; +"watch.labels.complication_text_areas.row2_column2.label" = "Row 2, Column 2"; +"watch.labels.complication_text_areas.row3_column1.description" = "The text to display in the first column of the third row."; +"watch.labels.complication_text_areas.row3_column1.label" = "Row 3, Column 1"; +"watch.labels.complication_text_areas.row3_column2.description" = "The text to display in the second column of the third row."; +"watch.labels.complication_text_areas.row3_column2.label" = "Row 3, Column 2"; +"watch.labels.complication_text_areas.trailing.description" = "The text to display on the trailing edge of the gauge."; +"watch.labels.complication_text_areas.trailing.label" = "Trailing"; +"watch.labels.no_action" = "No actions configured. Configure actions on your phone to dismiss this message."; +"watch.placeholder_complication_name" = "Placeholder"; +"widgets.actions.description" = "Perform Home Assistant actions."; +"widgets.actions.not_configured" = "No Actions Configured"; +"widgets.actions.title" = "Actions"; +"widgets.open_page.description" = "Open a frontend page in Home Assistant."; +"widgets.open_page.not_configured" = "No Pages Available"; +"widgets.open_page.title" = "Open Page"; +"yes_label" = "Yes"; diff --git a/Sources/App/Resources/he.lproj/Frontend.strings b/Sources/App/Resources/he.lproj/Frontend.strings new file mode 100644 index 0000000000..6366458a75 --- /dev/null +++ b/Sources/App/Resources/he.lproj/Frontend.strings @@ -0,0 +1,12 @@ +"panel::calendar" = "Calendar"; +"panel::config" = "Settings"; +"panel::developer_tools" = "Developer Tools"; +"panel::energy" = "Energy"; +"panel::history" = "History"; +"panel::logbook" = "Logbook"; +"panel::mailbox" = "Mailbox"; +"panel::map" = "Map"; +"panel::media_browser" = "Media"; +"panel::profile" = "Profile"; +"panel::shopping_list" = "Shopping List"; +"panel::states" = "Overview"; \ No newline at end of file diff --git a/Sources/App/Resources/he.lproj/InfoPlist.strings b/Sources/App/Resources/he.lproj/InfoPlist.strings new file mode 100644 index 0000000000..30de817afc --- /dev/null +++ b/Sources/App/Resources/he.lproj/InfoPlist.strings @@ -0,0 +1,12 @@ +"NSLocationAlwaysAndWhenInUseUsageDescription" = "We suggest selecting \"Always Allow\" for the best location experience. Selecting \"Only While Using the App\" will disable iBeacons, geofences, background location updates and accurate reporting."; +"NSLocationAlwaysUsageDescription" = "We always need access to your location for features like iBeacons, geofences, background location updates and accurate reporting."; +"NSLocationWhenInUseUsageDescription" = "Only allowing location access while app is in use will disable iBeacons, geofences, background location updates and accurate reporting."; +"NSMotionUsageDescription" = "Motion is used to improve location updates with current motion type, as well as provide basic pedometer data."; +"NSSiriUsageDescription" = "We use Siri to allow created shortcuts to interact with the app."; +"NSPhotoLibraryAddUsageDescription" = "Photo Library access is needed to allow saving photos from the web view."; +"NSPhotoLibraryUsageDescription" = "Photo Library access is needed to allow saving photos from the web view."; +"NSLocalNetworkUsageDescription" = "Locate and communicate with your Home Assistant instance."; +"NFCReaderUsageDescription" = "Reading and writing NFC tags allows you to trigger events."; +"SEND_LOCATION_APP_SHORTCUT_TITLE" = "Send Location"; +"NSCameraUsageDescription" = "Take photos and send them to your Home Assistant server."; +"NSCrossWebsiteTrackingUsageDescription" = "Optionally enable cross-website tracking if your configuration requires it."; diff --git a/Sources/App/Resources/he.lproj/Intents.strings b/Sources/App/Resources/he.lproj/Intents.strings new file mode 100644 index 0000000000..ed6a093e9c --- /dev/null +++ b/Sources/App/Resources/he.lproj/Intents.strings @@ -0,0 +1,292 @@ +"0aqrcy" = "Failed: ${error}"; + +"2KWKqM" = "Failed: Home Assistant is not reachable"; + +"2LIxe4" = "Failed: ${error}"; + +"2b0Uo4" = "Service"; + +"3jKamg" = "{{ now() }}"; + +"4fFx9y" = "Updated"; + +"4tpAXn" = "Just to confirm, you wanted ‘${server}’?"; + +"5ZzwZD" = "Failed: Home Assistant is not currently reachable"; + +"5bVvn0" = "Failed to update location: ${error}"; + +"637QjD" = "Error"; + +"6ozAdN" = "Camera Image"; + +"6qSzZG" = "There are ${count} options matching ‘${server}’."; + +"7boBzr" = "Call ${service} with data"; + +"8BMgur" = "Done"; + +"8skKro" = "Which actions?"; + +"A1pokw" = "Ask for Assistance"; + +"B28HAk" = "Assist"; + +"B4bXyc" = "Done"; + +"BafuI1" = "Language"; + +"BwZv3u" = "Which server?"; + +"CmlkE4" = "shortcut_event"; + +"DFWJB8" = "Open ${page}"; + +"DdnZyl" = "Got the latest still from ${cameraID}"; + +"DhCruz" = "Assist Result"; + +"DqfwpO" = "Server"; + +"DsasVx" = "Error"; + +"EA0zLv" = "There are ${count} options matching ‘${server}’."; + +"EWqRDj" = "Failed: ${error}"; + +"FBQiVD" = "Call a service on the Home Assistant instance"; + +"FZ9qkT" = "Render Template"; + +"FtQqoU" = "Location updated"; + +"GiSjQE" = "Failed: ${error}"; + +"HCM8Oe" = "Fire ${eventName} with data"; + +"HSLcG7" = "Event Name"; + +"HoH8wg" = "Actions"; + +"J3qct4" = "Server"; + +"JBMp1T" = "Just to confirm, you wanted ‘${server}’?"; + +"K4xrvF" = "Error"; + +"KHH48D" = "Failed: Home Assistant is not currently reachable"; + +"KJS2jk" = "Failed: ${error}"; + +"KM7mXC" = "Action"; + +"KSmKl0" = "Called ${domain}.${service}"; + +"KzOHYA" = "Failed: ${error}"; + +"LVEY3e" = "Camera ID"; + +"Lgymq2" = "Which service?"; + +"LsEMMg" = "Icon"; + +"MTPojJ" = "What is the event name?"; + +"MwEpGz" = "Done"; + +"N9iJpK" = "Get Camera Image"; + +"NUF0Pa" = "${result}"; + +"NdG9Jz" = "Renders the Home Assistant template"; + +"NsbUTz" = "Camera Entity"; + +"Q2Qp4a" = "Action"; + +"QCGKRz" = "Which language?"; + +"QVD2lM" = "Service Data"; + +"QmGpOH" = "Error"; + +"RCQAlr" = "Get image of ${cameraID}"; + +"RMQY3r" = "Send location to Home Assistant"; + +"RsOw3P" = "Successfully rendered template"; + +"T06Fka" = "Open a page in the frontend"; + +"U4u9Xz" = "There are ${count} options matching ‘${server}’."; + +"ULka1b" = "Actions"; + +"VP2XpM" = "Event Data"; + +"Wk7m4V" = "Server"; + +"X02YQ2" = "${action}"; + +"X4A8Cc" = "Service Domain"; + +"Xq7UuD" = "Render the provided template"; + +"YNQXwq" = "Error"; + +"YxAau3" = "There are ${count} options matching ‘${server}’."; + +"ZApPxB" = "Service Name"; + +"ZRPVYO" = "Open a page in the frontend"; + +"ZT9Mld" = "Event Name"; + +"ZmUm9Q" = "What template would you like to render?"; + +"Zoqsph" = "Page"; + +"aXZJt9" = "Just to confirm, you wanted ‘${server}’?"; + +"am5G4A" = "Just to confirm, you wanted ‘${server}’?"; + +"cK9jdK" = "Failed: ${error}"; + +"cLmTme" = "Action"; + +"cuflz3" = "Failed: Home Assistant is not currently reachable"; + +"d65H6l" = "Assist with \"${text}\""; + +"dFHPYK" = "Performed ${action}"; + +"eObn2i" = "Page"; + +"f1KX0Q" = "View and run actions"; + +"fkQXDn" = "There are ${count} options matching ‘${action}’."; + +"flUgtx" = "Which action?"; + +"fnz838" = "Sensors updated"; + +"foI0Fv" = "Failed: Home Assistant is not currently reachable"; + +"ftQHJw" = "Which server?"; + +"fuRWMi" = "Send ${location} to Home Assistant"; + +"g8g7Af" = "Perform the action"; + +"gI3mme" = "What service data do you want to send?"; + +"gePUyy" = "Location"; + +"glRCfJ" = "Failed: Home Assistant is not currently reachable"; + +"gv7Gg6" = "Location"; + +"h1xQ53" = "Update Sensors"; + +"hKNPaD" = "Server"; + +"hLFNfv" = "Failed: ${error}"; + +"hXWHln" = "Server"; + +"hfoDeC" = "Intent Language"; + +"hhPmPc" = "JSON"; + +"hsrlTY" = "Get a single still frame from a camera"; + +"iMvUhs" = "Open Page"; + +"jWGtvO" = "There are ${count} options matching ‘${server}’."; + +"jtqxOg" = "Just to confirm, you wanted ‘${action}’?"; + +"kGB23u" = "Perform Action"; + +"l2coEc" = "Which server?"; + +"lGqSSG" = "Failed to update sensors."; + +"lNmaCR" = "Template"; + +"lxHlyQ" = "Fire Event"; + +"mAibJP" = "Failed: Home Assistant is not currently reachable"; + +"mJ6CrP" = "Failed to send ${eventName}: ${error}"; + +"mZnRHS" = "${eventName} fired"; + +"mi6B62" = "What data do you want to send with the event?"; + +"ni0OSe" = "Just to confirm, you wanted ‘${server}’?"; + +"ns9RRU" = "Open Page"; + +"nzqmFA" = "Done"; + +"oHn0vD" = "Pages"; + +"oIWFyR" = "Failed: ${error}"; + +"onq5yE" = "Failed to call service with error: ${error}"; + +"pQhTjo" = "Failed: Home Assistant is not currently reachable"; + +"pjQR7t" = "Update Sensors"; + +"qYpzmu" = "Which pages?"; + +"qayMH6" = "Call Service"; + +"qzje4k" = "Failed: ${error}"; + +"r32M7N" = "Failed: ${error}"; + +"r6LXFe" = "Failed"; + +"rFSNpu" = "Done"; + +"rOlW1x" = "What location do you want to send?"; + +"rU3vhT" = "Server Identifier"; + +"sNFoWe" = "Which camera?"; + +"sQJAJS" = "Error"; + +"sfDGvk" = "Rendered Template"; + +"txfcnn" = "Text"; + +"tycImk" = "Performs an action defined in the app"; + +"uqeIcc" = "Assist with \"${text}\""; + +"v7TSD5" = "Send Location"; + +"vAIAF2" = "Send a sensor update to Home Assistant"; + +"vebd70" = "Server"; + +"wfPQQQ" = "Fire an event to the Home Assistant event bus"; + +"xN51te" = "Result"; + +"xfXG6b" = "Which server?"; + +"xhwpj4" = "Done"; + +"ypnPyU" = "Perform ${action}"; + +"yyjTxO" = "Error"; + +"zIiPij" = "${result}"; + +"zjdBxm" = "Which page?"; + diff --git a/Sources/App/Resources/he.lproj/Localizable.strings b/Sources/App/Resources/he.lproj/Localizable.strings new file mode 100644 index 0000000000..bfd02e9599 --- /dev/null +++ b/Sources/App/Resources/he.lproj/Localizable.strings @@ -0,0 +1,771 @@ +"about.acknowledgements.title" = "Acknowledgements"; +"about.beta.title" = "Join Beta"; +"about.chat.title" = "Chat"; +"about.documentation.title" = "Documentation"; +"about.easter_egg.message" = "i love you"; +"about.easter_egg.title" = "You found me!"; +"about.forums.title" = "Forums"; +"about.github.title" = "GitHub"; +"about.github_issue_tracker.title" = "GitHub Issue Tracker"; +"about.help_localize.title" = "Help localize the app!"; +"about.home_assistant_on_facebook.title" = "Home Assistant on Facebook"; +"about.home_assistant_on_twitter.title" = "Home Assistant on Twitter"; +"about.logo.app_title" = "Home Assistant Companion"; +"about.logo.tagline" = "Awaken Your Home"; +"about.review.title" = "Leave a review"; +"about.title" = "About"; +"about.website.title" = "Website"; +"actions_configurator.rows.background_color.title" = "Background Color"; +"actions_configurator.rows.icon.title" = "Icon"; +"actions_configurator.rows.icon_color.title" = "Icon Color"; +"actions_configurator.rows.name.title" = "Name"; +"actions_configurator.rows.text.title" = "Text"; +"actions_configurator.rows.text_color.title" = "Text Color"; +"actions_configurator.title" = "New Action"; +"actions_configurator.trigger_example.share" = "Share Contents"; +"actions_configurator.trigger_example.title" = "Example Trigger"; +"actions_configurator.visual_section.scene_defined" = "The appearance of this action is controlled by the scene configuration."; +"actions_configurator.visual_section.scene_hint_footer" = "You can also change these by customizing the Scene attributes: %@"; +"actions_configurator.visual_section.server_defined" = "The appearance of this action is controlled by the server configuration."; +"addButtonLabel" = "Add"; +"alerts.alert.ok" = "OK"; +"alerts.auth_required.message" = "The server has rejected your credentials, and you must sign in again to continue."; +"alerts.auth_required.title" = "You must sign in to continue"; +"alerts.confirm.cancel" = "Cancel"; +"alerts.confirm.ok" = "OK"; +"alerts.deprecations.notification_category.message" = "You must migrate to actions defined in the notification itself before %1$@."; +"alerts.deprecations.notification_category.title" = "Notification Categories are deprecated"; +"alerts.open_url_from_deep_link.message" = "Open URL (%@) from deep link?"; +"alerts.open_url_from_notification.message" = "Open URL (%@) found in notification?"; +"alerts.open_url_from_notification.title" = "Open URL?"; +"alerts.prompt.cancel" = "Cancel"; +"alerts.prompt.ok" = "OK"; +"always_open_label" = "Always Open"; +"cancel_label" = "Cancel"; +"cl_error.description.deferred_accuracy_too_low" = "Deferred mode is not supported for the requested accuracy."; +"cl_error.description.deferred_canceled" = "The request for deferred updates was canceled by your app or by the location manager."; +"cl_error.description.deferred_distance_filtered" = "Deferred mode does not support distance filters."; +"cl_error.description.deferred_failed" = "The location manager did not enter deferred mode for an unknown reason."; +"cl_error.description.deferred_not_updating_location" = "The manager did not enter deferred mode since updates were already disabled/paused."; +"cl_error.description.denied" = "Access to the location service was denied by the user."; +"cl_error.description.geocode_canceled" = "The geocode request was canceled."; +"cl_error.description.geocode_found_no_result" = "The geocode request yielded no result."; +"cl_error.description.geocode_found_partial_result" = "The geocode request yielded a partial result."; +"cl_error.description.heading_failure" = "The heading could not be determined."; +"cl_error.description.location_unknown" = "The location manager was unable to obtain a location value right now."; +"cl_error.description.network" = "The network was unavailable or a network error occurred."; +"cl_error.description.ranging_failure" = "A general ranging error occurred."; +"cl_error.description.ranging_unavailable" = "Ranging is disabled."; +"cl_error.description.region_monitoring_denied" = "Access to the region monitoring service was denied by the user."; +"cl_error.description.region_monitoring_failure" = "A registered region cannot be monitored."; +"cl_error.description.region_monitoring_response_delayed" = "Core Location will deliver events but they may be delayed."; +"cl_error.description.region_monitoring_setup_delayed" = "Core Location could not initialize the region monitoring feature immediately."; +"cl_error.description.unknown" = "Unknown Core Location error"; +"client_events.event_type.location_update" = "Location Update"; +"client_events.event_type.networkRequest" = "Network Request"; +"client_events.event_type.notification" = "Notification"; +"client_events.event_type.notification.title" = "Received a Push Notification: %@"; +"client_events.event_type.service_call" = "Service Call"; +"client_events.event_type.unknown" = "Unknown"; +"client_events.view.clear" = "Clear"; +"client_events.view.clear_confirm.message" = "This cannot be undone."; +"client_events.view.clear_confirm.title" = "Are you sure you want to clear all events?"; +"continue_label" = "Continue"; +"copy_label" = "Copy"; +"database.problem.delete" = "Delete Database & Quit App"; +"database.problem.quit" = "Quit App"; +"database.problem.title" = "Database Error"; +"debug_section_label" = "Debug"; +"delete" = "Delete"; +"done_label" = "Done"; +"error_label" = "Error"; +"extensions.map.location.new" = "New Location"; +"extensions.map.location.original" = "Original Location"; +"extensions.map.payload_missing_homeassistant.message" = "Payload didn't contain a homeassistant dictionary!"; +"extensions.map.value_missing_or_uncastable.latitude.message" = "Latitude wasn't found or couldn't be casted to string!"; +"extensions.map.value_missing_or_uncastable.longitude.message" = "Longitude wasn't found or couldn't be casted to string!"; +"extensions.notification_content.error.no_entity_id" = "No entity_id found in payload!"; +"extensions.notification_content.error.request.auth_failed" = "Authentication failed!"; +"extensions.notification_content.error.request.entity_not_found" = "Entity '%@' not found!"; +"extensions.notification_content.error.request.hls_unavailable" = "HLS stream unavailable"; +"extensions.notification_content.error.request.other" = "Got non-200 status code (%li)"; +"ha_api.api_error.cant_build_url" = "Cant build API URL"; +"ha_api.api_error.invalid_response" = "Received invalid response from Home Assistant"; +"ha_api.api_error.manager_not_available" = "HA API Manager is unavailable"; +"ha_api.api_error.mobile_app_component_not_loaded" = "The mobile_app component is not loaded. Please add it to your configuration, restart Home Assistant, and try again."; +"ha_api.api_error.must_upgrade_home_assistant" = "Your Home Assistant version (%@) is too old, you must upgrade to at least version %@ to use the app."; +"ha_api.api_error.not_configured" = "HA API not configured"; +"ha_api.api_error.unacceptable_status_code" = "Unacceptable status code %1$li."; +"ha_api.api_error.unexpected_type" = "Received response with result of type %1$@ but expected type %2$@."; +"ha_api.api_error.unknown" = "An unknown error occurred."; +"ha_api.api_error.update_not_possible" = "Operation could not be performed."; +"help_label" = "Help"; +"intents.server_required_for_value" = "Select a server before picking this value."; +"location_change_notification.app_shortcut.body" = "Location updated via App Shortcut"; +"location_change_notification.background_fetch.body" = "Current location delivery triggered via background fetch"; +"location_change_notification.beacon_region_enter.body" = "%@ entered via iBeacon"; +"location_change_notification.beacon_region_exit.body" = "%@ exited via iBeacon"; +"location_change_notification.launch.body" = "Location updated via app launch"; +"location_change_notification.manual.body" = "Location update triggered by user"; +"location_change_notification.periodic.body" = "Location updated via periodic update"; +"location_change_notification.push_notification.body" = "Location updated via push notification"; +"location_change_notification.region_enter.body" = "%@ entered"; +"location_change_notification.region_exit.body" = "%@ exited"; +"location_change_notification.signaled.body" = "Location updated via update signal"; +"location_change_notification.significant_location_update.body" = "Significant location change detected"; +"location_change_notification.siri.body" = "Location update triggered by Siri"; +"location_change_notification.title" = "Location change"; +"location_change_notification.unknown.body" = "Location updated via unknown method"; +"location_change_notification.url_scheme.body" = "Location updated via URL Scheme"; +"location_change_notification.visit.body" = "Location updated via Visit"; +"location_change_notification.x_callback_url.body" = "Location updated via X-Callback-URL"; +"menu.actions.configure" = "Configure…"; +"menu.actions.title" = "Actions"; +"menu.application.about" = "About %@"; +"menu.application.preferences" = "Preferences…"; +"menu.file.update_sensors" = "Update Sensors"; +"menu.help.help" = "%@ Help"; +"menu.status_item.quit" = "Quit"; +"menu.status_item.toggle" = "Toggle %1$@"; +"menu.view.reload_page" = "Reload Page"; +"nfc.detail.copy" = "Copy to Pasteboard"; +"nfc.detail.duplicate" = "Create a Duplicate"; +"nfc.detail.example_trigger" = "Example Trigger"; +"nfc.detail.fire" = "Fire Event"; +"nfc.detail.share" = "Share Identifier"; +"nfc.detail.tag_value" = "Tag Identifier"; +"nfc.detail.title" = "NFC Tag"; +"nfc.generic_tag_read" = "Tag Read"; +"nfc.list.description" = "NFC tags written by the app will show a notification when you bring your device near them. Activating the notification will launch the app and fire an event.\ +\ +Tags will work on any device with Home Assistant installed which has hardware support to read them."; +"nfc.list.learn_more" = "Learn More"; +"nfc.list.read_tag" = "Read Tag"; +"nfc.list.title" = "NFC Tags"; +"nfc.list.write_tag" = "Write Tag"; +"nfc.not_available" = "NFC is not available on this device"; +"nfc.read.error.generic_failure" = "Failed to read tag"; +"nfc.read.error.not_home_assistant" = "NFC tag is not a Home Assistant tag"; +"nfc.read.error.tag_invalid" = "NFC tag is invalid"; +"nfc.read.start_message" = "Hold your %@ near an NFC tag"; +"nfc.tag_read" = "NFC Tag Read"; +"nfc.write.error.capacity" = "NFC tag has insufficient capacity: needs %ld but only has %ld"; +"nfc.write.error.invalid_format" = "NFC tag is not NDEF format"; +"nfc.write.error.not_writable" = "NFC tag is read-only"; +"nfc.write.identifier_choice.manual" = "Manual"; +"nfc.write.identifier_choice.message" = "The identifier helps differentiate various tags."; +"nfc.write.identifier_choice.random" = "Random (Recommended)"; +"nfc.write.identifier_choice.title" = "What kind of tag identifier?"; +"nfc.write.manual_input.title" = "What identifier for the tag?"; +"nfc.write.start_message" = "Hold your %@ near a writable NFC tag"; +"nfc.write.success_message" = "Tag Written!"; +"no_label" = "No"; +"notification_service.failed_to_load" = "Failed to load attachment"; +"notification_service.loading_dynamic_actions" = "Loading Actions…"; +"notification_service.parser.camera.invalid_entity" = "entity_id provided was invalid."; +"notification_service.parser.url.invalid_url" = "The given URL was invalid."; +"notification_service.parser.url.no_url" = "No URL was provided."; +"notifications_configurator.action.rows.authentication_required.footer" = "When the user selects an action with this option, the system prompts the user to unlock the device. After unlocking, Home Assistant will be notified of the selected action."; +"notifications_configurator.action.rows.authentication_required.title" = "Authentication Required"; +"notifications_configurator.action.rows.destructive.footer" = "When enabled, the action button is displayed with special highlighting to indicate that it performs a destructive task."; +"notifications_configurator.action.rows.destructive.title" = "Destructive"; +"notifications_configurator.action.rows.foreground.footer" = "Enabling this will cause the app to launch if it's in the background when tapping a notification"; +"notifications_configurator.action.rows.foreground.title" = "Launch app"; +"notifications_configurator.action.rows.text_input_button_title.title" = "Button Title"; +"notifications_configurator.action.rows.text_input_placeholder.title" = "Placeholder"; +"notifications_configurator.action.rows.title.title" = "Title"; +"notifications_configurator.action.text_input.title" = "Text Input"; +"notifications_configurator.category.example_call.title" = "Example Service Call"; +"notifications_configurator.category.navigation_bar.title" = "Category Configurator"; +"notifications_configurator.category.preview_notification.body" = "This is a test notification for the %@ notification category"; +"notifications_configurator.category.preview_notification.title" = "Test notification"; +"notifications_configurator.category.rows.actions.footer" = "Categories can have a maximum of 10 actions."; +"notifications_configurator.category.rows.actions.header" = "Actions"; +"notifications_configurator.category.rows.category_summary.default" = "%%u notifications in %%@"; +"notifications_configurator.category.rows.category_summary.footer" = "A format string for the summary description used when the system groups the category’s notifications. You can optionally use '%%u' to show the number of notifications in the group and '%%@' to show the summary argument provided in the push payload."; +"notifications_configurator.category.rows.category_summary.header" = "Category Summary"; +"notifications_configurator.category.rows.hidden_preview_placeholder.default" = "%%u notifications"; +"notifications_configurator.category.rows.hidden_preview_placeholder.footer" = "This text is only displayed if you have notification previews hidden. Use '%%u' for the number of messages with the same thread identifier."; +"notifications_configurator.category.rows.hidden_preview_placeholder.header" = "Hidden Preview Placeholder"; +"notifications_configurator.category.rows.name.title" = "Name"; +"notifications_configurator.identifier" = "Identifier"; +"notifications_configurator.new_action.title" = "New Action"; +"notifications_configurator.settings.footer" = "Identifier must contain only letters and underscores and be uppercase. It must be globally unique to the app."; +"notifications_configurator.settings.footer.id_set" = "Identifier can not be changed after creation. You must delete and recreate the action to change the identifier."; +"notifications_configurator.settings.header" = "Settings"; +"off_label" = "Off"; +"ok_label" = "OK"; +"on_label" = "On"; +"onboarding.connect.mac_safari_warning.message" = "Try restarting Safari if the login form does not open."; +"onboarding.connect.mac_safari_warning.title" = "Launching Safari"; +"onboarding.connect.title" = "Connecting to %@"; +"onboarding.connection_error.more_info_button" = "More Info"; +"onboarding.connection_error.title" = "Failed to Connect"; +"onboarding.connection_test_result.authentication_unsupported.description" = "Authentication type is unsupported%@."; +"onboarding.connection_test_result.basic_auth.description" = "HTTP Basic Authentication is unsupported."; +"onboarding.connection_test_result.certificate_error.action_dont_trust" = "Don't Trust"; +"onboarding.connection_test_result.certificate_error.action_trust" = "Trust Certificate"; +"onboarding.connection_test_result.certificate_error.title" = "Failed to connect securely"; +"onboarding.connection_test_result.client_certificate.description" = "Client Certificate Authentication is not supported."; +"onboarding.connection_test_result.error_code" = "Error Code:"; +"onboarding.connection_test_result.local_network_permission.description" = "\"Local Network\" privacy permission may have been denied. You can change this in the system Settings app."; +"onboarding.device_name_check.error.prompt" = "What device name should be used instead?"; +"onboarding.device_name_check.error.rename_action" = "Rename"; +"onboarding.device_name_check.error.title" = "A device already exists with the name '%1$@'"; +"onboarding.manual_setup.connect" = "Connect"; +"onboarding.manual_setup.couldnt_make_url.message" = "The value '%@' was not a valid URL."; +"onboarding.manual_setup.couldnt_make_url.title" = "Could not create a URL"; +"onboarding.manual_setup.description" = "The URL of your Home Assistant server. Make sure it includes the protocol and port."; +"onboarding.manual_setup.no_scheme.message" = "Should we try connecting using http:// or https://?"; +"onboarding.manual_setup.no_scheme.title" = "URL entered without scheme"; +"onboarding.manual_setup.title" = "Enter URL"; +"onboarding.permissions.allow" = "Allow"; +"onboarding.permissions.allowed" = "Done"; +"onboarding.permissions.change_later_note" = "You can change this permission later in Settings"; +"onboarding.permissions.focus.bullet.automations" = "Focus-based automations"; +"onboarding.permissions.focus.bullet.instant" = "Instant updates when status changes"; +"onboarding.permissions.focus.description" = "Allow whether you are in focus mode to be sent to Home Assistant"; +"onboarding.permissions.focus.grant_description" = "Allow focus permission to create sensors for your focus status, also known as do-not-disturb."; +"onboarding.permissions.focus.title" = "Focus"; +"onboarding.permissions.location.bullet.automations" = "Presence-based automations"; +"onboarding.permissions.location.bullet.history" = "Track location history"; +"onboarding.permissions.location.bullet.wifi" = "Internal URL at home"; +"onboarding.permissions.location.description" = "Enable location services to allow presence detection automations."; +"onboarding.permissions.location.grant_description" = "Allow location permission to create a device_tracker for your device."; +"onboarding.permissions.location.title" = "Location"; +"onboarding.permissions.motion.bullet.activity" = "Sensor for current activity type"; +"onboarding.permissions.motion.bullet.distance" = "Sensor for distance moved"; +"onboarding.permissions.motion.bullet.steps" = "Sensor for step counts"; +"onboarding.permissions.motion.description" = "Allow motion activity and pedometer data to be sent to Home Assistant"; +"onboarding.permissions.motion.grant_description" = "Allow motion permission to create sensors for motion and pedometer data."; +"onboarding.permissions.motion.title" = "Motion & Pedometer"; +"onboarding.permissions.notification.bullet.alert" = "Get alerted from notifications"; +"onboarding.permissions.notification.bullet.badge" = "Update app icon badge"; +"onboarding.permissions.notification.bullet.commands" = "Send commands to your device"; +"onboarding.permissions.notification.description" = "Allow push notifications to be sent from your Home Assistant"; +"onboarding.permissions.notification.grant_description" = "Allow notification permission to create a notify service for your device."; +"onboarding.permissions.notification.title" = "Notifications"; +"onboarding.scanning.discovered_announcement" = "Discovered: %@"; +"onboarding.scanning.manual" = "Enter Address Manually"; +"onboarding.scanning.manual_hint" = "Not finding your server?"; +"onboarding.scanning.title" = "Scanning for Servers"; +"onboarding.welcome.description" = "This app connects to your Home Assistant server and allows integrating data about you and your phone.\ +\ +Home Assistant is free and open source home automation software with a focus on local control and privacy."; +"onboarding.welcome.title" = "Welcome to Home Assistant %@!"; +"open_label" = "Open"; +"preview_output" = "Preview Output"; +"requires_version" = "Requires %@ or later."; +"retry_label" = "Retry"; +"sensors.active.setting.time_until_idle" = "Time Until Idle"; +"sensors.geocoded_location.setting.use_zones" = "Use Zone Name"; +"settings.connection_section.activate_server" = "Activate"; +"settings.connection_section.activate_swipe_hint" = "Quickly activate using a three-finger swipe left or right when viewing a server."; +"settings.connection_section.add_server" = "Add Server"; +"settings.connection_section.all_servers" = "All Servers"; +"settings.connection_section.cloud_overrides_external" = "When connecting via Cloud, the External URL will not be used. You do not need to configure one unless you want to disable Cloud."; +"settings.connection_section.connecting_via" = "Connected via"; +"settings.connection_section.delete_server.message" = "Are you sure you wish to delete this server?"; +"settings.connection_section.delete_server.progress" = "Deleting Server…"; +"settings.connection_section.delete_server.title" = "Delete Server"; +"settings.connection_section.details" = "Details"; +"settings.connection_section.errors.cannot_remove_last_url" = "You cannot remove only available URL."; +"settings.connection_section.external_base_url.placeholder" = "https://homeassistant.myhouse.com"; +"settings.connection_section.external_base_url.title" = "External URL"; +"settings.connection_section.header" = "Connection"; +"settings.connection_section.home_assistant_cloud.title" = "Home Assistant Cloud"; +"settings.connection_section.internal_base_url.placeholder" = "e.g. http://homeassistant.local:8123/"; +"settings.connection_section.internal_base_url.title" = "Internal URL"; +"settings.connection_section.internal_url_hardware_addresses.add_new_ssid" = "Add New Hardware Address"; +"settings.connection_section.internal_url_hardware_addresses.footer" = "Internal URL will be used when the primary network interface has a MAC address matching one of these hardware addresses."; +"settings.connection_section.internal_url_hardware_addresses.header" = "Hardware Addresses"; +"settings.connection_section.internal_url_hardware_addresses.invalid" = "Hardware addresses must look like aa:bb:cc:dd:ee:ff"; +"settings.connection_section.internal_url_ssids.add_new_ssid" = "Add new SSID"; +"settings.connection_section.internal_url_ssids.footer" = "Internal URL will be used when connected to listed SSIDs"; +"settings.connection_section.internal_url_ssids.header" = "SSIDs"; +"settings.connection_section.internal_url_ssids.placeholder" = "MyFunnyNetworkName"; +"settings.connection_section.local_push_description" = "Directly connect to the Home Assistant server for push notifications when on internal SSIDs."; +"settings.connection_section.location_send_type.setting.exact" = "Exact"; +"settings.connection_section.location_send_type.setting.never" = "Never"; +"settings.connection_section.location_send_type.setting.zone_only" = "Zone Name Only"; +"settings.connection_section.location_send_type.title" = "Location Sent"; +"settings.connection_section.logged_in_as" = "Logged in as"; +"settings.connection_section.remote_ui_url.title" = "Remote UI URL"; +"settings.connection_section.sensor_send_type.setting.all" = "All"; +"settings.connection_section.sensor_send_type.setting.none" = "None"; +"settings.connection_section.sensor_send_type.title" = "Sensors Sent"; +"settings.connection_section.servers" = "Servers"; +"settings.connection_section.ssid_permission_and_accuracy_message" = "Accessing SSIDs in the background requires 'Always' location permission and 'Full' location accuracy. Tap here to change your settings."; +"settings.connection_section.ssid_permission_message" = "Accessing SSIDs in the background requires 'Always' location permission. Tap here to change your settings."; +"settings.connection_section.validate_error.edit_url" = "Edit URL"; +"settings.connection_section.validate_error.title" = "Error Saving URL"; +"settings.connection_section.validate_error.use_anyway" = "Use Anyway"; +"settings.connection_section.websocket.status.authenticating" = "Authenticating"; +"settings.connection_section.websocket.status.connected" = "Connected"; +"settings.connection_section.websocket.status.connecting" = "Connecting"; +"settings.connection_section.websocket.status.disconnected.error" = "Error: %1$@"; +"settings.connection_section.websocket.status.disconnected.next_retry" = "Next Retry: %1$@"; +"settings.connection_section.websocket.status.disconnected.retry_count" = "Retry Count: %1$li"; +"settings.connection_section.websocket.status.disconnected.title" = "Disconnected"; +"settings.connection_section.websocket.title" = "WebSocket"; +"settings.debugging.title" = "Debugging"; +"settings.details_section.location_settings_row.title" = "Location"; +"settings.details_section.notification_settings_row.title" = "Notifications"; +"settings.details_section.watch_row.title" = "Apple Watch"; +"settings.developer.annoying_background_notifications.title" = "Annoying Background Info"; +"settings.developer.camera_notification.notification.body" = "Expand this to show the camera content extension"; +"settings.developer.camera_notification.title" = "Show camera notification content extension"; +"settings.developer.copy_realm.alert.message" = "Copied Realm from %@ to %@"; +"settings.developer.copy_realm.alert.title" = "Copied Realm"; +"settings.developer.copy_realm.title" = "Copy Realm from app group to Documents"; +"settings.developer.crashlytics_test.fatal.notification.body" = "NOTE: This will not work if the debugger is connected! When you press OK, the app will crash. You must then re-open the app and wait up to 5 minutes for the crash to appear in the console"; +"settings.developer.crashlytics_test.fatal.notification.title" = "About to crash"; +"settings.developer.crashlytics_test.fatal.title" = "Test Crashlytics Fatal Error"; +"settings.developer.crashlytics_test.non_fatal.notification.body" = "When you press OK, a non-fatal error will be sent to Crashlytics. It may take up to 5 minutes to appear in the console."; +"settings.developer.crashlytics_test.non_fatal.notification.title" = "About to submit a non-fatal error"; +"settings.developer.crashlytics_test.non_fatal.title" = "Test Crashlytics Non-Fatal Error"; +"settings.developer.debug_strings.title" = "Debug strings"; +"settings.developer.export_log_files.title" = "Export log files"; +"settings.developer.footer" = "Don't use these if you don't know what you are doing!"; +"settings.developer.header" = "Developer"; +"settings.developer.map_notification.notification.body" = "Expand this to show the map content extension"; +"settings.developer.map_notification.title" = "Show map notification content extension"; +"settings.developer.show_log_files.title" = "Show log files in Finder"; +"settings.developer.sync_watch_context.title" = "Sync Watch Context"; +"settings.event_log.title" = "Event Log"; +"settings.location_history.detail.explanation" = "The purple circle is your location and its accuracy. Blue circles are your zones. You are inside a zone if the purple circle overlaps a blue circle. Orange circles are additional regions used for sub-100 m zones."; +"settings.location_history.empty" = "No Location History"; +"settings.location_history.title" = "Location History"; +"settings.navigation_bar.about_button.title" = "About"; +"settings.navigation_bar.title" = "Settings"; +"settings.reset_section.reset_alert.message" = "Your settings will be reset and this device will be unregistered from push notifications as well as removed from your Home Assistant configuration."; +"settings.reset_section.reset_alert.progress_message" = "Resetting…"; +"settings.reset_section.reset_alert.title" = "Reset"; +"settings.reset_section.reset_row.title" = "Reset"; +"settings.reset_section.reset_web_cache.title" = "Reset frontend cache"; +"settings.server_select.page_title" = "Server"; +"settings.server_select.title" = "Server"; +"settings.status_section.header" = "Status"; +"settings.status_section.location_name_row.placeholder" = "My Home Assistant"; +"settings.status_section.location_name_row.title" = "Name"; +"settings.status_section.version_row.placeholder" = "0.92.0"; +"settings.status_section.version_row.title" = "Version"; +"settings.template_edit.title" = "Edit Template"; +"settings_details.actions.actions_synced.empty" = "No Synced Actions"; +"settings_details.actions.actions_synced.footer" = "Actions defined in .yaml are not editable on device."; +"settings_details.actions.actions_synced.footer_no_actions" = "Actions may be also defined in the .yaml configuration."; +"settings_details.actions.actions_synced.header" = "Synced Actions"; +"settings_details.actions.footer" = "Actions are used in the Apple Watch app, App Icon Actions and the Today widget"; +"settings_details.actions.footer_mac" = "Actions are used in the application menu and widgets."; +"settings_details.actions.scenes.customize_action" = "Customize"; +"settings_details.actions.scenes.empty" = "No Scenes"; +"settings_details.actions.scenes.footer" = "When enabled, Scenes display alongside actions. When performed, they trigger scene changes."; +"settings_details.actions.scenes.title" = "Scene Actions"; +"settings_details.actions.scenes.select_all" = "Select All"; +"settings_details.actions.title" = "Actions"; +"settings_details.general.app_icon.enum.beta" = "Beta"; +"settings_details.general.app_icon.enum.black" = "Black"; +"settings_details.general.app_icon.enum.blue" = "Blue"; +"settings_details.general.app_icon.enum.caribbean_green" = "Caribbean Green"; +"settings_details.general.app_icon.enum.classic" = "Classic"; +"settings_details.general.app_icon.enum.cornflower_blue" = "Cornflower Blue"; +"settings_details.general.app_icon.enum.crimson" = "Crimson"; +"settings_details.general.app_icon.enum.dev" = "Dev"; +"settings_details.general.app_icon.enum.electric_violet" = "Electric Violet"; +"settings_details.general.app_icon.enum.fire_orange" = "Fire Orange"; +"settings_details.general.app_icon.enum.green" = "Green"; +"settings_details.general.app_icon.enum.old_beta" = "Old Beta"; +"settings_details.general.app_icon.enum.old_dev" = "Old Dev"; +"settings_details.general.app_icon.enum.old_release" = "Old Release"; +"settings_details.general.app_icon.enum.orange" = "Orange"; +"settings_details.general.app_icon.enum.pink" = "Pink"; +"settings_details.general.app_icon.enum.pride_bi" = "Pride: Bi"; +"settings_details.general.app_icon.enum.pride_poc" = "Pride: Progress"; +"settings_details.general.app_icon.enum.pride_rainbow" = "Pride: Rainbow"; +"settings_details.general.app_icon.enum.pride_trans" = "Pride: Trans"; +"settings_details.general.app_icon.enum.pride_non_binary" = "Pride: Non Binary"; +"settings_details.general.app_icon.enum.purple" = "Purple"; +"settings_details.general.app_icon.enum.red" = "Red"; +"settings_details.general.app_icon.enum.release" = "Release"; +"settings_details.general.app_icon.enum.white" = "White"; +"settings_details.general.app_icon.title" = "App Icon"; +"settings_details.general.device_name.title" = "Device Name"; +"settings_details.general.full_screen.title" = "Full Screen"; +"settings_details.general.launch_on_login.title" = "Launch App on Login"; +"settings_details.general.menu_bar_text.title" = "Menu Bar Text"; +"settings_details.general.open_in_browser.chrome" = "Google Chrome"; +"settings_details.general.open_in_browser.default" = "System Default"; +"settings_details.general.open_in_browser.firefox" = "Mozilla Firefox"; +"settings_details.general.open_in_browser.firefoxFocus" = "Mozilla Firefox Focus"; +"settings_details.general.open_in_browser.firefoxKlar" = "Mozilla Firefox Klar"; +"settings_details.general.open_in_browser.safari" = "Apple Safari"; +"settings_details.general.open_in_browser.safari_in_app" = "Apple Safari (in app)"; +"settings_details.general.open_in_browser.title" = "Open Links In"; +"settings_details.general.open_in_private_tab.title" = "Open in Private Tab"; +"settings_details.general.page_zoom.default" = "%@ (Default)"; +"settings_details.general.page_zoom.title" = "Page Zoom"; +"settings_details.general.pinch_to_zoom.title" = "Pinch to Zoom"; +"settings_details.general.restoration.title" = "Remember Last Page"; +"settings_details.general.title" = "General"; +"settings_details.general.visibility.options.dock" = "Dock"; +"settings_details.general.visibility.options.dock_and_menu_bar" = "Dock and Menu Bar"; +"settings_details.general.visibility.options.menu_bar" = "Menu Bar"; +"settings_details.general.visibility.title" = "Show App In…"; +"settings_details.location.background_refresh.disabled" = "Disabled"; +"settings_details.location.background_refresh.enabled" = "Enabled"; +"settings_details.location.background_refresh.title" = "Background Refresh"; +"settings_details.location.location_accuracy.full" = "Full"; +"settings_details.location.location_accuracy.reduced" = "Reduced"; +"settings_details.location.location_accuracy.title" = "Location Accuracy"; +"settings_details.location.location_permission.always" = "Always"; +"settings_details.location.location_permission.needs_request" = "Disabled"; +"settings_details.location.location_permission.never" = "Never"; +"settings_details.location.location_permission.title" = "Location Permission"; +"settings_details.location.location_permission.while_in_use" = "While In Use"; +"settings_details.location.motion_permission.denied" = "Denied"; +"settings_details.location.motion_permission.enabled" = "Enabled"; +"settings_details.location.motion_permission.needs_request" = "Disabled"; +"settings_details.location.motion_permission.title" = "Motion Permission"; +"settings_details.location.notifications.background_fetch.title" = "Background Fetch Notifications"; +"settings_details.location.notifications.beacon_enter.title" = "Enter Zone via iBeacon Notifications"; +"settings_details.location.notifications.beacon_exit.title" = "Exit Zone via iBeacon Notifications"; +"settings_details.location.notifications.enter.title" = "Enter Zone Notifications"; +"settings_details.location.notifications.exit.title" = "Exit Zone Notifications"; +"settings_details.location.notifications.header" = "Location Notifications"; +"settings_details.location.notifications.location_change.title" = "Significant Location Change Notifications"; +"settings_details.location.notifications.push_notification.title" = "Pushed Location Request Notifications"; +"settings_details.location.notifications.url_scheme.title" = "URL Scheme Location Notifications"; +"settings_details.location.notifications.x_callback_url.title" = "X-Callback-URL Location Notifications"; +"settings_details.location.title" = "Location"; +"settings_details.location.update_location" = "Update Location"; +"settings_details.location.updates.background.title" = "Background fetch"; +"settings_details.location.updates.footer" = "Manual location updates can always be triggered"; +"settings_details.location.updates.header" = "Update sources"; +"settings_details.location.updates.notification.title" = "Push notification request"; +"settings_details.location.updates.significant.title" = "Significant location change"; +"settings_details.location.updates.zone.title" = "Zone enter/exit"; +"settings_details.location.zones.beacon.prop_not_set.value" = "Not set"; +"settings_details.location.zones.beacon_major.title" = "iBeacon Major"; +"settings_details.location.zones.beacon_minor.title" = "iBeacon Minor"; +"settings_details.location.zones.beacon_uuid.title" = "iBeacon UUID"; +"settings_details.location.zones.enter_exit_tracked.title" = "Enter/exit tracked"; +"settings_details.location.zones.footer" = "To disable location tracking add track_ios: false to each zones settings or under customize."; +"settings_details.location.zones.location.title" = "Location"; +"settings_details.location.zones.radius.label" = "%li m"; +"settings_details.location.zones.radius.title" = "Radius"; +"settings_details.notifications.badge_section.automatic_setting.description" = "Resets the badge to 0 every time you launch the app."; +"settings_details.notifications.badge_section.automatic_setting.title" = "Automatically"; +"settings_details.notifications.badge_section.button.title" = "Reset Badge"; +"settings_details.notifications.categories.deprecated_note" = "Categories are no longer required for actionable notifications and will be removed in a future release."; +"settings_details.notifications.categories.header" = "Categories"; +"settings_details.notifications.categories_synced.empty" = "No Synced Categories"; +"settings_details.notifications.categories_synced.footer" = "Categories defined in .yaml are not editable on device."; +"settings_details.notifications.categories_synced.footer_no_categories" = "Categories may be also defined in the .yaml configuration."; +"settings_details.notifications.categories_synced.header" = "Synced Categories"; +"settings_details.notifications.info" = "Use the mobile_app notify service to send notifications to your device."; +"settings_details.notifications.local_push.status.available" = "Available (%1$@)"; +"settings_details.notifications.local_push.status.disabled" = "Disabled"; +"settings_details.notifications.local_push.status.establishing" = "Establishing"; +"settings_details.notifications.local_push.status.unavailable" = "Unavailable"; +"settings_details.notifications.local_push.status.unsupported" = "Unsupported"; +"settings_details.notifications.local_push.title" = "Local Push"; +"settings_details.notifications.new_category.title" = "New Category"; +"settings_details.notifications.permission.disabled" = "Denied"; +"settings_details.notifications.permission.enabled" = "Enabled"; +"settings_details.notifications.permission.needs_request" = "Disabled"; +"settings_details.notifications.permission.title" = "Permission"; +"settings_details.notifications.prompt_to_open_urls.title" = "Confirm before opening URL"; +"settings_details.notifications.push_id_section.header" = "Push ID"; +"settings_details.notifications.push_id_section.not_registered" = "Not registered for remote notifications"; +"settings_details.notifications.rate_limits.attempts" = "Attempts"; +"settings_details.notifications.rate_limits.delivered" = "Delivered"; +"settings_details.notifications.rate_limits.errors" = "Errors"; +"settings_details.notifications.rate_limits.footer" = "You are allowed 300 push notifications per 24 hours. Rate limits reset at midnight Universal Coordinated Time (UTC)."; +"settings_details.notifications.rate_limits.footer_with_param" = "You are allowed %u push notifications per 24 hours. Rate limits reset at midnight Universal Coordinated Time (UTC)."; +"settings_details.notifications.rate_limits.header" = "Rate Limits"; +"settings_details.notifications.rate_limits.resets_in" = "Resets In"; +"settings_details.notifications.rate_limits.total" = "Total"; +"settings_details.notifications.sounds.bundled" = "Bundled"; +"settings_details.notifications.sounds.error.cant_build_library_sounds_path" = "Can't build ~/Library/Sounds path: %@"; +"settings_details.notifications.sounds.error.cant_get_directory_contents" = "Can't list directory contents: %@"; +"settings_details.notifications.sounds.error.cant_get_file_sharing_path" = "Can't access file sharing sounds directory: %@"; +"settings_details.notifications.sounds.error.conversion_failed" = "Failed to convert audio to PCM 32 bit 48khz: %@"; +"settings_details.notifications.sounds.error.copy_error" = "Failed to copy file: %@"; +"settings_details.notifications.sounds.error.delete_error" = "Failed to delete file: %@"; +"settings_details.notifications.sounds.footer" = "Built-in, system, or custom sounds can be used with your notifications."; +"settings_details.notifications.sounds.import_custom" = "Import custom sound"; +"settings_details.notifications.sounds.import_file_sharing" = "Import sounds from iTunes File Sharing"; +"settings_details.notifications.sounds.import_mac_instructions" = "Add custom sounds to your Sounds folder to use them in notifications. Use their filename as the sound value in the service call."; +"settings_details.notifications.sounds.import_mac_open_folder" = "Open Folder in Finder"; +"settings_details.notifications.sounds.import_system" = "Import system sounds"; +"settings_details.notifications.sounds.imported" = "Imported"; +"settings_details.notifications.sounds.imported_alert.message" = "%li sounds were imported. Please restart your phone to complete the import."; +"settings_details.notifications.sounds.imported_alert.title" = "Sounds Imported"; +"settings_details.notifications.sounds.system" = "System"; +"settings_details.notifications.sounds.title" = "Sounds"; +"settings_details.notifications.title" = "Notifications"; +"settings_details.privacy.alerts.description" = "Allows checking for important alerts like security vulnerabilities."; +"settings_details.privacy.alerts.title" = "Alerts"; +"settings_details.privacy.analytics.generic_description" = "Allows collection of basic information about your device and interactions with the app. No user identifiable data is shared, including your Home Assistant URLs and tokens. You must restart the app for changes to this setting to take effect."; +"settings_details.privacy.analytics.generic_title" = "Analytics"; +"settings_details.privacy.crash_reporting.description" = "Allows for deeper tracking of crashes and other errors in the app, leading to faster fixes being published. No user identifiable information is sent, other than basic device information. You must restart the app for changes to this setting to take effect."; +"settings_details.privacy.crash_reporting.sentry" = "This feature currently uses Sentry as the report destination."; +"settings_details.privacy.crash_reporting.title" = "Crash Reporting"; +"settings_details.privacy.messaging.description" = "Firebase Cloud Messaging must be enabled for push notifications to function."; +"settings_details.privacy.messaging.title" = "Firebase Cloud Messaging"; +"settings_details.privacy.title" = "Privacy"; +"settings_details.updates.check_for_updates.include_betas" = "Include Beta Releases"; +"settings_details.updates.check_for_updates.title" = "Automatically Check for Updates"; +"settings_details.watch.title" = "Apple Watch"; +"settings_sensors.detail.attributes" = "Attributes"; +"settings_sensors.detail.device_class" = "Device Class"; +"settings_sensors.detail.enabled" = "Enabled"; +"settings_sensors.detail.icon" = "Icon"; +"settings_sensors.detail.state" = "State"; +"settings_sensors.disabled_state_replacement" = "Disabled"; +"settings_sensors.focus_permission.title" = "Focus Permission"; +"settings_sensors.last_updated.footer" = "Last Updated %@"; +"settings_sensors.loading_error.title" = "Failed to load sensors"; +"settings_sensors.periodic_update.description" = "When enabled, these sensors will update with this frequency while the app is open in the foreground."; +"settings_sensors.periodic_update.description_mac" = "When enabled, these sensors will update with this frequency while the app is open. Some sensors will update automatically more often."; +"settings_sensors.periodic_update.off" = "Off"; +"settings_sensors.periodic_update.title" = "Periodic Update"; +"settings_sensors.settings.footer" = "Changes will be applied on the next update."; +"settings_sensors.settings.header" = "Settings"; +"settings_sensors.title" = "Sensors"; +"share_extension.entered_placeholder" = "'entered' in event"; +"share_extension.error.title" = "Couldn't Send"; +"success_label" = "Success"; +"token_error.connection_failed" = "Connection failed."; +"token_error.expired" = "Token is expired."; +"token_error.token_unavailable" = "Token is unavailable."; +"updater.check_for_updates_menu.title" = "Check for Updates…"; +"updater.no_updates_available.on_latest_version" = "You're on the latest version!"; +"updater.no_updates_available.title" = "Check for Updates"; +"updater.update_available.open" = "View '%@'"; +"updater.update_available.title" = "Update Available"; +"url_handler.call_service.confirm.title" = "Call service?"; +"url_handler.call_service.confirm.message" = "Do you want to call the service %@?"; +"url_handler.call_service.error.message" = "An error occurred while attempting to call service %@: %@"; +"url_handler.call_service.success.message" = "Successfully called %@"; +"url_handler.call_service.success.title" = "Called service"; +"url_handler.error.action_not_found" = "Action Not Found"; +"url_handler.fire_event.confirm.title" = "Fire event?"; +"url_handler.fire_event.confirm.message" = "Do you want to fire the event %@?"; +"url_handler.fire_event.error.message" = "An error occurred while attempting to fire event %@: %@"; +"url_handler.fire_event.success.message" = "Successfully fired event %@"; +"url_handler.fire_event.success.title" = "Fired event"; +"url_handler.no_service.message" = "%@ is not a valid route"; +"url_handler.send_location.confirm.title" = "Send location?"; +"url_handler.send_location.confirm.message" = "Do you want to send your location?"; +"url_handler.send_location.error.message" = "An unknown error occurred while attempting to send location: %@"; +"url_handler.send_location.success.message" = "Sent a one shot location"; +"url_handler.send_location.success.title" = "Sent location"; +"url_handler.render_template.confirm.title" = "Render template?"; +"url_handler.render_template.confirm.message" = "Do you want to render %@?"; +"url_handler.x_callback_url.error.eventNameMissing" = "eventName must be defined"; +"url_handler.x_callback_url.error.general" = "A general error occurred"; +"url_handler.x_callback_url.error.serviceMissing" = "service (e.g. homeassistant.turn_on) must be defined"; +"url_handler.x_callback_url.error.templateMissing" = "A renderable template must be defined"; +"username_label" = "Username"; +"watch.configurator.delete.button" = "Delete Complication"; +"watch.configurator.delete.message" = "Are you sure you want to delete this Complication? This cannot be undone."; +"watch.configurator.delete.title" = "Delete Complication?"; +"watch.configurator.list.description" = "Configure a new Complication using the Add button. Once saved, you can choose it on your Apple Watch or in the Watch app."; +"watch.configurator.list.manual_updates.footer" = "Automatic updates occur 4 times per hour. Manual updates can also be done using notifications."; +"watch.configurator.list.manual_updates.manually_update" = "Update Complications"; +"watch.configurator.list.manual_updates.remaining" = "Remaining"; +"watch.configurator.list.manual_updates.state.not_enabled" = "Not Enabled"; +"watch.configurator.list.manual_updates.state.not_installed" = "Not Installed"; +"watch.configurator.list.manual_updates.state.not_paired" = "No Device"; +"watch.configurator.list.manual_updates.title" = "Manual Updates"; +"watch.configurator.new.multiple_complication_info" = "Adding another Complication for the same type as an existing one requires watchOS 7 or newer."; +"watch.configurator.new.title" = "New Complication"; +"watch.configurator.preview_error.not_number" = "Expected a number but got %1$@: '%2$@'"; +"watch.configurator.preview_error.out_of_range" = "Expected a number between 0.0 and 1.0 but got %1$f"; +"watch.configurator.rows.color.title" = "Color"; +"watch.configurator.rows.column_2_alignment.options.leading" = "Leading"; +"watch.configurator.rows.column_2_alignment.options.trailing" = "Trailing"; +"watch.configurator.rows.column_2_alignment.title" = "Column 2 Alignment"; +"watch.configurator.rows.display_name.title" = "Display Name"; +"watch.configurator.rows.gauge.color.title" = "Color"; +"watch.configurator.rows.gauge.gauge_type.options.closed" = "Closed"; +"watch.configurator.rows.gauge.gauge_type.options.open" = "Open"; +"watch.configurator.rows.gauge.gauge_type.title" = "Type"; +"watch.configurator.rows.gauge.style.options.fill" = "Fill"; +"watch.configurator.rows.gauge.style.options.ring" = "Ring"; +"watch.configurator.rows.gauge.style.title" = "Style"; +"watch.configurator.rows.gauge.title" = "Gauge"; +"watch.configurator.rows.icon.choose.title" = "Choose an icon"; +"watch.configurator.rows.icon.color.title" = "Color"; +"watch.configurator.rows.is_public.title" = "Show When Locked"; +"watch.configurator.rows.ring.color.title" = "Color"; +"watch.configurator.rows.ring.ring_type.options.closed" = "Closed"; +"watch.configurator.rows.ring.ring_type.options.open" = "Open"; +"watch.configurator.rows.ring.ring_type.title" = "Type"; +"watch.configurator.rows.ring.value.title" = "Fractional value"; +"watch.configurator.rows.template.selector_title" = "Choose a template"; +"watch.configurator.rows.template.title" = "Template"; +"watch.configurator.sections.gauge.footer" = "The gauge to display in the complication."; +"watch.configurator.sections.gauge.header" = "Gauge"; +"watch.configurator.sections.icon.footer" = "The image to display in the complication."; +"watch.configurator.sections.icon.header" = "Icon"; +"watch.configurator.sections.ring.footer" = "The ring showing progress surrounding the text."; +"watch.configurator.sections.ring.header" = "Ring"; +"watch.labels.complication_group.circular_small.description" = "Use circular small complications to display content in the corners of the Color watch face."; +"watch.labels.complication_group.circular_small.name" = "Circular Small"; +"watch.labels.complication_group.extra_large.description" = "Use the extra large complications to display content on the X-Large watch faces."; +"watch.labels.complication_group.extra_large.name" = "Extra Large"; +"watch.labels.complication_group.graphic.description" = "Use graphic complications to display visually rich content in the Infograph and Infograph Modular clock faces."; +"watch.labels.complication_group.graphic.name" = "Graphic"; +"watch.labels.complication_group.modular.description" = "Use modular small complications to display content in the Modular watch face."; +"watch.labels.complication_group.modular.name" = "Modular"; +"watch.labels.complication_group.utilitarian.description" = "Use the utilitarian complications to display content in the Utility, Motion, Mickey Mouse, and Minnie Mouse watch faces."; +"watch.labels.complication_group.utilitarian.name" = "Utilitarian"; +"watch.labels.complication_group_member.circular_small.description" = "A small circular area used in the Color clock face."; +"watch.labels.complication_group_member.circular_small.name" = "Circular Small"; +"watch.labels.complication_group_member.circular_small.short_name" = "Circular Small"; +"watch.labels.complication_group_member.extra_large.description" = "A large square area used in the X-Large clock face."; +"watch.labels.complication_group_member.extra_large.name" = "Extra Large"; +"watch.labels.complication_group_member.extra_large.short_name" = "Extra Large"; +"watch.labels.complication_group_member.graphic_bezel.description" = "A small square area used in the Modular clock face."; +"watch.labels.complication_group_member.graphic_bezel.name" = "Graphic Bezel"; +"watch.labels.complication_group_member.graphic_bezel.short_name" = "Bezel"; +"watch.labels.complication_group_member.graphic_circular.description" = "A large rectangular area used in the Modular clock face."; +"watch.labels.complication_group_member.graphic_circular.name" = "Graphic Circular"; +"watch.labels.complication_group_member.graphic_circular.short_name" = "Circular"; +"watch.labels.complication_group_member.graphic_corner.description" = "A small square or rectangular area used in the Utility, Mickey, Chronograph, and Simple clock faces."; +"watch.labels.complication_group_member.graphic_corner.name" = "Graphic Corner"; +"watch.labels.complication_group_member.graphic_corner.short_name" = "Corner"; +"watch.labels.complication_group_member.graphic_rectangular.description" = "A small rectangular area used in the in the Photos, Motion, and Timelapse clock faces."; +"watch.labels.complication_group_member.graphic_rectangular.name" = "Graphic Rectangular"; +"watch.labels.complication_group_member.graphic_rectangular.short_name" = "Rectangular"; +"watch.labels.complication_group_member.modular_large.description" = "A large rectangular area that spans the width of the screen in the Utility and Mickey clock faces."; +"watch.labels.complication_group_member.modular_large.name" = "Modular Large"; +"watch.labels.complication_group_member.modular_large.short_name" = "Large"; +"watch.labels.complication_group_member.modular_small.description" = "A curved area that fills the corners in the Infograph clock face."; +"watch.labels.complication_group_member.modular_small.name" = "Modular Small"; +"watch.labels.complication_group_member.modular_small.short_name" = "Small"; +"watch.labels.complication_group_member.utilitarian_large.description" = "A circular area used in the Infograph and Infograph Modular clock faces."; +"watch.labels.complication_group_member.utilitarian_large.name" = "Utilitarian Large"; +"watch.labels.complication_group_member.utilitarian_large.short_name" = "Large"; +"watch.labels.complication_group_member.utilitarian_small.description" = "A circular area with optional curved text placed along the bezel of the Infograph clock face."; +"watch.labels.complication_group_member.utilitarian_small.name" = "Utilitarian Small"; +"watch.labels.complication_group_member.utilitarian_small.short_name" = "Small"; +"watch.labels.complication_group_member.utilitarian_small_flat.description" = "A large rectangular area used in the Infograph Modular clock face."; +"watch.labels.complication_group_member.utilitarian_small_flat.name" = "Utilitarian Small Flat"; +"watch.labels.complication_group_member.utilitarian_small_flat.short_name" = "Small Flat"; +"watch.labels.complication_template.circular_small_ring_image.description" = "A template for displaying a single image surrounded by a configurable progress ring."; +"watch.labels.complication_template.circular_small_ring_text.description" = "A template for displaying a short text string encircled by a configurable progress ring."; +"watch.labels.complication_template.circular_small_simple_image.description" = "A template for displaying a single image."; +"watch.labels.complication_template.circular_small_simple_text.description" = "A template for displaying a short text string."; +"watch.labels.complication_template.circular_small_stack_image.description" = "A template for displaying an image with a line of text below it."; +"watch.labels.complication_template.circular_small_stack_text.description" = "A template for displaying two text strings stacked on top of each other."; +"watch.labels.complication_template.extra_large_columns_text.description" = "A template for displaying two rows and two columns of text."; +"watch.labels.complication_template.extra_large_ring_image.description" = "A template for displaying an image encircled by a configurable progress ring."; +"watch.labels.complication_template.extra_large_ring_text.description" = "A template for displaying text encircled by a configurable progress ring."; +"watch.labels.complication_template.extra_large_simple_image.description" = "A template for displaying an image."; +"watch.labels.complication_template.extra_large_simple_text.description" = "A template for displaying a small amount of text"; +"watch.labels.complication_template.extra_large_stack_image.description" = "A template for displaying a single image with a short line of text below it."; +"watch.labels.complication_template.extra_large_stack_text.description" = "A template for displaying two strings stacked one on top of the other."; +"watch.labels.complication_template.graphic_bezel_circular_text.description" = "A template for displaying a circular complication with text along the bezel."; +"watch.labels.complication_template.graphic_circular_closed_gauge_image.description" = "A template for displaying a full-color circular image and a closed circular gauge."; +"watch.labels.complication_template.graphic_circular_closed_gauge_text.description" = "A template for displaying text inside a closed circular gauge."; +"watch.labels.complication_template.graphic_circular_image.description" = "A template for displaying a full-color circular image."; +"watch.labels.complication_template.graphic_circular_open_gauge_image.description" = "A template for displaying a full-color circular image, an open gauge, and text."; +"watch.labels.complication_template.graphic_circular_open_gauge_range_text.description" = "A template for displaying text inside an open gauge, with leading and trailing text for the gauge."; +"watch.labels.complication_template.graphic_circular_open_gauge_simple_text.description" = "A template for displaying text inside an open gauge, with a single piece of text for the gauge."; +"watch.labels.complication_template.graphic_corner_circular_image.description" = "A template for displaying an image in the clock face’s corner."; +"watch.labels.complication_template.graphic_corner_gauge_image.description" = "A template for displaying an image and a gauge in the clock face’s corner."; +"watch.labels.complication_template.graphic_corner_gauge_text.description" = "A template for displaying text and a gauge in the clock face’s corner."; +"watch.labels.complication_template.graphic_corner_stack_text.description" = "A template for displaying stacked text in the clock face’s corner."; +"watch.labels.complication_template.graphic_corner_text_image.description" = "A template for displaying an image and text in the clock face’s corner."; +"watch.labels.complication_template.graphic_rectangular_large_image.description" = "A template for displaying a large rectangle containing header text and an image."; +"watch.labels.complication_template.graphic_rectangular_standard_body.description" = "A template for displaying a large rectangle containing text."; +"watch.labels.complication_template.graphic_rectangular_text_gauge.description" = "A template for displaying a large rectangle containing text and a gauge."; +"watch.labels.complication_template.modular_large_columns.description" = "A template for displaying multiple columns of data."; +"watch.labels.complication_template.modular_large_standard_body.description" = "A template for displaying a header row and two lines of text"; +"watch.labels.complication_template.modular_large_table.description" = "A template for displaying a header row and columns"; +"watch.labels.complication_template.modular_large_tall_body.description" = "A template for displaying a header row and a tall row of body text."; +"watch.labels.complication_template.modular_small_columns_text.description" = "A template for displaying two rows and two columns of text"; +"watch.labels.complication_template.modular_small_ring_image.description" = "A template for displaying an image encircled by a configurable progress ring"; +"watch.labels.complication_template.modular_small_ring_text.description" = "A template for displaying text encircled by a configurable progress ring"; +"watch.labels.complication_template.modular_small_simple_image.description" = "A template for displaying an image."; +"watch.labels.complication_template.modular_small_simple_text.description" = "A template for displaying a small amount of text."; +"watch.labels.complication_template.modular_small_stack_image.description" = "A template for displaying a single image with a short line of text below it."; +"watch.labels.complication_template.modular_small_stack_text.description" = "A template for displaying two strings stacked one on top of the other."; +"watch.labels.complication_template.style.circular_image" = "Circular Image"; +"watch.labels.complication_template.style.circular_text" = "Circular Text"; +"watch.labels.complication_template.style.closed_gauge_image" = "Closed Gauge Image"; +"watch.labels.complication_template.style.closed_gauge_text" = "Closed Gauge Text"; +"watch.labels.complication_template.style.columns" = "Columns"; +"watch.labels.complication_template.style.columns_text" = "Columns Text"; +"watch.labels.complication_template.style.flat" = "Flat"; +"watch.labels.complication_template.style.gauge_image" = "Gauge Image"; +"watch.labels.complication_template.style.gauge_text" = "Gauge Text"; +"watch.labels.complication_template.style.large_image" = "Large Image"; +"watch.labels.complication_template.style.open_gauge_image" = "Open Gauge Image"; +"watch.labels.complication_template.style.open_gauge_range_text" = "Open Gauge Range Text"; +"watch.labels.complication_template.style.open_gauge_simple_text" = "Open Gauge Simple Text"; +"watch.labels.complication_template.style.ring_image" = "Ring Image"; +"watch.labels.complication_template.style.ring_text" = "Ring Text"; +"watch.labels.complication_template.style.simple_image" = "Simple Image"; +"watch.labels.complication_template.style.simple_text" = "Simple Text"; +"watch.labels.complication_template.style.square" = "Square"; +"watch.labels.complication_template.style.stack_image" = "Stack Image"; +"watch.labels.complication_template.style.stack_text" = "Stack Text"; +"watch.labels.complication_template.style.standard_body" = "Standard Body"; +"watch.labels.complication_template.style.table" = "Table"; +"watch.labels.complication_template.style.tall_body" = "Tall Body"; +"watch.labels.complication_template.style.text_gauge" = "Text Gauge"; +"watch.labels.complication_template.style.text_image" = "Text Image"; +"watch.labels.complication_template.utilitarian_large_flat.description" = "A template for displaying an image and string in a single long line."; +"watch.labels.complication_template.utilitarian_small_flat.description" = "A template for displaying an image and text in a single line."; +"watch.labels.complication_template.utilitarian_small_ring_image.description" = "A template for displaying an image encircled by a configurable progress ring"; +"watch.labels.complication_template.utilitarian_small_ring_text.description" = "A template for displaying text encircled by a configurable progress ring."; +"watch.labels.complication_template.utilitarian_small_square.description" = "A template for displaying a single square image."; +"watch.labels.complication_text_areas.body1.description" = "The main body text to display in the complication."; +"watch.labels.complication_text_areas.body1.label" = "Body 1"; +"watch.labels.complication_text_areas.body2.description" = "The secondary body text to display in the complication."; +"watch.labels.complication_text_areas.body2.label" = "Body 2"; +"watch.labels.complication_text_areas.bottom.description" = "The text to display at the bottom of the gauge."; +"watch.labels.complication_text_areas.bottom.label" = "Bottom"; +"watch.labels.complication_text_areas.center.description" = "The text to display in the complication."; +"watch.labels.complication_text_areas.center.label" = "Center"; +"watch.labels.complication_text_areas.header.description" = "The header text to display in the complication."; +"watch.labels.complication_text_areas.header.label" = "Header"; +"watch.labels.complication_text_areas.inner.description" = "The inner text to display in the complication."; +"watch.labels.complication_text_areas.inner.label" = "Inner"; +"watch.labels.complication_text_areas.inside_ring.description" = "The text to display in the ring of the complication."; +"watch.labels.complication_text_areas.inside_ring.label" = "Inside Ring"; +"watch.labels.complication_text_areas.leading.description" = "The text to display on the leading edge of the gauge."; +"watch.labels.complication_text_areas.leading.label" = "Leading"; +"watch.labels.complication_text_areas.line1.description" = "The text to display on the top line of the complication."; +"watch.labels.complication_text_areas.line1.label" = "Line 1"; +"watch.labels.complication_text_areas.line2.description" = "The text to display on the bottom line of the complication."; +"watch.labels.complication_text_areas.line2.label" = "Line 2"; +"watch.labels.complication_text_areas.outer.description" = "The outer text to display in the complication."; +"watch.labels.complication_text_areas.outer.label" = "Outer"; +"watch.labels.complication_text_areas.row1_column1.description" = "The text to display in the first column of the first row."; +"watch.labels.complication_text_areas.row1_column1.label" = "Row 1, Column 1"; +"watch.labels.complication_text_areas.row1_column2.description" = "The text to display in the second column of the first row."; +"watch.labels.complication_text_areas.row1_column2.label" = "Row 1, Column 2"; +"watch.labels.complication_text_areas.row2_column1.description" = "The text to display in the first column of the second row."; +"watch.labels.complication_text_areas.row2_column1.label" = "Row 2, Column 1"; +"watch.labels.complication_text_areas.row2_column2.description" = "The text to display in the second column of the second row."; +"watch.labels.complication_text_areas.row2_column2.label" = "Row 2, Column 2"; +"watch.labels.complication_text_areas.row3_column1.description" = "The text to display in the first column of the third row."; +"watch.labels.complication_text_areas.row3_column1.label" = "Row 3, Column 1"; +"watch.labels.complication_text_areas.row3_column2.description" = "The text to display in the second column of the third row."; +"watch.labels.complication_text_areas.row3_column2.label" = "Row 3, Column 2"; +"watch.labels.complication_text_areas.trailing.description" = "The text to display on the trailing edge of the gauge."; +"watch.labels.complication_text_areas.trailing.label" = "Trailing"; +"watch.labels.no_action" = "No actions configured. Configure actions on your phone to dismiss this message."; +"watch.placeholder_complication_name" = "Placeholder"; +"widgets.actions.description" = "Perform Home Assistant actions."; +"widgets.actions.not_configured" = "No Actions Configured"; +"widgets.actions.title" = "Actions"; +"widgets.open_page.description" = "Open a frontend page in Home Assistant."; +"widgets.open_page.not_configured" = "No Pages Available"; +"widgets.open_page.title" = "Open Page"; +"yes_label" = "Yes"; diff --git a/Sources/App/Settings/DebugSettingsViewController.swift b/Sources/App/Settings/DebugSettingsViewController.swift index 9ecc1b863f..92d3723449 100644 --- a/Sources/App/Settings/DebugSettingsViewController.swift +++ b/Sources/App/Settings/DebugSettingsViewController.swift @@ -3,6 +3,7 @@ import MBProgressHUD import PromiseKit import RealmSwift import Shared +import SwiftUI import WebKit import XCGLogger @@ -130,7 +131,7 @@ class DebugSettingsViewController: HAFormViewController { UNUserNotificationCenter.current().add(notificationRequest) } - private func logs() -> Section { + private func logs() -> Eureka.Section { let section = Section() section <<< SettingsButtonRow { @@ -156,7 +157,7 @@ class DebugSettingsViewController: HAFormViewController { return section } - private func reset() -> Section { + private func reset() -> Eureka.Section { let section = Section() section <<< ButtonRow { @@ -235,7 +236,7 @@ class DebugSettingsViewController: HAFormViewController { return section } - private func developerOptions() -> Section { + private func developerOptions() -> Eureka.Section { let section = Section(header: L10n.Settings.Developer.header, footer: L10n.Settings.Developer.footer) { $0.hidden = Condition(booleanLiteral: Current.appConfiguration.rawValue > 1) $0.tag = "developerOptions" @@ -374,13 +375,30 @@ class DebugSettingsViewController: HAFormViewController { ) alert.addAction(UIAlertAction(title: L10n.okLabel, style: .default, handler: { _ in -// SentrySDK.crash() + // SentrySDK.crash() })) self?.present(alert, animated: true, completion: nil) alert.popoverPresentationController?.sourceView = cell.formViewController()?.view } + if #available(iOS 16.4, *) { + section <<< ButtonRow { + $0.title = L10n.Settings.Developer.MockThreadCredentialsSharing.title + }.onCellSelection { [weak self] _, _ in + guard let server = Current.servers.all.first else { return } + let viewController = UIHostingController( + rootView: ThreadCredentialsSharingView( + viewModel: .init( + server: server, + threadClient: SimulatorThreadClientService() + ) + ) + ) + self?.present(viewController, animated: true, completion: nil) + } + } + section <<< SwitchRow { $0.title = L10n.Settings.Developer.AnnoyingBackgroundNotifications.title $0.value = prefs.bool(forKey: XCGLogger.shouldNotifyUserDefaultsKey) diff --git a/Sources/App/ThreadCredentialsSharing/Mocks/SimulatorThreadClientService.swift b/Sources/App/ThreadCredentialsSharing/Mocks/SimulatorThreadClientService.swift new file mode 100644 index 0000000000..d60c1f5a54 --- /dev/null +++ b/Sources/App/ThreadCredentialsSharing/Mocks/SimulatorThreadClientService.swift @@ -0,0 +1,36 @@ +import Foundation + +@available(iOS 13, *) +final class SimulatorThreadClientService: THClientProtocol { + var retrieveAllCredentialsCalled = false + + func retrieveAllCredentials() async throws -> [ThreadCredential] { + retrieveAllCredentialsCalled = true + return [ + .init( + networkName: "test", + networkKey: "test", + extendedPANID: "test", + borderAgentID: "test", + activeOperationalDataSet: "test", + pskc: "test", + channel: 25, + panID: "test", + creationDate: nil, + lastModificationDate: Date() + ), + .init( + networkName: "test", + networkKey: "test", + extendedPANID: "test", + borderAgentID: "test2", + activeOperationalDataSet: "test", + pskc: "test", + channel: 25, + panID: "test", + creationDate: nil, + lastModificationDate: Date() + ), + ] + } +} diff --git a/Sources/App/ThreadCredentialsSharing/Tests/ThreadCredentialsSharingViewModelTests.swift b/Sources/App/ThreadCredentialsSharing/Tests/ThreadCredentialsSharingViewModelTests.swift new file mode 100644 index 0000000000..2029364985 --- /dev/null +++ b/Sources/App/ThreadCredentialsSharing/Tests/ThreadCredentialsSharingViewModelTests.swift @@ -0,0 +1,30 @@ +@testable import HomeAssistant +import XCTest + +@available(iOS 13, *) +final class ThreadCredentialsSharingViewModelTests: XCTestCase { + private var sut: ThreadCredentialsSharingViewModel! + private var mockClient: SimulatorThreadClientService! + + override func setUpWithError() throws { + mockClient = SimulatorThreadClientService() + sut = .init( + server: ServerFixture.standard, + threadClient: mockClient + ) + } + + override func tearDownWithError() throws { + sut = nil + mockClient = nil + } + + func test_retrieveAllCredentials_calls_retrieveAllCredentials() async { + // When + await sut.retrieveAllCredentials() + + // Then + XCTAssertTrue(mockClient.retrieveAllCredentialsCalled) + XCTAssertEqual(sut.credentials.count, 2) + } +} diff --git a/Sources/App/ThreadCredentialsSharing/ThreadClientService.swift b/Sources/App/ThreadCredentialsSharing/ThreadClientService.swift new file mode 100644 index 0000000000..9f8ed2e434 --- /dev/null +++ b/Sources/App/ThreadCredentialsSharing/ThreadClientService.swift @@ -0,0 +1,55 @@ +import Foundation +import Shared + +@available(iOS 13, *) +protocol THClientProtocol { + func retrieveAllCredentials() async throws -> [ThreadCredential] +} + +struct ThreadCredential { + let networkName: String + let networkKey: String + let extendedPANID: String + let borderAgentID: String + let activeOperationalDataSet: String + let pskc: String + let channel: UInt8 + let panID: String + let creationDate: Date? + let lastModificationDate: Date? +} + +#if canImport(ThreadNetwork) +import ThreadNetwork + +@available(iOS 15, *) +final class ThreadClientService: THClientProtocol { + private let client = THClient() + + func retrieveAllCredentials() async throws -> [ThreadCredential] { + let placeholder = "Unknown" + return try await client.allCredentials().map { credential in + ThreadCredential( + networkName: credential.networkName ?? placeholder, + networkKey: credential.networkKey?.hexadecimal ?? placeholder, + extendedPANID: credential.extendedPANID?.hexadecimal ?? placeholder, + borderAgentID: credential.borderAgentID?.hexadecimal ?? placeholder, + activeOperationalDataSet: credential.activeOperationalDataSet?.hexadecimal ?? placeholder, + pskc: credential.pskc?.hexadecimal ?? placeholder, + channel: credential.channel, + panID: credential.panID?.hexadecimal ?? placeholder, + creationDate: credential.creationDate, + lastModificationDate: credential.lastModificationDate + ) + } + } +} +#else +/// For SwiftUI Preview +@available(iOS 15, *) +final class ThreadClientService: THClientProtocol { + func retrieveAllCredentials() async throws -> [ThreadCredential] { + [] + } +} +#endif diff --git a/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharing+build.swift b/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharing+build.swift new file mode 100644 index 0000000000..39cb7f4c31 --- /dev/null +++ b/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharing+build.swift @@ -0,0 +1,14 @@ +import Foundation +import Shared + +@available(iOS 16.4, *) +extension ThreadCredentialsSharingView { + static func build(server: Server) -> ThreadCredentialsSharingView { + .init( + viewModel: .init( + server: server, + threadClient: ThreadClientService() + ) + ) + } +} diff --git a/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharingView.swift b/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharingView.swift new file mode 100644 index 0000000000..1ecb5d7458 --- /dev/null +++ b/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharingView.swift @@ -0,0 +1,135 @@ +import Shared +import SwiftUI + +@available(iOS 16.4, *) +struct ThreadCredentialsSharingView: View { + @Environment(\.dismiss) private var dismiss + @StateObject private var viewModel: ThreadCredentialsSharingViewModel + + init(viewModel: ThreadCredentialsSharingViewModel) { + self._viewModel = .init(wrappedValue: viewModel) + } + + var body: some View { + NavigationView { + Group { + if viewModel.showLoader { + progressView + } else { + credentialsList + } + } + .navigationTitle(L10n.Thread.Credentials.screenTitle) + .navigationBarTitleDisplayMode(.inline) + .background(Color(uiColor: .secondarySystemBackground)) + .toolbar(content: { + ToolbarItem(placement: .topBarTrailing) { + Button { + dismiss() + } label: { + Text(L10n.doneLabel) + } + } + }) + .alert(alertTitle, isPresented: $viewModel.showAlert) { + errorAlertActions + } message: { + if case let .error(_, message) = viewModel.alertType { + Text(message) + } + } + } + .navigationViewStyle(.stack) + .onAppear { + Task { + await viewModel.retrieveAllCredentials() + } + } + } + + private var alertTitle: String { + if case let .error(title, _) = viewModel.alertType { + return title + } else if case let .success(title) = viewModel.alertType { + return title + } else { + return "" + } + } + + @ViewBuilder + private var errorAlertActions: some View { + Button { + /* no-op */ + } label: { + Text(L10n.doneLabel) + } + + if case .error = viewModel.alertType { + Button { + Task { + await viewModel.retrieveAllCredentials() + } + } label: { + Text(L10n.retryLabel) + } + } + } + + private var progressView: some View { + ProgressView() + .progressViewStyle(.circular) + .scaleEffect(CGSize(width: 2, height: 2)) + } + + @ViewBuilder + private var credentialsList: some View { + if viewModel.credentials.isEmpty { + Text("You don't have credentials available on your iCloud Keychain.") + .multilineTextAlignment(.center) + } else { + List(viewModel.credentials, id: \.borderAgentID) { credential in + makeCredentialCard(credential: credential) + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) + } + .listStyle(.plain) + } + } + + private func makeCredentialCard(credential: ThreadCredential) -> some View { + CardView(backgroundColor: Color(uiColor: .systemBackground)) { + makeCardPropertyView( + title: L10n.Thread.Credentials.networkNameTitle, + value: credential.networkName + ) + makeCardPropertyView( + title: L10n.Thread.Credentials.borderAgentIdTitle, + value: credential.borderAgentID + ) + makeCardPropertyView( + title: L10n.Thread.Credentials.networkKeyTitle, + value: credential.networkKey + ) + Button { + viewModel.shareCredentialWithHomeAssistant(credential: credential) + } label: { + Text(L10n.Thread.Credentials.shareCredentialsButtonTitle) + } + .buttonStyle(.textButton) + } + } + + private func makeCardPropertyView(title: String, value: String) -> some View { + VStack(alignment: .leading, spacing: .zero) { + Group { + Text(title) + .font(.footnote) + Text(value) + .textSelection(.enabled) + .font(.body.bold()) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + } +} diff --git a/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharingViewModel.swift b/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharingViewModel.swift new file mode 100644 index 0000000000..43c254172d --- /dev/null +++ b/Sources/App/ThreadCredentialsSharing/ThreadCredentialsSharingViewModel.swift @@ -0,0 +1,61 @@ +import Foundation +import HAKit +import Shared + +@available(iOS 13, *) +final class ThreadCredentialsSharingViewModel: ObservableObject { + enum AlertType { + case success(title: String) + case error(title: String, message: String) + } + + @Published var credentials: [ThreadCredential] = [] + @Published var showAlert = false + @Published var showLoader = false + @Published var alertType: AlertType? + + private let threadClient: THClientProtocol + private let connection: HAConnection + + init(server: Server, threadClient: THClientProtocol) { + self.threadClient = threadClient + self.connection = Current.api(for: server).connection + } + + @MainActor + func retrieveAllCredentials() async { + showLoader = true + do { + credentials = try await threadClient.retrieveAllCredentials() + } catch { + showAlert(type: .error(title: L10n.errorLabel, message: "Error message: \(error.localizedDescription)")) + } + showLoader = false + } + + @MainActor + func shareCredentialWithHomeAssistant(credential: ThreadCredential) { + let request = HARequest(type: .webSocket("thread/add_dataset_tlv"), data: [ + "tlv": credential.activeOperationalDataSet, + "source": "iOS-app", + ]) + connection.send(request).promise.pipe { [weak self] result in + guard let self else { return } + switch result { + case .fulfilled: + self.showAlert(type: .success(title: L10n.successLabel)) + case let .rejected(error): + self + .showAlert(type: .error( + title: L10n.errorLabel, + message: "Error message: \(error.localizedDescription)" + )) + } + } + } + + private func showAlert(type: AlertType) { + alertType = type + showAlert = true + } +} diff --git a/Sources/App/Utilities/MenuManager.swift b/Sources/App/Utilities/MenuManager.swift index 3cb299fc42..5f7096177d 100644 --- a/Sources/App/Utilities/MenuManager.swift +++ b/Sources/App/Utilities/MenuManager.swift @@ -404,8 +404,8 @@ class MenuManager { Current.macBridge.configureStatusItem(using: AppMacBridgeStatusItemConfiguration( isVisible: Current.settingsStore.locationVisibility.isStatusItemVisible, - image: Asset.statusItemIcon.image.cgImage!, - imageSize: Asset.statusItemIcon.image.size, + image: Asset.SharedAssets.statusItemIcon.image.cgImage!, + imageSize: Asset.SharedAssets.statusItemIcon.image.size, accessibilityLabel: appName, items: menuItems, primaryActionHandler: { callbackInfo in diff --git a/Sources/App/WebView/Tests/WebViewExternalBusMessageTests.swift b/Sources/App/WebView/Tests/WebViewExternalBusMessageTests.swift new file mode 100644 index 0000000000..5bc86651f5 --- /dev/null +++ b/Sources/App/WebView/Tests/WebViewExternalBusMessageTests.swift @@ -0,0 +1,17 @@ +@testable import HomeAssistant +import XCTest +final class WebViewExternalBusMessageTests: XCTestCase { + func test_externalBus_messageKeys() { + XCTAssertEqual(WebViewExternalBusMessage.configGet.rawValue, "config/get") + XCTAssertEqual(WebViewExternalBusMessage.configScreenShow.rawValue, "config_screen/show") + XCTAssertEqual(WebViewExternalBusMessage.haptic.rawValue, "haptic") + XCTAssertEqual(WebViewExternalBusMessage.connectionStatus.rawValue, "connection-status") + XCTAssertEqual(WebViewExternalBusMessage.tagRead.rawValue, "tag/read") + XCTAssertEqual(WebViewExternalBusMessage.tagWrite.rawValue, "tag/write") + XCTAssertEqual(WebViewExternalBusMessage.themeUpdate.rawValue, "theme-update") + XCTAssertEqual(WebViewExternalBusMessage.matterCommission.rawValue, "matter/commission") + XCTAssertEqual(WebViewExternalBusMessage.threadImportCredentials.rawValue, "thread/import_credentials") + + XCTAssertEqual(WebViewExternalBusMessage.allCases.count, 9) + } +} diff --git a/Sources/App/WebView/WebViewController.swift b/Sources/App/WebView/WebViewController.swift index 4b928e6eff..4aed363ed0 100644 --- a/Sources/App/WebView/WebViewController.swift +++ b/Sources/App/WebView/WebViewController.swift @@ -8,21 +8,22 @@ import MBProgressHUD import PromiseKit import Shared import SwiftMessages +import SwiftUI import UIKit import WebKit -class WebViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, UIViewControllerRestoration { +final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, UIViewControllerRestoration { var webView: WKWebView! let server: Server - var urlObserver: NSKeyValueObservation? - var tokens = [HACancellable]() + private var urlObserver: NSKeyValueObservation? + private var tokens = [HACancellable]() - let refreshControl = UIRefreshControl() - let sidebarGestureRecognizer: UIScreenEdgePanGestureRecognizer + private let refreshControl = UIRefreshControl() + private let sidebarGestureRecognizer: UIScreenEdgePanGestureRecognizer - var keepAliveTimer: Timer? + private var keepAliveTimer: Timer? private var initialURL: URL? static func viewController( @@ -36,7 +37,7 @@ class WebViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, U } } - let settingsButton: UIButton! = { + private let settingsButton: UIButton! = { let button = UIButton() button.setImage( MaterialDesignIcons.cogIcon.image(ofSize: CGSize(width: 36, height: 36), color: .white), @@ -1001,78 +1002,82 @@ extension WebViewController: WKScriptMessageHandler { var response: Guarantee? - switch incomingMessage.MessageType { - case "config/get": - response = Guarantee { seal in - DispatchQueue.global(qos: .userInitiated).async { - seal(WebSocketMessage( - id: incomingMessage.ID!, - type: "result", - result: [ - "hasSettingsScreen": !Current.isCatalyst, - "canWriteTag": Current.tags.isNFCAvailable, - "canCommissionMatter": Current.matter.isAvailable, - ] - )) + if let externalBusMessage = WebViewExternalBusMessage(rawValue: incomingMessage.MessageType) { + switch externalBusMessage { + case .configGet: + response = Guarantee { seal in + DispatchQueue.global(qos: .userInitiated).async { + seal(WebSocketMessage( + id: incomingMessage.ID!, + type: "result", + result: [ + "hasSettingsScreen": !Current.isCatalyst, + "canWriteTag": Current.tags.isNFCAvailable, + "canCommissionMatter": Current.matter.isAvailable, + "canImportThreadCredentials": Current.matter.threadCredentialsSharingEnabled, + ] + )) + } + } + case .configScreenShow: + showSettingsViewController() + case .haptic: + guard let hapticType = incomingMessage.Payload?["hapticType"] as? String else { + Current.Log.error("Received haptic via bus but hapticType was not string! \(incomingMessage)") + return + } + handleHaptic(hapticType) + case .connectionStatus: + guard let connEvt = incomingMessage.Payload?["event"] as? String else { + Current.Log.error("Received connection-status via bus but event was not string! \(incomingMessage)") + return + } + // Possible values: connected, disconnected, auth-invalid + UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseInOut, animations: { + self.settingsButton.alpha = connEvt == "connected" ? 0.0 : 1.0 + }, completion: nil) + case .tagRead: + response = Current.tags.readNFC().map { tag in + WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": true, "tag": tag]) + }.recover { _ in + .value(WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": false])) + } + case .tagWrite: + let (promise, seal) = Guarantee.pending() + response = promise.map { success in + WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": success]) } - } - case "config_screen/show": - showSettingsViewController() - case "haptic": - guard let hapticType = incomingMessage.Payload?["hapticType"] as? String else { - Current.Log.error("Received haptic via bus but hapticType was not string! \(incomingMessage)") - return - } - handleHaptic(hapticType) - case "connection-status": - guard let connEvt = incomingMessage.Payload?["event"] as? String else { - Current.Log.error("Received connection-status via bus but event was not string! \(incomingMessage)") - return - } - // Possible values: connected, disconnected, auth-invalid - UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseInOut, animations: { - self.settingsButton.alpha = connEvt == "connected" ? 0.0 : 1.0 - }, completion: nil) - case "tag/read": - response = Current.tags.readNFC().map { tag in - WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": true, "tag": tag]) - }.recover { _ in - .value(WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": false])) - } - case "tag/write": - let (promise, seal) = Guarantee.pending() - response = promise.map { success in - WebSocketMessage(id: incomingMessage.ID!, type: "result", result: ["success": success]) - } - firstly { () throws -> Promise<(tag: String, name: String?)> in - if let tag = incomingMessage.Payload?["tag"] as? String, tag.isEmpty == false { - return .value((tag: tag, name: incomingMessage.Payload?["name"] as? String)) - } else { - throw HomeAssistantAPI.APIError.invalidResponse + firstly { () throws -> Promise<(tag: String, name: String?)> in + if let tag = incomingMessage.Payload?["tag"] as? String, tag.isEmpty == false { + return .value((tag: tag, name: incomingMessage.Payload?["name"] as? String)) + } else { + throw HomeAssistantAPI.APIError.invalidResponse + } + }.then { tagInfo in + Current.tags.writeNFC(value: tagInfo.tag) + }.done { _ in + Current.Log.info("wrote tag via external bus") + seal(true) + }.catch { error in + Current.Log.error("couldn't write tag via external bus: \(error)") + seal(false) } - }.then { tagInfo in - Current.tags.writeNFC(value: tagInfo.tag) - }.done { _ in - Current.Log.info("wrote tag via external bus") - seal(true) - }.catch { error in - Current.Log.error("couldn't write tag via external bus: \(error)") - seal(false) - } - case "theme-update": - webView.evaluateJavaScript("notifyThemeColors()", completionHandler: nil) - case "matter/commission": - Current.matter.commission(server).done { - Current.Log.info("commission call completed") - }.catch { error in - // we don't show a user-visible error because even a successful operation will return 'cancelled' - // but the errors aren't public, so we can't compare -- the apple ui shows errors visually though - Current.Log.error(error) + case .themeUpdate: + webView.evaluateJavaScript("notifyThemeColors()", completionHandler: nil) + case .matterCommission: + Current.matter.commission(server).done { + Current.Log.info("commission call completed") + }.catch { error in + // we don't show a user-visible error because even a successful operation will return 'cancelled' + // but the errors aren't public, so we can't compare -- the apple ui shows errors visually though + Current.Log.error(error) + } + case .threadImportCredentials: + threadCredentialsRequested() } - default: + } else { Current.Log.error("unknown: \(incomingMessage.MessageType)") - return } response?.then { [self] outgoing in @@ -1102,6 +1107,16 @@ extension WebViewController: WKScriptMessageHandler { } } } + + private func threadCredentialsRequested() { + if #available(iOS 16.4, *) { + let threadDebugView = UIHostingController(rootView: ThreadCredentialsSharingView(viewModel: .init( + server: server, + threadClient: ThreadClientService() + ))) + present(threadDebugView, animated: true) + } + } } extension WebViewController: UIScrollViewDelegate { diff --git a/Sources/App/WebView/WebViewExternalBusMessage.swift b/Sources/App/WebView/WebViewExternalBusMessage.swift new file mode 100644 index 0000000000..5d28a6cc7e --- /dev/null +++ b/Sources/App/WebView/WebViewExternalBusMessage.swift @@ -0,0 +1,13 @@ +import Foundation + +enum WebViewExternalBusMessage: String, CaseIterable { + case configGet = "config/get" + case configScreenShow = "config_screen/show" + case haptic + case connectionStatus = "connection-status" + case tagRead = "tag/read" + case tagWrite = "tag/write" + case themeUpdate = "theme-update" + case matterCommission = "matter/commission" + case threadImportCredentials = "thread/import_credentials" +} diff --git a/Sources/Extensions/Widgets/Actions/WidgetActions.swift b/Sources/Extensions/Widgets/Actions/WidgetActions.swift index ccc721bf62..c07e202232 100644 --- a/Sources/Extensions/Widgets/Actions/WidgetActions.swift +++ b/Sources/Extensions/Widgets/Actions/WidgetActions.swift @@ -29,6 +29,7 @@ struct WidgetActions: Widget { ) } ) + .contentMarginsDisabledIfAvailable() .configurationDisplayName(L10n.Widgets.Actions.title) .description(L10n.Widgets.Actions.description) .supportedFamilies({ diff --git a/Sources/Extensions/Widgets/Common/WidgetBackground.swift b/Sources/Extensions/Widgets/Common/WidgetBackground.swift new file mode 100644 index 0000000000..7cdafaf82b --- /dev/null +++ b/Sources/Extensions/Widgets/Common/WidgetBackground.swift @@ -0,0 +1,14 @@ +import Foundation +import SwiftUI + +extension View { + func widgetBackground(_ backgroundView: some View) -> some View { + if #available(iOS 17.0, *) { + return containerBackground(for: .widget) { + backgroundView + } + } else { + return background(backgroundView) + } + } +} diff --git a/Sources/Extensions/Widgets/Common/WidgetBasicContainerView.swift b/Sources/Extensions/Widgets/Common/WidgetBasicContainerView.swift index 9249a67459..fd66c1639b 100644 --- a/Sources/Extensions/Widgets/Common/WidgetBasicContainerView.swift +++ b/Sources/Extensions/Widgets/Common/WidgetBasicContainerView.swift @@ -15,11 +15,14 @@ struct WidgetBasicContainerView: View { } var body: some View { - switch contents.count { - case 0: emptyViewGenerator() - case 1: singleView(for: contents.first!) - default: multiView(for: contents) + Group { + switch contents.count { + case 0: emptyViewGenerator() + case 1: singleView(for: contents.first!) + default: multiView(for: contents) + } } + .widgetBackground(Color.clear) } func singleView(for model: WidgetBasicViewModel) -> some View { diff --git a/Sources/Extensions/Widgets/Common/WidgetBasicView.swift b/Sources/Extensions/Widgets/Common/WidgetBasicView.swift index 9eb5781908..53fdabf274 100644 --- a/Sources/Extensions/Widgets/Common/WidgetBasicView.swift +++ b/Sources/Extensions/Widgets/Common/WidgetBasicView.swift @@ -95,8 +95,8 @@ enum WidgetBasicSizeStyle { } struct WidgetBasicView: View { - let model: WidgetBasicViewModel - let sizeStyle: WidgetBasicSizeStyle + private let model: WidgetBasicViewModel + private let sizeStyle: WidgetBasicSizeStyle init(model: WidgetBasicViewModel, sizeStyle: WidgetBasicSizeStyle) { self.model = model @@ -106,8 +106,6 @@ struct WidgetBasicView: View { var body: some View { ZStack(alignment: .leading) { - model.backgroundColor - Rectangle().fill( LinearGradient( gradient: .init(colors: [.white.opacity(0.06), .black.opacity(0.06)]), @@ -177,7 +175,8 @@ struct WidgetBasicView: View { if let subtext = subtext { subtext } - }.padding( + } + .padding( [.leading, .trailing] ).padding( [.top, .bottom], @@ -185,5 +184,6 @@ struct WidgetBasicView: View { ) } } + .background(model.backgroundColor) } } diff --git a/Sources/Extensions/Widgets/Common/WidgetContentMargin.swift b/Sources/Extensions/Widgets/Common/WidgetContentMargin.swift new file mode 100644 index 0000000000..b75e191bd9 --- /dev/null +++ b/Sources/Extensions/Widgets/Common/WidgetContentMargin.swift @@ -0,0 +1,12 @@ +import Foundation +import SwiftUI + +extension WidgetConfiguration { + func contentMarginsDisabledIfAvailable() -> some WidgetConfiguration { + if #available(iOSApplicationExtension 17.0, *) { + return self.contentMarginsDisabled() + } else { + return self + } + } +} diff --git a/Sources/Extensions/Widgets/OpenPage/WidgetOpenPage.swift b/Sources/Extensions/Widgets/OpenPage/WidgetOpenPage.swift index 93c9694149..3defb4b759 100644 --- a/Sources/Extensions/Widgets/OpenPage/WidgetOpenPage.swift +++ b/Sources/Extensions/Widgets/OpenPage/WidgetOpenPage.swift @@ -34,6 +34,7 @@ struct WidgetOpenPage: Widget { ) } ) + .contentMarginsDisabledIfAvailable() .configurationDisplayName(L10n.Widgets.OpenPage.title) .description(L10n.Widgets.OpenPage.description) .supportedFamilies({ diff --git a/Sources/Shared/API/Fixtures/ServerFixture.swift b/Sources/Shared/API/Fixtures/ServerFixture.swift new file mode 100644 index 0000000000..9e79241cd8 --- /dev/null +++ b/Sources/Shared/API/Fixtures/ServerFixture.swift @@ -0,0 +1,30 @@ +import Foundation +import Shared + +struct ServerFixture { + static var standard = Server(identifier: "123", getter: { + .init( + name: "A Name", + connection: .init( + externalURL: nil, + internalURL: nil, + cloudhookURL: nil, + remoteUIURL: nil, + webhookID: "", + webhookSecret: nil, + internalSSIDs: nil, + internalHardwareAddresses: nil, + isLocalPushEnabled: false, + securityExceptions: .init(exceptions: []) + ), + token: .init( + accessToken: "", + refreshToken: "", + expiration: Date() + ), + version: "123" + ) + }, setter: { _ in + true + }) +} diff --git a/Sources/App/Resources/Assets.swift b/Sources/Shared/Assets/Assets.swift similarity index 75% rename from Sources/App/Resources/Assets.swift rename to Sources/Shared/Assets/Assets.swift index e267604b26..9938208197 100644 --- a/Sources/App/Resources/Assets.swift +++ b/Sources/Shared/Assets/Assets.swift @@ -11,36 +11,42 @@ // Deprecated typealiases @available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") -internal typealias AssetColorTypeAlias = ColorAsset.Color +public typealias AssetColorTypeAlias = ColorAsset.Color @available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") -internal typealias AssetImageTypeAlias = ImageAsset.Image +public typealias AssetImageTypeAlias = ImageAsset.Image // swiftlint:disable superfluous_disable_command file_length implicit_return // MARK: - Asset Catalogs // swiftlint:disable identifier_name line_length nesting type_body_length type_name -internal enum Asset { - internal static let launchScreenBackground = ColorAsset(name: "launchScreen-background") - internal static let launchScreenLogo = ImageAsset(name: "launchScreen-logo") - internal static let logo = ImageAsset(name: "Logo") - internal static let statusItemIcon = ImageAsset(name: "statusItemIcon") +public enum Asset { + public enum Colors { + public static let haPrimary = ColorAsset(name: "haPrimary") + public static let onSurface = ColorAsset(name: "onSurface") + } + public enum SharedAssets { + public static let launchScreenBackground = ColorAsset(name: "launchScreen-background") + public static let launchScreenLogo = ImageAsset(name: "launchScreen-logo") + public static let logo = ImageAsset(name: "Logo") + public static let statusItemIcon = ImageAsset(name: "statusItemIcon") + } } // swiftlint:enable identifier_name line_length nesting type_body_length type_name // MARK: - Implementation Details -internal final class ColorAsset { - internal fileprivate(set) var name: String +public final class ColorAsset { + public fileprivate(set) var name: String #if os(macOS) - internal typealias Color = NSColor + public typealias Color = NSColor #elseif os(iOS) || os(tvOS) || os(watchOS) - internal typealias Color = UIColor + public typealias Color = UIColor #endif @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) - internal private(set) lazy var color: Color = { + public private(set) lazy var color: Color = { guard let color = Color(asset: self) else { fatalError("Unable to load color asset named \(name).") } @@ -49,7 +55,7 @@ internal final class ColorAsset { #if os(iOS) || os(tvOS) @available(iOS 11.0, tvOS 11.0, *) - internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { + public func color(compatibleWith traitCollection: UITraitCollection) -> Color { let bundle = BundleToken.bundle guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { fatalError("Unable to load color asset named \(name).") @@ -63,7 +69,7 @@ internal final class ColorAsset { } } -internal extension ColorAsset.Color { +public extension ColorAsset.Color { @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) convenience init?(asset: ColorAsset) { let bundle = BundleToken.bundle @@ -77,17 +83,17 @@ internal extension ColorAsset.Color { } } -internal struct ImageAsset { - internal fileprivate(set) var name: String +public struct ImageAsset { + public fileprivate(set) var name: String #if os(macOS) - internal typealias Image = NSImage + public typealias Image = NSImage #elseif os(iOS) || os(tvOS) || os(watchOS) - internal typealias Image = UIImage + public typealias Image = UIImage #endif @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) - internal var image: Image { + public var image: Image { let bundle = BundleToken.bundle #if os(iOS) || os(tvOS) let image = Image(named: name, in: bundle, compatibleWith: nil) @@ -105,7 +111,7 @@ internal struct ImageAsset { #if os(iOS) || os(tvOS) @available(iOS 8.0, tvOS 9.0, *) - internal func image(compatibleWith traitCollection: UITraitCollection) -> Image { + public func image(compatibleWith traitCollection: UITraitCollection) -> Image { let bundle = BundleToken.bundle guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { fatalError("Unable to load image asset named \(name).") @@ -115,7 +121,7 @@ internal struct ImageAsset { #endif } -internal extension ImageAsset.Image { +public extension ImageAsset.Image { @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) @available(macOS, deprecated, message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") diff --git a/Sources/App/Resources/Assets.xcassets/LaunchScreen/Contents.json b/Sources/Shared/Assets/Colors.xcassets/Contents.json similarity index 100% rename from Sources/App/Resources/Assets.xcassets/LaunchScreen/Contents.json rename to Sources/Shared/Assets/Colors.xcassets/Contents.json diff --git a/Sources/Shared/Assets/Colors.xcassets/haPrimary.colorset/Contents.json b/Sources/Shared/Assets/Colors.xcassets/haPrimary.colorset/Contents.json new file mode 100644 index 0000000000..ff0b97a53a --- /dev/null +++ b/Sources/Shared/Assets/Colors.xcassets/haPrimary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF8", + "green" : "0xAE", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFC", + "green" : "0xDF", + "red" : "0x99" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Shared/Assets/Colors.xcassets/onSurface.colorset/Contents.json b/Sources/Shared/Assets/Colors.xcassets/onSurface.colorset/Contents.json new file mode 100644 index 0000000000..c82f2840f9 --- /dev/null +++ b/Sources/Shared/Assets/Colors.xcassets/onSurface.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.160", + "blue" : "0x1E", + "green" : "0x1C", + "red" : "0x1A" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.160", + "blue" : "0xE5", + "green" : "0xE2", + "red" : "0xE2" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Shared/Assets/SharedAssets.xcassets/Contents.json b/Sources/Shared/Assets/SharedAssets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/Sources/Shared/Assets/SharedAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/Contents.json b/Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/App/Resources/Assets.xcassets/LaunchScreen/launchScreen-background.colorset/Contents.json b/Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/launchScreen-background.colorset/Contents.json similarity index 100% rename from Sources/App/Resources/Assets.xcassets/LaunchScreen/launchScreen-background.colorset/Contents.json rename to Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/launchScreen-background.colorset/Contents.json diff --git a/Sources/App/Resources/Assets.xcassets/LaunchScreen/launchScreen-logo.imageset/Contents.json b/Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/launchScreen-logo.imageset/Contents.json similarity index 100% rename from Sources/App/Resources/Assets.xcassets/LaunchScreen/launchScreen-logo.imageset/Contents.json rename to Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/launchScreen-logo.imageset/Contents.json diff --git a/Sources/App/Resources/Assets.xcassets/LaunchScreen/launchScreen-logo.imageset/home-assistant-wordmark-vertical-color-on-dark.pdf b/Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/launchScreen-logo.imageset/home-assistant-wordmark-vertical-color-on-dark.pdf similarity index 100% rename from Sources/App/Resources/Assets.xcassets/LaunchScreen/launchScreen-logo.imageset/home-assistant-wordmark-vertical-color-on-dark.pdf rename to Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/launchScreen-logo.imageset/home-assistant-wordmark-vertical-color-on-dark.pdf diff --git a/Sources/App/Resources/Assets.xcassets/LaunchScreen/launchScreen-logo.imageset/home-assistant-wordmark-vertical-color-on-light.pdf b/Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/launchScreen-logo.imageset/home-assistant-wordmark-vertical-color-on-light.pdf similarity index 100% rename from Sources/App/Resources/Assets.xcassets/LaunchScreen/launchScreen-logo.imageset/home-assistant-wordmark-vertical-color-on-light.pdf rename to Sources/Shared/Assets/SharedAssets.xcassets/LaunchScreen/launchScreen-logo.imageset/home-assistant-wordmark-vertical-color-on-light.pdf diff --git a/Sources/Shared/Assets/SharedAssets.xcassets/Logo.imageset/Contents.json b/Sources/Shared/Assets/SharedAssets.xcassets/Logo.imageset/Contents.json new file mode 100644 index 0000000000..9c00f8d9e4 --- /dev/null +++ b/Sources/Shared/Assets/SharedAssets.xcassets/Logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "home-assistant-logomark-color-on-light.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/App/Resources/Assets.xcassets/Logo.imageset/home-assistant-logomark-color-on-light.pdf b/Sources/Shared/Assets/SharedAssets.xcassets/Logo.imageset/home-assistant-logomark-color-on-light.pdf similarity index 100% rename from Sources/App/Resources/Assets.xcassets/Logo.imageset/home-assistant-logomark-color-on-light.pdf rename to Sources/Shared/Assets/SharedAssets.xcassets/Logo.imageset/home-assistant-logomark-color-on-light.pdf diff --git a/Sources/App/Resources/Assets.xcassets/statusItemIcon.imageset/Contents.json b/Sources/Shared/Assets/SharedAssets.xcassets/statusItemIcon.imageset/Contents.json similarity index 100% rename from Sources/App/Resources/Assets.xcassets/statusItemIcon.imageset/Contents.json rename to Sources/Shared/Assets/SharedAssets.xcassets/statusItemIcon.imageset/Contents.json diff --git a/Sources/App/Resources/Assets.xcassets/statusItemIcon.imageset/home-assistant-logomark-monochrome-on-light-small.pdf b/Sources/Shared/Assets/SharedAssets.xcassets/statusItemIcon.imageset/home-assistant-logomark-monochrome-on-light-small.pdf similarity index 100% rename from Sources/App/Resources/Assets.xcassets/statusItemIcon.imageset/home-assistant-logomark-monochrome-on-light-small.pdf rename to Sources/Shared/Assets/SharedAssets.xcassets/statusItemIcon.imageset/home-assistant-logomark-monochrome-on-light-small.pdf diff --git a/Sources/Shared/DesignSystem/Components/CardView.swift b/Sources/Shared/DesignSystem/Components/CardView.swift new file mode 100644 index 0000000000..a6a3639fe6 --- /dev/null +++ b/Sources/Shared/DesignSystem/Components/CardView.swift @@ -0,0 +1,41 @@ +import SwiftUI + +@available(iOS 13, *) +public struct CardView: View { + public let content: () -> Content + public let backgroundColor: Color? + + public init(backgroundColor: Color? = nil, @ViewBuilder content: @escaping () -> Content) { + self.backgroundColor = backgroundColor + self.content = content + } + + public var body: some View { + VStack(spacing: .zero) { + content() + .padding() + } + .frame(maxWidth: .infinity) + .background(backgroundColor) + /* Corner radius is duplicated to assure even with a background color it will + keep the corner radius */ + .cornerRadius(HACornerRadius.standard) + .overlay( + RoundedRectangle(cornerRadius: HACornerRadius.standard) + .stroke( + Color(Asset.Colors.onSurface.name), lineWidth: 1 + ) + ) + } +} + +@available(iOS 13, *) +#Preview { + VStack { + CardView { + Text("abc") + } + .padding() + } + .background(Color.yellow) +} diff --git a/Sources/Shared/DesignSystem/Components/HAButton.swift b/Sources/Shared/DesignSystem/Components/HAButton.swift new file mode 100644 index 0000000000..4f9b1482c7 --- /dev/null +++ b/Sources/Shared/DesignSystem/Components/HAButton.swift @@ -0,0 +1,32 @@ +import SwiftUI + +@available(iOS 13, *) +public struct TextButton: ButtonStyle { + private let backgroundColor = Color.asset(Asset.Colors.haPrimary) + + public func makeBody(configuration: Configuration) -> some View { + configuration.label + .frame(minHeight: 40) + .multilineTextAlignment(.center) + .padding(.horizontal, 22) + .padding(.vertical, 10) + .foregroundColor(.asset(Asset.Colors.haPrimary)) + .background(configuration.isPressed ? backgroundColor.opacity(0.08) : backgroundColor.opacity(0.12)) + .clipShape(RoundedRectangle(cornerRadius: .infinity)) + } +} + +@available(iOS 13, *) +public extension ButtonStyle where Self == TextButton { + static var textButton: some ButtonStyle { + TextButton() + } +} + +@available(iOS 13, *) +#Preview { + Button(action: {}) { + Text("Hello World") + } + .buttonStyle(.textButton) +} diff --git a/Sources/Shared/Environment/MatterWrapper.swift b/Sources/Shared/Environment/MatterWrapper.swift index e898e770f2..74fb8d70b2 100644 --- a/Sources/Shared/Environment/MatterWrapper.swift +++ b/Sources/Shared/Environment/MatterWrapper.swift @@ -16,6 +16,19 @@ public class MatterWrapper { #endif }() + public var threadCredentialsSharingEnabled: Bool { + // For now mac is not returning thread credentials for some reason + #if canImport(ThreadNetwork) && !targetEnvironment(macCatalyst) + if #available(iOS 16.4, *) { + return true + } else { + return false + } + #else + return false + #endif + } + public var lastCommissionServerIdentifier: Identifier? { get { Current.settingsStore.prefs.string(forKey: "lastCommissionServerID").flatMap { .init(rawValue: $0) } } set { Current.settingsStore.prefs.set(newValue?.rawValue, forKey: "lastCommissionServerID") } diff --git a/Sources/Shared/Extensions/Data+Hexadecimal.swift b/Sources/Shared/Extensions/Data+Hexadecimal.swift new file mode 100644 index 0000000000..de3ab8303a --- /dev/null +++ b/Sources/Shared/Extensions/Data+Hexadecimal.swift @@ -0,0 +1,8 @@ +import Foundation + +public extension Data { + var hexadecimal: String { + map { String(format: "%02x", $0) } + .joined() + } +} diff --git a/Sources/Shared/Resources/Swiftgen/Strings.swift b/Sources/Shared/Resources/Swiftgen/Strings.swift index 95edcc36ee..9d53b4b540 100644 --- a/Sources/Shared/Resources/Swiftgen/Strings.swift +++ b/Sources/Shared/Resources/Swiftgen/Strings.swift @@ -1125,6 +1125,10 @@ public enum L10n { public static var body: String { return L10n.tr("Localizable", "settings.developer.map_notification.notification.body") } } } + public enum MockThreadCredentialsSharing { + /// Simulator Thread Credentials Sharing + public static var title: String { return L10n.tr("Localizable", "settings.developer.mock_thread_credentials_sharing.title") } + } public enum ShowLogFiles { /// Show log files in Finder public static var title: String { return L10n.tr("Localizable", "settings.developer.show_log_files.title") } @@ -1769,6 +1773,21 @@ public enum L10n { } } + public enum Thread { + public enum Credentials { + /// Border Agent ID + public static var borderAgentIdTitle: String { return L10n.tr("Localizable", "thread.credentials.border_agent_id_title") } + /// Network Key + public static var networkKeyTitle: String { return L10n.tr("Localizable", "thread.credentials.network_key_title") } + /// Network Name + public static var networkNameTitle: String { return L10n.tr("Localizable", "thread.credentials.network_name_title") } + /// Thread Credentials + public static var screenTitle: String { return L10n.tr("Localizable", "thread.credentials.screen_title") } + /// Share credential with Home Assistant + public static var shareCredentialsButtonTitle: String { return L10n.tr("Localizable", "thread.credentials.share_credentials_button_title") } + } + } + public enum TokenError { /// Connection failed. public static var connectionFailed: String { return L10n.tr("Localizable", "token_error.connection_failed") } diff --git a/Sources/Shared/Utilities/Color+ColorAsset.swift b/Sources/Shared/Utilities/Color+ColorAsset.swift new file mode 100644 index 0000000000..67aea32701 --- /dev/null +++ b/Sources/Shared/Utilities/Color+ColorAsset.swift @@ -0,0 +1,9 @@ +import Foundation +import SwiftUI + +@available(iOS 13.0, *) +public extension Color { + static func asset(_ colorAsset: ColorAsset) -> Color { + Color(colorAsset.name, bundle: Bundle(for: SettingsStore.self)) + } +} diff --git a/Sources/Shared/Utilities/HACornerRadius.swift b/Sources/Shared/Utilities/HACornerRadius.swift new file mode 100644 index 0000000000..b303a0aed4 --- /dev/null +++ b/Sources/Shared/Utilities/HACornerRadius.swift @@ -0,0 +1,5 @@ +import Foundation + +public enum HACornerRadius { + public static let standard: CGFloat = 8 +} diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/Apple_Watch_App_Icon.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/Apple_Watch_App_Icon.png new file mode 100644 index 0000000000..eaff7d9f2f Binary files /dev/null and b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/Apple_Watch_App_Icon.png differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-100x100.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-100x100.png deleted file mode 100644 index 0592197c3e..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-100x100.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-1024x1024.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-1024x1024.png deleted file mode 100644 index c6ae93b2d2..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-1024x1024.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-172x172.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-172x172.png deleted file mode 100644 index 676b973393..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-172x172.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-196x196.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-196x196.png deleted file mode 100644 index 88d4692cf9..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-196x196.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-216x216.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-216x216.png deleted file mode 100644 index 276658ae47..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-216x216.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-48x48.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-48x48.png deleted file mode 100644 index 130339bf36..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-48x48.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-55x55.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-55x55.png deleted file mode 100644 index 386344273b..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-55x55.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-58x58.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-58x58.png deleted file mode 100644 index 04ed775f4c..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-58x58.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-80x80.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-80x80.png deleted file mode 100644 index 93fd03617e..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-80x80.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-87x87.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-87x87.png deleted file mode 100644 index 7fa9bb65b5..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-87x87.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-88x88.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-88x88.png deleted file mode 100644 index f4e9e1df3e..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.appiconset/release-88x88.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/Apple_Watch_App_Icon-1.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/Apple_Watch_App_Icon-1.png new file mode 100644 index 0000000000..18347a0c2d Binary files /dev/null and b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/Apple_Watch_App_Icon-1.png differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-100x100.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-100x100.png deleted file mode 100644 index 9bb5d188ac..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-100x100.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-1024x1024.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-1024x1024.png deleted file mode 100644 index 51c934b9ca..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-1024x1024.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-172x172.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-172x172.png deleted file mode 100644 index 203a7e05db..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-172x172.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-196x196.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-196x196.png deleted file mode 100644 index cd58a8bb3c..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-196x196.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-216x216.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-216x216.png deleted file mode 100644 index 9b586d3db6..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-216x216.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-48x48.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-48x48.png deleted file mode 100644 index 33dcb3c31b..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-48x48.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-55x55.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-55x55.png deleted file mode 100644 index af6f5851d6..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-55x55.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-58x58.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-58x58.png deleted file mode 100644 index a02ba4ddd5..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-58x58.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-80x80.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-80x80.png deleted file mode 100644 index 738e40298d..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-80x80.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-87x87.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-87x87.png deleted file mode 100644 index 8268365c88..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-87x87.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-88x88.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-88x88.png deleted file mode 100644 index 5ccfcd963c..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.beta.appiconset/beta-88x88.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/Apple_Watch_App_Icon-2.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/Apple_Watch_App_Icon-2.png new file mode 100644 index 0000000000..dd47a67041 Binary files /dev/null and b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/Apple_Watch_App_Icon-2.png differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-100x100.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-100x100.png deleted file mode 100644 index fdc2651797..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-100x100.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-1024x1024.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-1024x1024.png deleted file mode 100644 index 7b016490a8..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-1024x1024.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-172x172.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-172x172.png deleted file mode 100644 index 1a9e0a5882..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-172x172.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-196x196.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-196x196.png deleted file mode 100644 index b543f1c838..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-196x196.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-216x216.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-216x216.png deleted file mode 100644 index 8bb5b048a0..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-216x216.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-48x48.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-48x48.png deleted file mode 100644 index 7414b2f7c8..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-48x48.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-55x55.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-55x55.png deleted file mode 100644 index 6f5571f459..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-55x55.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-58x58.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-58x58.png deleted file mode 100644 index ed56961688..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-58x58.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-80x80.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-80x80.png deleted file mode 100644 index 0243b91df2..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-80x80.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-87x87.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-87x87.png deleted file mode 100644 index cadeb79031..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-87x87.png and /dev/null differ diff --git a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-88x88.png b/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-88x88.png deleted file mode 100644 index 58a02285c6..0000000000 Binary files a/Sources/WatchApp/Assets.xcassets/WatchIcon.dev.appiconset/dev-88x88.png and /dev/null differ diff --git a/Tests/Shared/Webhook/WebhookResponseUpdateComplications.test.swift b/Tests/Shared/Webhook/WebhookResponseUpdateComplications.test.swift index 13181b35ce..1ca95c5e28 100644 --- a/Tests/Shared/Webhook/WebhookResponseUpdateComplications.test.swift +++ b/Tests/Shared/Webhook/WebhookResponseUpdateComplications.test.swift @@ -8,6 +8,7 @@ class WebhookResponseUpdateComplicationsTests: XCTestCase { private var api: FakeHomeAssistantAPI! private var webhookManager: FakeWebhookManager! private var realm: Realm! + private var handler: WebhookResponseUpdateComplications? override func setUpWithError() throws { try super.setUpWithError() @@ -126,8 +127,8 @@ class WebhookResponseUpdateComplicationsTests: XCTestCase { realm.add(complications) } - var handler = WebhookResponseUpdateComplications(api: api) - handler.watchComplicationClass = FakeWatchComplication.self + handler = WebhookResponseUpdateComplications(api: api) + handler?.watchComplicationClass = FakeWatchComplication.self let request = WebhookResponseUpdateComplications.request(for: Set(complications))! let result: [String: Any] = [ @@ -136,12 +137,14 @@ class WebhookResponseUpdateComplicationsTests: XCTestCase { "c3|fwc3k1": 3, ] + guard let handler = handler else { return } + let expectation = self.expectation(description: "result") handler.handle(request: .value(request), result: .value(result)).done { handlerResult in XCTAssertNil(handlerResult.notification) expectation.fulfill() } - wait(for: [expectation], timeout: 10) + wait(for: [expectation], timeout: 30) let complication0Updates = FakeWatchComplication.rawRenderedUpdates["c1"] let complication2Updates = FakeWatchComplication.rawRenderedUpdates["c3"] diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a9fb2f7a5e..d790c15128 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -184,8 +184,10 @@ lane :update_strings do 'es' => 'es-ES', 'es-419' => 'es', # es-MX is missing from the frontend, so we copy es over below + 'et' => 'et', 'fi' => 'fi', 'fr' => 'fr', + 'he' => 'he', 'hu' => 'hu', 'id' => 'id', 'it' => 'it', diff --git a/swiftgen.yml b/swiftgen.yml index 631f8fe736..12834f8e70 100644 --- a/swiftgen.yml +++ b/swiftgen.yml @@ -22,10 +22,14 @@ ib: - templateName: segues-swift5 output: Sources/App/Resources/Segues.swift xcassets: - inputs: Sources/App/Resources/Assets.xcassets + inputs: + - Sources/Shared/Assets/SharedAssets.xcassets + - Sources/Shared/Assets/Colors.xcassets outputs: templateName: swift5 - output: Sources/App/Resources/Assets.swift + output: Sources/Shared/Assets/Assets.swift + params: + publicAccess: true plist: inputs: - Sources/Shared/Resources/Info.plist diff --git a/swiftgen.yml.file-list.in b/swiftgen.yml.file-list.in index 2d69abcc73..0b5fdf0aac 100644 --- a/swiftgen.yml.file-list.in +++ b/swiftgen.yml.file-list.in @@ -3,6 +3,7 @@ $(SRCROOT)/Sources/App/ClientEvents/ClientEvents.storyboard # json $(SRCROOT)/Tools/MaterialDesignIcons.json +Tools/icons.stencil # plist $(SRCROOT)/Sources/Shared/Resources/Info.plist @@ -12,3 +13,4 @@ $(SRCROOT)/Sources/App/Resources/en.lproj/Localizable.strings # xcassets $(SRCROOT)/Sources/App/Resources/Assets.xcassets +$(SRCROOT)/Sources/App/Resources/Colors.xcassets \ No newline at end of file diff --git a/swiftgen.yml.file-list.out b/swiftgen.yml.file-list.out index d686e031ec..b5d6cabf2d 100644 --- a/swiftgen.yml.file-list.out +++ b/swiftgen.yml.file-list.out @@ -12,4 +12,4 @@ $(SRCROOT)/Sources/Shared/Resources/SwiftGen/SharedPlist.swift $(SRCROOT)/Sources/Shared/Resources/SwiftGen/Strings.swift # xcassets -$(SRCROOT)/Sources/App/Resources/Assets.swift +$(SRCROOT)/Sources/App/Resources/Assets.swift \ No newline at end of file