diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index da408584f..0e97c11ec 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -628,6 +628,7 @@ 659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; }; 661EF50C1F7D4B0BC8A7AAE3 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44ABA63DBE7F76C58260B43B /* EmoteRoomTimelineView.swift */; }; 663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; }; + 665314B4CB31904F107D2D3B /* HealthChatAppSettingsHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */; }; 666D8D01A3258B31CE5FD046 /* 94 in Resources */ = {isa = PBXBuildFile; fileRef = 5B609486E3B9AF012AEE3E78 /* 94 */; }; 6681D6D3ADF69EBD2625F29A /* KnockedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E8F4D7D61B80EBD5CB92F8A /* KnockedRoomProxyMock.swift */; }; 66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D879DC5515B1D42577F96C94 /* RoomSelectionScreen.swift */; }; @@ -684,6 +685,7 @@ 6CBC544A7CC4DDDBDED4D0A6 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 4B1F71AC585827E6C416C15A /* AppIcon.icon */; }; 6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; }; 6DC8E43BA04AC2AC4EB2EB97 /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.swift */; }; + 6DDA932A28F531791BDBFB08 /* HealthChatBrandColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A60C2712F5607406E8B1C80 /* HealthChatBrandColors.swift */; }; 6E03A710799E6C65C0AB36BC /* TargetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D829FD8958376614504B18 /* TargetConfiguration.swift */; }; 6E0C68918BEC5D27A4830BA6 /* PlaybackSpeedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBB8E5436E1BE0D6814BB7D /* PlaybackSpeedButton.swift */; }; 6E391F7F628D984AF44385D9 /* RoomAttachmentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */; }; @@ -956,6 +958,7 @@ 95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; }; 9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; }; 95B690EA99A3385D4B8A593D /* NotificationToneManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94A6A57FE7E71F7605070AA /* NotificationToneManager.swift */; }; + 95C338B0F2101D8A8CA16A98 /* HealthChatCompoundHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09E2B1AB937F2D01815495F /* HealthChatCompoundHook.swift */; }; 95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */; }; 9603EEF6DE980BB1D15D4707 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A3E8741D199CD1A37F4CBF /* UIView.swift */; }; 962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; }; @@ -1442,6 +1445,7 @@ E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; }; E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; }; E7DB506E77CED28D6FE6203D /* ManageAuthorizedSpacesScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8E7C716B8DC2F71E9AC439 /* ManageAuthorizedSpacesScreenModels.swift */; }; + E7F27870397639C204402434 /* HealthChatAppHooks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B439012B010EC4C3DD24D0 /* HealthChatAppHooks.swift */; }; E81E8B7A8ADDB2A912A609F6 /* CallIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2DB32B72EA4772E2C9E3B3 /* CallIntent.swift */; }; E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.swift */; }; E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; }; @@ -2452,6 +2456,7 @@ 796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = ""; }; 79A106D76741914F82664959 /* StateStoreViewModelV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModelV2.swift; sourceTree = ""; }; 7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenCoordinator.swift; sourceTree = ""; }; + 7A60C2712F5607406E8B1C80 /* HealthChatBrandColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatBrandColors.swift; sourceTree = ""; }; 7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityTests.swift; sourceTree = ""; }; 7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelProtocol.swift; sourceTree = ""; }; 7AB74D407F0E413E7593B369 /* SpaceAddRoomsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceAddRoomsScreenViewModelTests.swift; sourceTree = ""; }; @@ -3063,6 +3068,8 @@ DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = ""; }; DFFB0E7C6D8E190AFA0176DC /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uz; path = uz.lproj/Localizable.stringsdict; sourceTree = ""; }; E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = ""; }; + E09E2B1AB937F2D01815495F /* HealthChatCompoundHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatCompoundHook.swift; sourceTree = ""; }; + E0B439012B010EC4C3DD24D0 /* HealthChatAppHooks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatAppHooks.swift; sourceTree = ""; }; E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = ""; }; E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = ""; }; E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = ""; }; @@ -3071,6 +3078,7 @@ E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelTests.swift; sourceTree = ""; }; E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = ""; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; + E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatAppSettingsHook.swift; sourceTree = ""; }; E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = ""; }; E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenViewModelProtocol.swift; sourceTree = ""; }; E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenTests.swift; sourceTree = ""; }; @@ -3749,6 +3757,7 @@ isa = PBXGroup; children = ( B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */, + 801BA878EE879E01E82F3A5F /* HealthChat */, 2ED530A4DD98266D31E5D8E1 /* Hooks */, ); path = AppHooks; @@ -5563,6 +5572,17 @@ path = View; sourceTree = ""; }; + 801BA878EE879E01E82F3A5F /* HealthChat */ = { + isa = PBXGroup; + children = ( + E0B439012B010EC4C3DD24D0 /* HealthChatAppHooks.swift */, + E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */, + 7A60C2712F5607406E8B1C80 /* HealthChatBrandColors.swift */, + E09E2B1AB937F2D01815495F /* HealthChatCompoundHook.swift */, + ); + path = HealthChat; + sourceTree = ""; + }; 8039515BAA53B7C3275AC64A /* Client */ = { isa = PBXGroup; children = ( @@ -7754,40 +7774,40 @@ ORGANIZATIONNAME = Element; TargetAttributes = { 0E28CD62691FDBC63147D5E3 = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; TestTargetID = C0FAEB81CFD9776CD78CE489; }; 19F0C845D67E9BEA4BE7133E = { DevelopmentTeam = "$(DEVELOPMENT_TEAM)"; }; 32C23C8D224D46EFE62AFAD0 = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; }; 392E7AA23D7BA2CEEB25CF73 = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; }; 676A87694F0F247B2AF1A142 = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; }; 7A17BE29BAC81ADBAC6349D9 = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; }; 96435A765678A64A956EE59C = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; }; C0C687DE1D270F9895FEE186 = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; TestTargetID = C0FAEB81CFD9776CD78CE489; }; C0FAEB81CFD9776CD78CE489 = { DevelopmentTeam = "$(DEVELOPMENT_TEAM)"; }; D3DB351B7FBE0F49649171FC = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; TestTargetID = C0FAEB81CFD9776CD78CE489; }; F8E276FD6DC43EADB85241BC = { - DevelopmentTeam = 7J4U792NQT; + DevelopmentTeam = 6G37K72W82; }; FEB53A5BC378C913769656D8 = { DevelopmentTeam = "$(DEVELOPMENT_TEAM)"; @@ -8831,6 +8851,10 @@ 4295E5F850897710A51AE114 /* GeoURI.swift in Sources */, F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */, E8C4D9F93F0DCED211D5F187 /* HTMLParserStyle.swift in Sources */, + E7F27870397639C204402434 /* HealthChatAppHooks.swift in Sources */, + 665314B4CB31904F107D2D3B /* HealthChatAppSettingsHook.swift in Sources */, + 6DDA932A28F531791BDBFB08 /* HealthChatBrandColors.swift in Sources */, + 95C338B0F2101D8A8CA16A98 /* HealthChatCompoundHook.swift in Sources */, 0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */, 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */, 62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */, @@ -10243,11 +10267,11 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_DISPLAY_NAME = "Element X"; - APP_GROUP_IDENTIFIER = group.io.element; + APP_DISPLAY_NAME = HealthChat; + APP_GROUP_IDENTIFIER = group.ch.healthchat; APP_NAME = ElementX; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "colors/accent-color"; - BASE_BUNDLE_IDENTIFIER = io.element.elementx; + BASE_BUNDLE_IDENTIFIER = ch.healthchat.element; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -10285,7 +10309,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = 7J4U792NQT; + DEVELOPMENT_TEAM = 6G37K72W82; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -10304,7 +10328,7 @@ MARKETING_VERSION = 26.09.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - PRODUCTION_APP_NAME = Element; + PRODUCTION_APP_NAME = HealthChat; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = NO; @@ -10318,11 +10342,11 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_DISPLAY_NAME = "Element X"; - APP_GROUP_IDENTIFIER = group.io.element; + APP_DISPLAY_NAME = HealthChat; + APP_GROUP_IDENTIFIER = group.ch.healthchat; APP_NAME = ElementX; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "colors/accent-color"; - BASE_BUNDLE_IDENTIFIER = io.element.elementx; + BASE_BUNDLE_IDENTIFIER = ch.healthchat.element; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -10360,7 +10384,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 7J4U792NQT; + DEVELOPMENT_TEAM = 6G37K72W82; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -10386,7 +10410,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - PRODUCTION_APP_NAME = Element; + PRODUCTION_APP_NAME = HealthChat; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = NO; diff --git a/ElementX/Resources/AppIcon.icon/Assets/AppIcon.png b/ElementX/Resources/AppIcon.icon/Assets/AppIcon.png index 99f361dca..628119a21 100644 Binary files a/ElementX/Resources/AppIcon.icon/Assets/AppIcon.png and b/ElementX/Resources/AppIcon.icon/Assets/AppIcon.png differ diff --git a/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/Contents.json index 87959425c..5e4a02eef 100644 --- a/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/Contents.json +++ b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/Contents.json @@ -1,15 +1,23 @@ { "images" : [ { - "filename" : "app-logo.pdf", - "idiom" : "universal" + "filename" : "app-logo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "app-logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "app-logo@3x.png", + "idiom" : "universal", + "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true } } diff --git a/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo.pdf b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo.pdf deleted file mode 100644 index 3189df079..000000000 Binary files a/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo.png b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo.png new file mode 100644 index 000000000..9a149366c Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo.png differ diff --git a/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo@2x.png b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo@2x.png new file mode 100644 index 000000000..86dd2a580 Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo@2x.png differ diff --git a/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo@3x.png b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo@3x.png new file mode 100644 index 000000000..394f99ce2 Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/images/app-logo.imageset/app-logo@3x.png differ diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index be5cd08b2..6961fb531 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -1,4 +1,5 @@ "screen_home_tab_search" = "Search"; +"screen_onboarding_welcome_title_ios" = "Welcome to HealthChat"; "screen_search_empty_state_message" = "Search for chats and messages"; "screen_search_empty_state_title" = "Start searching..."; "screen_search_no_results_message" = "There are no results for “%1$@.” Try a new search term."; diff --git a/ElementX/Sources/AppHooks/HealthChat/HealthChatAppHooks.swift b/ElementX/Sources/AppHooks/HealthChat/HealthChatAppHooks.swift new file mode 100644 index 000000000..1c71859e2 --- /dev/null +++ b/ElementX/Sources/AppHooks/HealthChat/HealthChatAppHooks.swift @@ -0,0 +1,21 @@ +// +// Copyright 2026 Novaloop AG +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +/// Registers the HealthChat hooks. +/// +/// `AppHooks` is a concrete type and `AppCoordinator` calls `setUp()` on it directly, so this method +/// wins over the empty `AppHooksProtocol` default via static dispatch — mirroring how Element's own +/// Enterprise fork provides its `setUp()`. This file is compiled only into the main app; the NSE and +/// Share Extension keep the empty default (they never drive authentication). +extension AppHooks { + func setUp() { + #if IS_MAIN_APP + registerAppSettingsHook(HealthChatAppSettingsHook()) + registerCompoundHook(HealthChatCompoundHook()) + #endif + } +} diff --git a/ElementX/Sources/AppHooks/HealthChat/HealthChatAppSettingsHook.swift b/ElementX/Sources/AppHooks/HealthChat/HealthChatAppSettingsHook.swift new file mode 100644 index 000000000..161ef8d97 --- /dev/null +++ b/ElementX/Sources/AppHooks/HealthChat/HealthChatAppSettingsHook.swift @@ -0,0 +1,40 @@ +// +// Copyright 2026 Novaloop AG +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Foundation + +/// Pins HealthChat's server, OIDC and push configuration by overriding `AppSettings` at startup. +/// +/// - `accountProviders` / `allowOtherAccountProviders` lock every user to `healthchat.ch` (the server name; +/// `.well-known` delegates to `matrix.healthchat.ch`) and hide the change-server / create-account UI. +/// - `oAuthRedirectURL` uses the app's own URL scheme so the OIDC/MAS callback needs no associated domain. +/// - Legacy `element.io` help/encryption/copyright URLs are passed through unchanged for now. +struct HealthChatAppSettingsHook: AppSettingsHookProtocol { + func configure(_ appSettings: AppSettings) -> AppSettings { + appSettings.override(accountProviders: ["healthchat.ch"], + allowOtherAccountProviders: false, + hideBrandChrome: false, + pushGatewayBaseURL: "https://sygnal.healthchat.ch", + oAuthRedirectURL: "ch.healthchat.element:/oauth/callback", + websiteURL: "https://healthchat.ch", + logoURL: "https://app.healthchat.ch/themes/element/img/logos/element-logo.png", + copyrightURL: appSettings.copyrightURL, + acceptableUseURL: "https://www.healthchat.ch/datenschutz", + privacyURL: "https://www.healthchat.ch/datenschutz", + encryptionURL: appSettings.encryptionURL, + deviceVerificationURL: appSettings.deviceVerificationURL, + chatBackupDetailsURL: appSettings.chatBackupDetailsURL, + identityPinningViolationDetailsURL: appSettings.identityPinningViolationDetailsURL, + historySharingDetailsURL: appSettings.historySharingDetailsURL, + elementWebHosts: ["app.healthchat.ch"], + accountProvisioningHost: appSettings.accountProvisioningHost, + bugReportApplicationID: appSettings.bugReportApplicationID, + analyticsTermsURL: appSettings.analyticsTermsURL, + mapTilerConfiguration: AppSettings.bundledMapTilerConfiguration) + return appSettings + } +} diff --git a/ElementX/Sources/AppHooks/HealthChat/HealthChatBrandColors.swift b/ElementX/Sources/AppHooks/HealthChat/HealthChatBrandColors.swift new file mode 100644 index 000000000..ca13a1917 --- /dev/null +++ b/ElementX/Sources/AppHooks/HealthChat/HealthChatBrandColors.swift @@ -0,0 +1,33 @@ +// +// Copyright 2026 Novaloop AG +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import UIKit + +/// HealthChat brand palette, ported from the legacy Element iOS client. +/// These light/dark values are applied to Compound's accent token family by `HealthChatCompoundHook`. +/// +/// **Note:** `nonisolated` is required. This target defaults to `MainActor` isolation, but UIKit resolves +/// the dynamic colour providers below on SwiftUI's render thread, which traps on an isolated closure. +nonisolated enum HealthChatBrandColors { + /// Primary brand blue — light `#1F4D85`, dark `#376A91`. + static let brand = dynamic(light: hex(0x1F4D85), dark: hex(0x376A91)) + /// Pressed state — a slightly darker brand blue. + static let brandPressed = dynamic(light: hex(0x17385F), dark: hex(0x2A5171)) + /// Subtle accent background / tint — light `#EBF0F7`, dark `#1A2D47`. + static let tint = dynamic(light: hex(0xEBF0F7), dark: hex(0x1A2D47)) + + private static func dynamic(light: UIColor, dark: UIColor) -> UIColor { + UIColor { $0.userInterfaceStyle == .dark ? dark : light } + } + + private static func hex(_ value: Int, alpha: CGFloat = 1) -> UIColor { + UIColor(red: CGFloat((value >> 16) & 0xFF) / 255, + green: CGFloat((value >> 8) & 0xFF) / 255, + blue: CGFloat(value & 0xFF) / 255, + alpha: alpha) + } +} diff --git a/ElementX/Sources/AppHooks/HealthChat/HealthChatCompoundHook.swift b/ElementX/Sources/AppHooks/HealthChat/HealthChatCompoundHook.swift new file mode 100644 index 000000000..fac80258e --- /dev/null +++ b/ElementX/Sources/AppHooks/HealthChat/HealthChatCompoundHook.swift @@ -0,0 +1,84 @@ +// +// Copyright 2026 Novaloop AG +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Compound +import SwiftUI +import UIKit + +/// Recolours Compound's accent token family to the HealthChat brand. +/// Applied once at startup from `AppCoordinator` via `AppHooks.compoundHook`. Because the override +/// runs at app startup (not in previews), preview snapshots keep the default Compound colours. +struct HealthChatCompoundHook: CompoundHookProtocol { + func override(colors: CompoundColors, uiColors: CompoundUIColors) { + let brand = HealthChatBrandColors.brand + let tint = HealthChatBrandColors.tint + + // Solid accent → brand blue (primary + send buttons, links, accent icons, badges, borders). + // NB: bgAccentRest is a SOLID fill (behind a white icon on the send button), not a subtle tint. + let solidColorTokens: [KeyPath] = [ + \.bgActionPrimaryRest, + \.bgAccentRest, + \.iconAccentPrimary, + \.iconAccentTertiary, + \.textActionPrimary, + \.textActionAccent, + \.textLinkExternal, + \.borderInteractivePrimary, + \.textBadgeAccent + ] + let solidUIColorTokens: [KeyPath] = [ + \.bgActionPrimaryRest, + \.bgAccentRest, + \.iconAccentPrimary, + \.iconAccentTertiary, + \.textActionPrimary, + \.textActionAccent, + \.textLinkExternal, + \.borderInteractivePrimary, + \.textBadgeAccent + ] + for keyPath in solidColorTokens { + colors.override(keyPath, with: Color(brand)) + } + for keyPath in solidUIColorTokens { + uiColors.override(keyPath, with: brand) + } + + // Primary button pressed state. The disabled state stays Compound's neutral grey. + colors.override(\.bgActionPrimaryPressed, with: Color(HealthChatBrandColors.brandPressed)) + uiColors.override(\.bgActionPrimaryPressed, with: HealthChatBrandColors.brandPressed) + + // Gradient "super"/action buttons → brand blue (SwiftUI-only concept). + let gradientTokens: [KeyPath] = [ + \.gradientActionStop1, + \.gradientActionStop2, + \.gradientActionStop3, + \.gradientActionStop4 + ] + for keyPath in gradientTokens { + colors.override(keyPath, with: Color(brand)) + } + + // Subtle accent backgrounds (paired with accent-coloured foregrounds) → brand tint. + let tintColorTokens: [KeyPath] = [ + \.bgAccentSelected, + \.bgBadgeAccent, + \.borderAccentSubtle + ] + let tintUIColorTokens: [KeyPath] = [ + \.bgAccentSelected, + \.bgBadgeAccent, + \.borderAccentSubtle + ] + for keyPath in tintColorTokens { + colors.override(keyPath, with: Color(tint)) + } + for keyPath in tintUIColorTokens { + uiColors.override(keyPath, with: tint) + } + } +} diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index a2539f737..0ea846a90 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -12,6 +12,8 @@ import Foundation internal nonisolated enum UntranslatedL10n { /// Search internal static var screenHomeTabSearch: String { return UntranslatedL10n.tr("Untranslated", "screen_home_tab_search") } + /// Welcome to HealthChat + internal static var screenOnboardingWelcomeTitleIos: String { return UntranslatedL10n.tr("Untranslated", "screen_onboarding_welcome_title_ios") } /// Search for chats and messages internal static var screenSearchEmptyStateMessage: String { return UntranslatedL10n.tr("Untranslated", "screen_search_empty_state_message") } /// Start searching... diff --git a/ElementX/Sources/Screens/Authentication/StartScreen/View/AuthenticationStartScreen.swift b/ElementX/Sources/Screens/Authentication/StartScreen/View/AuthenticationStartScreen.swift index 910be583f..f04d12d65 100644 --- a/ElementX/Sources/Screens/Authentication/StartScreen/View/AuthenticationStartScreen.swift +++ b/ElementX/Sources/Screens/Authentication/StartScreen/View/AuthenticationStartScreen.swift @@ -76,7 +76,7 @@ struct AuthenticationStartScreen: View { if !context.viewState.hideBrandChrome { VStack(spacing: 8) { - Text(L10n.screenOnboardingWelcomeTitle) + Text(UntranslatedL10n.screenOnboardingWelcomeTitleIos) .font(.compound.headingLGBold) .foregroundColor(.compound.textPrimary) .multilineTextAlignment(.center) diff --git a/ElementX/SupportingFiles/ElementX.entitlements b/ElementX/SupportingFiles/ElementX.entitlements index 87136945b..6bb0cab4f 100644 --- a/ElementX/SupportingFiles/ElementX.entitlements +++ b/ElementX/SupportingFiles/ElementX.entitlements @@ -6,13 +6,9 @@ development com.apple.developer.associated-domains - applinks:element.io - applinks:app.element.io - applinks:staging.element.io - applinks:develop.element.io - applinks:mobile.element.io + applinks:app.healthchat.ch applinks:matrix.to - webcredentials:*.element.io + webcredentials:*.healthchat.ch com.apple.developer.usernotifications.communication @@ -21,14 +17,12 @@ com.apple.security.application-groups $(APP_GROUP_IDENTIFIER) - $(CLASSIC_APP_GROUP_IDENTIFIER) com.apple.security.network.client keychain-access-groups $(KEYCHAIN_ACCESS_GROUP_IDENTIFIER) - $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER) diff --git a/ElementX/SupportingFiles/Info.plist b/ElementX/SupportingFiles/Info.plist index 0c956f384..dbd532d2f 100644 --- a/ElementX/SupportingFiles/Info.plist +++ b/ElementX/SupportingFiles/Info.plist @@ -30,7 +30,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - $(PRODUCT_NAME) + $(APP_DISPLAY_NAME) CFBundlePackageType APPL CFBundleShortVersionString diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index 4b16c08c1..aa9fd2f0e 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -51,6 +51,9 @@ targets: UILaunchScreen: UIColorName: colors/background-color CFBundleDisplayName: $(APP_DISPLAY_NAME) + # HealthChat: without this CFBundleName defaults to $(PRODUCT_NAME) (ElementX, kept for the module + # name), which is what the web authentication session's sign-in alert shows — not CFBundleDisplayName. + CFBundleName: $(APP_DISPLAY_NAME) CFBundleShortVersionString: $(MARKETING_VERSION) CFBundleVersion: $(CURRENT_PROJECT_VERSION) CFBundleURLTypes: [ @@ -118,23 +121,19 @@ targets: properties: aps-environment: development com.apple.developer.associated-domains: - - applinks:element.io - - applinks:app.element.io - - applinks:staging.element.io - - applinks:develop.element.io - - applinks:mobile.element.io + # HealthChat: app.healthchat.ch universal links + webcredentials (OAuth/autofill). matrix.to kept for permalinks. + - applinks:app.healthchat.ch - applinks:matrix.to - # - applinks:localhost?mode=developer # developer mode only works with https (but self-signed is fine). - - webcredentials:*.element.io + - webcredentials:*.healthchat.ch com.apple.developer.usernotifications.communication: true com.apple.security.app-sandbox: true com.apple.security.application-groups: + # HealthChat: dropped $(CLASSIC_APP_GROUP_IDENTIFIER) (group.im.vector) — owned by Element's team, not provisionable here. - $(APP_GROUP_IDENTIFIER) - - $(CLASSIC_APP_GROUP_IDENTIFIER) com.apple.security.network.client: true keychain-access-groups: + # HealthChat: dropped $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER) (im.vector.app.*) — not provisionable here. - $(KEYCHAIN_ACCESS_GROUP_IDENTIFIER) - - $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER) settings: base: diff --git a/Variants/HealthChat/healthchat.yml b/Variants/HealthChat/healthchat.yml new file mode 100644 index 000000000..c03356763 --- /dev/null +++ b/Variants/HealthChat/healthchat.yml @@ -0,0 +1,13 @@ +# HealthChat product identity. +# Included permanently from project.yml (this fork always builds as HealthChat). +# Only build-setting scalars belong here — XcodeGen concatenates arrays on include-merge, +# so entitlement arrays (associated-domains, app-groups, keychain-groups) are edited directly +# in ElementX/SupportingFiles/target.yml instead. +# NB: APP_NAME (internal PRODUCT_NAME / module name) is intentionally left as ElementX so that +# `@testable import ElementX` and $(PRODUCT_MODULE_NAME) keep working. +settings: + APP_DISPLAY_NAME: HealthChat + PRODUCTION_APP_NAME: HealthChat + APP_GROUP_IDENTIFIER: group.ch.healthchat + BASE_BUNDLE_IDENTIFIER: ch.healthchat.element + DEVELOPMENT_TEAM: 6G37K72W82 diff --git a/project.yml b/project.yml index 37068a41e..8b7578b7e 100644 --- a/project.yml +++ b/project.yml @@ -68,7 +68,7 @@ include: - path: Components/MapLibre/MapLibreInterface/SupportingFiles/target.yml - path: Components/MapLibre/MapLibreShim/SupportingFiles/target.yml - path: Components/SDKMocks/SupportingFiles/target.yml -# - path: MyAppVariant/override.yml +- path: Variants/HealthChat/healthchat.yml packages: # Element/Matrix dependencies