diff --git a/HomeAssistant.xcodeproj/project.pbxproj b/HomeAssistant.xcodeproj/project.pbxproj index a5641444eb..f2c2abe147 100644 --- a/HomeAssistant.xcodeproj/project.pbxproj +++ b/HomeAssistant.xcodeproj/project.pbxproj @@ -56,7 +56,6 @@ 110AA55C25B36630005061A0 /* ServerAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AA55B25B36630005061A0 /* ServerAlerter.swift */; }; 110AA55D25B36630005061A0 /* ServerAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AA55B25B36630005061A0 /* ServerAlerter.swift */; }; 110AA57B25B38C02005061A0 /* ServerAlerter.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AA57A25B38C02005061A0 /* ServerAlerter.test.swift */; }; - 110E694424E77125004AA96D /* WidgetActionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110E694324E77125004AA96D /* WidgetActionsProvider.swift */; }; 110EC9FD251708D5009C9A1B /* Shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03D891720E0A85200D4F28D /* Shared.framework */; }; 110EC9FE251708D5009C9A1B /* Shared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D03D891720E0A85200D4F28D /* Shared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 110ED55425A5604F00489AF7 /* MacBridgeScreenImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110ED55325A5604F00489AF7 /* MacBridgeScreenImpl.swift */; }; @@ -186,7 +185,6 @@ 1171507224DFCDEE0065E874 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1171507124DFCDEE0065E874 /* Assets.xcassets */; }; 1171507624DFCDEE0065E874 /* HomeAssistant-Extensions-Widgets.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1171506924DFCDE60065E874 /* HomeAssistant-Extensions-Widgets.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1171507B24DFCE0D0065E874 /* Shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03D891720E0A85200D4F28D /* Shared.framework */; }; - 1171508124DFCEC50065E874 /* WidgetActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1171508024DFCEC50065E874 /* WidgetActions.swift */; }; 117318AB25199E1A0013E010 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 117318AA25199E1A0013E010 /* AppKit.framework */; }; 117318AD25199E220013E010 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 117318AC25199E220013E010 /* Foundation.framework */; }; 11764A6C26817FC3007D47F3 /* UserDefaultsValueSync.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11764A6B26817FC3007D47F3 /* UserDefaultsValueSync.test.swift */; }; @@ -246,8 +244,6 @@ 119DC15824B6A33F00AAB204 /* ZeroLatitude.gpx in Resources */ = {isa = PBXBuildFile; fileRef = 119DC15724B6A33E00AAB204 /* ZeroLatitude.gpx */; }; 119DE933263325C20099F7D8 /* IconDrawable+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119DE913263325790099F7D8 /* IconDrawable+Settings.swift */; }; 119DE934263325C20099F7D8 /* IconDrawable+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119DE913263325790099F7D8 /* IconDrawable+Settings.swift */; }; - 119EC3C724D5119300617D51 /* MobileAppConfigAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119EC3C624D5119300617D51 /* MobileAppConfigAction.swift */; }; - 119EC3C824D5119300617D51 /* MobileAppConfigAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 119EC3C624D5119300617D51 /* MobileAppConfigAction.swift */; }; 11A183B32511BCF300CA326A /* LifecycleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A183B22511BCF300CA326A /* LifecycleManager.swift */; }; 11A31CAF252128D300D50A78 /* MacBridge.bundle in Embed Plugins */ = {isa = PBXBuildFile; fileRef = 1167402225198F9A00F51626 /* MacBridge.bundle */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 11A3BD2D26192210005237E6 /* LocalPushManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A3BD2C26192210005237E6 /* LocalPushManager.swift */; }; @@ -257,7 +253,6 @@ 11A48D7C24CA7D7F0021BDD9 /* NotificationAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DAC736215F06B100727D2A /* NotificationAction.swift */; }; 11A48D7D24CA7E4E0021BDD9 /* NotificationCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DAC734215F069300727D2A /* NotificationCategory.swift */; }; 11A48D7E24CA7E4E0021BDD9 /* NotificationCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DAC734215F069300727D2A /* NotificationCategory.swift */; }; - 11A48D7F24CA7E820021BDD9 /* Action+Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EE9B5624C68F5700404AF8 /* Action+Observation.swift */; }; 11A48D8124CA8ADB0021BDD9 /* NotificationCategory+Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A48D8024CA8ADB0021BDD9 /* NotificationCategory+Observation.swift */; }; 11A71C6B24A463FC00D9565F /* ZoneManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A71C6A24A463FC00D9565F /* ZoneManagerState.swift */; }; 11A71C6D24A4641600D9565F /* ZoneManagerEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A71C6C24A4641600D9565F /* ZoneManagerEvent.swift */; }; @@ -297,8 +292,6 @@ 11B38EE4275C54A200205C7B /* FireEventIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66C58B22150892A004AB261 /* FireEventIntentHandler.swift */; }; 11B38EE5275C54A200205C7B /* SendLocationIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66C58B42150898A004AB261 /* SendLocationIntentHandler.swift */; }; 11B38EE6275C54A200205C7B /* CallServiceIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66C58B02150891B004AB261 /* CallServiceIntentHandler.swift */; }; - 11B38EE7275C54A200205C7B /* PerformActionIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111858D324CB5B8900B8CDDC /* PerformActionIntentHandler.swift */; }; - 11B38EE8275C54A200205C7B /* WidgetActionsIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BD8BBC24E76BAD004B9A54 /* WidgetActionsIntentHandler.swift */; }; 11B38EE9275C54A200205C7B /* GetCameraImageIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DF8BC3221D047400370A59 /* GetCameraImageIntentHandler.swift */; }; 11B38EEA275C54A200205C7B /* PickAServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F2E7B227500DAD00CF144C /* PickAServerError.swift */; }; 11B38EEB275C54A200205C7B /* UpdateSensorsIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112B705A2526B1C500FEAA76 /* UpdateSensorsIntentHandler.swift */; }; @@ -309,8 +302,6 @@ 11B38EF0275C54A300205C7B /* FireEventIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66C58B22150892A004AB261 /* FireEventIntentHandler.swift */; }; 11B38EF1275C54A300205C7B /* SendLocationIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66C58B42150898A004AB261 /* SendLocationIntentHandler.swift */; }; 11B38EF2275C54A300205C7B /* CallServiceIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66C58B02150891B004AB261 /* CallServiceIntentHandler.swift */; }; - 11B38EF3275C54A300205C7B /* PerformActionIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111858D324CB5B8900B8CDDC /* PerformActionIntentHandler.swift */; }; - 11B38EF4275C54A300205C7B /* WidgetActionsIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BD8BBC24E76BAD004B9A54 /* WidgetActionsIntentHandler.swift */; }; 11B38EF5275C54A300205C7B /* GetCameraImageIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DF8BC3221D047400370A59 /* GetCameraImageIntentHandler.swift */; }; 11B38EF6275C54A300205C7B /* PickAServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F2E7B227500DAD00CF144C /* PickAServerError.swift */; }; 11B38EF7275C54A300205C7B /* UpdateSensorsIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112B705A2526B1C500FEAA76 /* UpdateSensorsIntentHandler.swift */; }; @@ -403,8 +394,6 @@ 11EE9B4C24C5181A00404AF8 /* ModelManager.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EE9B4B24C5181A00404AF8 /* ModelManager.test.swift */; }; 11EE9B4E24C6089800404AF8 /* RealmPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EE9B4D24C6089800404AF8 /* RealmPersistable.swift */; }; 11EE9B4F24C6089800404AF8 /* RealmPersistable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EE9B4D24C6089800404AF8 /* RealmPersistable.swift */; }; - 11EE9B5424C62EB300404AF8 /* RealmScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EE9B5324C62EB300404AF8 /* RealmScene.swift */; }; - 11EE9B5524C62EB300404AF8 /* RealmScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EE9B5324C62EB300404AF8 /* RealmScene.swift */; }; 11EF62DA24C3687D00BABB64 /* ZoneManagerRegionFilter.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EF62D924C3687D00BABB64 /* ZoneManagerRegionFilter.test.swift */; }; 11EFCDD324F5F39100314D85 /* WebViewWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EFCDD224F5F39100314D85 /* WebViewWindowController.swift */; }; 11EFCDD624F5FA8D00314D85 /* WebViewSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EFCDD524F5FA8D00314D85 /* WebViewSceneDelegate.swift */; }; @@ -914,13 +903,6 @@ 42955C3A2F20E2E800E398E8 /* ConnectivityCheckState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42955C372F20E2E800E398E8 /* ConnectivityCheckState.swift */; }; 42955C3B2F20E2E800E398E8 /* ConnectivityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42955C362F20E2E800E398E8 /* ConnectivityChecker.swift */; }; 42955C3C2F20E2E800E398E8 /* ConnectivityCheckView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42955C382F20E2E800E398E8 /* ConnectivityCheckView.swift */; }; - 4296C36D2B90DB640051B63C /* IntentActionAppEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C36B2B90DB630051B63C /* IntentActionAppEntity.swift */; }; - 4296C36E2B90DB640051B63C /* PerformAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C36C2B90DB630051B63C /* PerformAction.swift */; }; - 4296C3762B91F0F50051B63C /* WidgetActionsAppIntentTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C3742B91F0860051B63C /* WidgetActionsAppIntentTimelineProvider.swift */; }; - 4296C3772B91F26A0051B63C /* IntentActionAppEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C36B2B90DB630051B63C /* IntentActionAppEntity.swift */; }; - 4296C3782B91F6260051B63C /* PerformAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C36C2B90DB630051B63C /* PerformAction.swift */; }; - 4296C37A2B9205450051B63C /* WidgetActionsAppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C3792B9205450051B63C /* WidgetActionsAppIntent.swift */; }; - 4296C37B2B92054C0051B63C /* WidgetActionsAppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C3792B9205450051B63C /* WidgetActionsAppIntent.swift */; }; 429764F62E93B21E004C26EE /* CircularGlassOrLegacyBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429764F52E93B21E004C26EE /* CircularGlassOrLegacyBackground.swift */; }; 4297ADA72C89C74A00790812 /* GRDB+Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4297ADA62C89C74A00790812 /* GRDB+Initialization.swift */; }; 4297ADA82C89C74A00790812 /* GRDB+Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4297ADA62C89C74A00790812 /* GRDB+Initialization.swift */; }; @@ -1258,7 +1240,7 @@ 651755E378F6F79AB401F05C /* AssistPipelineAddList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07701F2786F6D45E945CC1AA /* AssistPipelineAddList.swift */; }; 65286F3B745551AD4090EE6B /* Pods-iOS-SharedTesting-metadata.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4053903E4C54A6803204286E /* Pods-iOS-SharedTesting-metadata.plist */; }; 6596FA74E1A501276EA62D86 /* Pods_watchOS_Shared_watchOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD370D44DFFB906B05C3EB3A /* Pods_watchOS_Shared_watchOS.framework */; }; - 692BCBBA4EEEABCC76DBBECA /* Database/GRDB+Initialization.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50FA39BF16AD0BD782D0D7 /* Database/GRDB+Initialization.test.swift */; }; + 692BCBBA4EEEABCC76DBBECA /* GRDB+Initialization.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C50FA39BF16AD0BD782D0D7 /* GRDB+Initialization.test.swift */; }; 6FCEBAA2C8E9C5403055E73D /* IntentFanEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5E2F9F8F008EEA30C533FD /* IntentFanEntity.swift */; }; 70BD8A8EA1ABC5DC1F0A0D6E /* Pods_iOS_Shared_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C663B0750E0318469E7008C3 /* Pods_iOS_Shared_iOS.framework */; }; 71E0BF803A854C3B9F0CB726 /* HandlerLiveActivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D524991C142DBB38A1968 /* HandlerLiveActivityTests.swift */; }; @@ -1273,7 +1255,7 @@ 999549244371450BC98C700E /* Pods_iOS_Extensions_PushProvider.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 608CFDA223EBCDF01B946093 /* Pods_iOS_Extensions_PushProvider.framework */; }; 9D57ECBD5431BC00BDC16F1E /* NotificationActionEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F913E441276235B7A2D7B29 /* NotificationActionEditorView.swift */; }; A1619F1ED93FB8B0E7E53C38 /* KioskLifecycleBrightness.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373AE72CB925F044BAE18B62 /* KioskLifecycleBrightness.test.swift */; }; - A2F3A140CDD1EF1AEA6DFAB9 /* Database/DatabaseTableProtocol.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC31518EE9DC9E065AC508D9 /* Database/DatabaseTableProtocol.test.swift */; }; + A2F3A140CDD1EF1AEA6DFAB9 /* DatabaseTableProtocol.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC31518EE9DC9E065AC508D9 /* DatabaseTableProtocol.test.swift */; }; A596C4D1E125E6863C7D2034 /* ComplicationEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512A72C5F1BCC979E74F7629 /* ComplicationEditViewModel.swift */; }; A5A3C1932BE1F4A40EA78754 /* Pods-iOS-Extensions-Matter-metadata.plist in Resources */ = {isa = PBXBuildFile; fileRef = 392B0C44197C98E2653932A5 /* Pods-iOS-Extensions-Matter-metadata.plist */; }; A60E917B401A6D456F1DB630 /* ComplicationFamilySelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1861EB0361816DC9260D1F5E /* ComplicationFamilySelectView.swift */; }; @@ -1285,8 +1267,6 @@ A95FDD162F6B8A3E008EF72F /* HALockScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95FDD122F6B8A3E008EF72F /* HALockScreenView.swift */; }; A95FDD192F6B8A5B008EF72F /* LiveActivitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95FDD172F6B8A5B008EF72F /* LiveActivitySettingsView.swift */; }; AB3E076F146799C008ACB0EA /* Pods_iOS_Extensions_NotificationContent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7D8DAEFAD435091FDDD61E7 /* Pods_iOS_Extensions_NotificationContent.framework */; }; - AC710001000000000000AA01 /* ActionsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC720001000000000000AA01 /* ActionsSettingsView.swift */; }; - AC710002000000000000AA01 /* ActionsSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC720002000000000000AA01 /* ActionsSettingsViewModel.swift */; }; AC730001000000000000AA01 /* AppIconShortcutConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC730011000000000000AA01 /* AppIconShortcutConfig.swift */; }; AC730002000000000000AA01 /* AppIconShortcutConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC730011000000000000AA01 /* AppIconShortcutConfig.swift */; }; AC730003000000000000AA01 /* AppIconShortcutConfigTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC730012000000000000AA01 /* AppIconShortcutConfigTable.swift */; }; @@ -1512,14 +1492,11 @@ B6A258492232539900ADD202 /* WebhookUpdateLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A258472232539900ADD202 /* WebhookUpdateLocation.swift */; }; B6AAD7A41D827DD40090B220 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAD7A31D827DD40090B220 /* NotificationService.swift */; }; B6AAD7A81D827DD40090B220 /* HomeAssistant-Extensions-NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = B6AAD7A11D827DD40090B220 /* HomeAssistant-Extensions-NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - B6B2E6A5216ACE4400D39A26 /* ActionConfiguratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B2E6A4216ACE4400D39A26 /* ActionConfiguratorView.swift */; }; B6B74CB6228397D100D58A68 /* WatchHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66D6B1F2227A2EA009D8B90 /* WatchHelpers.swift */; }; B6B74CB82283983300D58A68 /* WatchComplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B6B14E215B6866003DE2DD /* WatchComplication.swift */; }; B6B74CB92283983300D58A68 /* WatchComplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B6B14E215B6866003DE2DD /* WatchComplication.swift */; }; B6B74CBA2283983800D58A68 /* CLKComplication+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B6B14B215B1E86003DE2DD /* CLKComplication+Strings.swift */; }; B6B74CBC228398DD00D58A68 /* WKInterfaceDevice+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69769832162430300FFFAD6 /* WKInterfaceDevice+Size.swift */; }; - B6B74CBD228399AB00D58A68 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B2E6A1216AC21400D39A26 /* Action.swift */; }; - B6B74CBE228399AC00D58A68 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B2E6A1216AC21400D39A26 /* Action.swift */; }; B6C091232151F90300A326DC /* LocationHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C0911E2151F90300A326DC /* LocationHistory.swift */; }; B6C09153215206BB00A326DC /* Shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03D891720E0A85200D4F28D /* Shared.framework */; }; B6CC5D862159D10D00833E5D /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6CC5D842159D10D00833E5D /* Interface.storyboard */; }; @@ -1539,7 +1516,7 @@ B6E42613215C4333007FEB7E /* Shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03D891720E0A85200D4F28D /* Shared.framework */; }; BB77559927344584B2C0E987 /* OnboardingAuthError.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1A7DD090A1D41ADB9374E7A /* OnboardingAuthError.test.swift */; }; BD1044995DE13A04C0FA039A /* Pods_iOS_Extensions_Widgets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D9C81015FD7A8FA8716E4F2 /* Pods_iOS_Extensions_Widgets.framework */; }; - BECCC152A4E3F69A8EF5A6F3 /* Database/TableSchemaTests.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EE9A0E08E6FEBDDE425D0D4 /* Database/TableSchemaTests.test.swift */; }; + BECCC152A4E3F69A8EF5A6F3 /* TableSchemaTests.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EE9A0E08E6FEBDDE425D0D4 /* TableSchemaTests.test.swift */; }; C10D762EFE08D347D0538339 /* Pods-iOS-Shared-iOS-Tests-Shared-metadata.plist in Resources */ = {isa = PBXBuildFile; fileRef = B2F5238669D8A7416FBD2B55 /* Pods-iOS-Shared-iOS-Tests-Shared-metadata.plist */; }; C1AE883A374C598B5BCCAE23 /* CustomWidgetIntentHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7FF77B68D19A4AD68F8FE3 /* CustomWidgetIntentHelper.swift */; }; C35621B95F7E4548BC8F6D75 /* FolderEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BECEB2525564358A124F818 /* FolderEditView.swift */; }; @@ -1595,6 +1572,7 @@ D9BF1EFF40733A4A1D03B9C8 /* CustomWidgetIntentHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7FF77B68D19A4AD68F8FE3 /* CustomWidgetIntentHelper.swift */; }; DA6F4C18D66EDBA5DCEAE833 /* DatabaseMigration.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892F0EF22A0B9F20AAEE4CCA /* DatabaseMigration.test.swift */; }; DA6F4C18D66EDBA5DCEAE833 /* Database/DatabaseMigration.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892F0EF22A0B9F20AAEE4CCA /* Database/DatabaseMigration.test.swift */; }; + DA6F4C18D66EDBA5DCEAE833 /* DatabaseMigration.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892F0EF22A0B9F20AAEE4CCA /* DatabaseMigration.test.swift */; }; DB54626ADCE0C32094C8C0B9 /* LoadingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFD0D30836C69840AB63A8A /* LoadingButton.swift */; }; DEFBE1A5E9A005B0A5392D27 /* KioskLocalization.test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4593A60DBF019E6C91AAA7 /* KioskLocalization.test.swift */; }; E3A02409794174F002C8BB4F /* IconSearchPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC23DE131CA8813C2DBD657 /* IconSearchPicker.swift */; }; @@ -1923,7 +1901,6 @@ 1109F82324A25A41002590F2 /* SensorContainer.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorContainer.test.swift; sourceTree = ""; }; 110AA55B25B36630005061A0 /* ServerAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerAlerter.swift; sourceTree = ""; }; 110AA57A25B38C02005061A0 /* ServerAlerter.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerAlerter.test.swift; sourceTree = ""; }; - 110E694324E77125004AA96D /* WidgetActionsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetActionsProvider.swift; sourceTree = ""; }; 110E694524E771AB004AA96D /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = ""; }; 110ED55325A5604F00489AF7 /* MacBridgeScreenImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacBridgeScreenImpl.swift; sourceTree = ""; }; 110ED56225A563D600489AF7 /* DisplaySensor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySensor.swift; sourceTree = ""; }; @@ -1945,7 +1922,6 @@ 11169CBA262FD6E1005EF90A /* NSLayoutConstraint+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraint+Additions.swift"; sourceTree = ""; }; 11169CEB262FE3A2005EF90A /* VideoAudioAttachmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoAudioAttachmentViewController.swift; sourceTree = ""; }; 1117FB4B250C5F7C00895C13 /* DeviceBattery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceBattery.swift; sourceTree = ""; }; - 111858D324CB5B8900B8CDDC /* PerformActionIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformActionIntentHandler.swift; sourceTree = ""; }; 11195F6A267EFB1F003DF674 /* NotificationManagerLocalPushInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerLocalPushInterface.swift; sourceTree = ""; }; 11195F6C267EFC15003DF674 /* HACancellable+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HACancellable+App.swift"; sourceTree = ""; }; 11195F6E267EFC8E003DF674 /* NotificationManagerLocalPushInterfaceDirect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerLocalPushInterfaceDirect.swift; sourceTree = ""; }; @@ -2057,7 +2033,6 @@ 1171506F24DFCDE60065E874 /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = ""; }; 1171507124DFCDEE0065E874 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1171507324DFCDEE0065E874 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 1171508024DFCEC50065E874 /* WidgetActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetActions.swift; sourceTree = ""; }; 117318AA25199E1A0013E010 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/System/Library/Frameworks/AppKit.framework; sourceTree = DEVELOPER_DIR; }; 117318AC25199E220013E010 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 11764A6B26817FC3007D47F3 /* UserDefaultsValueSync.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsValueSync.test.swift; sourceTree = ""; }; @@ -2104,7 +2079,6 @@ 119D765E2492F8FA00183C5F /* UIApplication+BackgroundTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+BackgroundTask.swift"; sourceTree = ""; }; 119DC15724B6A33E00AAB204 /* ZeroLatitude.gpx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = ZeroLatitude.gpx; sourceTree = ""; }; 119DE913263325790099F7D8 /* IconDrawable+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IconDrawable+Settings.swift"; sourceTree = ""; }; - 119EC3C624D5119300617D51 /* MobileAppConfigAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileAppConfigAction.swift; sourceTree = ""; }; 119EC3D724D5395B00617D51 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Intents.strings"; sourceTree = ""; }; 119EC3D924D5395B00617D51 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; }; 119EC3DA24D5395C00617D51 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; @@ -2170,7 +2144,6 @@ 11BC9E5624FDC1C900B9FBF7 /* ActiveSensor.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSensor.test.swift; sourceTree = ""; }; 11BD7B2C25B52E8D001826F0 /* MacBridgeStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacBridgeStatusItem.swift; sourceTree = ""; }; 11BD7B3C25B53D37001826F0 /* AppMacBridgeStatusItemConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMacBridgeStatusItemConfiguration.swift; sourceTree = ""; }; - 11BD8BBC24E76BAD004B9A54 /* WidgetActionsIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetActionsIntentHandler.swift; sourceTree = ""; }; 11C4627E24B04CB800031902 /* Promise+RetryNetworking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+RetryNetworking.swift"; sourceTree = ""; }; 11C4628124B053A800031902 /* WebhookResponseUpdateSensors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebhookResponseUpdateSensors.swift; sourceTree = ""; }; 11C4628724B109C000031902 /* WebhookResponseLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebhookResponseLocation.swift; sourceTree = ""; }; @@ -2211,8 +2184,6 @@ 11EE9B4824C5116F00404AF8 /* LegacyModelManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyModelManager.swift; sourceTree = ""; }; 11EE9B4B24C5181A00404AF8 /* ModelManager.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelManager.test.swift; sourceTree = ""; }; 11EE9B4D24C6089800404AF8 /* RealmPersistable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmPersistable.swift; sourceTree = ""; }; - 11EE9B5324C62EB300404AF8 /* RealmScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmScene.swift; sourceTree = ""; }; - 11EE9B5624C68F5700404AF8 /* Action+Observation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Action+Observation.swift"; sourceTree = ""; }; 11EF62D924C3687D00BABB64 /* ZoneManagerRegionFilter.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoneManagerRegionFilter.test.swift; sourceTree = ""; }; 11EFB44B29D89FD100CE4B05 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Intents.strings; sourceTree = ""; }; 11EFB44C29D89FD100CE4B05 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Frontend.strings; sourceTree = ""; }; @@ -2646,10 +2617,6 @@ 42955C362F20E2E800E398E8 /* ConnectivityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityChecker.swift; sourceTree = ""; }; 42955C372F20E2E800E398E8 /* ConnectivityCheckState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityCheckState.swift; sourceTree = ""; }; 42955C382F20E2E800E398E8 /* ConnectivityCheckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityCheckView.swift; sourceTree = ""; }; - 4296C36B2B90DB630051B63C /* IntentActionAppEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentActionAppEntity.swift; sourceTree = ""; }; - 4296C36C2B90DB630051B63C /* PerformAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformAction.swift; sourceTree = ""; }; - 4296C3742B91F0860051B63C /* WidgetActionsAppIntentTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetActionsAppIntentTimelineProvider.swift; sourceTree = ""; }; - 4296C3792B9205450051B63C /* WidgetActionsAppIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WidgetActionsAppIntent.swift; path = Sources/Extensions/AppIntents/WidgetActionsAppIntent.swift; sourceTree = SOURCE_ROOT; }; 429764F52E93B21E004C26EE /* CircularGlassOrLegacyBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularGlassOrLegacyBackground.swift; sourceTree = ""; }; 4297ADA42C89C43F00790812 /* AppEntitiesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEntitiesModel.swift; sourceTree = ""; }; 4297ADA62C89C74A00790812 /* GRDB+Initialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GRDB+Initialization.swift"; sourceTree = ""; }; @@ -3100,7 +3067,7 @@ 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 = ""; }; 592EED7A6C2444872F11C17B /* Pods-iOS-Extensions-NotificationService-metadata.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "Pods-iOS-Extensions-NotificationService-metadata.plist"; path = "Pods/Pods-iOS-Extensions-NotificationService-metadata.plist"; sourceTree = ""; }; 5BFD0D30836C69840AB63A8A /* LoadingButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LoadingButton.swift; sourceTree = ""; }; - 5C50FA39BF16AD0BD782D0D7 /* Database/GRDB+Initialization.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Database/GRDB+Initialization.test.swift"; sourceTree = ""; }; + 5C50FA39BF16AD0BD782D0D7 /* GRDB+Initialization.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Database/GRDB+Initialization.test.swift"; sourceTree = ""; }; 5D4737412F241342009A70EA /* FolderDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderDetailView.swift; sourceTree = ""; }; 5E95733B72864AB3B9607B57 /* MockLiveActivityRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLiveActivityRegistry.swift; sourceTree = ""; }; 5FF9C3A30E10A8E214623EBB /* ComplicationListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ComplicationListView.swift; sourceTree = ""; }; @@ -3125,7 +3092,7 @@ 825E1E44BA9ABF1BF53733D3 /* KioskConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KioskConstants.swift; sourceTree = ""; }; 862436CFE6E3F4B31500EFB2 /* ComplicationListViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ComplicationListViewModel.swift; sourceTree = ""; }; 86BFD63671D2D0A012DFE169 /* Pods-iOS-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-App/Pods-iOS-App.debug.xcconfig"; sourceTree = ""; }; - 892F0EF22A0B9F20AAEE4CCA /* Database/DatabaseMigration.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database/DatabaseMigration.test.swift; sourceTree = ""; }; + 892F0EF22A0B9F20AAEE4CCA /* DatabaseMigration.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database/DatabaseMigration.test.swift; sourceTree = ""; }; 8A34A5417D650BBBE9D2D7C0 /* ControlFanValueProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlFanValueProvider.swift; sourceTree = ""; }; 8D6888525DCF492642BA7EA3 /* FanIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FanIntent.swift; sourceTree = ""; }; 9249824D575933DFA1530BB2 /* Pods-watchOS-WatchExtension-Watch-metadata.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "Pods-watchOS-WatchExtension-Watch-metadata.plist"; path = "Pods/Pods-watchOS-WatchExtension-Watch-metadata.plist"; sourceTree = ""; }; @@ -3138,7 +3105,7 @@ 9C4E5E27229D992A0044C8EC /* HomeAssistant.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = HomeAssistant.xcconfig; sourceTree = ""; }; 9D84964A844E6CD21F16D3AB /* Pods-watchOS-WatchExtension-Watch.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-watchOS-WatchExtension-Watch.debug.xcconfig"; path = "Pods/Target Support Files/Pods-watchOS-WatchExtension-Watch/Pods-watchOS-WatchExtension-Watch.debug.xcconfig"; sourceTree = ""; }; 9DA2D62699FC44A99AB37480 /* WatchFolderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchFolderRow.swift; sourceTree = ""; }; - 9EE9A0E08E6FEBDDE425D0D4 /* Database/TableSchemaTests.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database/TableSchemaTests.test.swift; sourceTree = ""; }; + 9EE9A0E08E6FEBDDE425D0D4 /* TableSchemaTests.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database/TableSchemaTests.test.swift; sourceTree = ""; }; 9F913E441276235B7A2D7B29 /* NotificationActionEditorView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationActionEditorView.swift; sourceTree = ""; }; 9F9398CFD66E4C66DC39E1D3 /* Pods-iOS-Extensions-PushProvider.beta.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-Extensions-PushProvider.beta.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-Extensions-PushProvider/Pods-iOS-Extensions-PushProvider.beta.xcconfig"; sourceTree = ""; }; A1A7DD090A1D41ADB9374E7A /* OnboardingAuthError.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingAuthError.test.swift; sourceTree = ""; }; @@ -3151,8 +3118,6 @@ A95FDD172F6B8A5B008EF72F /* LiveActivitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivitySettingsView.swift; sourceTree = ""; }; AA48C686F844D08C426A8D74 /* Pods_Tests_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tests_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AC24B1CAB85767B8171BB850 /* Pods-iOS-Extensions-NotificationContent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-Extensions-NotificationContent.release.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-Extensions-NotificationContent/Pods-iOS-Extensions-NotificationContent.release.xcconfig"; sourceTree = ""; }; - AC720001000000000000AA01 /* ActionsSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionsSettingsView.swift; sourceTree = ""; }; - AC720002000000000000AA01 /* ActionsSettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionsSettingsViewModel.swift; sourceTree = ""; }; AC730011000000000000AA01 /* AppIconShortcutConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppIconShortcutConfig.swift; sourceTree = ""; }; AC730012000000000000AA01 /* AppIconShortcutConfigTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppIconShortcutConfigTable.swift; sourceTree = ""; }; AC730021000000000000AA01 /* AppIconShortcutItemsUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppIconShortcutItemsUpdater.swift; sourceTree = ""; }; @@ -3421,8 +3386,6 @@ B6AAD7A11D827DD40090B220 /* HomeAssistant-Extensions-NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "HomeAssistant-Extensions-NotificationService.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; B6AAD7A31D827DD40090B220 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; B6AAD7A51D827DD40090B220 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B6B2E6A1216AC21400D39A26 /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; - B6B2E6A4216ACE4400D39A26 /* ActionConfiguratorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionConfiguratorView.swift; sourceTree = ""; }; B6B6B14B215B1E86003DE2DD /* CLKComplication+Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLKComplication+Strings.swift"; sourceTree = ""; }; B6B6B14E215B6866003DE2DD /* WatchComplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchComplication.swift; sourceTree = ""; }; B6C0911E2151F90300A326DC /* LocationHistory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationHistory.swift; sourceTree = ""; }; @@ -3468,7 +3431,7 @@ B6FD0574228411B200AC45BA /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; B7D8DAEFAD435091FDDD61E7 /* Pods_iOS_Extensions_NotificationContent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_Extensions_NotificationContent.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B833A17275EC47FA65A3235A /* YamlPreviewSection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = YamlPreviewSection.swift; sourceTree = ""; }; - BC31518EE9DC9E065AC508D9 /* Database/DatabaseTableProtocol.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database/DatabaseTableProtocol.test.swift; sourceTree = ""; }; + BC31518EE9DC9E065AC508D9 /* DatabaseTableProtocol.test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database/DatabaseTableProtocol.test.swift; sourceTree = ""; }; BC9B77AAC44845DC9EE48759 /* Pods_iOS_Extensions_Intents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_Extensions_Intents.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BDC6ACBDCC2C47510C37E4C8 /* NotificationCategoryListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationCategoryListView.swift; sourceTree = ""; }; BEF9A7008EFA4A6FC9E02B5E /* Pods-iOS-Extensions-Intents.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-Extensions-Intents.release.xcconfig"; path = "Pods/Target Support Files/Pods-iOS-Extensions-Intents/Pods-iOS-Extensions-Intents.release.xcconfig"; sourceTree = ""; }; @@ -3747,15 +3710,6 @@ path = TestNotifications; sourceTree = ""; }; - 110E693E24E770BD004AA96D /* Actions */ = { - isa = PBXGroup; - children = ( - 1171508024DFCEC50065E874 /* WidgetActions.swift */, - 110E694324E77125004AA96D /* WidgetActionsProvider.swift */, - ); - path = Actions; - sourceTree = ""; - }; 1115018C2528411200DCFA94 /* Sources */ = { isa = PBXGroup; children = ( @@ -3984,7 +3938,6 @@ 3E02C0F82CAD9EC700102131 /* Sensor */, 42F958962BB4680100497981 /* Assist */, 115560DF27010D6700A8F818 /* Common */, - 110E693E24E770BD004AA96D /* Actions */, 115560EA27012ED000A8F818 /* OpenPage */, 1171508324DFCF960065E874 /* Resources */, 4289DDAC2C85D595003591C2 /* Scene */, @@ -4125,7 +4078,6 @@ 11AD2E2B2528FDEB00FBC437 /* Observation */ = { isa = PBXGroup; children = ( - 11EE9B5624C68F5700404AF8 /* Action+Observation.swift */, 11A48D8024CA8ADB0021BDD9 /* NotificationCategory+Observation.swift */, ); path = Observation; @@ -4298,12 +4250,10 @@ B6DF8BC3221D047400370A59 /* GetCameraImageIntentHandler.swift */, 11B38EE1275C547A00205C7B /* IntentHandlerFactory.swift */, 115560E4270116AF00A8F818 /* OpenPageIntentHandler.swift */, - 111858D324CB5B8900B8CDDC /* PerformActionIntentHandler.swift */, 11F2E7B227500DAD00CF144C /* PickAServerError.swift */, B62817EF221D269B000BA86A /* RenderTemplateIntentHandler.swift */, B66C58B42150898A004AB261 /* SendLocationIntentHandler.swift */, 112B705A2526B1C500FEAA76 /* UpdateSensorsIntentHandler.swift */, - 11BD8BBC24E76BAD004B9A54 /* WidgetActionsIntentHandler.swift */, ); path = Intents; sourceTree = ""; @@ -4498,7 +4448,6 @@ 3997926C2B7F906C00231B54 /* MobileAppConfig */ = { isa = PBXGroup; children = ( - 119EC3C624D5119300617D51 /* MobileAppConfigAction.swift */, 399792692B7F904A00231B54 /* MobileAppConfigPushCategory.swift */, 3997926D2B7F907B00231B54 /* MobileAppConfigPush.swift */, 399792702B7F909900231B54 /* MobileAppConfig.swift */, @@ -4826,15 +4775,6 @@ path = Widgets; sourceTree = ""; }; - 420F53E62C4E9AA9003C8415 /* Action */ = { - isa = PBXGroup; - children = ( - 4296C36B2B90DB630051B63C /* IntentActionAppEntity.swift */, - 4296C36C2B90DB630051B63C /* PerformAction.swift */, - ); - path = Action; - sourceTree = ""; - }; 420F53E92C4E9D43003C8415 /* Widget */ = { isa = PBXGroup; children = ( @@ -5473,7 +5413,6 @@ isa = PBXGroup; children = ( 3E02C0F92CAD9EDF00102131 /* Sensor */, - 420F53E62C4E9AA9003C8415 /* Action */, 42D3E4A62C5D009A00444BE6 /* Script */, 4296C3722B91F06D0051B63C /* Widget */, 403AE9292C2F3A9200D48147 /* IntentServerAppEntitiy.swift */, @@ -5487,7 +5426,6 @@ 42C0F7C52D4791E800BD5C76 /* StatePrecision.swift */, 3E02C0FA2CAD9EEB00102131 /* Sensor */, 420F53F02C4EA314003C8415 /* ReloadWidgetsAppIntent.swift */, - 4296C3732B91F0730051B63C /* Actions */, 403AE90A2C2E28A200D48147 /* Gauge */, 4080D5C02C319AF400099C88 /* Details */, 42D3E4B22C5D2BA400444BE6 /* Script */, @@ -5495,15 +5433,6 @@ path = Widget; sourceTree = ""; }; - 4296C3732B91F0730051B63C /* Actions */ = { - isa = PBXGroup; - children = ( - 4296C3792B9205450051B63C /* WidgetActionsAppIntent.swift */, - 4296C3742B91F0860051B63C /* WidgetActionsAppIntentTimelineProvider.swift */, - ); - path = Actions; - sourceTree = ""; - }; 429821122CD0DD71005ECD39 /* Bluetooth */ = { isa = PBXGroup; children = ( @@ -6318,9 +6247,6 @@ 42DF7DDD2EC53D50003F3C4A /* SettingsItem.swift */, 42DF7DDF2EC53D85003F3C4A /* ServersListView.swift */, 42DF7DE12EC53DBC003F3C4A /* HomeAssistantAccountRowView.swift */, - B6B2E6A4216ACE4400D39A26 /* ActionConfiguratorView.swift */, - AC720001000000000000AA01 /* ActionsSettingsView.swift */, - AC720002000000000000AA01 /* ActionsSettingsViewModel.swift */, ); path = Settings; sourceTree = ""; @@ -7363,6 +7289,10 @@ BC31518EE9DC9E065AC508D9 /* Database/DatabaseTableProtocol.test.swift */, 5C50FA39BF16AD0BD782D0D7 /* Database/GRDB+Initialization.test.swift */, 9EE9A0E08E6FEBDDE425D0D4 /* Database/TableSchemaTests.test.swift */, + 892F0EF22A0B9F20AAEE4CCA /* DatabaseMigration.test.swift */, + BC31518EE9DC9E065AC508D9 /* DatabaseTableProtocol.test.swift */, + 5C50FA39BF16AD0BD782D0D7 /* GRDB+Initialization.test.swift */, + 9EE9A0E08E6FEBDDE425D0D4 /* TableSchemaTests.test.swift */, 11EE9B4B24C5181A00404AF8 /* ModelManager.test.swift */, 11BC9E5424FDB88200B9FBF7 /* ActiveStateManager.test.swift */, 1104FCCE253275CF00B8BE34 /* WatchBackgroundRefreshScheduler.test.swift */, @@ -7445,11 +7375,9 @@ 4297ADA42C89C43F00790812 /* AppEntitiesModel.swift */, 11EE9B4824C5116F00404AF8 /* LegacyModelManager.swift */, 11EE9B4D24C6089800404AF8 /* RealmPersistable.swift */, - 11EE9B5324C62EB300404AF8 /* RealmScene.swift */, B6B6B14E215B6866003DE2DD /* WatchComplication.swift */, B6DAC734215F069300727D2A /* NotificationCategory.swift */, B6DAC736215F06B100727D2A /* NotificationAction.swift */, - B6B2E6A1216AC21400D39A26 /* Action.swift */, ); path = Models; sourceTree = ""; @@ -8250,7 +8178,7 @@ packageReferences = ( 420E64BB2D676B2400A31E86 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, 42B89EA62E05CC54000224A2 /* XCRemoteSwiftPackageReference "WebRTC" */, - 42E00D0F2E1E7487006D140D /* XCLocalSwiftPackageReference "Sources/SharedPush" */, + 42E00D0F2E1E7487006D140D /* XCLocalSwiftPackageReference "SharedPush" */, 4237E6372E5333370023B673 /* XCRemoteSwiftPackageReference "ZIPFoundation" */, 42B18FD52F38CA2300A1537A /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 42F384032FB49C9500390AFC /* XCRemoteSwiftPackageReference "DebugSwift" */, @@ -8779,14 +8707,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Tests-App/Pods-Tests-App-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Tests-App/Pods-Tests-App-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Tests-App/Pods-Tests-App-frameworks.sh\"\n"; @@ -8924,14 +8848,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-iOS-App/Pods-iOS-App-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-iOS-App/Pods-iOS-App-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-App/Pods-iOS-App-frameworks.sh\"\n"; @@ -8967,14 +8887,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-iOS-Shared-iOS-Tests-Shared/Pods-iOS-Shared-iOS-Tests-Shared-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-iOS-Shared-iOS-Tests-Shared/Pods-iOS-Shared-iOS-Tests-Shared-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-Shared-iOS-Tests-Shared/Pods-iOS-Shared-iOS-Tests-Shared-frameworks.sh\"\n"; @@ -9074,14 +8990,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-watchOS-WatchExtension-Watch/Pods-watchOS-WatchExtension-Watch-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-watchOS-WatchExtension-Watch/Pods-watchOS-WatchExtension-Watch-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-watchOS-WatchExtension-Watch/Pods-watchOS-WatchExtension-Watch-frameworks.sh\"\n"; @@ -9187,11 +9099,9 @@ 420E2AE32C4746BB004921D8 /* WidgetBasicViewModel.swift in Sources */, 42A935A22C7FEBA100FCF504 /* ControlScript.swift in Sources */, 427647222C8F36DB0027B21F /* LightIntent.swift in Sources */, - 4296C3762B91F0F50051B63C /* WidgetActionsAppIntentTimelineProvider.swift in Sources */, 42F158482CA15FA7009C7201 /* SwitchIntent.swift in Sources */, 42C1012A2CD3DB8A0012BA78 /* CoverIntent.swift in Sources */, 426CBB6C2C9C550D003CA3AC /* IntentSwitchEntity.swift in Sources */, - 110E694424E77125004AA96D /* WidgetActionsProvider.swift in Sources */, A95FDD142F6B8A3E008EF72F /* HADynamicIslandView.swift in Sources */, A95FDD152F6B8A3E008EF72F /* HALiveActivityConfiguration.swift in Sources */, A95FDD162F6B8A3E008EF72F /* HALockScreenView.swift in Sources */, @@ -9230,7 +9140,6 @@ JARVIS0002000000000005 /* TodoItemCompleteAppIntent.swift in Sources */, 422F88132D42806400706A0A /* CustomWidgetToggleAppIntent.swift in Sources */, 4080D5C62C319B0A00099C88 /* WidgetDetailsAppIntent.swift in Sources */, - 4296C3772B91F26A0051B63C /* IntentActionAppEntity.swift in Sources */, 42AC94A42CF872520050A62C /* TileCardStyleModifier.swift in Sources */, 42F158462CA15C99009C7201 /* ControlSwitch.swift in Sources */, 4203961B2EF16A4700C9DF74 /* ButtonIntent.swift in Sources */, @@ -9265,7 +9174,6 @@ 420396482EF16A4700C9DF74 /* ControlOpenSwitch.swift in Sources */, 420396492EF16A4700C9DF74 /* ControlOpenSwitchValueProvider.swift in Sources */, 3E02C0E82CA7FCF400102131 /* WidgetSensors.swift in Sources */, - 1171508124DFCEC50065E874 /* WidgetActions.swift in Sources */, 4289DDB32C85D6B3003591C2 /* IntentSceneEntity.swift in Sources */, 3E4087EE2CE62B5A0085DF29 /* WidgetBasicViewProtocol.swift in Sources */, 42D3E4AE2C5D2AFA00444BE6 /* WidgetScripts.swift in Sources */, @@ -9277,14 +9185,12 @@ 425FF0562C8216B3000AA641 /* AssistAppIntent.swift in Sources */, 403AE9272C2F333A00D48147 /* WidgetGaugeView.swift in Sources */, 3E02C0F62CA8049500102131 /* WidgetSensorsAppIntentTimelineProvider.swift in Sources */, - 4296C3782B91F6260051B63C /* PerformAction.swift in Sources */, 42C101302CD3DC0C0012BA78 /* ControlCoverValueProvider.swift in Sources */, 42EF0AD12D4CDFF30088C91E /* UpdateWidgetItemConfirmationStateAppIntent.swift in Sources */, 42E65F082C8079FE00C4A6F2 /* ControlAssistValueProvider.swift in Sources */, 3E02C0E32CA7FCBF00102131 /* IntentSensorsAppEntity.swift in Sources */, 4080D5BE2C319AA000099C88 /* WidgetDetailsView.swift in Sources */, 4080D5BF2C319AA000099C88 /* WidgetDetails.swift in Sources */, - 4296C37B2B92054C0051B63C /* WidgetActionsAppIntent.swift in Sources */, 424627342C98D8E900EF7B43 /* WidgetBasicViewTintedWrapper.swift in Sources */, 403AE9092C2E220200D48147 /* WidgetGauge.swift in Sources */, 42EF0ACE2D4CDC0C0088C91E /* ResetAllCustomWidgetConfirmationAppIntent.swift in Sources */, @@ -9404,7 +9310,6 @@ 4240DF472E1D148F00FB0DE6 /* DeviceNameView.swift in Sources */, 4210CD032F155C4500B71FB9 /* AssistConfiguration.swift in Sources */, 420E2AE62C474710004921D8 /* WidgetBasicButtonView.swift in Sources */, - 11A48D7F24CA7E820021BDD9 /* Action+Observation.swift in Sources */, 11195F6B267EFB1F003DF674 /* NotificationManagerLocalPushInterface.swift in Sources */, 1112AE9B25F71775007A541A /* LocationHistoryListView.swift in Sources */, 426EE49B2CA4194E00A5EF4F /* OnboardingWelcomeView.swift in Sources */, @@ -9452,7 +9357,6 @@ 42E00D132E1E7807006D140D /* NotificationPermissionRequestView.swift in Sources */, 11DE823024FAE66F00E636B8 /* UIWindow+Additions.swift in Sources */, 42FCCFFB2B9B1C310057783F /* ThreadTransferCredentialToHAViewModel.swift in Sources */, - 4296C36E2B90DB640051B63C /* PerformAction.swift in Sources */, 4210CD102F16906800B71FB9 /* EntityRowView.swift in Sources */, 42B980D32DC24E1300BC5C08 /* SensorDetailLabelRowView.swift in Sources */, 42ABB0BB2C888BB10081461D /* CarPlayConfigurationViewModel.swift in Sources */, @@ -9526,10 +9430,7 @@ 42EF0ACD2D4CDC0C0088C91E /* ResetAllCustomWidgetConfirmationAppIntent.swift in Sources */, 423B5E0A2D6778370000CB95 /* WidgetBackground.swift in Sources */, 423B5E092D67781A0000CB95 /* WidgetBasicContainerView.swift in Sources */, - B6B2E6A5216ACE4400D39A26 /* ActionConfiguratorView.swift in Sources */, - AC710001000000000000AA01 /* ActionsSettingsView.swift in Sources */, 4269F48B2FADE46300164A52 /* AssistPromptMagicItemView.swift in Sources */, - AC710002000000000000AA01 /* ActionsSettingsViewModel.swift in Sources */, 42EFFAEC2C8882DD002F10FC /* CarPlayConfigurationView.swift in Sources */, 425EB4FD2F5F0ACB0067A678 /* TTSVoicePickerView.swift in Sources */, 42FA833D2F4722D00050095A /* WebViewController+EmptyState.swift in Sources */, @@ -9580,7 +9481,6 @@ 426910812F9B981700E4477B /* WebViewEmptyStateWrapperView.swift in Sources */, 42AA4C842C2DACAD00EA2E99 /* UIImage+Circle.swift in Sources */, 42F1DA612B4D4F31002729BC /* CarPlayNoServerAlert.swift in Sources */, - 4296C36D2B90DB640051B63C /* IntentActionAppEntity.swift in Sources */, 42EF0AD22D4D1A920088C91E /* UpdateWidgetItemConfirmationStateAppIntent.swift in Sources */, 42F1DA5B2B4BF7DF002729BC /* WindowSizeObserver.swift in Sources */, 429106892BA9D5F700D452F9 /* AssistView+Build.swift in Sources */, @@ -9658,7 +9558,6 @@ 117EBC32261D398B00F5334A /* ZoneManagerAccuracyFuzzer.swift in Sources */, 42A47A902C4548E100C9B43D /* ImprovDiscoverView.swift in Sources */, 113FB1132515A065000AC680 /* ScaleFactorMutator.swift in Sources */, - 4296C37A2B9205450051B63C /* WidgetActionsAppIntent.swift in Sources */, 428ED98B2E9E4FBF0019113B /* CheckmarkDrawOnView.swift in Sources */, 1185DFB3271FF53800ED7D9A /* OnboardingAuthStepSensors.swift in Sources */, 425573D12B5576E600145217 /* CarPlayDomainsListTemplate+Build.swift in Sources */, @@ -10034,7 +9933,6 @@ 11195F73267F01E4003DF674 /* HACancellable+App.swift in Sources */, 427A7CE02EBDFB4200D17841 /* AppArea+Queries.swift in Sources */, 42E3B8B92D8AC63300F5D084 /* Float+HA.swift in Sources */, - B6B74CBE228399AC00D58A68 /* Action.swift in Sources */, 11358AF024FCA8BE0074C4E2 /* ActiveStateManager.swift in Sources */, 11B38EF1275C54A300205C7B /* SendLocationIntentHandler.swift in Sources */, 1120C580274638330046C38B /* PerServerContainer.swift in Sources */, @@ -10086,7 +9984,6 @@ 42D996E62D89863A001737A0 /* Bool+HA.swift in Sources */, B6221F6522266F9F00502A30 /* WebhookRequest.swift in Sources */, 42CE8FBB2B46DB6200C707F9 /* Assets.swift in Sources */, - 11B38EF4275C54A300205C7B /* WidgetActionsIntentHandler.swift in Sources */, 11F3847C24FB27FC00CB0D74 /* DeviceWrapperBatteryObserver.swift in Sources */, 11B38EFA275C54A300205C7B /* FocusStatusIntentHandler.swift in Sources */, 4210CD002F155B7900B71FB9 /* AssistConfigurationTable.swift in Sources */, @@ -10105,9 +10002,7 @@ 11F2F26F25871D8200F61F7C /* NotificationAttachmentParserURL.swift in Sources */, 11F2F24225871CB000F61F7C /* NotificationAttachmentParser.swift in Sources */, 42F73F562E259A0900B704A9 /* BaseSensorUpdateSignaler.swift in Sources */, - 11B38EF3275C54A300205C7B /* PerformActionIntentHandler.swift in Sources */, B67CE8AC22200F220034C1D0 /* AuthenticationRoutes.swift in Sources */, - 119EC3C824D5119300617D51 /* MobileAppConfigAction.swift in Sources */, 115BC82F2677093A00452430 /* FocusStatusWrapper.swift in Sources */, B67CE89E22200F220034C1D0 /* DiscoveredHomeAssistant.swift in Sources */, 1182620824F9C492000795C6 /* HACoreMediaObjectCamera.swift in Sources */, @@ -10145,7 +10040,6 @@ B67CE89D22200F220034C1D0 /* ConfigResponse.swift in Sources */, 42FDA9372DAFEA4E00111F22 /* DeviceClass.swift in Sources */, 11F855D924DF6C7A0018013E /* MaterialDesignIcons.swift in Sources */, - 11EE9B5524C62EB300404AF8 /* RealmScene.swift in Sources */, 11AF4D20249C8AF1006C74C0 /* ConnectivitySensor.swift in Sources */, 42C0F7CA2D47936100BD5C76 /* StatePrecision.swift in Sources */, 11F2F27F258725D300F61F7C /* NotificationAttachmentErrorImage.swift in Sources */, @@ -10285,7 +10179,6 @@ 1164D9DE25FB1B9800515E8A /* UIBarButtonItem+Additions.swift in Sources */, 428EDB692DAFD9B900A271A1 /* HAEntity+DeviceClass.swift in Sources */, 11B38EEA275C54A200205C7B /* PickAServerError.swift in Sources */, - B6B74CBD228399AB00D58A68 /* Action.swift in Sources */, 428DC00A2F0CAAE7003B08D5 /* EntityProvider+Details.swift in Sources */, A95FDD0F2F6B8A19008EF72F /* HandlerLiveActivity.swift in Sources */, 11CB98CA249E62E700B05222 /* Version+HA.swift in Sources */, @@ -10421,7 +10314,6 @@ 1104FCBF2532755400B8BE34 /* WatchBackgroundRefreshScheduler.swift in Sources */, 429821172CD0DDCD005ECD39 /* HAButtonStyles.swift in Sources */, 4206DE5A2E25055E00142E85 /* WebsiteDataStoreHandler.swift in Sources */, - 11B38EE8275C54A200205C7B /* WidgetActionsIntentHandler.swift in Sources */, B6D3B4ED225B26900082BB4F /* SensorContainer.swift in Sources */, 11B38EEE275C54A200205C7B /* FocusStatusIntentHandler.swift in Sources */, 427A7CD92EBDFB1700D17841 /* AppArea.swift in Sources */, @@ -10436,7 +10328,6 @@ 42F3E1492E1D22B400F4E6FC /* HATextField.swift in Sources */, B6C091232151F90300A326DC /* LocationHistory.swift in Sources */, D0BE440A2104224600C74314 /* TokenInfo.swift in Sources */, - 119EC3C724D5119300617D51 /* MobileAppConfigAction.swift in Sources */, 42A3B63E2BD918D6007BC0F3 /* MaterialDesignIcons+Encodable.swift in Sources */, 427E92BE2D65E43A0001566B /* WidgetInteractionType.swift in Sources */, 420CFC652D3F9C2C009A94F3 /* HAppEntityTable.swift in Sources */, @@ -10449,7 +10340,6 @@ 42A9C1022FBB000100D0C0DE /* AllowedTag.swift in Sources */, 42A9C1052FBB000100D0C0DE /* AllowedTagTable.swift in Sources */, 42DC8B792E169FA300D9999E /* Color+Hex.swift in Sources */, - 11B38EE7275C54A200205C7B /* PerformActionIntentHandler.swift in Sources */, 11FA53F2251071D2008D9506 /* NSItemProvider+Additions.swift in Sources */, 4264906B2C0F1B5F002155CC /* AssistChatItem.swift in Sources */, D0C8847A2122A65800CCB501 /* SettingsStore.swift in Sources */, @@ -10507,7 +10397,6 @@ 42FCD0072B9B1DA10057783F /* CollapsibleView.swift in Sources */, 425FBA1E2C9C75A300CB5DBB /* DataWidgetsUpdater.swift in Sources */, 11521BBC25400284009C5C72 /* CrashReporter.swift in Sources */, - 11EE9B5424C62EB300404AF8 /* RealmScene.swift in Sources */, 11F2F26E25871D8200F61F7C /* NotificationAttachmentParserURL.swift in Sources */, 1105CE1C272B9CB300F33BD8 /* ServerManager.swift in Sources */, 42C95E822F7EDAAA00112233 /* ServerManagerPersistence.swift in Sources */, @@ -10542,6 +10431,10 @@ A2F3A140CDD1EF1AEA6DFAB9 /* Database/DatabaseTableProtocol.test.swift in Sources */, 692BCBBA4EEEABCC76DBBECA /* Database/GRDB+Initialization.test.swift in Sources */, BECCC152A4E3F69A8EF5A6F3 /* Database/TableSchemaTests.test.swift in Sources */, + DA6F4C18D66EDBA5DCEAE833 /* DatabaseMigration.test.swift in Sources */, + A2F3A140CDD1EF1AEA6DFAB9 /* DatabaseTableProtocol.test.swift in Sources */, + 692BCBBA4EEEABCC76DBBECA /* GRDB+Initialization.test.swift in Sources */, + BECCC152A4E3F69A8EF5A6F3 /* TableSchemaTests.test.swift in Sources */, 11267D0925BBA9FE00F28E5C /* Updater.test.swift in Sources */, 11A3F08C24ECE88C0018D84F /* WebhookUpdateLocation.test.swift in Sources */, 42FDCA272F0C7EB900C92958 /* EntityRegistry.test.swift in Sources */, @@ -12423,7 +12316,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 42E00D0F2E1E7487006D140D /* XCLocalSwiftPackageReference "Sources/SharedPush" */ = { + 42E00D0F2E1E7487006D140D /* XCLocalSwiftPackageReference "SharedPush" */ = { isa = XCLocalSwiftPackageReference; relativePath = Sources/SharedPush; }; @@ -12495,7 +12388,7 @@ }; 4273F7DF2E258827000629F7 /* SharedPush */ = { isa = XCSwiftPackageProductDependency; - package = 42E00D0F2E1E7487006D140D /* XCLocalSwiftPackageReference "Sources/SharedPush" */; + package = 42E00D0F2E1E7487006D140D /* XCLocalSwiftPackageReference "SharedPush" */; productName = SharedPush; }; 427692E22B98B82500F24321 /* SharedPush */ = { diff --git a/Podfile.lock b/Podfile.lock index fd70af4cd5..3226215137 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -195,6 +195,6 @@ SPEC CHECKSUMS: Version: de5907f2c5d0f3cf21708db7801d1d5401139486 XCGLogger: 1943831ef907df55108b0b18657953f868de973b -PODFILE CHECKSUM: 63209440b17ff6d23dca34a6c8fe5c61067dbac6 +PODFILE CHECKSUM: d601418d28009e934d3c93f1b929585f03f8aa35 COCOAPODS: 1.15.2 diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift index 3c797690f1..75e2b8b703 100644 --- a/Sources/App/AppDelegate.swift +++ b/Sources/App/AppDelegate.swift @@ -180,12 +180,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { sceneManager.activateAnyScene(for: .settings) } - @objc func openActionsPreferences() { - precondition(Current.sceneManager.supportsMultipleScenes) - let delegate: Guarantee = sceneManager.scene(for: .init(activity: .settings)) - delegate.done { $0.pushActions(animated: true) } - } - @objc func openHelp() { openURLInBrowser( URL(string: "https://companion.home-assistant.io")!, @@ -438,7 +432,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func setupModels() { // Force Realm migration to happen now _ = Realm.live() - Action.setupObserver() NotificationCategory.setupObserver() } diff --git a/Sources/App/Frontend/IncomingURLHandler.swift b/Sources/App/Frontend/IncomingURLHandler.swift index 8d4837f506..62445dd813 100644 --- a/Sources/App/Frontend/IncomingURLHandler.swift +++ b/Sources/App/Frontend/IncomingURLHandler.swift @@ -18,7 +18,6 @@ class IncomingURLHandler { case callService = "call_service" case fireEvent = "fire_event" case sendLocation = "send_location" - case performAction = "perform_action" case assist case navigate case invite @@ -60,8 +59,6 @@ class IncomingURLHandler { message: L10n.UrlHandler.SendLocation.Confirm.message, handler: { self.sendLocationURLHandler() } ) - case .performAction: - performActionURLHandler(url, serviceData: serviceData) case .camera: guard #available(iOS 16.0, *), var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { @@ -338,21 +335,7 @@ class IncomingURLHandler { if let identifier = AppIconShortcutItemsUpdater.identifier(from: shortcutItem.type) { return handleAppIconShortcut(identifier: identifier) } - if - let action = Current.realm().object(ofType: Action.self, forPrimaryKey: shortcutItem.type), - let server = Current.servers.server(for: action) { - Current.sceneManager.showFullScreenConfirm( - icon: MaterialDesignIcons(named: action.IconName), - text: action.Text, - onto: .value(windowController.window) - ) - - return Current.api(for: server)? - .HandleAction(actionID: shortcutItem.type, source: .AppShortcut) ?? - .init(error: HomeAssistantAPI.APIError.noAPIAvailable) - } else { - return .init(error: HomeAssistantAPI.APIError.notConfigured) - } + return .init(error: HomeAssistantAPI.APIError.notConfigured) } } } @@ -902,44 +885,4 @@ extension IncomingURLHandler { ) } } - - private func performActionURLHandler(_ url: URL, serviceData: [String: String]) { - let pathComponents = url.pathComponents - guard pathComponents.count > 1 else { - Current.Log.error("not enough path components for perform action handler") - return - } - - let source: AppTriggerSource = { - if - let sourceString = serviceData["source"], - let source = AppTriggerSource(rawValue: sourceString) { - return source - } else { - return .URLHandler - } - }() - - let actionID = url.pathComponents[1] - - guard - let action = Current.realm().object(ofType: Action.self, forPrimaryKey: actionID), - let server = Current.servers.server(for: action), - let api = Current.api(for: server) else { - Current.sceneManager.showFullScreenConfirm( - icon: .alertCircleIcon, - text: L10n.UrlHandler.Error.actionNotFound, - onto: .value(windowController.window) - ) - return - } - - Current.sceneManager.showFullScreenConfirm( - icon: MaterialDesignIcons(named: action.IconName), - text: action.Text, - onto: .value(windowController.window) - ) - - api.HandleAction(actionID: actionID, source: source).cauterize() - } } diff --git a/Sources/App/Frontend/WebView/WebViewController+Alerts.swift b/Sources/App/Frontend/WebView/WebViewController+Alerts.swift index eb690730bc..3a74b87d40 100644 --- a/Sources/App/Frontend/WebView/WebViewController+Alerts.swift +++ b/Sources/App/Frontend/WebView/WebViewController+Alerts.swift @@ -477,16 +477,6 @@ extension WebViewController { showEmptyState() } - func showActionAutomationEditorNotAvailable() { - let alert = UIAlertController( - title: L10n.Alerts.ActionAutomationEditor.Unavailable.title, - message: L10n.Alerts.ActionAutomationEditor.Unavailable.body, - preferredStyle: .alert - ) - alert.addAction(.init(title: L10n.okLabel, style: .default)) - present(alert, animated: true) - } - func openDebug() { let controller = UIHostingController(rootView: AnyView( NavigationView { diff --git a/Sources/App/Frontend/WebView/WebViewController+Navigation.swift b/Sources/App/Frontend/WebView/WebViewController+Navigation.swift index 438a208190..ba83cdb02a 100644 --- a/Sources/App/Frontend/WebView/WebViewController+Navigation.swift +++ b/Sources/App/Frontend/WebView/WebViewController+Navigation.swift @@ -104,29 +104,6 @@ extension WebViewController { } } - func openActionAutomationEditor(actionId: String) { - guard server.info.version >= .externalBusCommandAutomationEditor else { - showActionAutomationEditorNotAvailable() - return - } - _ = webViewExternalMessageHandler.sendExternalBus(message: .init( - command: WebViewExternalBusOutgoingMessage.showAutomationEditor.rawValue, - payload: [ - "config": [ - "trigger": [ - [ - "platform": "event", - "event_type": "ios.action_fired", - "event_data": [ - "actionID": actionId, - ], - ], - ], - ], - ] - )) - } - func getLatestConfig() { _ = Current.api(for: server)?.getConfig() } diff --git a/Sources/App/Resources/Base.lproj/Intents.intentdefinition b/Sources/App/Resources/Base.lproj/Intents.intentdefinition index 96a1c94e3b..6d29baf908 100644 --- a/Sources/App/Resources/Base.lproj/Intents.intentdefinition +++ b/Sources/App/Resources/Base.lproj/Intents.intentdefinition @@ -1204,338 +1204,6 @@ INIntentVerb Do - - INIntentCategory - generic - INIntentConfigurable - - INIntentDescription - Performs an action defined in the app - INIntentDescriptionID - tycImk - INIntentKeyParameter - action - INIntentLastParameterTag - 4 - INIntentManagedParameterCombinations - - action - - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Perform ${action} - INIntentParameterCombinationTitleID - ypnPyU - INIntentParameterCombinationUpdatesLinked - - - - INIntentName - PerformAction - INIntentParameterCombinations - - action - - INIntentParameterCombinationIsPrimary - - INIntentParameterCombinationSubtitle - Perform the action - INIntentParameterCombinationSubtitleID - g8g7Af - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - ${action} - INIntentParameterCombinationTitleID - X02YQ2 - - - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterCustomDisambiguation - - INIntentParameterDisplayName - Action - INIntentParameterDisplayNameID - Q2Qp4a - INIntentParameterDisplayPriority - 1 - INIntentParameterName - action - INIntentParameterObjectType - IntentAction - INIntentParameterObjectTypeNamespace - sI7YSe - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${action}’. - INIntentParameterPromptDialogFormatStringID - fkQXDn - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${action}’? - INIntentParameterPromptDialogFormatStringID - jtqxOg - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Which action? - INIntentParameterPromptDialogFormatStringID - flUgtx - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsResolution - - INIntentParameterTag - 4 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeConciseFormatString - Performed ${action} - INIntentResponseCodeConciseFormatStringID - dFHPYK - INIntentResponseCodeFormatString - Done - INIntentResponseCodeFormatStringID - xhwpj4 - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeConciseFormatString - Failed: ${error} - INIntentResponseCodeConciseFormatStringID - EWqRDj - INIntentResponseCodeFormatString - Failed: ${error} - INIntentResponseCodeFormatStringID - r32M7N - INIntentResponseCodeName - failure - - - INIntentResponseLastParameterTag - 5 - INIntentResponseParameters - - - INIntentResponseParameterDisplayName - Error - INIntentResponseParameterDisplayNameID - DsasVx - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - error - INIntentResponseParameterTag - 4 - INIntentResponseParameterType - String - - - INIntentResponseParameterDisplayName - Action - INIntentResponseParameterDisplayNameID - KM7mXC - INIntentResponseParameterDisplayPriority - 2 - INIntentResponseParameterName - action - INIntentResponseParameterObjectType - IntentAction - INIntentResponseParameterObjectTypeNamespace - sI7YSe - INIntentResponseParameterTag - 5 - INIntentResponseParameterType - Object - - - - INIntentTitle - Perform Action - INIntentTitleID - kGB23u - INIntentType - Custom - INIntentVerb - Run - - - INIntentCategory - information - INIntentDescription - View and run actions - INIntentDescriptionID - f1KX0Q - INIntentEligibleForWidgets - - INIntentIneligibleForSuggestions - - INIntentLastParameterTag - 3 - INIntentName - WidgetActions - INIntentParameters - - - INIntentParameterArraySizes - - - INIntentParameterArraySizeSize - 2 - INIntentParameterArraySizeSizeClass - Small - - - INIntentParameterArraySizeSize - 4 - INIntentParameterArraySizeSizeClass - Medium - - - INIntentParameterArraySizeSize - 10 - INIntentParameterArraySizeSizeClass - Large - - - INIntentParameterArraySizeSize - 20 - INIntentParameterArraySizeSizeClass - ExtraLarge - - - INIntentParameterArraySizeSize - 1 - INIntentParameterArraySizeSizeClass - AccessoryInline - - - INIntentParameterArraySizeSize - 1 - INIntentParameterArraySizeSizeClass - AccessoryCorner - - - INIntentParameterArraySizeSize - 1 - INIntentParameterArraySizeSizeClass - AccessoryCircular - - - INIntentParameterArraySizeSize - 2 - INIntentParameterArraySizeSizeClass - AccessoryRectangular - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Actions - INIntentParameterDisplayNameID - HoH8wg - INIntentParameterDisplayPriority - 1 - INIntentParameterFixedSizeArray - 1 - INIntentParameterName - actions - INIntentParameterObjectType - IntentAction - INIntentParameterObjectTypeNamespace - sI7YSe - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Which actions? - INIntentParameterPromptDialogFormatStringID - 8skKro - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsDynamicEnumeration - - INIntentParameterSupportsMultipleValues - - INIntentParameterTag - 3 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - - INIntentTitle - Actions - INIntentTitleID - ULka1b - INIntentType - Custom - INIntentVerb - Open - INIntentCategory share @@ -2496,69 +2164,6 @@ INTypes - - INTypeDisplayName - Action - INTypeDisplayNameID - cLmTme - INTypeLastPropertyTag - 101 - INTypeName - IntentAction - INTypeProperties - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 1 - INTypePropertyName - identifier - INTypePropertyTag - 1 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 2 - INTypePropertyName - displayString - INTypePropertyTag - 2 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 3 - INTypePropertyName - pronunciationHint - INTypePropertyTag - 3 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 4 - INTypePropertyName - alternativeSpeakableMatches - INTypePropertySupportsMultipleValues - - INTypePropertyTag - 4 - INTypePropertyType - SpeakableString - - - INTypeDisplayName Page diff --git a/Sources/App/Resources/Info.plist b/Sources/App/Resources/Info.plist index 47ca91fee7..574809ba64 100644 --- a/Sources/App/Resources/Info.plist +++ b/Sources/App/Resources/Info.plist @@ -45,11 +45,9 @@ FireEventIntent GetCameraImageIntent OpenPageIntent - PerformActionIntent RenderTemplateIntent SendLocationIntent UpdateSensorsIntent - WidgetActionsIntent WidgetOpenPageIntent AssistInAppIntent AssistIntent @@ -142,13 +140,11 @@ GetCameraImageIntent INSendMessageIntent OpenPageIntent - PerformActionIntent ReloadWidgetsAppIntent RenderTemplateIntent ScriptAppIntent SendLocationIntent UpdateSensorsIntent - WidgetActionsIntent WidgetOpenPageIntent UIApplicationSceneManifest diff --git a/Sources/App/Resources/en.lproj/Localizable.strings b/Sources/App/Resources/en.lproj/Localizable.strings index a8270f18ce..a60c6a7e15 100644 --- a/Sources/App/Resources/en.lproj/Localizable.strings +++ b/Sources/App/Resources/en.lproj/Localizable.strings @@ -15,26 +15,9 @@ "about.review.title" = "Leave a review"; "about.title" = "About"; "about.website.title" = "Website"; -"actions_configurator.action.create_automation" = "Create automation"; -"actions_configurator.action.footer" = "Define what will be executed when Action is performed, alternatively you can use the example trigger below manually."; -"actions_configurator.action.title" = "Execute"; -"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"; "alert.confirmation.delete_entities.message" = "This will clean your entities from database and it will only reload the next time you open the app from zero."; "alert.confirmation.generic.title" = "Are you sure?"; -"alerts.action_automation_editor.unavailable.body" = "To automatically create an automation for an Action please update your Home Assistant to at least version 2024.2"; -"alerts.action_automation_editor.unavailable.title" = "Please update Home Assistant"; "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"; @@ -85,11 +68,6 @@ "app_intents.open_experimental_dashboard.description" = "Opens the experimental dashboard"; "app_intents.open_experimental_dashboard.title" = "Open Experimental Dashboard"; "app_intents.open_state_icon.title" = "Icon for open state"; -"app_intents.perform_action.action_parameter_configuration" = "Which action?"; -"app_intents.perform_action.action_parameter_confirmation" = "Just to confirm, you wanted ‘%@’?"; -"app_intents.perform_action.action_parameter_disambiguation_intro" = "There are %@ options matching ‘%@’."; -"app_intents.perform_action.response_failure" = "Failed: %@"; -"app_intents.perform_action.response_success" = "Done"; "app_intents.scenes.failure_message.content" = "Scene \"%@\" failed to execute, please check your logs."; "app_intents.scenes.icon.title" = "Icon"; "app_intents.scenes.parameter.scene.title" = "Scene"; @@ -113,7 +91,6 @@ "app_intents.state.target" = "Target state"; "app_intents.state.toggle" = "Toggle"; "app_intents.switch.title" = "Switch"; -"app_intents.widget_action.actions_parameter_configuration" = "Which actions?"; "assist.button.finish_recording.title" = "Tap to finish recording..."; "assist.button.listening.title" = "Listening..."; "assist.carplay.playback_help.change_playback.detail" = "Choose Download and play if Stream does not play audio in your car."; @@ -176,9 +153,6 @@ "cameras.drag_to_reorder" = "Drag and drop to reorder"; "cameras.no_server_found" = "No server found for camera: %@"; "cancel_label" = "Cancel"; -"carPlay.action.execute.in_progress" = "Executing..."; -"carPlay.action.intro.item.body" = "Tap to continue on your iPhone"; -"carPlay.action.intro.item.title" = "Create your first action"; "carPlay.config.tabs.title" = "Tabs"; "carPlay.debug.delete_db.alert.failed.message" = "Failed to delete configuration, error: %@"; "carPlay.debug.delete_db.alert.title" = "Are you sure you want to delete CarPlay configuration? This can't be reverted"; @@ -240,18 +214,16 @@ "carPlay.lock.confirmation.title" = "Are you sure you want to perform lock action on %@?"; "carPlay.navigation.button.next" = "Next"; "carPlay.navigation.button.previous" = "Previous"; -"carPlay.navigation.tab.actions" = "Actions"; "carPlay.navigation.tab.areas" = "Areas"; "carPlay.navigation.tab.domains" = "Control"; "carPlay.navigation.tab.quick_access" = "Quick access"; "carPlay.navigation.tab.settings" = "Settings"; -"carPlay.no_actions.title" = "Open iOS Companion App to create actions for CarPlay."; "carPlay.no_entities.title" = "No CarPlay compatible entities available."; -"carPlay.notification.action.intro.body" = "Tap to create your first iOS Action"; -"carPlay.notification.action.intro.title" = "Create iOS Action"; "carPlay.notification.quick_access.intro.body" = "Tap to create your CarPlay configuration."; "carPlay.notification.quick_access.intro.title" = "Create CarPlay configuration"; +"carPlay.quick_access.execute.in_progress" = "Executing..."; "carPlay.quick_access.intro.item.title" = "Create your CarPlay configuration"; +"carPlay.quick_access.intro.item.body" = "Tap to continue on your iPhone"; "carPlay.state.loading.title" = "Loading…"; "carPlay.tabs.active.delete_action.title" = "Swipe left to remove tab"; "carPlay.tabs.active.title" = "Active"; @@ -554,7 +526,6 @@ This server requires a client certificate (mTLS) but the operation was cancelled "kiosk.security.section" = "Security & Display"; "kiosk.security.taps_required" = "Taps Required: %li"; "kiosk.title" = "Kiosk Mode"; -"legacy_actions.disclaimer" = "Legacy iOS Actions are not the recommended way to interact with Home Assistant anymore, please use Scripts, Scenes and Automations directly in your Widgets, Apple Watch and CarPlay."; "live_activity.empty_state" = "No active Live Activities"; "live_activity.end_all.button" = "End All Activities"; "live_activity.end_all.confirm.button" = "End All"; @@ -611,8 +582,6 @@ This server requires a client certificate (mTLS) but the operation was cancelled "magic_item.edit" = "Save"; "magic_item.icon_color.title" = "Icon color"; "magic_item.icon_name.title" = "Icon name"; -"magic_item.item_type.action.list.title" = "iOS Actions"; -"magic_item.item_type.action.list.warning.title" = "We will stop supporting iOS Actions in the future, please consider using Home Assistant scripts or scenes instead."; "magic_item.item_type.app.list.title" = "App"; "magic_item.item_type.assist_prompt.title" = "Assist prompt"; "magic_item.item_type.assist_prompt.unsupported.title" = "Assist prompt (iOS 26.4+)"; @@ -626,8 +595,6 @@ This server requires a client certificate (mTLS) but the operation was cancelled "magic_item.require_confirmation.title" = "Require confirmation"; "magic_item.text_color.title" = "Text color"; "magic_item.use_custom_colors.title" = "Use custom colors"; -"menu.actions.configure" = "Configure…"; -"menu.actions.title" = "Actions"; "menu.application.about" = "About %@"; "menu.application.preferences" = "Preferences…"; "menu.file.update_sensors" = "Update Sensors"; @@ -686,6 +653,7 @@ Tags will work on any device with Home Assistant installed which has hardware su "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.example_trigger.title" = "Example Trigger"; "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."; @@ -1063,22 +1031,6 @@ Home Assistant is open source, advocates for privacy and runs locally in your ho "settings.widgets.select.title" = "Add to Widget"; "settings.widgets.title" = "Widgets"; "settings.widgets.your_widgets.title" = "Your widgets"; -"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.carPlay.available.title" = "Show in CarPlay"; -"settings_details.actions.footer" = "Actions are used in the Apple Watch app, App Icon Actions, the Today widget and CarPlay."; -"settings_details.actions.learn.button.title" = "Introduction to iOS Actions"; -"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.select_all" = "Select All"; -"settings_details.actions.scenes.title" = "Scene Actions"; -"settings_details.actions.server_controlled.update.title" = "Update server Actions"; -"settings_details.actions.title" = "Actions"; -"settings_details.actions.use_custom_colors.title" = "Use custom colors"; -"settings_details.actions.watch.available.title" = "Show in Watch"; "settings_details.general.app_icon.current_selected.title" = "- Selected"; "settings_details.general.app_icon.enum.beta" = "Beta"; "settings_details.general.app_icon.enum.black" = "Black"; @@ -1136,7 +1088,6 @@ Home Assistant is open source, advocates for privacy and runs locally in your ho "settings_details.http.warning.message" = "Unencrypted connections expose your credentials and personal data to potential interception. Always use HTTPS for remote access to protect your privacy and security."; "settings_details.http.warning.title" = "Security Warning"; "settings_details.learn_more" = "Learn more"; -"settings_details.legacy_actions.title" = "(Legacy) iOS Actions"; "settings_details.location.background_refresh.disabled" = "Disabled"; "settings_details.location.background_refresh.enabled" = "Enabled"; "settings_details.location.background_refresh.title" = "Background Refresh"; @@ -1317,7 +1268,6 @@ Home Assistant is open source, advocates for privacy and runs locally in your ho "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.message" = "Do you want to fire the event %@?"; "url_handler.fire_event.confirm.title" = "Fire event?"; "url_handler.fire_event.error.message" = "An error occurred while attempting to fire event %@: %@"; @@ -1580,10 +1530,6 @@ Home Assistant is open source, advocates for privacy and runs locally in your ho "widgets.action.name.navigate" = "Navigate"; "widgets.action.name.nothing" = "Nothing"; "widgets.action.name.run_script" = "Run Script"; -"widgets.actions.description" = "Perform Home Assistant actions."; -"widgets.actions.not_configured" = "No Actions Configured"; -"widgets.actions.parameters.action" = "Action"; -"widgets.actions.title" = "Actions"; "widgets.assist.action_title" = "Ask Assist"; "widgets.assist.description" = "Open Assist in the app"; "widgets.assist.title" = "Assist"; @@ -1744,3 +1690,4 @@ Home Assistant is open source, advocates for privacy and runs locally in your ho "whats_new.title" = "What's New in The Companion App"; "whats_new.version_format" = "Version %@"; "yes_label" = "Yes"; +"yaml_preview.share" = "Share Contents"; diff --git a/Sources/App/Scenes/SettingsSceneDelegate.swift b/Sources/App/Scenes/SettingsSceneDelegate.swift index 075af1e49e..cbfebc63f4 100644 --- a/Sources/App/Scenes/SettingsSceneDelegate.swift +++ b/Sources/App/Scenes/SettingsSceneDelegate.swift @@ -29,10 +29,4 @@ import UIKit } #endif } - - func pushActions(animated: Bool) { - // SwiftUI SettingsView handles navigation internally - // This method is kept for compatibility but no longer does anything - Current.Log.info("pushActions called on SwiftUI SettingsView - navigation handled internally") - } } diff --git a/Sources/App/Settings/AppIconShortcuts/AppIconShortcutItemsUpdater.swift b/Sources/App/Settings/AppIconShortcuts/AppIconShortcutItemsUpdater.swift index af42e814ac..da75233db2 100644 --- a/Sources/App/Settings/AppIconShortcuts/AppIconShortcutItemsUpdater.swift +++ b/Sources/App/Settings/AppIconShortcuts/AppIconShortcutItemsUpdater.swift @@ -22,14 +22,17 @@ enum AppIconShortcutItemsUpdater { let magicItemProvider = Current.magicItemProvider() magicItemProvider.loadInformation { _ in let config = (try? AppIconShortcutConfig.config()) ?? AppIconShortcutConfig() - let configuredShortcutItems = config.items.prefix(maximumShortcutItems).map { item in - UIApplicationShortcutItem( - type: shortcutType(for: item), - localizedTitle: title(for: item, provider: magicItemProvider), - localizedSubtitle: subtitle(for: item, provider: magicItemProvider), - icon: icon(for: item, provider: magicItemProvider) - ) - } + let configuredShortcutItems = config.items + .filter { $0.type != .unsupported } + .prefix(maximumShortcutItems) + .map { item in + UIApplicationShortcutItem( + type: shortcutType(for: item), + localizedTitle: title(for: item, provider: magicItemProvider), + localizedSubtitle: subtitle(for: item, provider: magicItemProvider), + icon: icon(for: item, provider: magicItemProvider) + ) + } let shortcutItems = forcedShortcutItems + configuredShortcutItems publish(shortcutItems: shortcutItems) } @@ -87,8 +90,6 @@ enum AppIconShortcutItemsUpdater { private static func icon(for item: MagicItem, provider: MagicItemProviderProtocol) -> UIApplicationShortcutIcon? { switch item.type { - case .action: - return .init(systemSymbol: .boltFill) case .script: return .init(systemSymbol: .applescriptFill) case .scene: @@ -99,6 +100,8 @@ enum AppIconShortcutItemsUpdater { return .init(systemSymbol: .folderFill) case .assistPipeline, .assistPrompt: return .init(systemSymbol: .micFill) + case .unsupported: + return nil } } } diff --git a/Sources/App/Settings/AppleWatch/HomeCustomization/FolderDetailView.swift b/Sources/App/Settings/AppleWatch/HomeCustomization/FolderDetailView.swift index 5a320d8c0d..defcbb0248 100644 --- a/Sources/App/Settings/AppleWatch/HomeCustomization/FolderDetailView.swift +++ b/Sources/App/Settings/AppleWatch/HomeCustomization/FolderDetailView.swift @@ -76,7 +76,12 @@ struct FolderDetailView: View { customization: nil ) - if item.type == .action { + NavigationLink { + MagicItemCustomizationView(mode: .edit, context: .watch, item: item) { updatedMagicItem in + viewModel.updateItemInFolder(folderId: folderId, item: updatedMagicItem) + } + .environment(\.colorScheme, .dark) + } label: { HStack { Image(uiImage: image(for: item, itemInfo: itemInfo)) .renderingMode(.original) @@ -85,22 +90,6 @@ struct FolderDetailView: View { Image(systemSymbol: .line3Horizontal) .foregroundStyle(.gray) } - } else { - NavigationLink { - MagicItemCustomizationView(mode: .edit, context: .watch, item: item) { updatedMagicItem in - viewModel.updateItemInFolder(folderId: folderId, item: updatedMagicItem) - } - .environment(\.colorScheme, .dark) - } label: { - HStack { - Image(uiImage: image(for: item, itemInfo: itemInfo)) - .renderingMode(.original) - Text(item.name(info: itemInfo)) - .frame(maxWidth: .infinity, alignment: .leading) - Image(systemSymbol: .line3Horizontal) - .foregroundStyle(.gray) - } - } } } diff --git a/Sources/App/Settings/AppleWatch/HomeCustomization/WatchConfigurationView.swift b/Sources/App/Settings/AppleWatch/HomeCustomization/WatchConfigurationView.swift index 596b13db5b..dc2a5ab9f7 100644 --- a/Sources/App/Settings/AppleWatch/HomeCustomization/WatchConfigurationView.swift +++ b/Sources/App/Settings/AppleWatch/HomeCustomization/WatchConfigurationView.swift @@ -249,9 +249,7 @@ struct WatchConfigurationView: View { @ViewBuilder private func makeListItemRow(item: MagicItem, info: MagicItem.Info) -> some View { - if item.type == .action { - itemRow(item: item, info: info) - } else if item.type == .folder { + if item.type == .folder { NavigationLink { FolderDetailView( folderId: item.id, diff --git a/Sources/App/Settings/CarPlay/CarPlayConfigurationView.swift b/Sources/App/Settings/CarPlay/CarPlayConfigurationView.swift index c67d2bf3cb..d67e83f2f3 100644 --- a/Sources/App/Settings/CarPlay/CarPlayConfigurationView.swift +++ b/Sources/App/Settings/CarPlay/CarPlayConfigurationView.swift @@ -211,9 +211,7 @@ struct CarPlayConfigurationView: View { @ViewBuilder private func makeListItemRow(item: MagicItem, info: MagicItem.Info) -> some View { - if item.type == .action { - itemRow(item: item, info: info) - } else if item.type == .assistPrompt { + if item.type == .assistPrompt { NavigationLink { AssistPromptMagicItemView(mode: .edit, item: item) { updatedMagicItem in viewModel.updateItem(updatedMagicItem) diff --git a/Sources/App/Settings/Components/YamlPreviewSection.swift b/Sources/App/Settings/Components/YamlPreviewSection.swift index a89122c679..b90f2bf8c7 100644 --- a/Sources/App/Settings/Components/YamlPreviewSection.swift +++ b/Sources/App/Settings/Components/YamlPreviewSection.swift @@ -17,7 +17,7 @@ struct YamlPreviewSection: View { init( header: String, - shareTitle: String = L10n.ActionsConfigurator.TriggerExample.share, + shareTitle: String = L10n.YamlPreview.share, yaml: String ) { self.header = header diff --git a/Sources/App/Settings/MagicItem/Add/MagicItemAddView.swift b/Sources/App/Settings/MagicItem/Add/MagicItemAddView.swift index b0a447cc93..c7975bc453 100644 --- a/Sources/App/Settings/MagicItem/Add/MagicItemAddView.swift +++ b/Sources/App/Settings/MagicItem/Add/MagicItemAddView.swift @@ -14,7 +14,6 @@ struct MagicItemAddView: View { case entities case scripts case scenes - case legacyiOSActions case assistPipelines } @@ -48,7 +47,6 @@ struct MagicItemAddView: View { options.append(.scripts) options.append(.scenes) } - options.append(.legacyiOSActions) } if [.carPlay, .appIconShortcut].contains(context), #available(iOS 26.0, *) { options.append(.assistPipelines) @@ -66,12 +64,6 @@ struct MagicItemAddView: View { NavigationView { Group { switch viewModel.selectedItemType { - case .actions: - List { - pickerView - actionsList - } - .searchable(text: $viewModel.searchText) case .entities: VStack { pickerView @@ -103,7 +95,6 @@ struct MagicItemAddView: View { } .onAppear { autoSelectItemType() - viewModel.loadContent() if viewModel.selectedServerId == nil { viewModel.selectedServerId = Current.servers.all.first?.identifier.rawValue @@ -139,9 +130,6 @@ struct MagicItemAddView: View { case .entities: Text(verbatim: L10n.MagicItem.ItemType.Entity.List.title) .tag(MagicItemAddType.entities) - case .legacyiOSActions: - Text(verbatim: L10n.MagicItem.ItemType.Action.List.title) - .tag(MagicItemAddType.actions) case .scripts: Text(verbatim: L10n.MagicItem.ItemType.Script.List.title) .tag(MagicItemAddType.scripts) @@ -177,8 +165,6 @@ struct MagicItemAddView: View { return .scripts case .scenes: return .scenes - case .legacyiOSActions: - return .actions case .assistPipelines: return .assistPipelines } @@ -192,35 +178,6 @@ struct MagicItemAddView: View { } } - @ViewBuilder - private var actionsList: some View { - actionsDeprecationDisclaimer - ForEach(viewModel.actions, id: \.ID) { action in - if visibleForSearch(title: action.Text, entityId: action.ID) { - Button(action: { - itemToAdd(.init(id: action.ID, serverId: action.serverIdentifier, type: .action)) - dismiss() - }, label: { - EntityRowView(optionalTitle: action.Text, accessoryImageSystemSymbol: .plusCircleFill) - }) - .tint(Color(uiColor: .label)) - } - } - } - - private var actionsDeprecationDisclaimer: some View { - Section { - Button { - viewModel.selectedItemType = .scripts - } label: { - Text(verbatim: L10n.MagicItem.ItemType.Action.List.Warning.title) - } - .buttonStyle(.bordered) - .tint(.red) - .listRowBackground(Color.clear) - } - } - @ViewBuilder private func entitiesPerServerList(domainFilter: Domain? = nil) -> some View { EntityPicker( @@ -253,12 +210,6 @@ struct MagicItemAddView: View { } ) } - - private func visibleForSearch(title: String, entityId: String) -> Bool { - viewModel.searchText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || - title.lowercased().contains(viewModel.searchText.lowercased()) || - entityId.lowercased().contains(viewModel.searchText.lowercased()) - } } #Preview { diff --git a/Sources/App/Settings/MagicItem/Add/MagicItemAddViewModel.swift b/Sources/App/Settings/MagicItem/Add/MagicItemAddViewModel.swift index 2ddd145955..62cbf80f81 100644 --- a/Sources/App/Settings/MagicItem/Add/MagicItemAddViewModel.swift +++ b/Sources/App/Settings/MagicItem/Add/MagicItemAddViewModel.swift @@ -1,13 +1,8 @@ import Combine import Foundation -import GRDB -import HAKit -import PromiseKit -import Shared enum MagicItemAddType { case scripts - case actions case scenes case entities case assistPipelines @@ -15,19 +10,5 @@ enum MagicItemAddType { final class MagicItemAddViewModel: ObservableObject { @Published var selectedItemType = MagicItemAddType.scripts - @Published var actions: [Action] = [] - @Published var searchText: String = "" @Published var selectedServerId: String? - - @MainActor - func loadContent() { - loadActions() - } - - @MainActor - private func loadActions() { - actions = Current.realm().objects(Action.self) - .filter({ $0.Scene == nil }) - .sorted(by: { $0.Position < $1.Position }) - } } diff --git a/Sources/App/Settings/Notifications/NotificationActionEditorView.swift b/Sources/App/Settings/Notifications/NotificationActionEditorView.swift index 95c251ded8..63c76b320d 100644 --- a/Sources/App/Settings/Notifications/NotificationActionEditorView.swift +++ b/Sources/App/Settings/Notifications/NotificationActionEditorView.swift @@ -70,7 +70,7 @@ struct NotificationActionEditorView: View { } YamlPreviewSection( - header: L10n.ActionsConfigurator.TriggerExample.title, + header: L10n.NotificationsConfigurator.Action.ExampleTrigger.title, yaml: yamlPreview ) } diff --git a/Sources/App/Settings/Observation/Action+Observation.swift b/Sources/App/Settings/Observation/Action+Observation.swift deleted file mode 100644 index 93a902c9eb..0000000000 --- a/Sources/App/Settings/Observation/Action+Observation.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Foundation -import PromiseKit -import RealmSwift -import Shared -#if !targetEnvironment(macCatalyst) -import NotificationCenter -#endif -import Intents -import WidgetKit - -extension Action { - static func setupObserver() { - let actions = Current.realm() - .objects(Action.self) - .sorted(byKeyPath: #keyPath(Action.Position)) - - Current.modelManager.observe(for: AnyRealmCollection(actions)) { collection in - let invalidateMenu = Promise { seal in - UIMenuSystem.main.setNeedsRebuild() - seal.fulfill(()) - } - - let updateShortcuts = Promise { seal in - if !Current.isCatalyst { - AppIconShortcutItemsUpdater.update() - } - seal.fulfill(()) - } - - let updateWidgetKitWidgets = Promise { seal in - WidgetCenter.shared.reloadTimelines(ofKind: WidgetsKind.actions.rawValue) - - seal.fulfill(()) - } - - let updateSuggestions = Promise { seal in - // if we ever want to start donating more than actions, this needs to be pulled out to a helper - let intents = collection.map { PerformActionIntent(action: $0) } - INVoiceShortcutCenter.shared.setShortcutSuggestions(Array(intents.map { .intent($0) })) - seal.fulfill(()) - } - - return when(resolved: [ - invalidateMenu, - updateShortcuts, - updateWidgetKitWidgets, - updateSuggestions, - ]).asVoid() - } - } -} diff --git a/Sources/App/Settings/Settings/ActionConfiguratorView.swift b/Sources/App/Settings/Settings/ActionConfiguratorView.swift deleted file mode 100644 index 407bf9cba7..0000000000 --- a/Sources/App/Settings/Settings/ActionConfiguratorView.swift +++ /dev/null @@ -1,400 +0,0 @@ -import PromiseKit -import RealmSwift -import Shared -import SwiftUI - -/// View model that holds the mutable fields of an `Action` being edited. -/// -/// Backing `Action` is a Realm `Object`, so we mirror its editable fields onto -/// `@Published` properties to make SwiftUI redraws reliable and keep Realm -/// writes out of the view body. -final class ActionConfiguratorViewModel: ObservableObject { - @Published var name: String - @Published var text: String - @Published var iconName: String - @Published var iconColor: String - @Published var textColor: String - @Published var backgroundColor: String - @Published var useCustomColors: Bool - @Published var serverIdentifier: String - - private let sourceAction: Action - let isNewAction: Bool - - var isServerControlled: Bool { sourceAction.isServerControlled } - var triggerType: Action.TriggerType { sourceAction.triggerType } - var showInWatch: Bool { sourceAction.showInWatch } - - init(action: Action?) { - if let action { - let copy = Action(value: action) - self.sourceAction = copy - self.isNewAction = false - } else { - let fresh = Action() - if let firstServer = Current.servers.all.first { - fresh.serverIdentifier = firstServer.identifier.rawValue - } - self.sourceAction = fresh - self.isNewAction = true - } - self.name = sourceAction.Name - self.text = sourceAction.Text - self.iconName = sourceAction.IconName - self.iconColor = sourceAction.IconColor - self.textColor = sourceAction.TextColor - self.backgroundColor = sourceAction.BackgroundColor - self.useCustomColors = sourceAction.useCustomColors - - // Fall back to the first available server if the stored identifier is empty - // or points to a server that no longer exists. Common for actions imported from - // older single-server installs. - let allServerIds = Set(Current.servers.all.map(\.identifier.rawValue)) - if sourceAction.serverIdentifier.isEmpty || !allServerIds.contains(sourceAction.serverIdentifier) { - self.serverIdentifier = Current.servers.all.first?.identifier.rawValue ?? sourceAction.serverIdentifier - } else { - self.serverIdentifier = sourceAction.serverIdentifier - } - } - - func canConfigure(_ keyPath: PartialKeyPath) -> Bool { - sourceAction.canConfigure(keyPath) - } - - /// Returns an unmanaged `Action` with the view model values applied. Safe to - /// hand to the `onSave` callback for Realm persistence. - func buildAction() -> Action { - let result = Action(value: sourceAction) - result.Name = name - result.Text = text - result.IconName = iconName - result.IconColor = iconColor - result.TextColor = textColor - result.BackgroundColor = backgroundColor - result.useCustomColors = useCustomColors - result.serverIdentifier = serverIdentifier - return result - } -} - -/// SwiftUI editor for a legacy `Action`. Replaces the Eureka-based `ActionConfigurator`. -/// -/// The caller is responsible for persisting the resulting `Action` to Realm when -/// `onSave` is invoked. -struct ActionConfiguratorView: View { - @StateObject private var viewModel: ActionConfiguratorViewModel - - @Environment(\.dismiss) private var dismiss - - private let onSave: (Action, _ openAutomationEditor: Bool) -> Void - - init( - action: Action?, - onSave: @escaping (Action, _ openAutomationEditor: Bool) -> Void - ) { - self._viewModel = StateObject(wrappedValue: ActionConfiguratorViewModel(action: action)) - self.onSave = onSave - } - - var body: some View { - Form { - previewSection - nameAndServerSection - textSection - visualsSection - customColorsSection - executeSection - } - .navigationTitle(title) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - // `if` directly inside `.toolbar` requires iOS 16+ ToolbarContentBuilder. - // Move the conditional inside the item so it works on iOS 15 too. - ToolbarItem(placement: .primaryAction) { - if hasEditableFields { - Button(L10n.saveLabel) { - save(openAutomationEditor: false) - } - .disabled(!isValid) - } - } - } - } - - private var title: String { - if viewModel.name.isEmpty, viewModel.isNewAction { - return L10n.ActionsConfigurator.title - } - return viewModel.name - } - - private var isValid: Bool { - !viewModel.name.trimmingCharacters(in: .whitespaces).isEmpty - && !viewModel.text.trimmingCharacters(in: .whitespaces).isEmpty - } - - private var hasEditableFields: Bool { - viewModel.canConfigure(\Action.Name) - || viewModel.canConfigure(\Action.Text) - || viewModel.canConfigure(\Action.IconName) - || viewModel.canConfigure(\Action.IconColor) - || viewModel.canConfigure(\Action.TextColor) - || viewModel.canConfigure(\Action.BackgroundColor) - || viewModel.canConfigure(\Action.useCustomColors) - } - - // MARK: - Sections - - @ViewBuilder - private var previewSection: some View { - if viewModel.showInWatch { - Section { - WidgetPreviewView(viewModel: viewModel) - .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0)) - .listRowBackground(Color.clear) - } - } - } - - private var nameAndServerSection: some View { - Section { - LabeledField(title: L10n.ActionsConfigurator.Rows.Name.title) { - TextField(L10n.ActionsConfigurator.Rows.Name.title, text: $viewModel.name) - .disabled(!viewModel.canConfigure(\Action.Name)) - .multilineTextAlignment(.trailing) - } - - // When the action is server-controlled, Text isn't editable, but still display it here - // to match the old layout. - if !viewModel.canConfigure(\Action.Text), viewModel.isServerControlled { - LabeledField(title: L10n.ActionsConfigurator.Rows.Text.title) { - TextField(L10n.ActionsConfigurator.Rows.Text.title, text: $viewModel.text) - .disabled(true) - .multilineTextAlignment(.trailing) - } - } - - ActionServerPicker( - selectedServerId: $viewModel.serverIdentifier, - isDisabled: !viewModel.canConfigure(\Action.serverIdentifier) - ) - } - } - - @ViewBuilder - private var textSection: some View { - if viewModel.canConfigure(\Action.Text) { - Section { - LabeledField(title: L10n.ActionsConfigurator.Rows.Text.title) { - TextField(L10n.ActionsConfigurator.Rows.Text.title, text: $viewModel.text) - .multilineTextAlignment(.trailing) - } - } - } - } - - @ViewBuilder - private var visualsSection: some View { - let canConfigureIcon = viewModel.canConfigure(\Action.IconName) - let canConfigureIconColor = viewModel.canConfigure(\Action.IconColor) - - if !canConfigureIcon, !canConfigureIconColor { - Section { - switch viewModel.triggerType { - case .event: - Text(L10n.ActionsConfigurator.VisualSection.serverDefined) - .foregroundStyle(.secondary) - case .scene: - Text(L10n.ActionsConfigurator.VisualSection.sceneDefined) - .foregroundStyle(.secondary) - } - } - } else { - Section { - if canConfigureIcon { - iconPickerRow - } - if canConfigureIconColor { - ColorPicker( - L10n.ActionsConfigurator.Rows.IconColor.title, - selection: Binding( - get: { Color(hex: viewModel.iconColor) }, - set: { newColor in - viewModel.iconColor = newColor.hex() ?? viewModel.iconColor - } - ), - supportsOpacity: false - ) - } - } footer: { - if viewModel.triggerType == .scene { - Text(L10n.ActionsConfigurator.VisualSection.sceneHintFooter( - ListFormatter.localizedString(byJoining: ["text_color", "background_color", "icon_color"]) - )) - } - } - } - } - - @ViewBuilder - private var iconPickerRow: some View { - HStack { - Text(L10n.ActionsConfigurator.Rows.Icon.title) - Spacer() - IconPicker( - selectedIcon: Binding( - get: { MaterialDesignIcons(named: viewModel.iconName) }, - set: { newIcon in - if let newIcon { - viewModel.iconName = newIcon.name - } - } - ), - selectedColor: Binding( - get: { Color(hex: viewModel.iconColor) }, - set: { _ in /* no-op */ } - ) - ) - } - } - - @ViewBuilder - private var customColorsSection: some View { - let canConfigureTextColor = viewModel.canConfigure(\Action.TextColor) - let canConfigureBackgroundColor = viewModel.canConfigure(\Action.BackgroundColor) - let canConfigureUseCustom = viewModel.canConfigure(\Action.useCustomColors) - - if canConfigureUseCustom || canConfigureTextColor || canConfigureBackgroundColor { - Section { - Toggle(L10n.SettingsDetails.Actions.UseCustomColors.title, isOn: $viewModel.useCustomColors) - .disabled(!canConfigureUseCustom) - - if viewModel.useCustomColors { - if canConfigureTextColor { - ColorPicker( - L10n.ActionsConfigurator.Rows.TextColor.title, - selection: Binding( - get: { Color(hex: viewModel.textColor) }, - set: { newColor in - viewModel.textColor = newColor.hex() ?? viewModel.textColor - } - ), - supportsOpacity: false - ) - } - if canConfigureBackgroundColor { - ColorPicker( - L10n.ActionsConfigurator.Rows.BackgroundColor.title, - selection: Binding( - get: { Color(hex: viewModel.backgroundColor) }, - set: { newColor in - viewModel.backgroundColor = newColor.hex() ?? viewModel.backgroundColor - } - ), - supportsOpacity: false - ) - } - } - } - } - } - - private var executeSection: some View { - Section { - Button { - save(openAutomationEditor: true) - } label: { - Label( - L10n.ActionsConfigurator.Action.createAutomation, - systemSymbol: .arrowUpForwardSquare - ) - } - .disabled(!isValid) - } header: { - Text(L10n.ActionsConfigurator.Action.title) - } footer: { - Text(L10n.ActionsConfigurator.Action.footer) - } - } - - // MARK: - Save - - private func save(openAutomationEditor: Bool) { - guard isValid else { return } - onSave(viewModel.buildAction(), openAutomationEditor) - dismiss() - } -} - -// MARK: - Helpers - -private struct LabeledField: View { - let title: String - @ViewBuilder var content: () -> Content - - var body: some View { - HStack { - Text(title) - Spacer() - content() - } - } -} - -/// SwiftUI replacement for the Eureka `ServerSelectRow` that writes the chosen -/// server identifier into a binding. -struct ActionServerPicker: View { - @Binding var selectedServerId: String - let isDisabled: Bool - - @StateObject private var observer = ServersObserver() - - var body: some View { - Picker(L10n.Settings.ServerSelect.title, selection: $selectedServerId) { - ForEach(observer.servers, id: \.identifier) { server in - Text(server.info.name) - .tag(server.identifier.rawValue) - } - } - .pickerStyle(.menu) - .disabled(isDisabled) - } -} - -// MARK: - Widget Preview - -struct WidgetPreviewView: View { - @ObservedObject var viewModel: ActionConfiguratorViewModel - - var body: some View { - VStack { - WidgetBasicButtonView( - model: .init( - id: UUID().uuidString, - title: viewModel.text, - subtitle: nil, - interactionType: .widgetURL(URL(string: "homeassistant://perform_action")!), - icon: MaterialDesignIcons(named: viewModel.iconName), - textColor: Color(hex: viewModel.textColor), - iconColor: Color(hex: viewModel.iconColor), - backgroundColor: Color(hex: viewModel.backgroundColor), - useCustomColors: viewModel.useCustomColors - ), - sizeStyle: .compact, - tinted: false - ) - .padding() - .frame(width: 340, height: 100) - } - .frame(maxWidth: .infinity) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) - } -} - -// MARK: - Previews - -#Preview { - NavigationView { - ActionConfiguratorView(action: nil, onSave: { _, _ in }) - } -} diff --git a/Sources/App/Settings/Settings/ActionsSettingsView.swift b/Sources/App/Settings/Settings/ActionsSettingsView.swift deleted file mode 100644 index 2f41274b6c..0000000000 --- a/Sources/App/Settings/Settings/ActionsSettingsView.swift +++ /dev/null @@ -1,262 +0,0 @@ -import PromiseKit -import RealmSwift -import Shared -import SwiftUI - -/// SwiftUI replacement for the legacy Eureka "Actions" settings screen. -/// -/// Displays: -/// - A disclaimer about legacy actions. -/// - The list of locally-created actions (reorderable / deletable). -/// - Scene actions (toggle + customize navigation). -/// - Server-controlled actions (read-only). -/// - A button to refresh server actions. -struct ActionsSettingsView: View { - @StateObject private var viewModel = ActionsSettingsViewModel() - - @State private var editingAction: Action? - - var body: some View { - List { - disclaimerSection - localActionsSection - serverActionsSection - serverUpdateSection - } - .listStyle(.insetGrouped) - .navigationTitle(L10n.SettingsDetails.LegacyActions.title) - .sheet(item: $editingAction) { action in - ActionEditorSheet(action: action, isNew: false) { updated, openAutomationEditor in - handleSheetSave(updated: updated, openAutomationEditor: openAutomationEditor) - } - } - } - - private var hasAnyEditableActions: Bool { - !viewModel.localActions.isEmpty - } - - // MARK: - Sections - - private var disclaimerSection: some View { - Section { - Text(L10n.LegacyActions.disclaimer) - .font(.footnote) - .foregroundStyle(.secondary) - } - } - - @ViewBuilder - private var localActionsSection: some View { - Section { - if !viewModel.localActions.isEmpty { - ForEach(viewModel.localActions) { snapshot in - Button { - if let loaded = viewModel.loadAction(id: snapshot.actionID) { - editingAction = loaded - } - } label: { - ActionRowView(snapshot: snapshot) - } - .buttonStyle(.plain) - } - .onDelete { indexSet in - viewModel.deleteLocalActions(at: indexSet) - } - .onMove { source, destination in - viewModel.moveLocalActions(from: source, to: destination) - } - } - } - } - - @ViewBuilder - private var serverActionsSection: some View { - Section { - if viewModel.serverActions.isEmpty { - Text(L10n.SettingsDetails.Actions.ActionsSynced.empty) - .foregroundStyle(.secondary) - } else { - ForEach(viewModel.serverActions) { snapshot in - Button { - if let loaded = viewModel.loadAction(id: snapshot.actionID) { - editingAction = loaded - } - } label: { - ActionRowView(snapshot: snapshot) - } - .buttonStyle(.plain) - } - } - } header: { - Text(L10n.SettingsDetails.Actions.ActionsSynced.header) - } footer: { - if viewModel.serverActions.isEmpty { - Text(L10n.SettingsDetails.Actions.ActionsSynced.footerNoActions) - } else { - Text(L10n.SettingsDetails.Actions.ActionsSynced.footer) - } - } - } - - private var serverUpdateSection: some View { - Section { - Button { - viewModel.refreshServerActions() - } label: { - HStack { - Text(L10n.SettingsDetails.Actions.ServerControlled.Update.title) - Spacer() - if viewModel.isRefreshing { - ProgressView() - } - } - } - .disabled(viewModel.isRefreshing) - } - } - - /// Mirrors the legacy `ActionConfigurator` "Create Automation" path: server-controlled - /// actions should never be persisted from this sheet (they're owned by the server), - /// even though the same callback funnels both Save and Create-Automation actions. - private func handleSheetSave(updated: Action, openAutomationEditor: Bool) { - if openAutomationEditor, updated.isServerControlled { - // Don't write back; just open the automation editor. - } else { - viewModel.save(action: updated) - } - if openAutomationEditor { - openAutomationEditorIfAvailable(for: updated) - } - } - - private func openAutomationEditorIfAvailable(for action: Action) { - Current.sceneManager.webViewWindowControllerPromise - .then(\.webViewControllerPromise) - .done { controller in - controller.openActionAutomationEditor(actionId: action.ID) - }.cauterize() - } -} - -// MARK: - Row views - -private struct ActionRowView: View { - let snapshot: ActionRowSnapshot - - var body: some View { - HStack(spacing: DesignSystem.Spaces.two) { - Image( - uiImage: MaterialDesignIcons(named: snapshot.iconName) - .image(ofSize: MaterialDesignIcons.settingsIconSize, color: .label) - ) - .renderingMode(.template) - VStack(alignment: .leading, spacing: 2) { - Text(snapshot.name) - .foregroundStyle(.primary) - Text(snapshot.text.isEmpty ? L10n.ActionsConfigurator.Rows.Text.title : snapshot.text) - .font(.footnote) - .foregroundStyle(.secondary) - } - Spacer() - Image(systemSymbol: .chevronRight) - .font(.caption) - .foregroundStyle(.tertiary) - } - .contentShape(Rectangle()) - } -} - -private struct SceneActionRowView: View { - let scene: ActionsSceneSnapshot - let onToggle: (Bool) -> Void - let onCustomize: () -> Void - - @State private var isEnabled: Bool - - init(scene: ActionsSceneSnapshot, onToggle: @escaping (Bool) -> Void, onCustomize: @escaping () -> Void) { - self.scene = scene - self.onToggle = onToggle - self.onCustomize = onCustomize - self._isEnabled = State(initialValue: scene.actionEnabled) - } - - var body: some View { - VStack(alignment: .leading, spacing: DesignSystem.Spaces.one) { - Toggle(isOn: Binding( - get: { isEnabled }, - set: { newValue in - isEnabled = newValue - onToggle(newValue) - } - )) { - HStack(spacing: DesignSystem.Spaces.two) { - if let iconName = scene.icon { - let icon = MaterialDesignIcons(serversideValueNamed: iconName) - Image(uiImage: icon.image( - ofSize: MaterialDesignIcons.settingsIconSize, - color: .label - )) - .renderingMode(.template) - } - Text(scene.name ?? scene.identifier) - } - } - if isEnabled { - Button(L10n.SettingsDetails.Actions.Scenes.customizeAction) { - onCustomize() - } - .font(.footnote) - } - } - // Keep local @State in sync with the upstream Realm-backed snapshot, otherwise - // an external change to `actionEnabled` (sync, another screen, etc.) would not - // update the toggle while the row is on screen. - .onChange(of: scene.actionEnabled) { newValue in - if newValue != isEnabled { - isEnabled = newValue - } - } - } -} - -// MARK: - Sheet wrapper - -private struct ActionEditorSheet: View { - @Environment(\.dismiss) private var dismiss - let action: Action - let isNew: Bool - let onSave: (Action, _ openAutomationEditor: Bool) -> Void - - var body: some View { - NavigationView { - ActionConfiguratorView(action: isNew ? nil : action) { updated, openAutomationEditor in - onSave(updated, openAutomationEditor) - } - .toolbar { - CloseButton { - dismiss() - } - } - } - .navigationViewStyle(.stack) - } -} - -// MARK: - Action Identifiable - -extension Action: @retroactive Identifiable { - // The compiler synthesises `typealias ID = String` from the `Identifiable` - // conformance, which lives on `Action` at module scope and shadows the - // stored `ID` property in *any* extension. The only way to disambiguate is - // `self.ID`. SwiftFormat's `--self init-only` rule strips that, so disable - // `redundantSelf` for the one line that needs the qualification. - // swiftformat:disable:next redundantSelf - public var id: String { self.ID } -} - -#Preview { - NavigationView { - ActionsSettingsView() - } -} diff --git a/Sources/App/Settings/Settings/ActionsSettingsViewModel.swift b/Sources/App/Settings/Settings/ActionsSettingsViewModel.swift deleted file mode 100644 index 3d11496df7..0000000000 --- a/Sources/App/Settings/Settings/ActionsSettingsViewModel.swift +++ /dev/null @@ -1,188 +0,0 @@ -import Foundation -import PromiseKit -import RealmSwift -import Shared - -/// Value-type snapshot of a scene for safe SwiftUI rendering. -struct ActionsSceneSnapshot: Identifiable, Equatable { - var id: String { identifier } - let identifier: String - let name: String? - let icon: String? - let actionEnabled: Bool -} - -/// Value-type snapshot of an `Action` row for safe SwiftUI rendering. -struct ActionRowSnapshot: Identifiable, Equatable { - var id: String { actionID } - let actionID: String - let name: String - let text: String - let iconName: String -} - -/// View model for `ActionsSettingsView`. -/// -/// Wraps Realm observation for `Action` and `RLMScene` results and exposes them -/// as `@Published` snapshot arrays so the SwiftUI view does not touch Realm -/// directly from its body. -final class ActionsSettingsViewModel: ObservableObject { - @Published private(set) var localActions: [ActionRowSnapshot] = [] - @Published private(set) var serverActions: [ActionRowSnapshot] = [] - @Published private(set) var scenes: [ActionsSceneSnapshot] = [] - @Published private(set) var isRefreshing: Bool = false - - private let realm: Realm - private var actionsToken: NotificationToken? - private var scenesToken: NotificationToken? - - init() { - self.realm = Current.realm() - setupObservers() - } - - deinit { - actionsToken?.invalidate() - scenesToken?.invalidate() - } - - // MARK: - Observation - - private func setupObservers() { - let actions = realm.objects(Action.self) - .sorted(byKeyPath: "Position") - .filter("Scene == nil") - - actionsToken = actions.observe { [weak self] _ in - self?.refresh(actions: actions) - } - refresh(actions: actions) - - let scenes = realm.objects(RLMScene.self) - .sorted(byKeyPath: RLMScene.positionKeyPath) - scenesToken = scenes.observe { [weak self] _ in - self?.refresh(scenes: scenes) - } - refresh(scenes: scenes) - } - - private func refresh(actions: Results) { - localActions = actions.filter("isServerControlled == false").map(Self.snapshot(from:)) - serverActions = actions.filter("isServerControlled == true").map(Self.snapshot(from:)) - } - - private static func snapshot(from action: Action) -> ActionRowSnapshot { - ActionRowSnapshot( - actionID: action.ID, - name: action.Name, - text: action.Text, - iconName: action.IconName - ) - } - - /// Loads an unmanaged copy of the managed `Action` with the given identifier, or - /// `nil` if it no longer exists (deleted concurrently). - func loadAction(id: String) -> Action? { - guard let stored = realm.object(ofType: Action.self, forPrimaryKey: id) else { return nil } - return Action(value: stored) - } - - private func refresh(scenes: Results) { - self.scenes = scenes.map { - ActionsSceneSnapshot( - identifier: $0.identifier, - name: $0.name, - icon: $0.icon, - actionEnabled: $0.actionEnabled - ) - } - } - - // MARK: - Mutations - - func save(action: Action) { - // For brand-new local actions (no managed copy yet) assign a position at the end of - // the current local list. Without this, the default `Position = 0` makes the new - // action sort to the top of the manual section once Realm publishes the change. - let isNewLocal = action.Scene == nil - && !action.isServerControlled - && realm.object(ofType: Action.self, forPrimaryKey: action.ID) == nil - if isNewLocal { - action.Position = Action.PositionOffset.manual.rawValue + localActions.count - } - realm.reentrantWrite { [realm] in - realm.add(action, update: .all) - }.done { [weak self] in - self?.updatePositions() - }.cauterize() - } - - func deleteLocalActions(at offsets: IndexSet) { - let ids = offsets.compactMap { index -> String? in - guard index < localActions.count else { return nil } - return localActions[index].actionID - } - guard !ids.isEmpty else { return } - realm.reentrantWrite { [realm] in - realm.delete(realm.objects(Action.self).filter("ID IN %@", ids)) - }.cauterize() - } - - func moveLocalActions(from source: IndexSet, to destination: Int) { - var reordered = localActions - reordered.move(fromOffsets: source, toOffset: destination) - localActions = reordered - - let ids = reordered.map(\.actionID) - realm.reentrantWrite { [realm] in - let stored = realm.objects(Action.self).filter("ID IN %@", ids) - for action in stored { - guard let newIndex = ids.firstIndex(of: action.ID) else { continue } - action.Position = Action.PositionOffset.manual.rawValue + newIndex - } - }.cauterize() - } - - func setSceneEnabled(_ sceneId: String, enabled: Bool) { - realm.reentrantWrite { [realm] in - guard let scene = realm.object(ofType: RLMScene.self, forPrimaryKey: sceneId) else { return } - scene.actionEnabled = enabled - }.cauterize() - } - - func firstAction(forSceneId sceneId: String) -> Action? { - guard let scene = realm.object(ofType: RLMScene.self, forPrimaryKey: sceneId) else { - return nil - } - return scene.actions.first.map { Action(value: $0) } - } - - func refreshServerActions() { - isRefreshing = true - let result = Current.modelManager.fetch() - result.pipe { [weak self] result in - DispatchQueue.main.async { - self?.isRefreshing = false - } - switch result { - case .fulfilled: - break - case let .rejected(error): - Current.Log.error("Failed to manually update server Actions: \(error.localizedDescription)") - } - } - } - - // MARK: - Helpers - - private func updatePositions() { - let ids = localActions.map(\.actionID) - realm.reentrantWrite { [realm] in - let stored = realm.objects(Action.self).filter("ID IN %@", ids) - for action in stored { - guard let newIndex = ids.firstIndex(of: action.ID) else { continue } - action.Position = Action.PositionOffset.manual.rawValue + newIndex - } - }.cauterize() - } -} diff --git a/Sources/App/Settings/Settings/SettingsItem.swift b/Sources/App/Settings/Settings/SettingsItem.swift index e26e3fffac..481edeed81 100644 --- a/Sources/App/Settings/Settings/SettingsItem.swift +++ b/Sources/App/Settings/Settings/SettingsItem.swift @@ -16,7 +16,6 @@ enum SettingsItem: String, Hashable, CaseIterable { case watch case carPlay case complications - case actions case help case privacy case debugging @@ -38,7 +37,6 @@ enum SettingsItem: String, Hashable, CaseIterable { case .watch: return L10n.Settings.DetailsSection.WatchRowConfiguration.title case .carPlay: return "CarPlay" case .complications: return L10n.Settings.DetailsSection.WatchRowComplications.title - case .actions: return L10n.SettingsDetails.LegacyActions.title case .help: return L10n.helpLabel case .privacy: return L10n.SettingsDetails.Privacy.title case .debugging: return L10n.Settings.Debugging.title @@ -77,8 +75,6 @@ enum SettingsItem: String, Hashable, CaseIterable { MaterialDesignIconsImage(icon: .carBackIcon, size: 24) case .complications: MaterialDesignIconsImage(icon: .chartDonutIcon, size: 24) - case .actions: - MaterialDesignIconsImage(icon: .gamepadVariantOutlineIcon, size: 24) case .help: MaterialDesignIconsImage(icon: .helpCircleOutlineIcon, size: 24) case .privacy: @@ -138,8 +134,6 @@ enum SettingsItem: String, Hashable, CaseIterable { CarPlayConfigurationView(needsNavigationController: false) case .complications: SettingsComplicationsView() - case .actions: - ActionsSettingsView() case .help: EmptyView() case .privacy: @@ -186,10 +180,6 @@ enum SettingsItem: String, Hashable, CaseIterable { [.carPlay] } - static var legacyItems: [SettingsItem] { - [.actions] - } - static var helpItems: [SettingsItem] { [.help, .privacy, .debugging] } diff --git a/Sources/App/Settings/Settings/SettingsView.swift b/Sources/App/Settings/Settings/SettingsView.swift index 17c4ff67b8..4c670fb801 100644 --- a/Sources/App/Settings/Settings/SettingsView.swift +++ b/Sources/App/Settings/Settings/SettingsView.swift @@ -165,15 +165,6 @@ struct SettingsView: View { } } - // Legacy section - Section { - ForEach(SettingsItem.legacyItems, id: \.self) { item in - NavigationLink(destination: item.destinationView) { - settingsItemLabel(item) - } - } - } - // Help section Section { ForEach(SettingsItem.helpItems, id: \.self) { item in diff --git a/Sources/App/Utilities/MenuManager.swift b/Sources/App/Utilities/MenuManager.swift index 2b9f14e2c1..299ce8b564 100644 --- a/Sources/App/Utilities/MenuManager.swift +++ b/Sources/App/Utilities/MenuManager.swift @@ -6,8 +6,6 @@ import Shared import UIKit private extension UIMenu.Identifier { - static var haActions: Self { .init(rawValue: "ha.actions") } - static var haActionsConfigure: Self { .init(rawValue: "ha.actions.configure") } static var haHelp: Self { .init(rawValue: "ha.help") } static var haWebViewActions: Self { .init(rawValue: "ha.webViewActions") } static var haFile: Self { .init(rawValue: "ha.file") } @@ -123,13 +121,11 @@ enum StatusItemTitleRenderer { class MenuManager { let builder: UIMenuBuilder - let actionsWithImages: [(Action, UIImage)] // remember: this class is short-lived. it only exists for the duration of creating the menu. init(builder: UIMenuBuilder) { self.builder = builder - self.actionsWithImages = Self.actionsWithImages() update() } @@ -200,12 +196,6 @@ class MenuManager { builder.replaceChildren(ofMenu: .help) { _ in helpMenus() } - if builder.menu(for: .haActions) == nil { - builder.insertSibling(actionsMenu(), beforeMenu: .window) - } else { - builder.replace(menu: .haActions, with: actionsMenu()) - } - if builder.menu(for: .haWebViewActions) == nil { builder.insertSibling(webViewActionsMenu(), beforeMenu: .fullscreen) } else { @@ -326,92 +316,6 @@ class MenuManager { ] } - private static func actionsWithImages() -> [(Action, UIImage)] { - // Action+Observation calls reload, so when they change this all gets run again - Current.realm() - .objects(Action.self) - .sorted(byKeyPath: #keyPath(Action.Position)) - .map { action -> (Action, UIImage) in - let iconRect = CGRect(x: 0, y: 0, width: 28, height: 28) - - let image = UIKit.UIGraphicsImageRenderer(size: iconRect.size).image { _ in - let imageRect = iconRect.insetBy(dx: 3, dy: 3) - - UIColor(hex: action.BackgroundColor).set() - UIBezierPath(roundedRect: iconRect, cornerRadius: 6.0).fill() - - MaterialDesignIcons(named: action.IconName) - .image(ofSize: imageRect.size, color: UIColor(hex: action.IconColor)) - .draw(in: imageRect) - } - - return (action, image) - } - } - - private func actionsMenu() -> UIMenu { - let children = actionsWithImages.map { action, image in - UICommand( - title: action.Text, - image: image, - action: #selector(AppDelegate.openMenuUrl(_:)), - propertyList: Self.propertyList(for: action.widgetLinkURL) - ) - } + [ - UIMenu(title: "", image: nil, identifier: .haActionsConfigure, options: [.displayInline], children: [ - UICommand( - title: L10n.Menu.Actions.configure, - image: nil, - action: #selector(AppDelegate.openActionsPreferences), - propertyList: nil - ), - ]), - ] - - return UIMenu( - title: L10n.Menu.Actions.title, - image: nil, - identifier: .haActions, - children: Array(children) - ) - } - - private func actionsMenu() -> AppMacBridgeStatusItemMenuItem { - var items = [AppMacBridgeStatusItemMenuItem]() - items.append(contentsOf: actionsWithImages.compactMap { action, image in - let url = action.widgetLinkURL - - return .init( - name: action.Name, - image: image - ) { callbackInfo in - callbackInfo.activate() - - let delegate: Guarantee = Current.sceneManager.scene( - for: .init(activity: .webView) - ) - delegate.done { - $0.urlHandler?.handle(url: url) - } - } - }) - if !items.isEmpty { - items.append(.separator()) - } - items.append(.init(name: L10n.Menu.Actions.configure) { callbackInfo in - callbackInfo.activate() - - UIApplication.shared.sendAction( - #selector(AppDelegate.openActionsPreferences), - to: UIApplication.shared.delegate, - from: nil, - for: nil - ) - }) - - return AppMacBridgeStatusItemMenuItem(name: L10n.Menu.Actions.title, subitems: items) - } - private func webViewActionsMenu() -> UIMenu { var commands: [UIMenuElement] = [ UIKeyCommand( @@ -493,8 +397,6 @@ class MenuManager { var menuItems = [AppMacBridgeStatusItemMenuItem]() menuItems.append(toggleMenu()) menuItems.append(.separator()) - menuItems.append(actionsMenu()) - menuItems.append(.separator()) menuItems.append(contentsOf: aboutMenu()) menuItems.append(preferencesMenu()) menuItems.append(quitMenu()) diff --git a/Sources/CarPlay/Templates/Entities/CarPlayEntityListItem.swift b/Sources/CarPlay/Templates/Entities/CarPlayEntityListItem.swift index 030e73fa7e..e7bed5c49f 100644 --- a/Sources/CarPlay/Templates/Entities/CarPlayEntityListItem.swift +++ b/Sources/CarPlay/Templates/Entities/CarPlayEntityListItem.swift @@ -4,7 +4,7 @@ import HAKit import Shared final class CarPlayEntityListItem: CarPlayListItemProvider { - static let executingSubtitle = L10n.CarPlay.Action.Execute.inProgress + static let executingSubtitle = L10n.CarPlay.QuickAccess.Execute.inProgress private static let minimumExecutingDuration: TimeInterval = 1.5 private struct DisplayContent { diff --git a/Sources/CarPlay/Templates/QuickAccess/CarPlayQuickAccessTemplate.swift b/Sources/CarPlay/Templates/QuickAccess/CarPlayQuickAccessTemplate.swift index 2b7c70fc9b..dc446daac0 100644 --- a/Sources/CarPlay/Templates/QuickAccess/CarPlayQuickAccessTemplate.swift +++ b/Sources/CarPlay/Templates/QuickAccess/CarPlayQuickAccessTemplate.swift @@ -47,7 +47,7 @@ final class CarPlayQuickAccessTemplate: CarPlayTemplateProvider { private lazy var introduceQuickAccessListItem: CPListItem = { let item = CPListItem( text: L10n.CarPlay.QuickAccess.Intro.Item.title, - detailText: L10n.CarPlay.Action.Intro.Item.body, + detailText: L10n.CarPlay.QuickAccess.Intro.Item.body, image: MaterialDesignIcons.homeLightningBoltIcon .carPlayIcon() ) diff --git a/Sources/CarPlay/Templates/QuickAccess/CarPlayQuickAccessViewModel.swift b/Sources/CarPlay/Templates/QuickAccess/CarPlayQuickAccessViewModel.swift index 019a14f509..ecd380ce89 100644 --- a/Sources/CarPlay/Templates/QuickAccess/CarPlayQuickAccessViewModel.swift +++ b/Sources/CarPlay/Templates/QuickAccess/CarPlayQuickAccessViewModel.swift @@ -25,10 +25,11 @@ final class CarPlayQuickAccessViewModel { } private func filterItems(_ items: [MagicItem]) -> [MagicItem] { + let supportedItems = items.filter { $0.type != .unsupported } if #available(iOS 26.4, *) { - return items + return supportedItems } else { - return items.filter { $0.type != .assistPipeline && $0.type != .assistPrompt } + return supportedItems.filter { $0.type != .assistPipeline && $0.type != .assistPrompt } } } diff --git a/Sources/Extensions/AppIntents/Action/IntentActionAppEntity.swift b/Sources/Extensions/AppIntents/Action/IntentActionAppEntity.swift deleted file mode 100644 index 2c5bfdae87..0000000000 --- a/Sources/Extensions/AppIntents/Action/IntentActionAppEntity.swift +++ /dev/null @@ -1,40 +0,0 @@ -import AppIntents -import Foundation -import Shared - -@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) -struct IntentActionAppEntity: AppEntity { - static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Action") - - struct IntentActionAppEntityQuery: EntityQuery, EntityStringQuery { - func entities(for identifiers: [IntentActionAppEntity.ID]) async throws -> [IntentActionAppEntity] { - getActionEntities().filter { identifiers.contains($0.id) } - } - - func entities(matching string: String) async throws -> [IntentActionAppEntity] { - getActionEntities().filter { $0.displayString.contains(string) } - } - - func suggestedEntities() async throws -> [IntentActionAppEntity] { - getActionEntities() - } - - private func getActionEntities() -> [IntentActionAppEntity] { - let actions = Current.realm().objects(Action.self).sorted(byKeyPath: #keyPath(Action.Position)) - return Array(actions.map { IntentActionAppEntity(id: $0.ID, displayString: $0.Name) }) - } - } - - static let defaultQuery = IntentActionAppEntityQuery() - - var id: String - var displayString: String - var displayRepresentation: DisplayRepresentation { - DisplayRepresentation(title: "\(displayString)") - } - - init(id: String, displayString: String) { - self.id = id - self.displayString = displayString - } -} diff --git a/Sources/Extensions/AppIntents/Action/PerformAction.swift b/Sources/Extensions/AppIntents/Action/PerformAction.swift deleted file mode 100644 index 591adcbd6c..0000000000 --- a/Sources/Extensions/AppIntents/Action/PerformAction.swift +++ /dev/null @@ -1,95 +0,0 @@ -import AppIntents -import Foundation -import PromiseKit -import Shared - -@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) -struct PerformAction: AppIntent, CustomIntentMigratedAppIntent, PredictableIntent { - static let intentClassName = "PerformActionIntent" - - static let title: LocalizedStringResource = "Perform Action" - static let description = IntentDescription("Performs an action defined in the app") - - @Parameter(title: "Action") - var action: IntentActionAppEntity? - - static var parameterSummary: some ParameterSummary { - Summary("Perform \(\.$action)") - } - - static var predictionConfiguration: some IntentPredictionConfiguration { - IntentPrediction(parameters: \.$action) { action in - DisplayRepresentation( - title: "\(action ?? .init(id: "-1", displayString: "Uknown action"))", - subtitle: "Perform the action" - ) - } - } - - @Parameter( - title: LocalizedStringResource( - "app_intents.scripts.haptic_confirmation.title", - defaultValue: "Haptic confirmation" - ), - default: false - ) - var hapticConfirmation: Bool - - func perform() async throws -> some IntentResult { - await Current.connectivity.syncNetworkInformation() - guard let intentAction = $action.wrappedValue, - let action = Current.realm().object(ofType: Action.self, forPrimaryKey: intentAction.id), - let server = Current.servers.server(for: action), - let api = Current.api(for: server) else { - Current.Log.warning("ActionID either does not exist or is not a string in the payload") - return .result() - } - - if hapticConfirmation { - AppIntentHaptics.notify() - } - - try await withCheckedThrowingContinuation { continuation in - api.HandleAction(actionID: action.ID, source: .AppShortcut).pipe { result in - switch result { - case .fulfilled: - continuation.resume() - case let .rejected(error): - Current.Log - .error( - "Failed to run action \(intentAction.displayString), error: \(error.localizedDescription)" - ) - continuation.resume(throwing: error) - } - } - } - - return .result() - } -} - -@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) -private extension IntentDialog { - static func actionParameterDisambiguationIntro(count: Int, action: IntentActionAppEntity) -> Self { - .init(stringLiteral: L10n.AppIntents.PerformAction.actionParameterDisambiguationIntro( - count, - action.displayString - )) - } - - static func actionParameterConfirmation(action: IntentActionAppEntity) -> Self { - .init(stringLiteral: L10n.AppIntents.PerformAction.actionParameterConfirmation(action.displayString)) - } - - static var actionParameterConfiguration: Self { - .init(stringLiteral: L10n.AppIntents.PerformAction.actionParameterConfiguration) - } - - static func responseSuccess(action: IntentActionAppEntity) -> Self { - .init(stringLiteral: L10n.AppIntents.PerformAction.responseSuccess) - } - - static func responseFailure(error: String) -> Self { - .init(stringLiteral: L10n.AppIntents.PerformAction.responseFailure(error)) - } -} diff --git a/Sources/Extensions/AppIntents/Widget/Actions/WidgetActionsAppIntentTimelineProvider.swift b/Sources/Extensions/AppIntents/Widget/Actions/WidgetActionsAppIntentTimelineProvider.swift deleted file mode 100644 index 0e436d5b6a..0000000000 --- a/Sources/Extensions/AppIntents/Widget/Actions/WidgetActionsAppIntentTimelineProvider.swift +++ /dev/null @@ -1,134 +0,0 @@ -import AppIntents -import RealmSwift -import Shared -import WidgetKit - -@available(iOS 17, *) -struct WidgetActionsAppIntentTimelineProvider: AppIntentTimelineProvider { - typealias Entry = WidgetActionsEntry - typealias Intent = WidgetActionsAppIntent - - func snapshot(for configuration: WidgetActionsAppIntent, in context: Context) async -> WidgetActionsEntry { - await withCheckedContinuation({ continuation in - Self.entry(for: configuration, in: context) { entries in - continuation.resume(returning: entries) - } - }) - } - - func timeline(for configuration: Intent, in context: Context) async -> Timeline { - let entries = await withCheckedContinuation({ continuation in - Self.entry(for: configuration, in: context) { entries in - continuation.resume(returning: entries) - } - }) - return .init( - entries: [entries], - policy: .after( - Current.date() - .addingTimeInterval(WidgetActionsDataSource.expiration.converted(to: .seconds).value) - ) - ) - } - - func placeholder(in context: Context) -> WidgetActionsEntry { - let count = WidgetFamilySizes.size(for: context.family) - let actions = stride(from: 0, to: count, by: 1).map { _ in - with(Action()) { - $0.Text = "Redacted Text" - $0.IconName = MaterialDesignIcons.bedEmptyIcon.name - } - } - - return WidgetActionsEntry(actions: actions) - } - - private static func entry(for configuration: Intent, in context: Context, completion: @escaping (Entry) -> Void) { - if !(configuration.actions?.isEmpty ?? true) { - var actions: [Action?] = [] - var intentActionCheckCount = 0 - configuration.actions?.forEach({ intentAction in - intentAction.asAction { action in - actions.append(action) - intentActionCheckCount += 1 - if intentActionCheckCount == (configuration.actions?.count ?? 0) { - completion(.init(actions: actions.compactMap({ $0 }))) - } - } - }) - } else { - defaultActions(in: context) { actions in - completion(.init(actions: actions)) - } - } - } - - private static func defaultActions(in context: Context, completion: @escaping ([Action]) -> Void) { - WidgetActionsDataSource.actions { allActions in - let maxCount = WidgetFamilySizes.size(for: context.family) - switch allActions.count { - case 0: - completion([]) - case ...maxCount: - completion(Array(allActions)) - default: - completion(Array(allActions[0 ..< maxCount])) - } - } - } -} - -@available(iOS 17, *) -extension IntentActionAppEntity { - func asAction(completion: @escaping (Action?) -> Void) { - guard id.isEmpty == false else { - completion(nil) - return - } - func getAction() -> Action? { - Current.realm(objectTypes: [Action.self, RLMScene.self]).object( - ofType: Action.self, - forPrimaryKey: id - ) - } - - /* - Workaround for iOS 18, - 'Realm accessed from incorrect thread.' if not called from main thread - while in iOS 17 it reports the same error if called in the main thread - */ - if #available(iOS 18, *) { - DispatchQueue.main.async { - completion(getAction()) - } - } else { - completion(getAction()) - } - } -} - -enum WidgetActionsDataSource { - static var expiration: Measurement { - .init(value: 24, unit: .hours) - } - - static func getActions() -> Results { - Current.realm(objectTypes: [Action.self, RLMScene.self]).objects(Action.self) - .sorted(byKeyPath: #keyPath(Action.Position)) - } - - static func actions(completion: @escaping (Results) -> Void) { - /* - Workaround for iOS 18, - 'Realm accessed from incorrect thread.' if not called from main thread - while in iOS 17 it reports the same error if called in the main thread - */ - if #available(iOS 18, *) { - DispatchQueue.main.async { - completion(getActions()) - } - } else { - completion(getActions()) - } - } -} diff --git a/Sources/Extensions/AppIntents/Widget/Script/WidgetScriptsAppIntent.swift b/Sources/Extensions/AppIntents/Widget/Script/WidgetScriptsAppIntent.swift index 03c92f4158..0494e675b8 100644 --- a/Sources/Extensions/AppIntents/Widget/Script/WidgetScriptsAppIntent.swift +++ b/Sources/Extensions/AppIntents/Widget/Script/WidgetScriptsAppIntent.swift @@ -62,10 +62,3 @@ struct WidgetScriptsAppIntent: AppIntent, WidgetConfigurationIntent { return .result(value: true) } } - -@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) -private extension IntentDialog { - static var actionsParameterConfiguration: Self { - .init(stringLiteral: L10n.AppIntents.WidgetAction.actionsParameterConfiguration) - } -} diff --git a/Sources/Extensions/AppIntents/WidgetActionsAppIntent.swift b/Sources/Extensions/AppIntents/WidgetActionsAppIntent.swift deleted file mode 100644 index 15a1f4120b..0000000000 --- a/Sources/Extensions/AppIntents/WidgetActionsAppIntent.swift +++ /dev/null @@ -1,52 +0,0 @@ -import AppIntents -import Foundation -import Shared - -@available(iOS 17.0, macOS 14.0, watchOS 10.0, *) -struct WidgetActionsAppIntent: AppIntent, WidgetConfigurationIntent, CustomIntentMigratedAppIntent { - static let intentClassName = "WidgetActionsIntent" - - static let title: LocalizedStringResource = .init("widgets.actions.title", defaultValue: "Actions") - static let description = IntentDescription( - .init("widgets.actions.description", defaultValue: "Perform Home Assistant actions.") - ) - - // ATTENTION: Unfortunately these sizes below can't be retrieved dynamically from widget family sizes. - // Check ``WidgetFamilySizes.swift`` as source of truth - @Parameter( - title: .init("widgets.actions.parameters.action", defaultValue: "Action"), - size: [ - .systemSmall: 3, - .systemMedium: 6, - .systemLarge: 12, - .systemExtraLarge: 20, - .accessoryInline: 1, - .accessoryCorner: 1, - .accessoryCircular: 1, - .accessoryRectangular: 2, - ] - ) - var actions: [IntentActionAppEntity]? - - static var parameterSummary: some ParameterSummary { - Summary() - } - - func perform() async throws -> some IntentResult { - await Current.connectivity.syncNetworkInformation() - guard let actions else { return .result() } - for action in actions { - let intent = PerformAction() - intent.action = action - let _ = try await intent.perform() - } - return .result() - } -} - -@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) -private extension IntentDialog { - static var actionsParameterConfiguration: Self { - .init(stringLiteral: L10n.AppIntents.WidgetAction.actionsParameterConfiguration) - } -} diff --git a/Sources/Extensions/Intents/Resources/Info.plist b/Sources/Extensions/Intents/Resources/Info.plist index 2cfc390236..d9662e20b9 100644 --- a/Sources/Extensions/Intents/Resources/Info.plist +++ b/Sources/Extensions/Intents/Resources/Info.plist @@ -42,12 +42,10 @@ GetCameraImageIntent INShareFocusStatusIntent OpenPageIntent - PerformActionIntent RenderTemplateIntent ScriptAppIntent SendLocationIntent UpdateSensorsIntent - WidgetActionsIntent WidgetOpenPageIntent diff --git a/Sources/Extensions/Watch/Home/MagicItemRow/WatchMagicViewRow.swift b/Sources/Extensions/Watch/Home/MagicItemRow/WatchMagicViewRow.swift index 8e2325ef55..e0c38ad82d 100644 --- a/Sources/Extensions/Watch/Home/MagicItemRow/WatchMagicViewRow.swift +++ b/Sources/Extensions/Watch/Home/MagicItemRow/WatchMagicViewRow.swift @@ -160,8 +160,8 @@ struct WatchMagicViewRow: View { ) ) WatchMagicViewRow( - item: .init(id: "1", serverId: "1", type: .action), - itemInfo: .init(id: "1", name: "New Action", iconName: "earth") + item: .init(id: "scene.one", serverId: "1", type: .scene), + itemInfo: .init(id: "1", name: "New scene", iconName: "earth") ) } .background(Color.red) diff --git a/Sources/Extensions/Widgets/Actions/WidgetActions.swift b/Sources/Extensions/Widgets/Actions/WidgetActions.swift deleted file mode 100644 index 86ab9705e1..0000000000 --- a/Sources/Extensions/Widgets/Actions/WidgetActions.swift +++ /dev/null @@ -1,95 +0,0 @@ -import AppIntents -import Intents -import Shared -import SwiftUI -import WidgetKit - -@available(iOS 17, *) -struct WidgetActions: Widget { - var body: some WidgetConfiguration { - AppIntentConfiguration( - kind: WidgetsKind.actions.rawValue, - provider: WidgetActionsAppIntentTimelineProvider() - ) { timelineEntry in - WidgetBasicContainerView( - emptyViewGenerator: { - AnyView(WidgetEmptyView(message: L10n.Widgets.Actions.notConfigured)) - }, - contents: timelineEntry.actions.map { action in - WidgetBasicViewModel( - id: action.ID, - title: action.Text, - subtitle: nil, - interactionType: .appIntent(.action(id: action.ID, name: action.Name)), - icon: MaterialDesignIcons(serversideValueNamed: action.IconName), - textColor: .init(hex: action.TextColor), - iconColor: .init(hex: action.IconColor), - backgroundColor: .init(hex: action.BackgroundColor), - useCustomColors: action.useCustomColors - ) - }, - type: .button - ) - } - .contentMarginsDisabledIfAvailable() - .configurationDisplayName(L10n.Widgets.Actions.title) - .description(L10n.Widgets.Actions.description) - .supportedFamilies(WidgetActionSupportedFamilies.families) - } -} - -struct LegacyWidgetActions: Widget { - var body: some WidgetConfiguration { - IntentConfiguration( - kind: WidgetsKind.actions.rawValue, - intent: WidgetActionsIntent.self, - provider: WidgetActionsProvider(), - content: { - WidgetBasicContainerView( - emptyViewGenerator: { - AnyView(WidgetEmptyView(message: L10n.Widgets.Actions.notConfigured)) - }, - contents: $0.actions.map { action in - WidgetBasicViewModel( - id: action.ID, - title: action.Text, - subtitle: nil, - interactionType: .widgetURL(action.widgetLinkURL), - icon: MaterialDesignIcons(serversideValueNamed: action.IconName), - textColor: .init(hex: action.TextColor), - iconColor: .init(hex: action.IconColor), - backgroundColor: .init(hex: action.BackgroundColor), - useCustomColors: action.useCustomColors - ) - }, - type: .button - ) - } - ) - .contentMarginsDisabledIfAvailable() - .configurationDisplayName(L10n.Widgets.Actions.title) - .description(L10n.Widgets.Actions.description) - .supportedFamilies(WidgetActionSupportedFamilies.legacyFamilies) - .onBackgroundURLSessionEvents(matching: nil) { identifier, completion in - Current.webhooks.handleBackground(for: identifier, completionHandler: completion) - } - } -} - -enum WidgetActionSupportedFamilies { - @available(iOS 16.0, *) - static let families: [WidgetFamily] = [ - .systemSmall, - .systemMedium, - .systemLarge, - .systemExtraLarge, - .accessoryCircular, - ] - - static let legacyFamilies: [WidgetFamily] = [ - .systemSmall, - .systemMedium, - .systemLarge, - .systemExtraLarge, - ] -} diff --git a/Sources/Extensions/Widgets/Actions/WidgetActionsProvider.swift b/Sources/Extensions/Widgets/Actions/WidgetActionsProvider.swift deleted file mode 100644 index 7a0fdea466..0000000000 --- a/Sources/Extensions/Widgets/Actions/WidgetActionsProvider.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Shared -import WidgetKit - -struct WidgetActionsEntry: TimelineEntry { - var date = Date() - var actions: [Action] = [] -} - -struct WidgetActionsProvider: IntentTimelineProvider { - typealias Intent = WidgetActionsIntent - typealias Entry = WidgetActionsEntry - - func placeholder(in context: Context) -> WidgetActionsEntry { - let count = WidgetFamilySizes.size(for: context.family) - let actions = stride(from: 0, to: count, by: 1).map { _ in - with(Action()) { - $0.Text = "Redacted Text" - $0.IconName = MaterialDesignIcons.bedEmptyIcon.name - } - } - - return .init(actions: actions) - } - - private static func defaultActions(in context: Context) -> [Action] { - let allActions = WidgetActionsDataSource.getActions() - let maxCount = WidgetFamilySizes.size(for: context.family) - - switch allActions.count { - case 0: return [] - case ...maxCount: return Array(allActions) - default: return Array(allActions[0 ..< maxCount]) - } - } - - private static func entry(for configuration: Intent, in context: Context) -> Entry { - if let existing = configuration.actions?.compactMap({ $0.asAction() }), !existing.isEmpty { - return .init(actions: existing) - } else { - return .init(actions: Self.defaultActions(in: context)) - } - } - - func getSnapshot(for configuration: Intent, in context: Context, completion: @escaping (Entry) -> Void) { - completion(Self.entry(for: configuration, in: context)) - } - - func getTimeline(for configuration: Intent, in context: Context, completion: @escaping (Timeline) -> Void) { - completion(.init(entries: [Self.entry(for: configuration, in: context)], policy: .never)) - } -} diff --git a/Sources/Extensions/Widgets/Common/WidgetBasicView.swift b/Sources/Extensions/Widgets/Common/WidgetBasicView.swift index e37c7cb0cb..7a3ff8cbdc 100644 --- a/Sources/Extensions/Widgets/Common/WidgetBasicView.swift +++ b/Sources/Extensions/Widgets/Common/WidgetBasicView.swift @@ -125,11 +125,6 @@ struct WidgetBasicView: View { return intent } switch widgetIntentType { - case .action: - let intent = PerformAction() - intent.action = IntentActionAppEntity(id: model.id, displayString: model.title) - intent.hapticConfirmation = true - return intent case let .script(id, entityId, serverId, name, showConfirmationNotification): let intent = ScriptAppIntent() intent.script = .init( diff --git a/Sources/Extensions/Widgets/Widgets.swift b/Sources/Extensions/Widgets/Widgets.swift index 10253bd9d0..7277073e62 100644 --- a/Sources/Extensions/Widgets/Widgets.swift +++ b/Sources/Extensions/Widgets/Widgets.swift @@ -27,7 +27,6 @@ struct WidgetsBundleLegacy: WidgetBundle { } #endif WidgetAssist() - LegacyWidgetActions() WidgetOpenPage() } } @@ -51,7 +50,6 @@ struct WidgetsBundle17: WidgetBundle { WidgetTodoList() WidgetGauge() WidgetDetails() - WidgetActions() WidgetOpenPage() WidgetSensors() } @@ -100,7 +98,6 @@ struct WidgetsBundle18: WidgetBundle { WidgetGauge() WidgetDetails() WidgetSensors() - WidgetActions() WidgetOpenPage() } } diff --git a/Sources/Shared/API/HAAPI.swift b/Sources/Shared/API/HAAPI.swift index b3554d1fab..9d77a5aee9 100644 --- a/Sources/Shared/API/HAAPI.swift +++ b/Sources/Shared/API/HAAPI.swift @@ -548,7 +548,7 @@ public class HomeAssistantAPI { public func GetMobileAppConfig() -> Promise { firstly { () -> Promise in - if server.info.version < .actionSyncing { + if server.info.version < .mobileAppConfig { let old: Promise = requestImmutable( path: "ios/push", callingFunctionName: "\(#function)" @@ -754,30 +754,6 @@ public class HomeAssistantAPI { return (eventType: "mobile_app_notification_action", eventData: eventData) } - public func actionEvent( - actionID: String, - actionName: String, - source: AppTriggerSource - ) -> (eventType: String, eventData: [String: String]) { - var eventData = sharedEventDeviceInfo - eventData["actionName"] = actionName - eventData["actionID"] = actionID - eventData["triggerSource"] = source.description - - return (eventType: "ios.action_fired", eventData: eventData) - } - - public func actionScene( - actionID: String, - source: AppTriggerSource - ) -> (serviceDomain: String, serviceName: String, serviceData: [String: String]) { - ( - serviceDomain: Domain.scene.rawValue, - serviceName: Service.turnOn.rawValue, - serviceData: ["entity_id": actionID] - ) - } - public func tagEvent( tagPath: String ) -> (eventType: String, eventData: [String: String]) { @@ -877,37 +853,6 @@ public class HomeAssistantAPI { }).asVoid() } - public func HandleAction(actionID: String, source: AppTriggerSource) -> Promise { - guard let action = Current.realm().object(ofType: Action.self, forPrimaryKey: actionID) else { - Current.Log.error("couldn't find action with id \(actionID)") - return .init(error: HomeAssistantAPI.APIError.cantBuildURL) - } - - let intent = PerformActionIntent(action: action) - INInteraction(intent: intent, response: nil).donate(completion: nil) - - switch action.triggerType { - case .event: - let actionInfo = actionEvent(actionID: action.ID, actionName: action.Name, source: source) - Current.Log.verbose("Sending action: \(actionInfo.eventType) payload: \(actionInfo.eventData)") - - return CreateEvent( - eventType: actionInfo.eventType, - eventData: actionInfo.eventData - ) - case .scene: - let serviceInfo = actionScene(actionID: action.ID, source: source) - Current.Log.verbose("activating scene: \(action.ID)") - - return CallService( - domain: serviceInfo.serviceDomain, - service: serviceInfo.serviceName, - serviceData: serviceInfo.serviceData, - triggerSource: source - ) - } - } - public func executeActionForDomainType(domain: Domain, entityId: String, state: String) -> Promise { var request: HATypedRequest? diff --git a/Sources/Shared/API/Models/Action.swift b/Sources/Shared/API/Models/Action.swift deleted file mode 100644 index 61b656c681..0000000000 --- a/Sources/Shared/API/Models/Action.swift +++ /dev/null @@ -1,262 +0,0 @@ -import Foundation -import ObjectMapper -import RealmSwift -import UIKit - -public final class Action: Object, ImmutableMappable, UpdatableModel { - public enum PositionOffset: Int { - case manual = 0 - case synced = 5000 - case scene = 1_000_000 - } - - @objc public dynamic var ID: String = UUID().uuidString - @objc public dynamic var Name: String = "" - @objc public dynamic var Text: String = "" - @objc public dynamic var IconName: String = MaterialDesignIcons.allCases.randomElement()!.name - @objc public dynamic var BackgroundColor: String - @objc public dynamic var IconColor: String - @objc public dynamic var TextColor: String - @objc public dynamic var Position: Int = 0 - @objc public dynamic var CreatedAt = Date() - @objc public dynamic var Scene: RLMScene? - @objc public dynamic var isServerControlled: Bool = false - @objc public dynamic var serverIdentifier: String = "" - @objc public dynamic var showInCarPlay: Bool = true - @objc public dynamic var showInWatch: Bool = true - @objc public dynamic var useCustomColors: Bool = false - - static func primaryKey(sourceIdentifier: String, serverIdentifier: String) -> String { - #warning("multiserver - primary key duplication") - return sourceIdentifier - } - - override public static func primaryKey() -> String? { - #keyPath(ID) - } - - static func serverIdentifierKey() -> String { - #keyPath(serverIdentifier) - } - - override public required init() { - let background = UIColor.randomBackgroundColor() - self.BackgroundColor = background.hexString() - if background.isLight { - self.TextColor = UIColor.black.hexString() - self.IconColor = UIColor.black.hexString() - } else { - self.TextColor = UIColor.white.hexString() - self.IconColor = UIColor.white.hexString() - } - - super.init() - } - - public func canConfigure(_ keyPath: PartialKeyPath) -> Bool { - if isServerControlled { - return false - } - - switch keyPath { - case \Action.BackgroundColor: - return Scene == nil || Scene?.backgroundColor == nil - case \Action.TextColor: - return Scene == nil || Scene?.textColor == nil - case \Action.IconColor: - return Scene == nil || Scene?.iconColor == nil - case \Action.IconName, - \Action.Name, - \Action.Text: - return Scene == nil - case \Action.serverIdentifier: - return Scene == nil - case \Action.showInCarPlay: - return Scene == nil - case \Action.showInWatch: - return Scene == nil - case \Action.useCustomColors: - return Scene == nil - default: - return true - } - } - - public required init(map: ObjectMapper.Map) throws { - // this is used for watch<->app syncing - self.ID = try map.value("ID") - self.Name = try map.value("Name") - self.Position = try map.value("Position") - self.BackgroundColor = try map.value("BackgroundColor") - self.IconName = try map.value("IconName") - self.IconColor = try map.value("IconColor") - self.Text = try map.value("Text") - self.TextColor = try map.value("TextColor") - self.CreatedAt = try map.value("CreatedAt", using: DateTransform()) - self.isServerControlled = try map.value("isServerControlled") - self.serverIdentifier = try map.value("serverIdentifier") - self.showInCarPlay = try map.value("showInCarPlay") - self.showInWatch = try map.value("showInWatch") - self.useCustomColors = try map.value("useCustomColors") - super.init() - } - - public func mapping(map: ObjectMapper.Map) { - ID >>> map["ID"] - Name >>> map["Name"] - Position >>> map["Position"] - BackgroundColor >>> map["BackgroundColor"] - IconName >>> map["IconName"] - IconColor >>> map["IconColor"] - Text >>> map["Text"] - TextColor >>> map["TextColor"] - CreatedAt >>> (map["CreatedAt"], DateTransform()) - isServerControlled >>> map["isServerControlled"] - serverIdentifier >>> map["serverIdentifier"] - showInCarPlay >>> map["showInCarPlay"] - showInWatch >>> map["showInWatch"] - useCustomColors >>> map["useCustomColors"] - } - - static func didUpdate(objects: [Action], server: Server, realm: Realm) { - for (idx, object) in objects.enumerated() { - object.Position = PositionOffset.synced.rawValue + server.info.sortOrder + idx - } - } - - static func willDelete(objects: [Action], server: Server?, realm: Realm) {} - - static var updateEligiblePredicate: NSPredicate { - .init(format: "isServerControlled == YES") - } - - public func update(with object: MobileAppConfigAction, server: Server, using realm: Realm) -> Bool { - Current.Log.info("Updating server configured Actions") - if self.realm == nil { - ID = object.name - Name = object.name - } else { - precondition(ID == object.name) - precondition(Name == object.name) - } - - isServerControlled = true - serverIdentifier = server.identifier.rawValue - Name = object.name - - if let backgroundColor = object.backgroundColor { - BackgroundColor = backgroundColor - } - - if let iconName = object.iconIcon { - IconName = iconName.normalizingIconString - } else { - let allCases = MaterialDesignIcons.allCases - IconName = allCases[abs(object.name.djb2hash % allCases.count)].name - } - - if let iconColor = object.iconColor { - IconColor = iconColor - } - - if let text = object.labelText { - Text = text - } else { - Text = object.name.replacingOccurrences(of: "_", with: " ").localizedCapitalized - } - - if let textColor = object.labelColor { - TextColor = textColor - } - - if let showInCarPlay = object.showInCarPlay { - self.showInCarPlay = showInCarPlay - } - - if let showInWatch = object.showInWatch { - self.showInWatch = showInWatch - } - - if let useCustomColors = object.useCustomColors { - self.useCustomColors = useCustomColors - } - - return true - } - - public enum TriggerType { - case event - case scene - } - - public var triggerType: TriggerType { - // we don't sync the scene information over to the watch, so checking ID which is synced - if ID.starts(with: "scene.") { - return .scene - } else { - return .event - } - } - - public func exampleTrigger(api: HomeAssistantAPI) -> String { - switch triggerType { - case .event: - let data = api.actionEvent(actionID: ID, actionName: Name, source: .Preview) - let eventDataStrings = data.eventData.map { $0 + ": " + $1 }.sorted() - let sourceStrings = AppTriggerSource.allCases.map(\.description).sorted() - - let indentation = "\n " - - return """ - - platform: event - event_type: \(data.eventType) - event_data: - # source may be one of: - # - \(sourceStrings.joined(separator: indentation + "# - ")) - \(eventDataStrings.joined(separator: indentation)) - """ - case .scene: - let data = api.actionScene(actionID: ID, source: .Preview) - let eventDataStrings = data.serviceData.map { $0 + ": " + $1 }.sorted() - - let indentation = "\n " - - return """ - # you can watch for the scene change - - platform: event - event_type: call_service - event_data: - domain: \(data.serviceDomain) - service: \(data.serviceName) - service_data: - \(eventDataStrings.joined(separator: indentation)) - """ - } - } - - public var widgetLinkURL: URL { - var components = URLComponents() - components.scheme = "homeassistant" - components.host = "perform_action" - components.path = "/" + ID - components.queryItems = [ - .init(name: "source", value: AppTriggerSource.Widget.rawValue), - ] - return components.url! - } -} - -public extension UIColor { - static func randomBackgroundColor() -> UIColor { - // avoiding: - // - super gray (low saturation) - // - super black (low brightness) - // - super white (high brightness) - UIColor( - hue: CGFloat.random(in: 0 ... 1.0), - saturation: CGFloat.random(in: 0.5 ... 1.0), - brightness: CGFloat.random(in: 0.25 ... 0.75), - alpha: 1.0 - ) - } -} diff --git a/Sources/Shared/API/Models/LegacyModelManager.swift b/Sources/Shared/API/Models/LegacyModelManager.swift index 803a776417..3ab3c26f1a 100644 --- a/Sources/Shared/API/Models/LegacyModelManager.swift +++ b/Sources/Shared/API/Models/LegacyModelManager.swift @@ -4,14 +4,14 @@ import PromiseKit import RealmSwift // Legacy manager which was previously used to handle all model updates and cleanup. -// Now it is used just for zones and legacy iOS Actions +// Now it is used just for zones and notification categories. public class LegacyModelManager: ServerObserver { private var notificationTokens = [NotificationToken]() private var hakitTokens = [HACancellable]() private var subscribedSubscriptions = [SubscribeDefinition]() private var cleanupDefinitions = [CleanupDefinition]() - private static var includedDomains: [Domain] = [.zone, .scene, .person] + private static var includedDomains: [Domain] = [.zone, .person] public var workQueue: DispatchQueue = .global(qos: .userInitiated) static var isAppInForeground: () -> Bool = { false } @@ -110,9 +110,7 @@ public class LegacyModelManager: ServerObserver { model: LocationError.self, createdKey: #keyPath(LocationError.CreatedAt) ), - CleanupDefinition(orphansOf: RLMScene.self), CleanupDefinition(orphansOf: RLMZone.self), - CleanupDefinition(orphansOf: Action.self), CleanupDefinition(orphansOf: NotificationCategory.self), CleanupDefinition( orphansOf: WatchComplication.self, @@ -250,7 +248,6 @@ public class LegacyModelManager: ServerObserver { public static let defaults: [Self] = [ .states(domain: "zone", type: RLMZone.self), - .states(domain: "scene", type: RLMScene.self), ] } @@ -287,14 +284,11 @@ public class LegacyModelManager: ServerObserver { public static let defaults: [Self] = [ FetchDefinition(update: { api, queue, manager in api.GetMobileAppConfig().then(on: queue) { - when(fulfilled: [ - manager.store( - type: NotificationCategory.self, - from: api.server, - sourceModels: $0.push.categories - ), - manager.store(type: Action.self, from: api.server, sourceModels: $0.actions), - ]) + manager.store( + type: NotificationCategory.self, + from: api.server, + sourceModels: $0.push.categories + ) } }), ] diff --git a/Sources/Shared/API/Models/RealmScene.swift b/Sources/Shared/API/Models/RealmScene.swift deleted file mode 100644 index 98b53fdff2..0000000000 --- a/Sources/Shared/API/Models/RealmScene.swift +++ /dev/null @@ -1,134 +0,0 @@ -import Foundation -import HAKit -import ObjectMapper -import RealmSwift - -public final class RLMScene: Object, UpdatableModel { - @objc public dynamic var identifier: String = "" - @objc public dynamic var serverIdentifier: String = "" - - @objc private dynamic var backingPosition: Int = 0 - public static var positionKeyPath: String { #keyPath(RLMScene.backingPosition) } - public var position: Int { - get { - backingPosition - } - set { - backingPosition = newValue - actions.forEach { $0.Position = newValue } - } - } - - @objc private dynamic var backingActionEnabled: Bool = false - public var actionEnabled: Bool { - get { - backingActionEnabled - } - set { - precondition(realm?.isInWriteTransaction == true) - guard let realm else { return } - backingActionEnabled = newValue - updateAction(realm: realm) - } - } - - public let actions = LinkingObjects(fromType: Action.self, property: #keyPath(Action.Scene)) - - @objc public dynamic var name: String? - @objc public dynamic var icon: String? - @objc public dynamic var backgroundColor: String? - @objc public dynamic var textColor: String? - @objc public dynamic var iconColor: String? - - public static func primaryKey(sourceIdentifier: String, serverIdentifier: String) -> String { - #warning("multiserver - primary key duplication") - return sourceIdentifier - } - - override public class func primaryKey() -> String? { - #keyPath(identifier) - } - - static func serverIdentifierKey() -> String { - #keyPath(serverIdentifier) - } - - static func didUpdate(objects: [RLMScene], server: Server, realm: Realm) { - let sorted = objects.sorted { lhs, rhs in - let lhsText = lhs.name ?? lhs.identifier - let rhsText = rhs.name ?? rhs.identifier - return lhsText < rhsText - } - - for (idx, object) in sorted.enumerated() { - object.position = Action.PositionOffset.scene.rawValue + server.info.sortOrder + idx - } - } - - static func willDelete(objects: [RLMScene], server: Server?, realm: Realm) { - // also delete our paired actions if they exist - let actions = realm.objects(Action.self).filter("ID in %@", objects.map(\.identifier)) - Current.Log.info("deleting actions \(Array(actions.map(\.ID)))") - realm.delete(actions) - } - - func update(with entity: HAEntity, server: Server, using realm: Realm) -> Bool { - precondition(entity.domain == "scene") - - if self.realm == nil { - identifier = entity.entityId - } else { - precondition(identifier == entity.entityId) - } - - serverIdentifier = server.identifier.rawValue - name = entity.attributes.friendlyName - icon = entity.attributes.icon ?? "mdi:palette" - backgroundColor = entity.attributes["background_color"] as? String - textColor = entity.attributes["text_color"] as? String - iconColor = entity.attributes["icon_color"] as? String - updateAction(realm: realm) - - return true - } - - private func updateAction(realm: Realm) { - guard actionEnabled else { - for action in actions { - realm.delete(action) - } - return - } - - let action = actions.first ?? Action() - if action.realm == nil { - action.ID = identifier - action.BackgroundColor = "#FFFFFF" - action.TextColor = "#000000" - action.IconColor = "#000000" - } else { - precondition(action.ID == identifier) - } - action.serverIdentifier = serverIdentifier - action.IconName = (icon ?? "mdi:alert").normalizingIconString - action.Position = position - action.Name = name ?? identifier - action.Text = name ?? identifier - - if let backgroundColor { - action.BackgroundColor = backgroundColor - } - - if let textColor { - action.TextColor = textColor - } - - if let iconColor { - action.IconColor = iconColor - } - - // we indirectly reference this action, so we _must_ manually persist it - action.Scene = self - realm.add(action, update: .all) - } -} diff --git a/Sources/Shared/API/Responses/MobileAppConfig/MobileAppConfig.swift b/Sources/Shared/API/Responses/MobileAppConfig/MobileAppConfig.swift index 7240314713..022bb17da2 100644 --- a/Sources/Shared/API/Responses/MobileAppConfig/MobileAppConfig.swift +++ b/Sources/Shared/API/Responses/MobileAppConfig/MobileAppConfig.swift @@ -3,15 +3,12 @@ import ObjectMapper public struct MobileAppConfig: ImmutableMappable { public var push: MobileAppConfigPush - public var actions: [MobileAppConfigAction] - init(push: MobileAppConfigPush = .init(), actions: [MobileAppConfigAction] = []) { + init(push: MobileAppConfigPush = .init()) { self.push = push - self.actions = actions } public init(map: Map) throws { self.push = (try? map.value("push")) ?? MobileAppConfigPush() - self.actions = map.value("actions", default: []) } } diff --git a/Sources/Shared/API/Responses/MobileAppConfig/MobileAppConfigAction.swift b/Sources/Shared/API/Responses/MobileAppConfig/MobileAppConfigAction.swift deleted file mode 100644 index 75b9096206..0000000000 --- a/Sources/Shared/API/Responses/MobileAppConfig/MobileAppConfigAction.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation -import ObjectMapper - -public struct MobileAppConfigAction: ImmutableMappable, UpdatableModelSource { - var name: String - var backgroundColor: String? - var labelText: String? - var labelColor: String? - var iconIcon: String? - var iconColor: String? - var showInCarPlay: Bool? - var showInWatch: Bool? - var useCustomColors: Bool? - - public init(map: Map) throws { - self.name = try map.value("name") - self.backgroundColor = try? map.value("background_color") - self.labelText = try? map.value("label.text") - self.labelColor = try? map.value("label.color") - self.iconIcon = try? map.value("icon.icon") - self.iconColor = try? map.value("icon.color") - self.showInCarPlay = try? map.value("show_in_carplay") - self.showInWatch = try? map.value("show_in_watch") - self.useCustomColors = try? map.value("use_custom_colors") - } - - public var primaryKey: String { name } -} diff --git a/Sources/Shared/Common/Extensions/Realm+Initialization.swift b/Sources/Shared/Common/Extensions/Realm+Initialization.swift index b34f922935..94ebf18eff 100644 --- a/Sources/Shared/Common/Extensions/Realm+Initialization.swift +++ b/Sources/Shared/Common/Extensions/Realm+Initialization.swift @@ -73,9 +73,7 @@ public extension Realm { // 5 - 2020-07-08 v2020.4 // 6 - 2020-07-12 v2020.4 - // 7 - 2020-07-20 v2020.5 (added RLMScene) // 9 - 2020-07-23 v2020.5 (primary key removal on NotificationAction) - // 10 - 2020-07-31 v2020.5 (added isServerControlled to Action) // 11 - 2020-08-12 v2020.5.2 (cleaning up duplicate NotificationCategory identifiers) // 12 - 2020-08-16 v2020.6 (mdi upgrade/migration to 5.x) // 13 - 2020-10-17 v2020.7 (allow multiple complications) @@ -87,8 +85,7 @@ public extension Realm { // 19 - 2021-11-27 v2021.12 (zone property renames) // 20…25 - 2022-08-13 v2022.x undoing realm automatic migration // 26 - 2022-08-13 v2022.x bumping mdi version - // 27 - 2024-01-18 v2024.x adding CarPlay toggle to Actions - // 28 - 2024-07-29 v2024.x Add option to use custom colors + // 29 - 2026-05-27 v2026.x Remove legacy iOS action Realm models // Current schema version from database if let currentSchemaVersion = try? schemaVersionAtURL(storeURL) { @@ -96,7 +93,7 @@ public extension Realm { } // New schema version - let schemaVersion: UInt64 = 28 + let schemaVersion: UInt64 = 29 Current.Log.verbose("Schema version defined: \(schemaVersion)") let config = Realm.Configuration( @@ -110,12 +107,6 @@ public extension Realm { } } - if oldVersion < 10 { - migration.enumerateObjects(ofType: Action.className()) { _, newObject in - newObject?["isServerControlled"] = false - } - } - if oldVersion < 11 { // Identifier is a primary key, and Realm is _suppose_ to prevent this from being possible // but it seems like some time in the past, it allowed the same identifier to be inserted >1 time @@ -147,20 +138,6 @@ public extension Realm { } } - if oldVersion < 15 { - migration.enumerateObjects(ofType: RLMScene.className()) { oldObject, newObject in - if let data = oldObject?["underlyingSceneData"] as? Data, - let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let attributes = json["attributes"] as? [String: Any] { - newObject?["backgroundColor"] = attributes["background_color"] as? String - newObject?["textColor"] = attributes["text_color"] as? String - newObject?["iconColor"] = attributes["icon_color"] as? String - newObject?["icon"] = attributes["icon"] as? String - newObject?["name"] = attributes["friendly_name"] as? String - } - } - } - if oldVersion < 16 { // nothing, it added an optional } @@ -178,9 +155,7 @@ public extension Realm { } migrate(NotificationCategory.self) - migrate(RLMScene.self) migrate(RLMZone.self) - migrate(Action.self) migration.enumerateObjects(ofType: WatchComplication.className()) { _, newObject in newObject?["serverIdentifier"] = Server.historicId.rawValue @@ -200,27 +175,8 @@ public extension Realm { } } - if oldVersion < 27 { - migration.enumerateObjects(ofType: Action.className()) { _, newObject in - newObject?["showInCarPlay"] = true - newObject?["showInWatch"] = true - } - } - - if oldVersion < 28 { - migration.enumerateObjects(ofType: Action.className()) { _, newObject in - newObject?["useCustomColors"] = false - } - } - do { // always do an MDI migration, since micro-managing whether it needs to be done is annoying - migration.enumerateObjects(ofType: Action.className()) { _, newObject in - let iconNameKey = "IconName" - if let oldIconName = newObject?[iconNameKey] as? String { - newObject?[iconNameKey] = MDIMigration.migrate(icon: oldIconName) - } - } migration.enumerateObjects(ofType: WatchComplication.className()) { _, newObject in let dataKey = "complicationData" let iconDictKey = "icon" diff --git a/Sources/Shared/Common/ServerProviding.swift b/Sources/Shared/Common/ServerProviding.swift index 28fb43d1b7..f8a5841f81 100644 --- a/Sources/Shared/Common/ServerProviding.swift +++ b/Sources/Shared/Common/ServerProviding.swift @@ -4,8 +4,6 @@ public protocol ServerIdentifierProviding { var serverIdentifier: String { get } } -extension Action: ServerIdentifierProviding {} - public protocol ServerIntentProviding { var server: IntentServer? { get } } diff --git a/Sources/Shared/Common/SiriIntents+ConvenienceInits.swift b/Sources/Shared/Common/SiriIntents+ConvenienceInits.swift index 7e8450b121..753b182f97 100644 --- a/Sources/Shared/Common/SiriIntents+ConvenienceInits.swift +++ b/Sources/Shared/Common/SiriIntents+ConvenienceInits.swift @@ -3,7 +3,6 @@ import Foundation import HAKit import Intents import MapKit -import UIColor_Hex_Swift public extension CallServiceIntent { convenience init(domain: String, service: String) { @@ -53,65 +52,6 @@ public extension SendLocationIntent { } } -public extension PerformActionIntent { - convenience init(action: Action) { - self.init() - self.action = .init(action: action) - - #if os(iOS) - let image = INImage( - icon: MaterialDesignIcons(named: action.IconName), - foreground: UIColor(hex: action.IconColor), - background: UIColor(hex: action.BackgroundColor) - ) - - // this should be: - // setImage(image, forParameterNamed: \Self.action) - // but this crashes at runtime, iOS 13 and iOS 14 at least - __setImage(image, forParameterNamed: "action") - #endif - } -} - -public extension IntentAction { - convenience init(action: Action) { - #if os(iOS) - self.init( - identifier: action.ID, - display: action.Name, - subtitle: nil, - image: INImage( - icon: MaterialDesignIcons(named: action.IconName), - foreground: UIColor(hex: action.IconColor), - background: UIColor(hex: action.BackgroundColor) - ) - ) - #else - self.init(identifier: action.ID, display: action.Name) - #endif - } - - func asActionWithUpdated() -> (updated: IntentAction, action: Action)? { - guard let action = asAction() else { - return nil - } - - return (.init(action: action), action) - } - - func asAction() -> Action? { - guard let identifier, identifier.isEmpty == false else { - return nil - } - - guard let result = Current.realm().object(ofType: Action.self, forPrimaryKey: identifier) else { - return nil - } - - return result - } -} - public extension IntentPanel { convenience init(panel: HAPanel, server: Server) { let image: INImage? diff --git a/Sources/Shared/Environment/AppConstants.swift b/Sources/Shared/Environment/AppConstants.swift index 85d47b3446..93314c53a2 100644 --- a/Sources/Shared/Environment/AppConstants.swift +++ b/Sources/Shared/Environment/AppConstants.swift @@ -368,7 +368,7 @@ public extension Version { static let canSendDeviceID: Version = .init(minor: 104) static let pedometerIconsAvailable: Version = .init(minor: 105) static let tagWebhookAvailable: Version = .init(minor: 114, prerelease: "b5") - static let actionSyncing: Version = .init(minor: 115, prerelease: "any0") + static let mobileAppConfig: Version = .init(minor: 115, prerelease: "any0") static let localPushConfirm: Version = .init(major: 2021, minor: 10, prerelease: "any0") static let externalBusCommandRestart: Version = .init(major: 2021, minor: 12, prerelease: "b6") static let updateLocationGPSOptional: Version = .init(major: 2022, minor: 2, prerelease: "any0") diff --git a/Sources/Shared/Intents/IntentHandlerFactory.swift b/Sources/Shared/Intents/IntentHandlerFactory.swift index 027191aadb..97beebf15d 100644 --- a/Sources/Shared/Intents/IntentHandlerFactory.swift +++ b/Sources/Shared/Intents/IntentHandlerFactory.swift @@ -15,8 +15,6 @@ public enum IntentHandlerFactory { return GetCameraImageIntentHandler() case is RenderTemplateIntent: return RenderTemplateIntentHandler() - case is PerformActionIntent: - return PerformActionIntentHandler() case is UpdateSensorsIntent: return UpdateSensorsIntentHandler() case is AssistIntent: @@ -25,8 +23,6 @@ public enum IntentHandlerFactory { return OpenPageIntentHandler() case is INShareFocusStatusIntent: return FocusStatusIntentHandler() - case is WidgetActionsIntent: - return WidgetActionsIntentHandler() case is AssistInAppIntent: return AssistInAppIntentHandler() default: diff --git a/Sources/Shared/Intents/PerformActionIntentHandler.swift b/Sources/Shared/Intents/PerformActionIntentHandler.swift deleted file mode 100644 index 341059d056..0000000000 --- a/Sources/Shared/Intents/PerformActionIntentHandler.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation -import Intents -import PromiseKit -import Realm - -class PerformActionIntentHandler: NSObject, PerformActionIntentHandling { - func handle( - intent: PerformActionIntent, - completion: @escaping (PerformActionIntentResponse) -> Void - ) { - guard let result = intent.action?.asActionWithUpdated() else { - completion(.init(code: .failure, userActivity: nil)) - return - } - - guard let server = Current.servers.server(for: result.action) else { - completion(.init(code: .failure, userActivity: nil)) - return - } - - firstly { - Current.api(for: server)? - .HandleAction(actionID: result.action.ID, source: .SiriShortcut) ?? - .init(error: HomeAssistantAPI.APIError.noAPIAvailable) - }.done { - completion(.success(action: result.updated)) - }.catch { error in - completion(.failure(error: error.localizedDescription)) - } - } - - func resolveAction( - for intent: PerformActionIntent, with completion: - @escaping (IntentActionResolutionResult) -> Void - ) { - if let result = intent.action?.asActionWithUpdated() { - Current.Log.info("using action \(String(describing: result.updated.identifier))") - completion(.success(with: result.updated)) - } else { - Current.Log.info("asking for value") - completion(.needsValue()) - } - } - - func provideActionOptions( - for intent: PerformActionIntent, - with completion: @escaping ([IntentAction]?, Error?) -> Void - ) { - let actions = Current.realm(objectTypes: [Action.self]).objects(Action.self) - .sorted(byKeyPath: #keyPath(Action.Position)) - let performActions = Array(actions.map { IntentAction(action: $0) }) - Current.Log.info { () -> String in - "providing " + performActions.map { action -> String in - (action.identifier ?? "?") + " (" + action.displayString + ")" - }.joined(separator: ", ") - } - completion(Array(performActions), nil) - } - - func provideActionOptionsCollection( - for intent: PerformActionIntent, - with completion: @escaping (INObjectCollection?, Error?) -> Void - ) { - provideActionOptions(for: intent) { actions, error in - completion(actions.flatMap { .init(items: $0) }, error) - } - } -} diff --git a/Sources/Shared/Intents/WidgetActionsIntentHandler.swift b/Sources/Shared/Intents/WidgetActionsIntentHandler.swift deleted file mode 100644 index fb321f38f7..0000000000 --- a/Sources/Shared/Intents/WidgetActionsIntentHandler.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation -import Intents -import PromiseKit - -class WidgetActionsIntentHandler: NSObject, WidgetActionsIntentHandling { - func provideActionsOptionsCollection( - for intent: WidgetActionsIntent, - with completion: @escaping (INObjectCollection?, Error?) -> Void - ) { - let actions = Current.realm(objectTypes: [Action.self]).objects(Action.self) - .sorted(byKeyPath: #keyPath(Action.Position)) - let performActions = Array(actions.map { IntentAction(action: $0) }) - completion(.init(items: performActions), nil) - } -} diff --git a/Sources/Shared/MagicItem/MagicItem+Migration.swift b/Sources/Shared/MagicItem/MagicItem+Migration.swift index 76ad9f6080..6859d923c3 100644 --- a/Sources/Shared/MagicItem/MagicItem+Migration.swift +++ b/Sources/Shared/MagicItem/MagicItem+Migration.swift @@ -11,6 +11,7 @@ extension MagicItemProvider { This can happen when the user deletes the server and add it back again. */ func migrateItemsIfNeeded(items: [MagicItem]) -> [MagicItem] { + let items = removingUnsupportedItems(from: items) let infos = items.compactMap { getInfo(for: $0) } if infos.count == items.count { @@ -23,9 +24,11 @@ extension MagicItemProvider { let replacementItems = missingItems.compactMap { item -> MagicItem? in switch item.type { - case .action, .assistPipeline, .assistPrompt: - // Actions and assist items do not require entity-based migration + case .assistPipeline, .assistPrompt: + // Assist items do not require entity-based migration return item + case .unsupported: + return nil default: return getSimilarItem(for: item) } @@ -37,6 +40,20 @@ extension MagicItemProvider { } } + private func removingUnsupportedItems(from items: [MagicItem]) -> [MagicItem] { + items.compactMap { item in + guard item.type != .unsupported else { + return nil + } + + var item = item + if let folderItems = item.items { + item.items = removingUnsupportedItems(from: folderItems) + } + return item + } + } + private func getSimilarItem(for item: MagicItem) -> MagicItem? { if let similarEntityInCache = entitiesPerServer.first(where: { dict in dict.value.contains { $0.entityId == item.id } diff --git a/Sources/Shared/MagicItem/MagicItem.swift b/Sources/Shared/MagicItem/MagicItem.swift index 1cf7c85fec..aba8cd6311 100644 --- a/Sources/Shared/MagicItem/MagicItem.swift +++ b/Sources/Shared/MagicItem/MagicItem.swift @@ -98,14 +98,24 @@ public struct MagicItem: Codable, Equatable, Hashable { } public enum ItemType: String, Codable { - /// aka iOS legacy Action - case action case script case scene case entity case folder case assistPipeline case assistPrompt + case unsupported + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let rawValue = try container.decode(String.self) + self = Self(rawValue: rawValue) ?? .unsupported + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } public struct Customization: Codable, Equatable, Hashable { @@ -162,7 +172,7 @@ public struct MagicItem: Codable, Equatable, Hashable { return MaterialDesignIcons(named: icon, fallback: .dotsGridIcon) } else { switch type { - case .action, .scene: + case .scene: icon = MaterialDesignIcons(named: info.iconName, fallback: .scriptTextOutlineIcon) case .script, .entity: icon = MaterialDesignIcons( @@ -175,6 +185,8 @@ public struct MagicItem: Codable, Equatable, Hashable { icon = .microphoneIcon case .assistPrompt: icon = .messageProcessingOutlineIcon + case .unsupported: + icon = .dotsGridIcon } } @@ -388,9 +400,6 @@ public extension MagicItem { triggerSource: source, shouldLog: true ) - case .action: - return Current.api(for: server)? - .HandleAction(actionID: id, source: source) case .scene: let domain = Domain.scene.rawValue return Current.api(for: server)?.CallService( @@ -412,7 +421,7 @@ public extension MagicItem { entityId: id, state: currentItemState ) - case .folder, .assistPipeline, .assistPrompt: + case .folder, .assistPipeline, .assistPrompt, .unsupported: // Folders and assist items don't execute direct actions return nil } diff --git a/Sources/Shared/MagicItem/MagicItemProvider.swift b/Sources/Shared/MagicItem/MagicItemProvider.swift index 821685c48a..c5fa1646e7 100644 --- a/Sources/Shared/MagicItem/MagicItemProvider.swift +++ b/Sources/Shared/MagicItem/MagicItemProvider.swift @@ -167,26 +167,6 @@ final class MagicItemProvider: MagicItemProviderProtocol { func getInfo(for item: MagicItem) -> MagicItem.Info? { switch item.type { - case .action: - guard let actionItem = Current.realm().object(ofType: Action.self, forPrimaryKey: item.id) else { - Current.Log - .error( - "Failed to get magic item Action info for item id: \(item.id), server id: \(String(describing: item.serverId))" - ) - return nil - } - return .init( - id: ServerEntity.uniqueId(serverId: actionItem.serverIdentifier, entityId: actionItem.ID), - name: actionItem.Text, - iconName: actionItem.IconName, - customization: .init( - iconColor: actionItem.IconColor, - textColor: actionItem.TextColor, - backgroundColor: actionItem.BackgroundColor, - // Legacy iOS Actions always run without confirmation as it previously did - requiresConfirmation: false - ) - ) case .script: guard let scriptsForServer = entitiesPerServer[item.serverId]? .filter({ $0.domain == Domain.script.rawValue }), @@ -265,12 +245,13 @@ final class MagicItemProvider: MagicItemProviderProtocol { iconName: iconName, customization: item.customization ) + case .unsupported: + return nil } } func getAreaName(for item: MagicItem) -> String? { - guard item.type != .action, - let entitiesForServer = entitiesPerServer[item.serverId] else { + guard let entitiesForServer = entitiesPerServer[item.serverId] else { return nil } diff --git a/Sources/Shared/Resources/Swiftgen/Strings.swift b/Sources/Shared/Resources/Swiftgen/Strings.swift index 4fd42f2e3b..50b91936ac 100644 --- a/Sources/Shared/Resources/Swiftgen/Strings.swift +++ b/Sources/Shared/Resources/Swiftgen/Strings.swift @@ -124,61 +124,6 @@ public enum L10n { } } - public enum ActionsConfigurator { - /// New Action - public static var title: String { return L10n.tr("Localizable", "actions_configurator.title") } - public enum Action { - /// Create automation - public static var createAutomation: String { return L10n.tr("Localizable", "actions_configurator.action.create_automation") } - /// Define what will be executed when Action is performed, alternatively you can use the example trigger below manually. - public static var footer: String { return L10n.tr("Localizable", "actions_configurator.action.footer") } - /// Execute - public static var title: String { return L10n.tr("Localizable", "actions_configurator.action.title") } - } - public enum Rows { - public enum BackgroundColor { - /// Background Color - public static var title: String { return L10n.tr("Localizable", "actions_configurator.rows.background_color.title") } - } - public enum Icon { - /// Icon - public static var title: String { return L10n.tr("Localizable", "actions_configurator.rows.icon.title") } - } - public enum IconColor { - /// Icon Color - public static var title: String { return L10n.tr("Localizable", "actions_configurator.rows.icon_color.title") } - } - public enum Name { - /// Name - public static var title: String { return L10n.tr("Localizable", "actions_configurator.rows.name.title") } - } - public enum Text { - /// Text - public static var title: String { return L10n.tr("Localizable", "actions_configurator.rows.text.title") } - } - public enum TextColor { - /// Text Color - public static var title: String { return L10n.tr("Localizable", "actions_configurator.rows.text_color.title") } - } - } - public enum TriggerExample { - /// Share Contents - public static var share: String { return L10n.tr("Localizable", "actions_configurator.trigger_example.share") } - /// Example Trigger - public static var title: String { return L10n.tr("Localizable", "actions_configurator.trigger_example.title") } - } - public enum VisualSection { - /// The appearance of this action is controlled by the scene configuration. - public static var sceneDefined: String { return L10n.tr("Localizable", "actions_configurator.visual_section.scene_defined") } - /// You can also change these by customizing the Scene attributes: %@ - public static func sceneHintFooter(_ p1: Any) -> String { - return L10n.tr("Localizable", "actions_configurator.visual_section.scene_hint_footer", String(describing: p1)) - } - /// The appearance of this action is controlled by the server configuration. - public static var serverDefined: String { return L10n.tr("Localizable", "actions_configurator.visual_section.server_defined") } - } - } - public enum Alert { public enum Confirmation { public enum DeleteEntities { @@ -193,14 +138,6 @@ public enum L10n { } public enum Alerts { - public enum ActionAutomationEditor { - public enum Unavailable { - /// To automatically create an automation for an Action please update your Home Assistant to at least version 2024.2 - public static var body: String { return L10n.tr("Localizable", "alerts.action_automation_editor.unavailable.body") } - /// Please update Home Assistant - public static var title: String { return L10n.tr("Localizable", "alerts.action_automation_editor.unavailable.title") } - } - } public enum Alert { /// OK public static var ok: String { return L10n.tr("Localizable", "alerts.alert.ok") } @@ -403,24 +340,6 @@ public enum L10n { /// Icon for open state public static var title: String { return L10n.tr("Localizable", "app_intents.open_state_icon.title") } } - public enum PerformAction { - /// Which action? - public static var actionParameterConfiguration: String { return L10n.tr("Localizable", "app_intents.perform_action.action_parameter_configuration") } - /// Just to confirm, you wanted ‘%@’? - public static func actionParameterConfirmation(_ p1: Any) -> String { - return L10n.tr("Localizable", "app_intents.perform_action.action_parameter_confirmation", String(describing: p1)) - } - /// There are %@ options matching ‘%@’. - public static func actionParameterDisambiguationIntro(_ p1: Any, _ p2: Any) -> String { - return L10n.tr("Localizable", "app_intents.perform_action.action_parameter_disambiguation_intro", String(describing: p1), String(describing: p2)) - } - /// Failed: %@ - public static func responseFailure(_ p1: Any) -> String { - return L10n.tr("Localizable", "app_intents.perform_action.response_failure", String(describing: p1)) - } - /// Done - public static var responseSuccess: String { return L10n.tr("Localizable", "app_intents.perform_action.response_success") } - } public enum Scenes { /// Run Scene public static var title: String { return L10n.tr("Localizable", "app_intents.scenes.title") } @@ -515,10 +434,6 @@ public enum L10n { /// Switch public static var title: String { return L10n.tr("Localizable", "app_intents.switch.title") } } - public enum WidgetAction { - /// Which actions? - public static var actionsParameterConfiguration: String { return L10n.tr("Localizable", "app_intents.widget_action.actions_parameter_configuration") } - } } public enum Assist { @@ -744,20 +659,6 @@ public enum L10n { } public enum CarPlay { - public enum Action { - public enum Execute { - /// Executing... - public static var inProgress: String { return L10n.tr("Localizable", "carPlay.action.execute.in_progress") } - } - public enum Intro { - public enum Item { - /// Tap to continue on your iPhone - public static var body: String { return L10n.tr("Localizable", "carPlay.action.intro.item.body") } - /// Create your first action - public static var title: String { return L10n.tr("Localizable", "carPlay.action.intro.item.title") } - } - } - } public enum Config { public enum Tabs { /// Tabs @@ -944,8 +845,6 @@ public enum L10n { public static var previous: String { return L10n.tr("Localizable", "carPlay.navigation.button.previous") } } public enum Tab { - /// Actions - public static var actions: String { return L10n.tr("Localizable", "carPlay.navigation.tab.actions") } /// Areas public static var areas: String { return L10n.tr("Localizable", "carPlay.navigation.tab.areas") } /// Control @@ -956,23 +855,11 @@ public enum L10n { public static var settings: String { return L10n.tr("Localizable", "carPlay.navigation.tab.settings") } } } - public enum NoActions { - /// Open iOS Companion App to create actions for CarPlay. - public static var title: String { return L10n.tr("Localizable", "carPlay.no_actions.title") } - } public enum NoEntities { /// No CarPlay compatible entities available. public static var title: String { return L10n.tr("Localizable", "carPlay.no_entities.title") } } public enum Notification { - public enum Action { - public enum Intro { - /// Tap to create your first iOS Action - public static var body: String { return L10n.tr("Localizable", "carPlay.notification.action.intro.body") } - /// Create iOS Action - public static var title: String { return L10n.tr("Localizable", "carPlay.notification.action.intro.title") } - } - } public enum QuickAccess { public enum Intro { /// Tap to create your CarPlay configuration. @@ -983,8 +870,14 @@ public enum L10n { } } public enum QuickAccess { + public enum Execute { + /// Executing... + public static var inProgress: String { return L10n.tr("Localizable", "carPlay.quick_access.execute.in_progress") } + } public enum Intro { public enum Item { + /// Tap to continue on your iPhone + public static var body: String { return L10n.tr("Localizable", "carPlay.quick_access.intro.item.body") } /// Create your CarPlay configuration public static var title: String { return L10n.tr("Localizable", "carPlay.quick_access.intro.item.title") } } @@ -2001,11 +1894,6 @@ public enum L10n { } } - public enum LegacyActions { - /// Legacy iOS Actions are not the recommended way to interact with Home Assistant anymore, please use Scripts, Scenes and Automations directly in your Widgets, Apple Watch and CarPlay. - public static var disclaimer: String { return L10n.tr("Localizable", "legacy_actions.disclaimer") } - } - public enum LiveActivity { /// No active Live Activities public static var emptyState: String { return L10n.tr("Localizable", "live_activity.empty_state") } @@ -2225,16 +2113,6 @@ public enum L10n { public static var title: String { return L10n.tr("Localizable", "magic_item.icon_name.title") } } public enum ItemType { - public enum Action { - public enum List { - /// iOS Actions - public static var title: String { return L10n.tr("Localizable", "magic_item.item_type.action.list.title") } - public enum Warning { - /// We will stop supporting iOS Actions in the future, please consider using Home Assistant scripts or scenes instead. - public static var title: String { return L10n.tr("Localizable", "magic_item.item_type.action.list.warning.title") } - } - } - } public enum App { public enum List { /// App @@ -2301,12 +2179,6 @@ public enum L10n { } public enum Menu { - public enum Actions { - /// Configure… - public static var configure: String { return L10n.tr("Localizable", "menu.actions.configure") } - /// Actions - public static var title: String { return L10n.tr("Localizable", "menu.actions.title") } - } public enum Application { /// About %@ public static func about(_ p1: Any) -> String { @@ -2490,6 +2362,10 @@ public enum L10n { /// Identifier public static var identifier: String { return L10n.tr("Localizable", "notifications_configurator.identifier") } public enum Action { + public enum ExampleTrigger { + /// Example Trigger + public static var title: String { return L10n.tr("Localizable", "notifications_configurator.action.example_trigger.title") } + } public enum Rows { public enum AuthenticationRequired { /// 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. @@ -3720,62 +3596,6 @@ public enum L10n { public enum SettingsDetails { /// Learn more public static var learnMore: String { return L10n.tr("Localizable", "settings_details.learn_more") } - public enum Actions { - /// Actions are used in the Apple Watch app, App Icon Actions, the Today widget and CarPlay. - public static var footer: String { return L10n.tr("Localizable", "settings_details.actions.footer") } - /// Actions - public static var title: String { return L10n.tr("Localizable", "settings_details.actions.title") } - public enum ActionsSynced { - /// No Synced Actions - public static var empty: String { return L10n.tr("Localizable", "settings_details.actions.actions_synced.empty") } - /// Actions defined in .yaml are not editable on device. - public static var footer: String { return L10n.tr("Localizable", "settings_details.actions.actions_synced.footer") } - /// Actions may be also defined in the .yaml configuration. - public static var footerNoActions: String { return L10n.tr("Localizable", "settings_details.actions.actions_synced.footer_no_actions") } - /// Synced Actions - public static var header: String { return L10n.tr("Localizable", "settings_details.actions.actions_synced.header") } - } - public enum CarPlay { - public enum Available { - /// Show in CarPlay - public static var title: String { return L10n.tr("Localizable", "settings_details.actions.carPlay.available.title") } - } - } - public enum Learn { - public enum Button { - /// Introduction to iOS Actions - public static var title: String { return L10n.tr("Localizable", "settings_details.actions.learn.button.title") } - } - } - public enum Scenes { - /// Customize - public static var customizeAction: String { return L10n.tr("Localizable", "settings_details.actions.scenes.customize_action") } - /// No Scenes - public static var empty: String { return L10n.tr("Localizable", "settings_details.actions.scenes.empty") } - /// When enabled, Scenes display alongside actions. When performed, they trigger scene changes. - public static var footer: String { return L10n.tr("Localizable", "settings_details.actions.scenes.footer") } - /// Select All - public static var selectAll: String { return L10n.tr("Localizable", "settings_details.actions.scenes.select_all") } - /// Scene Actions - public static var title: String { return L10n.tr("Localizable", "settings_details.actions.scenes.title") } - } - public enum ServerControlled { - public enum Update { - /// Update server Actions - public static var title: String { return L10n.tr("Localizable", "settings_details.actions.server_controlled.update.title") } - } - } - public enum UseCustomColors { - /// Use custom colors - public static var title: String { return L10n.tr("Localizable", "settings_details.actions.use_custom_colors.title") } - } - public enum Watch { - public enum Available { - /// Show in Watch - public static var title: String { return L10n.tr("Localizable", "settings_details.actions.watch.available.title") } - } - } - } public enum General { /// Basic App configuration, App Icon and web page settings. public static var body: String { return L10n.tr("Localizable", "settings_details.general.body") } @@ -3932,10 +3752,6 @@ public enum L10n { public static var title: String { return L10n.tr("Localizable", "settings_details.http.warning.title") } } } - public enum LegacyActions { - /// (Legacy) iOS Actions - public static var title: String { return L10n.tr("Localizable", "settings_details.legacy_actions.title") } - } public enum Location { /// Location public static var title: String { return L10n.tr("Localizable", "settings_details.location.title") } @@ -4551,10 +4367,6 @@ public enum L10n { public static var title: String { return L10n.tr("Localizable", "url_handler.call_service.success.title") } } } - public enum Error { - /// Action Not Found - public static var actionNotFound: String { return L10n.tr("Localizable", "url_handler.error.action_not_found") } - } public enum FireEvent { public enum Confirm { /// Do you want to fire the event %@? @@ -5484,18 +5296,6 @@ public enum L10n { public static var runScript: String { return L10n.tr("Localizable", "widgets.action.name.run_script") } } } - public enum Actions { - /// Perform Home Assistant actions. - public static var description: String { return L10n.tr("Localizable", "widgets.actions.description") } - /// No Actions Configured - public static var notConfigured: String { return L10n.tr("Localizable", "widgets.actions.not_configured") } - /// Actions - public static var title: String { return L10n.tr("Localizable", "widgets.actions.title") } - public enum Parameters { - /// Action - public static var action: String { return L10n.tr("Localizable", "widgets.actions.parameters.action") } - } - } public enum Assist { /// Ask Assist public static var actionTitle: String { return L10n.tr("Localizable", "widgets.assist.action_title") } @@ -5995,6 +5795,11 @@ public enum L10n { } } } + + public enum YamlPreview { + /// Share Contents + public static var share: String { return L10n.tr("Localizable", "yaml_preview.share") } + } } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces diff --git a/Sources/Shared/Watch/InteractiveImmediateMessages.swift b/Sources/Shared/Watch/InteractiveImmediateMessages.swift index 072f8b7ce4..5d430e2e1d 100644 --- a/Sources/Shared/Watch/InteractiveImmediateMessages.swift +++ b/Sources/Shared/Watch/InteractiveImmediateMessages.swift @@ -2,7 +2,6 @@ import Foundation public enum InteractiveImmediateMessages: String, CaseIterable { case ping - case actionRowPressed = "ActionRowPressed" case magicItemPressed case pushAction = "PushAction" case assistPipelinesFetch @@ -12,7 +11,6 @@ public enum InteractiveImmediateMessages: String, CaseIterable { public enum InteractiveImmediateResponses: String, CaseIterable { case pong - case actionRowPressedResponse = "ActionRowPressedResponse" case magicItemRowPressedResponse case pushActionResponse = "PushActionResponse" case assistPipelinesFetchResponse diff --git a/Sources/Shared/Widget/WidgetsKind.swift b/Sources/Shared/Widget/WidgetsKind.swift index 17ca2729aa..6e150b1422 100644 --- a/Sources/Shared/Widget/WidgetsKind.swift +++ b/Sources/Shared/Widget/WidgetsKind.swift @@ -2,7 +2,6 @@ import Foundation public enum WidgetsKind: String, CaseIterable { case assist = "WidgetAssist" - case actions = "WidgetActions" case openPage = "WidgetOpenPage" case gauge = "WidgetGauge" case details = "WidgetDetails" diff --git a/Sources/Shared/WidgetInteractionType.swift b/Sources/Shared/WidgetInteractionType.swift index 930be12b01..0ea5e6cb78 100644 --- a/Sources/Shared/WidgetInteractionType.swift +++ b/Sources/Shared/WidgetInteractionType.swift @@ -6,7 +6,6 @@ public enum WidgetInteractionType: Hashable, Encodable { } public enum WidgetIntentType: Hashable, Encodable { - case action(id: String, name: String) case script(id: String, entityId: String, serverId: String, name: String, showConfirmationNotification: Bool) /// Entities that can be toggled case toggle(entityId: String, domain: String, serverId: String) diff --git a/Sources/Watch/WatchCommunicatorService.swift b/Sources/Watch/WatchCommunicatorService.swift index e4588c831f..f3c58c5a12 100644 --- a/Sources/Watch/WatchCommunicatorService.swift +++ b/Sources/Watch/WatchCommunicatorService.swift @@ -67,8 +67,6 @@ final class WatchCommunicatorService { message.reply(.init(identifier: InteractiveImmediateResponses.pong.rawValue)) case .watchConfig: watchConfig(message: message) - case .actionRowPressed: - actionRowPressed(message: message) case .pushAction: pushAction(message: message) case .assistPipelinesFetch: @@ -184,23 +182,13 @@ final class WatchCommunicatorService { } guard let serverId = message.content["serverId"] as? String, - let server = Current.servers.all.first(where: { $0.identifier.rawValue == serverId }), - let api = Current.api(for: server) else { + let server = Current.servers.all.first(where: { $0.identifier.rawValue == serverId }) else { Current.Log.warning("Magic item press did not provide valid server info") message.reply(.init(identifier: responseIdentifier, content: ["fired": false])) return } switch type { - case .action: - firstly { - api.HandleAction(actionID: itemId, source: .Watch) - }.done { - message.reply(.init(identifier: responseIdentifier, content: ["fired": true])) - }.catch { err in - Current.Log.error("Error during action event fire: \(err)") - message.reply(.init(identifier: responseIdentifier, content: ["fired": false])) - } case .script: callService( server: server, @@ -240,6 +228,8 @@ final class WatchCommunicatorService { case .assistPipeline, .assistPrompt: // Assist items are not supported on Watch message.reply(.init(identifier: responseIdentifier, content: ["fired": false])) + case .unsupported: + message.reply(.init(identifier: responseIdentifier, content: ["fired": false])) } } @@ -276,27 +266,6 @@ final class WatchCommunicatorService { } } - private func actionRowPressed(message: InteractiveImmediateMessage) { - let responseIdentifier = InteractiveImmediateResponses.actionRowPressedResponse.rawValue - guard let actionID = message.content["ActionID"] as? String, - let action = Current.realm().object(ofType: Action.self, forPrimaryKey: actionID), - let server = Current.servers.server(for: action), - let api = Current.api(for: server) else { - Current.Log.warning("ActionID either does not exist or is not a string in the payload") - message.reply(.init(identifier: responseIdentifier, content: ["fired": false])) - return - } - - firstly { - api.HandleAction(actionID: actionID, source: .Watch) - }.done { - message.reply(.init(identifier: responseIdentifier, content: ["fired": true])) - }.catch { err in - Current.Log.error("Error during action event fire: \(err)") - message.reply(.init(identifier: responseIdentifier, content: ["fired": false])) - } - } - private func pushAction(message: InteractiveImmediateMessage) { let responseIdentifier = InteractiveImmediateResponses.pushActionResponse.rawValue diff --git a/Tests/App/Watch/WatchConfig.test.swift b/Tests/App/Watch/WatchConfig.test.swift index 8d80de6fd1..a0c3bf610e 100644 --- a/Tests/App/Watch/WatchConfig.test.swift +++ b/Tests/App/Watch/WatchConfig.test.swift @@ -96,7 +96,7 @@ struct WatchConfigurationViewModel_test { // Add multiple items to the folder let item1 = MagicItem(id: "script.one", serverId: "s1", type: .script) let item2 = MagicItem(id: "scene.two", serverId: "s1", type: .scene) - let item3 = MagicItem(id: "action.three", serverId: "s1", type: .action) + let item3 = MagicItem(id: "script.three", serverId: "s1", type: .script) viewModel.addItemToFolder(folderId: folderId, item: item1) viewModel.addItemToFolder(folderId: folderId, item: item2) @@ -109,7 +109,7 @@ struct WatchConfigurationViewModel_test { let rootItemIds = viewModel.watchConfig.items.map(\.id) #expect(!rootItemIds.contains("script.one"), "script.one should not be at root") #expect(!rootItemIds.contains("scene.two"), "scene.two should not be at root") - #expect(!rootItemIds.contains("action.three"), "action.three should not be at root") + #expect(!rootItemIds.contains("script.three"), "script.three should not be at root") // The folder should contain all 3 items let folder = viewModel.watchConfig.items[0] @@ -118,7 +118,7 @@ struct WatchConfigurationViewModel_test { let folderItemIds = folder.items?.map(\.id) ?? [] #expect(folderItemIds.contains("script.one"), "Folder should contain script.one") #expect(folderItemIds.contains("scene.two"), "Folder should contain scene.two") - #expect(folderItemIds.contains("action.three"), "Folder should contain action.three") + #expect(folderItemIds.contains("script.three"), "Folder should contain script.three") } @Test func addItemToNonExistentFolderDoesNothing() async throws { diff --git a/Tests/App/Widgets/WidgetsKindTests.swift b/Tests/App/Widgets/WidgetsKindTests.swift index 9beadc6c0c..e64a35ef94 100644 --- a/Tests/App/Widgets/WidgetsKindTests.swift +++ b/Tests/App/Widgets/WidgetsKindTests.swift @@ -5,7 +5,6 @@ final class WidgetsKindTests: XCTestCase { func testWidgetsKindCasesValues() { // Widgets XCTAssertEqual(WidgetsKind.assist.rawValue, "WidgetAssist") - XCTAssertEqual(WidgetsKind.actions.rawValue, "WidgetActions") XCTAssertEqual(WidgetsKind.openPage.rawValue, "WidgetOpenPage") XCTAssertEqual(WidgetsKind.gauge.rawValue, "WidgetGauge") XCTAssertEqual(WidgetsKind.details.rawValue, "WidgetDetails") @@ -35,6 +34,6 @@ final class WidgetsKindTests: XCTestCase { XCTAssertEqual(WidgetsKind.controlOpenSensor.rawValue, "controlOpenSensor") XCTAssertEqual(WidgetsKind.controlFan.rawValue, "controlFan") XCTAssertEqual(WidgetsKind.todoList.rawValue, "todoList") - XCTAssertEqual(WidgetsKind.allCases.count, 29) + XCTAssertEqual(WidgetsKind.allCases.count, 28) } } diff --git a/Tests/App/Widgets/WidgetsSizeTests.swift b/Tests/App/Widgets/WidgetsSizeTests.swift deleted file mode 100644 index 6b6ede630e..0000000000 --- a/Tests/App/Widgets/WidgetsSizeTests.swift +++ /dev/null @@ -1,8 +0,0 @@ -@testable import Extensions_Widgets -import Testing - -struct WidgetsSizeTests { - @Test func testWidgetsSizes() async throws { - WidgetActionsAppIntent(actions: .init()). - } -} diff --git a/Tests/Shared/Models/entityregistry.json b/Tests/Shared/Models/entityregistry.json index 0d731dbdf7..abce48caa1 100644 --- a/Tests/Shared/Models/entityregistry.json +++ b/Tests/Shared/Models/entityregistry.json @@ -20336,7 +20336,7 @@ "device_id": null, "disabled_by": null, "entity_category": null, - "entity_id": "automation.ios_actions", + "entity_id": "automation.mobile_app_shortcuts", "has_entity_name": false, "hidden_by": null, "icon": null, @@ -20352,7 +20352,7 @@ "should_expose": false } }, - "original_name": "iOS Actions", + "original_name": "Mobile App Shortcuts", "platform": "automation", "translation_key": null, "unique_id": "1705541760748" @@ -137485,4 +137485,4 @@ "translation_key": "device_uplink_mac", "unique_id": "device_uplink_mac-94:2a:6f:16:c0:9e" } - ] \ No newline at end of file + ]