mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-09 16:22:55 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54db79d0ad | |||
| 0431543c5c | |||
| f6d80ee936 | |||
| be0e75fe68 | |||
| 3e353857dd | |||
| e01f44cd87 | |||
| 4b3cd2c1d0 | |||
| f741adfbf4 | |||
| 6b81aeef3f | |||
| 2bd8865d4e | |||
| e4d4541c75 | |||
| 0588cdc542 | |||
| 68ef01cc87 | |||
| a7f8f667ff | |||
| 16c6727d29 | |||
| 01dc703b48 |
@@ -0,0 +1,298 @@
|
|||||||
|
---
|
||||||
|
name: releasing-healthchat
|
||||||
|
description: Cut a HealthChat TestFlight release of element-x-ios by tagging healthchat-v*. Use when the user asks to release, publish, tag, deploy, ship, or cut a new HealthChat iOS version, asks about the TestFlight or App Store release process, asks about renewing the Apple Distribution signing certificate, or asks about the required GitHub Actions secrets for the release pipeline.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Releasing HealthChat (element-x-ios)
|
||||||
|
|
||||||
|
Releases are driven by git tags of the form `healthchat-v<version>` pushed to
|
||||||
|
`origin`. The tag triggers `.github/workflows/release-healthchat.yml`, which runs
|
||||||
|
on the org's self-hosted macOS runner, builds a signed App Store IPA, and uploads
|
||||||
|
it to **TestFlight only**. No GitHub Release is created.
|
||||||
|
|
||||||
|
**App Store submission is always a manual step** in App Store Connect after a
|
||||||
|
TestFlight build has been validated — see "Promoting to App Store" below.
|
||||||
|
|
||||||
|
**HealthChat ships as an in-place update of the legacy element-ios app.** Same
|
||||||
|
bundle id (`ch.healthchat.element`), same team (`6G37K72W82`), same App Store
|
||||||
|
record. Existing users' keychain and app-group container survive the update. Read
|
||||||
|
"The migration contract" before changing anything that looks like an identifier.
|
||||||
|
|
||||||
|
## Preflight checklist
|
||||||
|
|
||||||
|
Verify each item before tagging. If any fail, fix and commit first.
|
||||||
|
|
||||||
|
- [ ] On branch `release/healthchat` with a clean working tree (`git status`).
|
||||||
|
- [ ] `HEALTHCHAT_CHANGES.md` has a section `## Changes in <new-version> (<date>)`
|
||||||
|
at the top. The pipeline's awk extractor matches this exact heading format;
|
||||||
|
no match ships a fallback placeholder as the TestFlight "What to Test" text.
|
||||||
|
- [ ] The version is **numeric only** — see "Version rules". This is the single
|
||||||
|
most common way to waste 30 minutes here.
|
||||||
|
- [ ] `CHANGES.md` is **not** used for HealthChat releases — upstream's
|
||||||
|
`release-to-github` owns it. Only edit `HEALTHCHAT_CHANGES.md`.
|
||||||
|
|
||||||
|
## Version rules
|
||||||
|
|
||||||
|
**Numeric only. One to three dot-separated integers.** `26.7.4`. Never `26.7.4-rc1`.
|
||||||
|
|
||||||
|
The marketing version lands verbatim in `CFBundleShortVersionString`, and Apple
|
||||||
|
rejects anything that is not at most three period-separated integers. A marker
|
||||||
|
version passes the tag regex and every local gate, then dies at the **upload
|
||||||
|
step, after ~30 minutes of signing**, with an ITMS error indistinguishable from a
|
||||||
|
signing failure.
|
||||||
|
|
||||||
|
The Fastfile guards this in `release_testflight`, before any build or signing work. Do not relax that guard.
|
||||||
|
|
||||||
|
The legacy element-ios skill documents `-rc1` / `-hotfix1` / `-renew-test` tag
|
||||||
|
suffixes. **Do not carry that convention over.** The workflow derives the version
|
||||||
|
by stripping only the `healthchat-v` prefix, so a suffix becomes part of the
|
||||||
|
version and hits the wall above. Put markers in the release notes, and use the
|
||||||
|
`workflow_dispatch` path for test builds.
|
||||||
|
|
||||||
|
`project.yml:56`'s `MARKETING_VERSION` is only a default — **the tag is the source
|
||||||
|
of truth**, and the pipeline injects the version at build time without touching
|
||||||
|
any tracked file. Rebases from upstream will drag `project.yml`'s version around
|
||||||
|
(`automatic-calendar-version.yml` no-ops in this fork); ignore it.
|
||||||
|
|
||||||
|
## Cutting the release (normal path)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from release/healthchat, fully committed:
|
||||||
|
git tag healthchat-v<MAJOR>.<MINOR>.<PATCH>
|
||||||
|
git push origin healthchat-v<MAJOR>.<MINOR>.<PATCH>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example: `git tag healthchat-v26.7.4`
|
||||||
|
|
||||||
|
The `healthchat-` prefix keeps these tags clear of upstream's `release/<version>`
|
||||||
|
and `nightly/*` namespaces.
|
||||||
|
|
||||||
|
The workflow triggers automatically. Watch it with `gh run watch` or the Actions
|
||||||
|
UI. The TestFlight build appears in App Store Connect within a few minutes of the
|
||||||
|
run finishing (Apple's processing queue).
|
||||||
|
|
||||||
|
**Build numbers are not encoded in tags.** `generate_build_number` in
|
||||||
|
`fastlane/Fastfile` produces a `YYYYMMDDHHMMSS` timestamp, so ordering is
|
||||||
|
monotonic and a retry never collides with a build number Apple has already seen.
|
||||||
|
|
||||||
|
## Manual / retry path (workflow_dispatch)
|
||||||
|
|
||||||
|
Use when retrying a failed run, testing the pipeline (e.g. cert-renewal
|
||||||
|
verification), or rebuilding without moving tags.
|
||||||
|
|
||||||
|
1. GitHub → Actions → "Release HealthChat" → **Run workflow**.
|
||||||
|
2. Fill in:
|
||||||
|
- **version** (required): e.g. `26.7.99`. Numeric only — same rule as tags.
|
||||||
|
- **release_notes_override** (optional): replaces the `HEALTHCHAT_CHANGES.md`
|
||||||
|
lookup. Use this to mark test builds, since the version cannot carry a marker.
|
||||||
|
3. Run. The IPA is built and uploaded to TestFlight exactly as the tag path would.
|
||||||
|
|
||||||
|
## Release notes
|
||||||
|
|
||||||
|
`HEALTHCHAT_CHANGES.md` is the single source. The workflow extracts the section
|
||||||
|
matching the version being released. Format:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Changes in 26.7.4 (2026-08-01)
|
||||||
|
|
||||||
|
🙌 Improvements
|
||||||
|
|
||||||
|
- Bullet one.
|
||||||
|
- Bullet two.
|
||||||
|
```
|
||||||
|
|
||||||
|
No matching section emits a workflow warning and falls back to
|
||||||
|
`HealthChat <version> — automated build from <ref>.` Prefer fixing the changelog.
|
||||||
|
A **missing file** is a hard error, not a fallback.
|
||||||
|
|
||||||
|
## After the release
|
||||||
|
|
||||||
|
- [ ] Workflow run is green in GitHub Actions.
|
||||||
|
- [ ] Build appears under App Store Connect → TestFlight within ~10 minutes.
|
||||||
|
- [ ] Internal testers are invited (the auto-distribution group may handle this).
|
||||||
|
- [ ] Artifacts (IPA, dSYM, Gym logs) attached to the run, retained 90 days.
|
||||||
|
|
||||||
|
## Never commit the post-lane tree
|
||||||
|
|
||||||
|
A local lane run rewrites `ElementX.xcodeproj/project.pbxproj` — `xcodegen`
|
||||||
|
regenerates it, then `disable_automatic_code_signing` and
|
||||||
|
`update_project_provisioning` write Manual signing and profile UUIDs into it. That
|
||||||
|
file is tracked.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -- ElementX.xcodeproj
|
||||||
|
```
|
||||||
|
|
||||||
|
Committing it pins every future build to one machine's profile UUIDs.
|
||||||
|
|
||||||
|
## Required GitHub Actions secrets
|
||||||
|
|
||||||
|
Five. Missing any fails the workflow at the signing step with a clear error.
|
||||||
|
|
||||||
|
| Secret | Source |
|
||||||
|
|---|---|
|
||||||
|
| `HEALTHCHAT_ASC_KEY_ID` | App Store Connect → Users and Access → Integrations → Team Keys (10-char Key ID) |
|
||||||
|
| `HEALTHCHAT_ASC_KEY_ISSUER_ID` | Same page, UUID at the top |
|
||||||
|
| `HEALTHCHAT_ASC_KEY_CONTENT` | `.p8` contents including the `-----BEGIN/END PRIVATE KEY-----` lines |
|
||||||
|
| `HEALTHCHAT_CERTIFICATES_P12` | Base64 distribution cert (`base64 -i healthchat.p12 \| pbcopy`) |
|
||||||
|
| `HEALTHCHAT_CERTIFICATES_P12_PASSWORD` | Password used when exporting the `.p12` |
|
||||||
|
|
||||||
|
**There is no `HEALTHCHAT_KEYCHAIN_PASSWORD`.** The legacy skill lists one and the
|
||||||
|
legacy workflow references it, but it exists at neither repo nor org level and
|
||||||
|
that pipeline runs green anyway — `import-codesign-certs` tolerates its absence.
|
||||||
|
Do not add it back.
|
||||||
|
|
||||||
|
Managing these needs the `snowping` GitHub account; this repo is INTERNAL and
|
||||||
|
invisible to the other account.
|
||||||
|
|
||||||
|
## Self-hosted runner requirements
|
||||||
|
|
||||||
|
Runs on `novaloop-mac-studio-001` — an **org-level** runner (`[self-hosted,
|
||||||
|
macOS, ARM64]`) shared with the legacy fork, not registered per-repo.
|
||||||
|
|
||||||
|
- **One Xcode, globally selected.** A single install at `/Applications/Xcode.app`
|
||||||
|
(**26.6**, build `17F113`, today) — no versioned `Xcode_*.app` siblings.
|
||||||
|
**Nothing pins it.** The Fastfile builds with whatever `xcode-select` points at, so
|
||||||
|
**whatever the runner has is what ships**. Upgrading the runner's Xcode silently
|
||||||
|
changes the compiler every subsequent release is built with — no tag, no diff, no
|
||||||
|
review. Treat that upgrade as a release-affecting change.
|
||||||
|
- **Do not reintroduce a version pin.** Pinning a version the runner lacks sends
|
||||||
|
`xcodes` into a multi-GB authenticated download that no CI secret can satisfy, and
|
||||||
|
the run hangs. Change the runner's Xcode instead.
|
||||||
|
- **Toolchain installed once by hand**, never by CI: `xcodegen swiftlint swiftformat
|
||||||
|
xcbeautify xcresultparser pkl` (`xcresultparser` is not in homebrew-core — only
|
||||||
|
`a7ex/homebrew-formulae/xcresultparser` ships it). `git-lfs`, `xcodes`, `brew` and
|
||||||
|
rbenv `ruby`/`bundle` are already present. There is deliberately **no `brew bundle`
|
||||||
|
step and no `Brewfile`**: this machine is shared with CI, and a per-run
|
||||||
|
`brew update` could upgrade `xcodegen` underneath a running release. CI asserts the
|
||||||
|
tools are present and fails loudly naming the fix; it never provisions.
|
||||||
|
- **No passwordless sudo.** Nothing in CI may call `sudo` — that is why upstream's
|
||||||
|
`setup_github_actions_environment` cannot run here.
|
||||||
|
- **`APPLE_ID`** exported as a machine-level shell variable — the `Appfile` reads
|
||||||
|
it and it is not carried in any secret.
|
||||||
|
- Network access to `developer.apple.com`, `api.appstoreconnect.apple.com`,
|
||||||
|
`github.com`, `rubygems.org`.
|
||||||
|
|
||||||
|
**A release's only Xcode provenance is its own log.** `release-healthchat.yml` runs
|
||||||
|
`xcodebuild -version` (`Record toolchain`) before every build, so each run records the
|
||||||
|
compiler that produced it. Nothing pins that version — upgrading the runner's Xcode
|
||||||
|
silently changes the compiler every subsequent release is built with, and that log
|
||||||
|
line is the only place it shows up. Do not reconstruct a past build's toolchain from
|
||||||
|
the runner: it answers for today, not for the build you are asking about. Read the
|
||||||
|
run's log.
|
||||||
|
|
||||||
|
## Renewing the Apple Distribution certificate (annual)
|
||||||
|
|
||||||
|
Apple Distribution certificates are valid for **exactly 1 year**. There is no
|
||||||
|
longer-lived option. Last renewed **2026-04-07**, so the next is due around
|
||||||
|
**April 2027**. Renew proactively.
|
||||||
|
|
||||||
|
1. **~4 weeks before expiry**, check on the Mac holding the private key:
|
||||||
|
```bash
|
||||||
|
security find-certificate -c "Apple Distribution: E-HealthSolutions GmbH" -p | openssl x509 -noout -enddate
|
||||||
|
```
|
||||||
|
2. **Generate a CSR** on whichever Mac will hold the new private key: Keychain
|
||||||
|
Access → Certificate Assistant → Request a Certificate from a Certificate
|
||||||
|
Authority… → "Saved to disk".
|
||||||
|
3. **Apple Developer portal → Certificates** → **+** → Apple Distribution → upload
|
||||||
|
the CSR → download the `.cer` → double-click to install.
|
||||||
|
4. **Regenerate all 3 App Store provisioning profiles** — they reference the old
|
||||||
|
cert and become invalid:
|
||||||
|
- `HealthChat App Store`
|
||||||
|
- `HealthChat NSE: App Store`
|
||||||
|
- `HealthChat Share Extension: App Store`
|
||||||
|
5. **Verify locally**: archive in Xcode → Distribute App → App Store Connect →
|
||||||
|
Upload. TestFlight accepting the build proves the cert works.
|
||||||
|
6. **Export as `.p12`**: Keychain Access → My Certificates → right-click the new
|
||||||
|
`Apple Distribution: E-HealthSolutions GmbH` → Export → set a password.
|
||||||
|
7. **Re-encode and update secrets**: `base64 -i healthchat-new.p12 | pbcopy`, then
|
||||||
|
replace `HEALTHCHAT_CERTIFICATES_P12` and `HEALTHCHAT_CERTIFICATES_P12_PASSWORD`.
|
||||||
|
8. **Run the workflow** via `workflow_dispatch` with a throwaway numeric version
|
||||||
|
to verify end-to-end. Discard the resulting build.
|
||||||
|
9. **Revoke the old cert** once the new one works in both Xcode and CI.
|
||||||
|
|
||||||
|
**Keep the expired `.p12` archived for 2 weeks** in case a rollback is needed.
|
||||||
|
|
||||||
|
## Promoting a TestFlight build to the App Store
|
||||||
|
|
||||||
|
TestFlight and App Store builds are the same binary. To release a validated one:
|
||||||
|
|
||||||
|
1. App Store Connect → My Apps → HealthChat → **TestFlight** → find the build.
|
||||||
|
2. Confirm it passed internal testing.
|
||||||
|
3. **Distribution** tab → **+** next to "iOS App" → "Prepare for Submission" →
|
||||||
|
fill in metadata → **Add for Review**.
|
||||||
|
4. Wait for review (24–48 hours typical).
|
||||||
|
5. Release manually, or configure automatic release.
|
||||||
|
|
||||||
|
**Deliberately not automated.** Automating submission needs `fastlane/metadata/`
|
||||||
|
plus HealthChat-branded screenshots (neither exists) and a `Deliverfile`
|
||||||
|
refactored for API key auth.
|
||||||
|
|
||||||
|
**Before the first submission**, confirm the install base against the iOS floor:
|
||||||
|
element-x requires **iOS 18.5**, the legacy required 15.1. Every user below 18.5
|
||||||
|
stays on 1.4.1 forever — the App Store will not offer them the update, silently.
|
||||||
|
That floor is upstream's and cannot be lowered.
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
- **Bad build, notes fine** → fix, commit, tag the next patch version. TestFlight
|
||||||
|
supersedes the bad build. (Build numbers are timestamps, so no version games.)
|
||||||
|
- **Bad release notes only** → edit `HEALTHCHAT_CHANGES.md`, then update the
|
||||||
|
TestFlight "What to Test" text in App Store Connect by hand — the pipeline only
|
||||||
|
writes it at upload time.
|
||||||
|
- **Wrong version number** → treat as burned; tag a higher one.
|
||||||
|
- **Rolling back to legacy 1.4.1 loses push** — element-x overwrites the user's
|
||||||
|
sygnal pusher in place on first launch (same app id and pushkey).
|
||||||
|
|
||||||
|
## The migration contract
|
||||||
|
|
||||||
|
**Read this before "cleaning up" anything.** HealthChat replaces the legacy app in
|
||||||
|
place, and these are what stop existing users being silently logged out.
|
||||||
|
|
||||||
|
- **`CLASSIC_APP_*` in `Variants/HealthChat/healthchat.yml` is load-bearing.**
|
||||||
|
`ClassicAppManager` reads the legacy account and crypto-store keys through them.
|
||||||
|
Upstream's defaults in `app.yml` point at Element's own `group.im.vector` /
|
||||||
|
`im.vector.app.*`, which this team cannot provision. **Never restore upstream's
|
||||||
|
values.** Failure is silent — `loadAccounts()` throws, and
|
||||||
|
`AuthenticationService` logs it without telling the user or you.
|
||||||
|
- **The classic keychain group in `ElementX/SupportingFiles/target.yml` must stay
|
||||||
|
in the `keychain-access-groups` array.** Entitlement arrays live there, never in
|
||||||
|
the variant — XcodeGen concatenates arrays on include-merge and only overrides
|
||||||
|
scalars.
|
||||||
|
- **The `.requiresBackup` fall-through in `AuthenticationStartScreenViewModel` is a
|
||||||
|
deliberate upstream divergence.** Upstream sends those accounts to a sheet that
|
||||||
|
tells the user to open Element Classic. That app no longer exists — this update
|
||||||
|
replaced it — and the sheet's button opens a URL scheme nothing registers. A
|
||||||
|
rebase that reverts this reintroduces a dead button.
|
||||||
|
- **Both apps share the `group.ch.healthchat` UserDefaults suite**
|
||||||
|
(`AppSettings.swift:56`). An unqualified `@UserPreference` name lands in a suite
|
||||||
|
the legacy also writes to. Collisions are cheap to introduce.
|
||||||
|
- **`hasSignedInBefore` voids migration testing.** It lives in that shared suite,
|
||||||
|
survives app deletion, and short-circuits `loadAccounts()`. Any device that has
|
||||||
|
run an element-x HealthChat build shows a plain login screen *while migration
|
||||||
|
works perfectly*. Test on a device that has never run an element-x HealthChat
|
||||||
|
build, or clear the `group.ch.healthchat` suite first;
|
||||||
|
`"Already signed in to Element X before, skipping loadAccounts"` in the log
|
||||||
|
means the run is **void, not failed**. A
|
||||||
|
`"Classic App not configured, skipping loadAccounts"` line is the opposite —
|
||||||
|
the `CLASSIC_APP_*` Info.plist keys are missing, `ClassicAppManager` never
|
||||||
|
initialised, and migration is broken for every user. Investigate, do not
|
||||||
|
dismiss.
|
||||||
|
|
||||||
|
## Known branch divergence on rebase
|
||||||
|
|
||||||
|
`fastlane/`, `.github/workflows/release-healthchat.yml`, `HEALTHCHAT_CHANGES.md`,
|
||||||
|
`Gemfile` and this skill are **fork-only** — upstream element-x-ios has
|
||||||
|
no `fastlane/` directory at all. A rebase conflict in any of them means something
|
||||||
|
upstream has started to overlap; keep `--ours` and investigate.
|
||||||
|
|
||||||
|
What does need watching:
|
||||||
|
|
||||||
|
- **`project.yml`'s `MARKETING_VERSION`** — `automatic-calendar-version.yml` and
|
||||||
|
`post-release.yml` are gated on `github.repository == 'element-hq/element-x-ios'`
|
||||||
|
and silently no-op here, so rebases drag upstream's version along. Harmless: the
|
||||||
|
tag is the source of truth.
|
||||||
|
- **`AuthenticationStartScreenViewModel.login(classicAppAccount:)`** — the only
|
||||||
|
upstream logic file this fork edits. See "The migration contract".
|
||||||
|
- **`app.yml`'s `CLASSIC_APP_*`** — upstream owns these and they stay pointed at
|
||||||
|
Element's ids. The variant overrides them. Do not "fix" `app.yml`.
|
||||||
@@ -11,6 +11,11 @@ permissions: {}
|
|||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: Tests
|
name: Tests
|
||||||
|
# HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, because
|
||||||
|
# permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Gated rather
|
||||||
|
# than deleted so the file keeps rebasing cleanly. Run locally with
|
||||||
|
# `swift run tools ci accessibility-tests`.
|
||||||
|
if: github.repository == 'element-hq/element-x-ios'
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
timeout-minutes: 150
|
timeout-minutes: 150
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ permissions: {}
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
|
# HealthChat: skip in this fork. It only triggers on changes under compound-ios/, which this fork
|
||||||
|
# has never edited, so it would only ever fire on an upstream sync and go red at checkout —
|
||||||
|
# permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Gated rather
|
||||||
|
# than deleted so the file keeps rebasing cleanly.
|
||||||
|
if: github.repository == 'element-hq/element-x-ios'
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # HealthChat: this fork is INTERNAL, so a metadata-only token 404s on checkout. Upstream is public, hence permissions: {} there.
|
||||||
|
issues: read # HealthChat: Danger reads the PR via the issues API (a PR is an issue there); pull-requests alone 403s on GET /issues/{n}.
|
||||||
|
pull-requests: write # HealthChat: Danger posts its report comment and inline SwiftLint review comments (Dangerfile.swift:5 sets inline: true) with GITHUB_TOKEN.
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
||||||
group: ${{ format('danger-ci-{0}', github.ref) }}
|
group: ${{ format('danger-ci-{0}', github.ref) }}
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ permissions: {}
|
|||||||
jobs:
|
jobs:
|
||||||
integration_tests:
|
integration_tests:
|
||||||
name: Integration Tests
|
name: Integration Tests
|
||||||
|
# HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, and it is the one
|
||||||
|
# job that still calls out to Element's Codecov — its upload guard is true on schedule events, so
|
||||||
|
# it POSTs slug/branch/SHA every night with continue-on-error hiding the failure. Fixing it would
|
||||||
|
# need a real healthchat.ch test account and three new secrets, and would still not work:
|
||||||
|
# HealthChatAppSettingsHook sets allowOtherAccountProviders: false, hiding the change-server UI
|
||||||
|
# these tests drive. Gated rather than deleted so the file keeps rebasing cleanly.
|
||||||
|
if: github.repository == 'element-hq/element-x-ios'
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
name: Release HealthChat
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'healthchat-v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Marketing version (e.g. 26.7.4). Must be 1-3 dot-separated integers — Apple rejects anything else.'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
release_notes_override:
|
||||||
|
description: 'Optional release notes. If empty, falls back to the HEALTHCHAT_CHANGES.md section for the given version.'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
# Minimum needed for actions/checkout to clone an internal repo. The workflow does not push,
|
||||||
|
# comment, or create releases — everything else authenticates with the ASC key and .p12.
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-healthchat-${{ github.event.inputs.version || github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Build & upload to TestFlight
|
||||||
|
runs-on: [self-hosted, macOS]
|
||||||
|
timeout-minutes: 90
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
# This repo's only submodule is element-hq's private Enterprise repo, which Novaloop
|
||||||
|
# cannot clone. Checking out submodules fails the job on its very first step.
|
||||||
|
submodules: false
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Record toolchain
|
||||||
|
# HealthChat: nothing pins Xcode (the runner carries a single globally-selected one), so this
|
||||||
|
# log line is the only record of which compiler produced a given TestFlight build.
|
||||||
|
run: xcodebuild -version
|
||||||
|
|
||||||
|
- name: Resolve marketing version
|
||||||
|
id: version
|
||||||
|
env:
|
||||||
|
# Read via env, never `${{ }}` inside the script: the input is attacker-controlled text
|
||||||
|
# that GitHub would splice into the shell before the regex below could reject it.
|
||||||
|
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
VERSION="$INPUT_VERSION"
|
||||||
|
else
|
||||||
|
VERSION="${GITHUB_REF_NAME#healthchat-v}"
|
||||||
|
fi
|
||||||
|
# Apple caps CFBundleShortVersionString at three integers. The Fastfile guards this too,
|
||||||
|
# but failing here costs seconds instead of a signing run.
|
||||||
|
if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
|
||||||
|
echo "::error::Invalid marketing version '$VERSION' — expected 1-3 dot-separated integers, e.g. 26.7.4"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Resolved marketing version: $VERSION"
|
||||||
|
|
||||||
|
- name: Extract release notes
|
||||||
|
id: notes
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
OVERRIDE: ${{ github.event.inputs.release_notes_override }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ -n "$OVERRIDE" ]]; then
|
||||||
|
NOTES="$OVERRIDE"
|
||||||
|
echo "Using release notes from the workflow input"
|
||||||
|
elif [[ ! -f HEALTHCHAT_CHANGES.md ]]; then
|
||||||
|
echo "::error::HEALTHCHAT_CHANGES.md is missing — it is the release-notes source"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
NOTES=$(awk -v ver="$VERSION" '
|
||||||
|
/^## Changes in / {
|
||||||
|
if (found) { exit }
|
||||||
|
if ($0 ~ "## Changes in " ver "( |$|\\()") { found = 1; next }
|
||||||
|
}
|
||||||
|
found { print }
|
||||||
|
' HEALTHCHAT_CHANGES.md | sed '/^[[:space:]]*$/{N;/^\n$/D;}')
|
||||||
|
if [[ -z "$NOTES" ]]; then
|
||||||
|
NOTES="HealthChat ${VERSION} — automated build from ${GITHUB_REF_NAME}."
|
||||||
|
echo "::warning::No HEALTHCHAT_CHANGES.md section found for $VERSION — using fallback"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo 'notes<<NOTES_EOF'
|
||||||
|
echo "$NOTES"
|
||||||
|
echo 'NOTES_EOF'
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Bundle install
|
||||||
|
run: |
|
||||||
|
bundle config path vendor/bundle
|
||||||
|
bundle install --jobs 4 --retry 3
|
||||||
|
|
||||||
|
- name: Import signing certificate
|
||||||
|
uses: apple-actions/import-codesign-certs@b610f78488812c1e56b20e6df63ec42d833f2d14 # v6.0.0
|
||||||
|
with:
|
||||||
|
p12-file-base64: ${{ secrets.HEALTHCHAT_CERTIFICATES_P12 }}
|
||||||
|
p12-password: ${{ secrets.HEALTHCHAT_CERTIFICATES_P12_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build & upload to TestFlight
|
||||||
|
run: bundle exec fastlane release_testflight
|
||||||
|
env:
|
||||||
|
# Passed via env rather than lane options so multi-line notes containing quotes,
|
||||||
|
# $, or backticks survive without shell escaping.
|
||||||
|
HEALTHCHAT_MARKETING_VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
HEALTHCHAT_RELEASE_NOTES: ${{ steps.notes.outputs.notes }}
|
||||||
|
APPSTORECONNECT_KEY_ID: ${{ secrets.HEALTHCHAT_ASC_KEY_ID }}
|
||||||
|
APPSTORECONNECT_KEY_ISSUER_ID: ${{ secrets.HEALTHCHAT_ASC_KEY_ISSUER_ID }}
|
||||||
|
APPSTORECONNECT_KEY_CONTENT: ${{ secrets.HEALTHCHAT_ASC_KEY_CONTENT }}
|
||||||
|
TEAM_ID: '6G37K72W82'
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: healthchat-${{ steps.version.outputs.version }}
|
||||||
|
path: |
|
||||||
|
out/ElementX.ipa
|
||||||
|
out/ElementX.app.dSYM.zip
|
||||||
|
build/Logs/Gym/*.log
|
||||||
|
retention-days: 90
|
||||||
|
if-no-files-found: warn
|
||||||
@@ -15,6 +15,12 @@ permissions: {}
|
|||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: Tests
|
name: Tests
|
||||||
|
# HealthChat: skip in this fork. It runs nightly Mon-Fri and fails at checkout, because
|
||||||
|
# permissions: {} leaves a metadata-only token that cannot read an INTERNAL repo. Fixing the
|
||||||
|
# permission would not help: the rebrand changed the start screen without re-recording any
|
||||||
|
# UITests snapshot, so it would fail on content instead. Gated rather than deleted so the file
|
||||||
|
# keeps rebasing cleanly. Run locally with `swift run tools ci ui-tests`.
|
||||||
|
if: github.repository == 'element-hq/element-x-ios'
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
strategy:
|
strategy:
|
||||||
|
|||||||
@@ -17,7 +17,14 @@ jobs:
|
|||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
|
||||||
# Skip in forks
|
# Skip in forks
|
||||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
# HealthChat: this job can never pass in this fork — it needs
|
||||||
|
# secrets.ELEMENT_BOT_TOKEN (Element's bot, absent here) and recursively
|
||||||
|
# checks out the private element-hq/element-ios-enterprise submodule, which
|
||||||
|
# Novaloop cannot obtain. The guard below only asks whether the PR comes
|
||||||
|
# FROM a fork, not whether this repo IS one, so it does not skip us. Gate on
|
||||||
|
# the upstream repo as post-release.yml does. Gated rather than deleted so
|
||||||
|
# the file keeps rebasing cleanly against upstream.
|
||||||
|
if: ${{ github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
||||||
|
|||||||
@@ -13,24 +13,78 @@ permissions: {}
|
|||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: Tests
|
name: Tests
|
||||||
runs-on: macos-26
|
# HealthChat: GitHub-hosted macos-26 could not finish this job inside
|
||||||
|
# timeout-minutes: 60 (two attempts: 51m42s, then killed at 60m44s), and macOS
|
||||||
|
# minutes bill at 10x. novaloop-mac-studio-001 is the org's Mac Studio, already
|
||||||
|
# used by the release pipeline; its labels are [self-hosted, macOS, ARM64].
|
||||||
|
runs-on: [self-hosted, macOS]
|
||||||
|
# Upstream's 60 is kept: with previews skipped (see Run tests) the build plus unit tests measured
|
||||||
|
# ~16 min here. A run approaching this budget is a signal, not normal — the earlier 60m timeouts
|
||||||
|
# were preview tests, not the build.
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # HealthChat: this fork is INTERNAL, so a metadata-only token 404s on checkout (and on git lfs pull). Upstream is public, hence permissions: {} there.
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
# Only allow a single run of this workflow on each branch, automatically cancelling older runs.
|
||||||
group: ${{ format('unit-tests-{0}', github.ref) }}
|
group: ${{ format('unit-tests-{0}', github.ref) }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
# HealthChat: upstream passes persist-credentials: false here, which the
|
||||||
|
# composite forwards to actions/checkout — stripping the auth header before
|
||||||
|
# its separate "Git LFS Pull" step runs. Upstream can afford that because
|
||||||
|
# its repo is public and git lfs pull works anonymously; this fork is
|
||||||
|
# INTERNAL, so the LFS batch endpoint would 404. Keep the composite's own
|
||||||
|
# default (true) so the pull is authenticated with GITHUB_TOKEN.
|
||||||
- uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
- uses: nschloe/action-cached-lfs-checkout@385a8ecc719e50b8c71af6ab01a624b486b7c3bc # v1.2.5
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Setup environment
|
# HealthChat: verify only — never provision. Upstream's
|
||||||
run: source ci_scripts/ci_common.sh && setup_github_actions_environment
|
# setup_github_actions_environment cannot run on this runner: it calls
|
||||||
|
# `sudo xcode-select -s /Applications/Xcode_26.5.0.app`, but the runner has no
|
||||||
|
# passwordless sudo and no such path (only /Applications/Xcode.app exists), and
|
||||||
|
# it calls `brew update && brew install ...` — novaloop-mac-studio-001 is the
|
||||||
|
# shared release machine, so a per-run `brew update` could upgrade xcodegen
|
||||||
|
# underneath the release pipeline. These tools are installed once by hand; CI
|
||||||
|
# only asserts they are present and fails loudly, naming the fix, if not.
|
||||||
|
- name: Verify environment
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
missing=""
|
||||||
|
formulae=""
|
||||||
|
for tool in xcodegen swiftlint swiftformat xcbeautify xcresultparser pkl git-lfs; do
|
||||||
|
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||||
|
missing="$missing $tool"
|
||||||
|
case "$tool" in
|
||||||
|
# Not in homebrew-core; only this tap ships it.
|
||||||
|
xcresultparser) formulae="$formulae a7ex/homebrew-formulae/xcresultparser" ;;
|
||||||
|
*) formulae="$formulae $tool" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
echo "::error::Missing on self-hosted runner:${missing}. Install by hand on novaloop-mac-studio-001, then re-run: brew install${formulae}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# The Xcode version is no longer pinned anywhere, so this is the only record
|
||||||
|
# of which Xcode built a given run.
|
||||||
|
xcodebuild -version
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: swift run -q tools ci unit-tests
|
# HealthChat: previews are skipped. PreviewTests renders a snapshot for every preview in the
|
||||||
|
# app and was the entire reason this job could not fit in an hour — it never once finished,
|
||||||
|
# on either runner. Unit tests alone take ~16 min here. Upstream skips them the same way in
|
||||||
|
# unit-tests-enterprise.yml. Cost: preview snapshot regressions are not caught in CI, so
|
||||||
|
# re-record and check them locally when changing UI.
|
||||||
|
env:
|
||||||
|
# HealthChat: VoiceMessageCacheTests cannot write its URLFileProtection.complete cache file on
|
||||||
|
# this runner's simulator (NSCocoaErrorDomain 513) — a runner-environment issue, not a code bug:
|
||||||
|
# the suite passes locally and upstream, and neither recreating+booting the device nor clearing
|
||||||
|
# state fixes it. Skip it here only, via -skip-testing. Drop this once the runner can write
|
||||||
|
# data-protected files again.
|
||||||
|
CI_SKIP_TESTS: VoiceMessageCacheTests
|
||||||
|
run: swift run -q tools ci unit-tests --skip-previews
|
||||||
|
|
||||||
- name: Archive artifacts
|
- name: Archive artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
@@ -44,7 +98,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
# Skip if not successful and in forks
|
# Skip if not successful and in forks
|
||||||
if: ${{ success() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
# HealthChat: the existing guard only asks whether the PR comes FROM a
|
||||||
|
# fork, not whether this repo IS one, so it is true here and the step
|
||||||
|
# runs without a CODECOV_TOKEN — which fails the job via
|
||||||
|
# fail_ci_if_error. Gate on the upstream repo as post-release.yml does.
|
||||||
|
if: ${{ success() && github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||||
with:
|
with:
|
||||||
report_type: coverage
|
report_type: coverage
|
||||||
@@ -56,7 +114,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload test results to Codecov
|
- name: Upload test results to Codecov
|
||||||
# Skip if cancelled and in forks
|
# Skip if cancelled and in forks
|
||||||
if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
# HealthChat: gated on the upstream repo for the same reason as the
|
||||||
|
# coverage step above — no CODECOV_TOKEN exists in this fork.
|
||||||
|
if: ${{ !cancelled() && github.repository == 'element-hq/element-x-ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
||||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
|
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
|
||||||
|
contents: read # HealthChat: this fork is INTERNAL, so a metadata-only token 404s on checkout. Upstream is public, hence permissions: {} there.
|
||||||
|
actions: read # HealthChat: upload-sarif additionally needs this on private/internal repos; public upstream does not.
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|||||||
@@ -49,3 +49,10 @@ test_output
|
|||||||
# Only commit Pkl packages (there may be other caches in here).
|
# Only commit Pkl packages (there may be other caches in here).
|
||||||
Secrets/vendor/*
|
Secrets/vendor/*
|
||||||
!Secrets/vendor/package-2
|
!Secrets/vendor/package-2
|
||||||
|
|
||||||
|
# fastlane release outputs
|
||||||
|
out/
|
||||||
|
provisioning_profiles/
|
||||||
|
# Regenerated by every fastlane invocation, including on the runner. Ignored rather than
|
||||||
|
# committed, matching element-ios.
|
||||||
|
fastlane/README.md
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ PRs must follow rules. Prefer Xcode MCP tools over terminal commands.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Releasing
|
||||||
|
|
||||||
|
HealthChat ship via tag `healthchat-v*` → TestFlight. Full runbook in
|
||||||
|
`.agents/skills/releasing-healthchat/SKILL.md`. Read skill before release, cert
|
||||||
|
renewal, or touching `fastlane/`. No duplicate runbook here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
### Build System
|
### Build System
|
||||||
|
|||||||
@@ -615,6 +615,7 @@
|
|||||||
659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; };
|
659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; };
|
||||||
661EF50C1F7D4B0BC8A7AAE3 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44ABA63DBE7F76C58260B43B /* EmoteRoomTimelineView.swift */; };
|
661EF50C1F7D4B0BC8A7AAE3 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44ABA63DBE7F76C58260B43B /* EmoteRoomTimelineView.swift */; };
|
||||||
663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; };
|
663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; };
|
||||||
|
665314B4CB31904F107D2D3B /* HealthChatAppSettingsHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */; };
|
||||||
6681D6D3ADF69EBD2625F29A /* KnockedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E8F4D7D61B80EBD5CB92F8A /* KnockedRoomProxyMock.swift */; };
|
6681D6D3ADF69EBD2625F29A /* KnockedRoomProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E8F4D7D61B80EBD5CB92F8A /* KnockedRoomProxyMock.swift */; };
|
||||||
66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D879DC5515B1D42577F96C94 /* RoomSelectionScreen.swift */; };
|
66832DE7B5C2E861045265DC /* RoomSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D879DC5515B1D42577F96C94 /* RoomSelectionScreen.swift */; };
|
||||||
66C48B5B35C861C2089DBB12 /* ToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510DA63AB273A68D659CEC95 /* ToolbarButton.swift */; };
|
66C48B5B35C861C2089DBB12 /* ToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510DA63AB273A68D659CEC95 /* ToolbarButton.swift */; };
|
||||||
@@ -669,6 +670,7 @@
|
|||||||
6CBC544A7CC4DDDBDED4D0A6 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 4B1F71AC585827E6C416C15A /* AppIcon.icon */; };
|
6CBC544A7CC4DDDBDED4D0A6 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 4B1F71AC585827E6C416C15A /* AppIcon.icon */; };
|
||||||
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; };
|
6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; };
|
||||||
6DC8E43BA04AC2AC4EB2EB97 /* AnalyticsPromptScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18486B87745B1811E7FBD3D2 /* AnalyticsPromptScreenModels.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 */; };
|
6E03A710799E6C65C0AB36BC /* TargetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D829FD8958376614504B18 /* TargetConfiguration.swift */; };
|
||||||
6E0C68918BEC5D27A4830BA6 /* PlaybackSpeedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBB8E5436E1BE0D6814BB7D /* PlaybackSpeedButton.swift */; };
|
6E0C68918BEC5D27A4830BA6 /* PlaybackSpeedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBB8E5436E1BE0D6814BB7D /* PlaybackSpeedButton.swift */; };
|
||||||
6E391F7F628D984AF44385D9 /* RoomAttachmentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */; };
|
6E391F7F628D984AF44385D9 /* RoomAttachmentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */; };
|
||||||
@@ -933,6 +935,7 @@
|
|||||||
95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; };
|
95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; };
|
||||||
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; };
|
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; };
|
||||||
95B690EA99A3385D4B8A593D /* NotificationToneManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B94A6A57FE7E71F7605070AA /* NotificationToneManager.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 */; };
|
95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */; };
|
||||||
9603EEF6DE980BB1D15D4707 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A3E8741D199CD1A37F4CBF /* UIView.swift */; };
|
9603EEF6DE980BB1D15D4707 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A3E8741D199CD1A37F4CBF /* UIView.swift */; };
|
||||||
962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; };
|
962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; };
|
||||||
@@ -1404,6 +1407,7 @@
|
|||||||
E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; };
|
E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DFE9642F0A46ECA0497B /* BlurHashEncode.swift */; };
|
||||||
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; };
|
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; };
|
||||||
E7DB506E77CED28D6FE6203D /* ManageAuthorizedSpacesScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8E7C716B8DC2F71E9AC439 /* ManageAuthorizedSpacesScreenModels.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 */; };
|
E81E8B7A8ADDB2A912A609F6 /* CallIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2DB32B72EA4772E2C9E3B3 /* CallIntent.swift */; };
|
||||||
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.swift */; };
|
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.swift */; };
|
||||||
E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; };
|
E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; };
|
||||||
@@ -2341,6 +2345,7 @@
|
|||||||
796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = "<group>"; };
|
796CBD0C56FA0D3AEDAB255B /* SessionVerificationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
79A106D76741914F82664959 /* StateStoreViewModelV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModelV2.swift; sourceTree = "<group>"; };
|
79A106D76741914F82664959 /* StateStoreViewModelV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModelV2.swift; sourceTree = "<group>"; };
|
||||||
7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
|
7A03E073077D92AA19C43DCF /* IdentityConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
7A60C2712F5607406E8B1C80 /* HealthChatBrandColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatBrandColors.swift; sourceTree = "<group>"; };
|
||||||
7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityTests.swift; sourceTree = "<group>"; };
|
7A6D867A7FBB70C6EFDBCBC5 /* AccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityTests.swift; sourceTree = "<group>"; };
|
||||||
7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangeRolesScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
7AB74D407F0E413E7593B369 /* SpaceAddRoomsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceAddRoomsScreenViewModelTests.swift; sourceTree = "<group>"; };
|
7AB74D407F0E413E7593B369 /* SpaceAddRoomsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceAddRoomsScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -2938,6 +2943,8 @@
|
|||||||
DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = "<group>"; };
|
DF3D25B3EDB283B5807EADCF /* ReadMarkerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
DFFB0E7C6D8E190AFA0176DC /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uz; path = uz.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
DFFB0E7C6D8E190AFA0176DC /* uz */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uz; path = uz.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = "<group>"; };
|
E06AAD6D9D3F5833E7A5A2F9 /* RoomListFilterModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListFilterModels.swift; sourceTree = "<group>"; };
|
||||||
|
E09E2B1AB937F2D01815495F /* HealthChatCompoundHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatCompoundHook.swift; sourceTree = "<group>"; };
|
||||||
|
E0B439012B010EC4C3DD24D0 /* HealthChatAppHooks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatAppHooks.swift; sourceTree = "<group>"; };
|
||||||
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
|
E0FCA0957FAA0E15A9F5579D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Untranslated.stringsdict; sourceTree = "<group>"; };
|
||||||
E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = "<group>"; };
|
E0FF9CB3EFA753277291F609 /* EncryptionResetScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = "<group>"; };
|
E10DA51DBC8C7E1460DBCCBD /* UserProfileListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileListRow.swift; sourceTree = "<group>"; };
|
||||||
@@ -2946,6 +2953,7 @@
|
|||||||
E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelTests.swift; sourceTree = "<group>"; };
|
E1714DD78BFE27ADD40A6F89 /* LinkNewDeviceScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = "<group>"; };
|
E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = "<group>"; };
|
||||||
E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; };
|
E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; };
|
||||||
|
E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthChatAppSettingsHook.swift; sourceTree = "<group>"; };
|
||||||
E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = "<group>"; };
|
E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenTests.swift; sourceTree = "<group>"; };
|
E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -3588,6 +3596,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */,
|
B6E4AB573FAEBB7B853DD04C /* AppHooks.swift */,
|
||||||
|
801BA878EE879E01E82F3A5F /* HealthChat */,
|
||||||
2ED530A4DD98266D31E5D8E1 /* Hooks */,
|
2ED530A4DD98266D31E5D8E1 /* Hooks */,
|
||||||
);
|
);
|
||||||
path = AppHooks;
|
path = AppHooks;
|
||||||
@@ -5366,6 +5375,17 @@
|
|||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
801BA878EE879E01E82F3A5F /* HealthChat */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E0B439012B010EC4C3DD24D0 /* HealthChatAppHooks.swift */,
|
||||||
|
E1ECC44D40D05D3A6F0DE0F7 /* HealthChatAppSettingsHook.swift */,
|
||||||
|
7A60C2712F5607406E8B1C80 /* HealthChatBrandColors.swift */,
|
||||||
|
E09E2B1AB937F2D01815495F /* HealthChatCompoundHook.swift */,
|
||||||
|
);
|
||||||
|
path = HealthChat;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
8039515BAA53B7C3275AC64A /* Client */ = {
|
8039515BAA53B7C3275AC64A /* Client */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -7480,34 +7500,34 @@
|
|||||||
ORGANIZATIONNAME = Element;
|
ORGANIZATIONNAME = Element;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
0E28CD62691FDBC63147D5E3 = {
|
0E28CD62691FDBC63147D5E3 = {
|
||||||
DevelopmentTeam = 7J4U792NQT;
|
DevelopmentTeam = 6G37K72W82;
|
||||||
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
||||||
};
|
};
|
||||||
19F0C845D67E9BEA4BE7133E = {
|
19F0C845D67E9BEA4BE7133E = {
|
||||||
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
||||||
};
|
};
|
||||||
32C23C8D224D46EFE62AFAD0 = {
|
32C23C8D224D46EFE62AFAD0 = {
|
||||||
DevelopmentTeam = 7J4U792NQT;
|
DevelopmentTeam = 6G37K72W82;
|
||||||
};
|
};
|
||||||
676A87694F0F247B2AF1A142 = {
|
676A87694F0F247B2AF1A142 = {
|
||||||
DevelopmentTeam = 7J4U792NQT;
|
DevelopmentTeam = 6G37K72W82;
|
||||||
};
|
};
|
||||||
7A17BE29BAC81ADBAC6349D9 = {
|
7A17BE29BAC81ADBAC6349D9 = {
|
||||||
DevelopmentTeam = 7J4U792NQT;
|
DevelopmentTeam = 6G37K72W82;
|
||||||
};
|
};
|
||||||
C0C687DE1D270F9895FEE186 = {
|
C0C687DE1D270F9895FEE186 = {
|
||||||
DevelopmentTeam = 7J4U792NQT;
|
DevelopmentTeam = 6G37K72W82;
|
||||||
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
||||||
};
|
};
|
||||||
C0FAEB81CFD9776CD78CE489 = {
|
C0FAEB81CFD9776CD78CE489 = {
|
||||||
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
||||||
};
|
};
|
||||||
D3DB351B7FBE0F49649171FC = {
|
D3DB351B7FBE0F49649171FC = {
|
||||||
DevelopmentTeam = 7J4U792NQT;
|
DevelopmentTeam = 6G37K72W82;
|
||||||
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
TestTargetID = C0FAEB81CFD9776CD78CE489;
|
||||||
};
|
};
|
||||||
F8E276FD6DC43EADB85241BC = {
|
F8E276FD6DC43EADB85241BC = {
|
||||||
DevelopmentTeam = 7J4U792NQT;
|
DevelopmentTeam = 6G37K72W82;
|
||||||
};
|
};
|
||||||
FEB53A5BC378C913769656D8 = {
|
FEB53A5BC378C913769656D8 = {
|
||||||
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
DevelopmentTeam = "$(DEVELOPMENT_TEAM)";
|
||||||
@@ -8510,6 +8530,10 @@
|
|||||||
4295E5F850897710A51AE114 /* GeoURI.swift in Sources */,
|
4295E5F850897710A51AE114 /* GeoURI.swift in Sources */,
|
||||||
F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */,
|
F3C9CAD26FD4D7D6EBACF501 /* HTMLFixtures.swift in Sources */,
|
||||||
E8C4D9F93F0DCED211D5F187 /* HTMLParserStyle.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 */,
|
0C1E537A49ABB386F7554D4A /* HighlightedTimelineItemModifier.swift in Sources */,
|
||||||
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */,
|
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */,
|
||||||
62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */,
|
62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */,
|
||||||
@@ -9833,11 +9857,11 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
APP_DISPLAY_NAME = "Element X";
|
APP_DISPLAY_NAME = HealthChat;
|
||||||
APP_GROUP_IDENTIFIER = group.io.element;
|
APP_GROUP_IDENTIFIER = group.ch.healthchat;
|
||||||
APP_NAME = ElementX;
|
APP_NAME = ElementX;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "colors/accent-color";
|
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_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
@@ -9869,13 +9893,13 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CLASSIC_APP_DEEP_LINK_URL = "element://open";
|
CLASSIC_APP_DEEP_LINK_URL = "element://open";
|
||||||
CLASSIC_APP_GROUP_IDENTIFIER = group.im.vector;
|
CLASSIC_APP_GROUP_IDENTIFIER = group.ch.healthchat;
|
||||||
CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).im.vector.app.keychain.shared";
|
CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).ch.healthchat.element.keychain.shared";
|
||||||
CLASSIC_APP_KEYCHAIN_SERVICE_IDENTIFIER = "im.vector.app.encryption-manager-service";
|
CLASSIC_APP_KEYCHAIN_SERVICE_IDENTIFIER = "ch.healthchat.element.encryption-manager-service";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
DEVELOPMENT_TEAM = 6G37K72W82;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
@@ -9894,7 +9918,7 @@
|
|||||||
MARKETING_VERSION = 26.07.3;
|
MARKETING_VERSION = 26.07.3;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCTION_APP_NAME = Element;
|
PRODUCTION_APP_NAME = HealthChat;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
@@ -9908,11 +9932,11 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
APP_DISPLAY_NAME = "Element X";
|
APP_DISPLAY_NAME = HealthChat;
|
||||||
APP_GROUP_IDENTIFIER = group.io.element;
|
APP_GROUP_IDENTIFIER = group.ch.healthchat;
|
||||||
APP_NAME = ElementX;
|
APP_NAME = ElementX;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "colors/accent-color";
|
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_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
@@ -9944,13 +9968,13 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CLASSIC_APP_DEEP_LINK_URL = "element://open";
|
CLASSIC_APP_DEEP_LINK_URL = "element://open";
|
||||||
CLASSIC_APP_GROUP_IDENTIFIER = group.im.vector;
|
CLASSIC_APP_GROUP_IDENTIFIER = group.ch.healthchat;
|
||||||
CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).im.vector.app.keychain.shared";
|
CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).ch.healthchat.element.keychain.shared";
|
||||||
CLASSIC_APP_KEYCHAIN_SERVICE_IDENTIFIER = "im.vector.app.encryption-manager-service";
|
CLASSIC_APP_KEYCHAIN_SERVICE_IDENTIFIER = "ch.healthchat.element.encryption-manager-service";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
DEVELOPMENT_TEAM = 6G37K72W82;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
@@ -9976,7 +10000,7 @@
|
|||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
PRODUCTION_APP_NAME = Element;
|
PRODUCTION_APP_NAME = HealthChat;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 265 KiB |
@@ -1,15 +1,23 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "app-logo.pdf",
|
"filename" : "app-logo.png",
|
||||||
"idiom" : "universal"
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "app-logo@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "app-logo@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
},
|
|
||||||
"properties" : {
|
|
||||||
"preserves-vector-representation" : true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -1,4 +1,5 @@
|
|||||||
"screen_home_tab_search" = "Search";
|
"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_message" = "Search for chats and messages";
|
||||||
"screen_search_empty_state_title" = "Start searching...";
|
"screen_search_empty_state_title" = "Start searching...";
|
||||||
"screen_search_no_results_message" = "There are no results for “%1$@.” Try a new search term.";
|
"screen_search_no_results_message" = "There are no results for “%1$@.” Try a new search term.";
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<CompoundColorTokens, Color>] = [
|
||||||
|
\.bgActionPrimaryRest,
|
||||||
|
\.bgAccentRest,
|
||||||
|
\.iconAccentPrimary,
|
||||||
|
\.iconAccentTertiary,
|
||||||
|
\.textActionPrimary,
|
||||||
|
\.textActionAccent,
|
||||||
|
\.textLinkExternal,
|
||||||
|
\.borderInteractivePrimary,
|
||||||
|
\.textBadgeAccent
|
||||||
|
]
|
||||||
|
let solidUIColorTokens: [KeyPath<CompoundUIColorTokens, UIColor>] = [
|
||||||
|
\.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<CompoundColorTokens, Color>] = [
|
||||||
|
\.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<CompoundColorTokens, Color>] = [
|
||||||
|
\.bgAccentSelected,
|
||||||
|
\.bgBadgeAccent,
|
||||||
|
\.borderAccentSubtle
|
||||||
|
]
|
||||||
|
let tintUIColorTokens: [KeyPath<CompoundUIColorTokens, UIColor>] = [
|
||||||
|
\.bgAccentSelected,
|
||||||
|
\.bgBadgeAccent,
|
||||||
|
\.borderAccentSubtle
|
||||||
|
]
|
||||||
|
for keyPath in tintColorTokens {
|
||||||
|
colors.override(keyPath, with: Color(tint))
|
||||||
|
}
|
||||||
|
for keyPath in tintUIColorTokens {
|
||||||
|
uiColors.override(keyPath, with: tint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ import Foundation
|
|||||||
internal nonisolated enum UntranslatedL10n {
|
internal nonisolated enum UntranslatedL10n {
|
||||||
/// Search
|
/// Search
|
||||||
internal static var screenHomeTabSearch: String { return UntranslatedL10n.tr("Untranslated", "screen_home_tab_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
|
/// Search for chats and messages
|
||||||
internal static var screenSearchEmptyStateMessage: String { return UntranslatedL10n.tr("Untranslated", "screen_search_empty_state_message") }
|
internal static var screenSearchEmptyStateMessage: String { return UntranslatedL10n.tr("Untranslated", "screen_search_empty_state_message") }
|
||||||
/// Start searching...
|
/// Start searching...
|
||||||
|
|||||||
+8
-7
@@ -113,13 +113,14 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
|||||||
|
|
||||||
private func login(classicAppAccount: ClassicAppAccount? = nil) async {
|
private func login(classicAppAccount: ClassicAppAccount? = nil) async {
|
||||||
if let classicAppAccount {
|
if let classicAppAccount {
|
||||||
if classicAppAccount.state.availableSecrets == .requiresBackup {
|
// HealthChat diverges from upstream here. Upstream sends `.requiresBackup` accounts to a sheet
|
||||||
state.bindings.showClassicAppBackupInstructions = true
|
// asking the user to set up key backup in Element Classic first. HealthChat ships with the same
|
||||||
} else {
|
// bundle id as the legacy app, so this update *replaces* it — there is no Classic app left to
|
||||||
await configureAccountProvider(classicAppAccount.serverName,
|
// open, and the sheet's only button resolves to nothing. Fall through to a normal sign-in
|
||||||
loginHint: "mxid:\(classicAppAccount.userID)",
|
// instead: the user re-verifies and loses only history that by definition has no backup.
|
||||||
fallbackHomeserverURL: classicAppAccount.homeserverURL)
|
await configureAccountProvider(classicAppAccount.serverName,
|
||||||
}
|
loginHint: "mxid:\(classicAppAccount.userID)",
|
||||||
|
fallbackHomeserverURL: classicAppAccount.homeserverURL)
|
||||||
} else if let serverName = state.serverName {
|
} else if let serverName = state.serverName {
|
||||||
await configureAccountProvider(serverName, loginHint: provisioningParameters?.loginHint)
|
await configureAccountProvider(serverName, loginHint: provisioningParameters?.loginHint)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ struct AuthenticationStartScreen: View {
|
|||||||
|
|
||||||
if !context.viewState.hideBrandChrome {
|
if !context.viewState.hideBrandChrome {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
Text(L10n.screenOnboardingWelcomeTitle)
|
Text(UntranslatedL10n.screenOnboardingWelcomeTitleIos)
|
||||||
.font(.compound.headingLGBold)
|
.font(.compound.headingLGBold)
|
||||||
.foregroundColor(.compound.textPrimary)
|
.foregroundColor(.compound.textPrimary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|||||||
@@ -6,13 +6,9 @@
|
|||||||
<string>development</string>
|
<string>development</string>
|
||||||
<key>com.apple.developer.associated-domains</key>
|
<key>com.apple.developer.associated-domains</key>
|
||||||
<array>
|
<array>
|
||||||
<string>applinks:element.io</string>
|
<string>applinks:app.healthchat.ch</string>
|
||||||
<string>applinks:app.element.io</string>
|
|
||||||
<string>applinks:staging.element.io</string>
|
|
||||||
<string>applinks:develop.element.io</string>
|
|
||||||
<string>applinks:mobile.element.io</string>
|
|
||||||
<string>applinks:matrix.to</string>
|
<string>applinks:matrix.to</string>
|
||||||
<string>webcredentials:*.element.io</string>
|
<string>webcredentials:*.healthchat.ch</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.usernotifications.communication</key>
|
<key>com.apple.developer.usernotifications.communication</key>
|
||||||
<true/>
|
<true/>
|
||||||
@@ -21,7 +17,6 @@
|
|||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||||
<string>$(CLASSIC_APP_GROUP_IDENTIFIER)</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>$(APP_DISPLAY_NAME)</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ targets:
|
|||||||
UILaunchScreen:
|
UILaunchScreen:
|
||||||
UIColorName: colors/background-color
|
UIColorName: colors/background-color
|
||||||
CFBundleDisplayName: $(APP_DISPLAY_NAME)
|
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)
|
CFBundleShortVersionString: $(MARKETING_VERSION)
|
||||||
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
|
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
|
||||||
CFBundleURLTypes: [
|
CFBundleURLTypes: [
|
||||||
@@ -115,22 +118,23 @@ targets:
|
|||||||
properties:
|
properties:
|
||||||
aps-environment: development
|
aps-environment: development
|
||||||
com.apple.developer.associated-domains:
|
com.apple.developer.associated-domains:
|
||||||
- applinks:element.io
|
# HealthChat: app.healthchat.ch universal links + webcredentials (OAuth/autofill). matrix.to kept for permalinks.
|
||||||
- applinks:app.element.io
|
- applinks:app.healthchat.ch
|
||||||
- applinks:staging.element.io
|
|
||||||
- applinks:develop.element.io
|
|
||||||
- applinks:mobile.element.io
|
|
||||||
- applinks:matrix.to
|
- applinks:matrix.to
|
||||||
# - applinks:localhost?mode=developer # developer mode only works with https (but self-signed is fine).
|
- webcredentials:*.healthchat.ch
|
||||||
- webcredentials:*.element.io
|
|
||||||
com.apple.developer.usernotifications.communication: true
|
com.apple.developer.usernotifications.communication: true
|
||||||
com.apple.security.app-sandbox: true
|
com.apple.security.app-sandbox: true
|
||||||
com.apple.security.application-groups:
|
com.apple.security.application-groups:
|
||||||
|
# HealthChat: dropped $(CLASSIC_APP_GROUP_IDENTIFIER) — not because it is un-provisionable,
|
||||||
|
# but because it resolves to group.ch.healthchat, the same value as $(APP_GROUP_IDENTIFIER)
|
||||||
|
# below. The legacy container is already entitled; listing it twice would duplicate the entry.
|
||||||
- $(APP_GROUP_IDENTIFIER)
|
- $(APP_GROUP_IDENTIFIER)
|
||||||
- $(CLASSIC_APP_GROUP_IDENTIFIER)
|
|
||||||
com.apple.security.network.client: true
|
com.apple.security.network.client: true
|
||||||
keychain-access-groups:
|
keychain-access-groups:
|
||||||
- $(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
|
- $(KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
|
||||||
|
# HealthChat: the legacy app's keychain group. ClassicAppManager reads the account and
|
||||||
|
# crypto-store keys from here to migrate existing users. Same team prefix, so it is
|
||||||
|
# provisionable — unlike Element's im.vector.app.* default in app.yml.
|
||||||
- $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
|
- $(CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER)
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "fastlane"
|
||||||
|
|
||||||
|
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||||
|
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
||||||
+350
@@ -0,0 +1,350 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
CFPropertyList (3.0.8)
|
||||||
|
abbrev (0.1.2)
|
||||||
|
addressable (2.9.0)
|
||||||
|
public_suffix (>= 2.0.2, < 8.0)
|
||||||
|
artifactory (3.0.17)
|
||||||
|
atomos (0.1.3)
|
||||||
|
aws-eventstream (1.4.0)
|
||||||
|
aws-partitions (1.1269.0)
|
||||||
|
aws-sdk-core (3.254.0)
|
||||||
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
|
aws-sigv4 (~> 1.9)
|
||||||
|
base64
|
||||||
|
bigdecimal
|
||||||
|
jmespath (~> 1, >= 1.6.1)
|
||||||
|
logger
|
||||||
|
aws-sdk-kms (1.130.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.254.0)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sdk-s3 (1.227.0)
|
||||||
|
aws-sdk-core (~> 3, >= 3.254.0)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.5)
|
||||||
|
aws-sigv4 (1.12.1)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
babosa (1.0.4)
|
||||||
|
base64 (0.3.0)
|
||||||
|
benchmark (0.5.0)
|
||||||
|
bigdecimal (4.1.2)
|
||||||
|
claide (1.1.0)
|
||||||
|
colored (1.2)
|
||||||
|
colored2 (3.1.2)
|
||||||
|
commander (4.6.0)
|
||||||
|
highline (~> 2.0.0)
|
||||||
|
csv (3.3.5)
|
||||||
|
declarative (0.0.20)
|
||||||
|
digest-crc (0.7.0)
|
||||||
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
|
domain_name (0.6.20240107)
|
||||||
|
dotenv (2.8.1)
|
||||||
|
emoji_regex (3.2.3)
|
||||||
|
excon (1.5.0)
|
||||||
|
logger
|
||||||
|
faraday (1.10.6)
|
||||||
|
faraday-em_http (~> 1.0)
|
||||||
|
faraday-em_synchrony (~> 1.0)
|
||||||
|
faraday-excon (~> 1.1)
|
||||||
|
faraday-httpclient (~> 1.0)
|
||||||
|
faraday-multipart (~> 1.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
|
faraday-net_http_persistent (~> 1.0)
|
||||||
|
faraday-patron (~> 1.0)
|
||||||
|
faraday-rack (~> 1.0)
|
||||||
|
faraday-retry (~> 1.0)
|
||||||
|
ruby2_keywords (>= 0.0.4)
|
||||||
|
faraday-cookie_jar (0.0.8)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
|
http-cookie (>= 1.0.0)
|
||||||
|
faraday-em_http (1.0.0)
|
||||||
|
faraday-em_synchrony (1.0.1)
|
||||||
|
faraday-excon (1.1.0)
|
||||||
|
faraday-httpclient (1.0.1)
|
||||||
|
faraday-multipart (1.2.0)
|
||||||
|
multipart-post (~> 2.0)
|
||||||
|
faraday-net_http (1.0.2)
|
||||||
|
faraday-net_http_persistent (1.2.0)
|
||||||
|
faraday-patron (1.0.0)
|
||||||
|
faraday-rack (1.0.0)
|
||||||
|
faraday-retry (1.0.4)
|
||||||
|
faraday_middleware (1.2.1)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
fastimage (2.4.1)
|
||||||
|
fastlane (2.237.0)
|
||||||
|
CFPropertyList (>= 2.3, < 5.0.0)
|
||||||
|
abbrev (~> 0.1)
|
||||||
|
addressable (>= 2.9.0, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
|
aws-sdk-s3 (~> 1.197)
|
||||||
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
|
base64 (~> 0.2)
|
||||||
|
benchmark (>= 0.1.0)
|
||||||
|
bundler (>= 2.4.0, < 5.0.0)
|
||||||
|
colored (~> 1.2)
|
||||||
|
commander (~> 4.6)
|
||||||
|
csv (~> 3.3)
|
||||||
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
|
excon (>= 0.71.0, < 2.0.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
|
faraday_middleware (~> 1.0)
|
||||||
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
fastlane-sirp (>= 1.1.0)
|
||||||
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
|
google-cloud-env (>= 1.6.0, < 2.3.0)
|
||||||
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
|
http-cookie (~> 1.0.5)
|
||||||
|
json (< 3.0.0)
|
||||||
|
jwt (>= 2.10.3, < 4)
|
||||||
|
logger (>= 1.6, < 2.0)
|
||||||
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
|
multi_json (~> 1.12)
|
||||||
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
|
mutex_m (~> 0.3)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
nkf (~> 0.2)
|
||||||
|
optparse (>= 0.1.1, < 1.0.0)
|
||||||
|
ostruct (>= 0.1.0)
|
||||||
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
|
security (= 0.1.5)
|
||||||
|
simctl (~> 1.6.3)
|
||||||
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
|
terminal-table (~> 3)
|
||||||
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
|
word_wrap (~> 1.0.0)
|
||||||
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
|
xcpretty (~> 0.4.1)
|
||||||
|
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||||
|
fastlane-plugin-brew (0.1.1)
|
||||||
|
fastlane-plugin-xcodegen (1.1.0)
|
||||||
|
fastlane-plugin-brew (~> 0.1.1)
|
||||||
|
fastlane-sirp (1.1.0)
|
||||||
|
gh_inspector (1.1.3)
|
||||||
|
google-apis-androidpublisher_v3 (0.104.0)
|
||||||
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
|
google-apis-core (0.18.0)
|
||||||
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
|
googleauth (~> 1.9)
|
||||||
|
httpclient (>= 2.8.3, < 3.a)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
mutex_m
|
||||||
|
representable (~> 3.0)
|
||||||
|
retriable (>= 2.0, < 4.a)
|
||||||
|
google-apis-iamcredentials_v1 (0.28.0)
|
||||||
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
|
google-apis-playcustomapp_v1 (0.18.0)
|
||||||
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
|
google-apis-storage_v1 (0.64.0)
|
||||||
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
|
google-cloud-core (1.9.0)
|
||||||
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
|
google-cloud-errors (~> 1.0)
|
||||||
|
google-cloud-env (2.2.2)
|
||||||
|
base64 (~> 0.2)
|
||||||
|
faraday (>= 1.0, < 3.a)
|
||||||
|
google-cloud-errors (1.7.0)
|
||||||
|
google-cloud-storage (1.62.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
digest-crc (~> 0.4)
|
||||||
|
google-apis-core (>= 0.18, < 2)
|
||||||
|
google-apis-iamcredentials_v1 (~> 0.18)
|
||||||
|
google-apis-storage_v1 (>= 0.42)
|
||||||
|
google-cloud-core (~> 1.6)
|
||||||
|
googleauth (~> 1.9)
|
||||||
|
mini_mime (~> 1.0)
|
||||||
|
google-logging-utils (0.2.0)
|
||||||
|
googleauth (1.17.1)
|
||||||
|
faraday (>= 1.0, < 3.a)
|
||||||
|
google-cloud-env (~> 2.2)
|
||||||
|
google-logging-utils (~> 0.1)
|
||||||
|
jwt (>= 1.4, < 4.0)
|
||||||
|
os (>= 0.9, < 2.0)
|
||||||
|
pstore (~> 0.1)
|
||||||
|
signet (>= 0.16, < 2.a)
|
||||||
|
highline (2.0.3)
|
||||||
|
http-cookie (1.0.8)
|
||||||
|
domain_name (~> 0.5)
|
||||||
|
httpclient (2.9.0)
|
||||||
|
mutex_m
|
||||||
|
jmespath (1.6.2)
|
||||||
|
json (2.21.1)
|
||||||
|
jwt (3.2.0)
|
||||||
|
base64
|
||||||
|
logger (1.7.0)
|
||||||
|
mini_magick (4.13.2)
|
||||||
|
mini_mime (1.1.5)
|
||||||
|
multi_json (1.21.1)
|
||||||
|
multipart-post (2.4.1)
|
||||||
|
mutex_m (0.3.0)
|
||||||
|
nanaimo (0.4.0)
|
||||||
|
naturally (2.3.0)
|
||||||
|
nkf (0.3.0)
|
||||||
|
optparse (0.8.1)
|
||||||
|
os (1.1.4)
|
||||||
|
ostruct (0.6.3)
|
||||||
|
plist (3.7.2)
|
||||||
|
pstore (0.2.1)
|
||||||
|
public_suffix (7.0.5)
|
||||||
|
rake (13.4.2)
|
||||||
|
representable (3.2.0)
|
||||||
|
declarative (< 0.1.0)
|
||||||
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
|
uber (< 0.2.0)
|
||||||
|
retriable (3.8.0)
|
||||||
|
rexml (3.4.4)
|
||||||
|
rouge (3.28.0)
|
||||||
|
ruby2_keywords (0.0.5)
|
||||||
|
rubyzip (2.4.1)
|
||||||
|
security (0.1.5)
|
||||||
|
signet (0.22.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
faraday (>= 0.17.5, < 3.a)
|
||||||
|
jwt (>= 1.5, < 4.0)
|
||||||
|
simctl (1.6.10)
|
||||||
|
CFPropertyList
|
||||||
|
naturally
|
||||||
|
terminal-notifier (2.0.0)
|
||||||
|
terminal-table (3.0.2)
|
||||||
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
|
trailblazer-option (0.1.2)
|
||||||
|
tty-cursor (0.7.1)
|
||||||
|
tty-screen (0.8.2)
|
||||||
|
tty-spinner (0.9.3)
|
||||||
|
tty-cursor (~> 0.7)
|
||||||
|
uber (0.1.0)
|
||||||
|
unicode-display_width (2.6.0)
|
||||||
|
word_wrap (1.0.0)
|
||||||
|
xcodeproj (1.28.1)
|
||||||
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
|
atomos (~> 0.1.3)
|
||||||
|
base64
|
||||||
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
colored2 (~> 3.1)
|
||||||
|
nanaimo (~> 0.4.0)
|
||||||
|
nkf
|
||||||
|
rexml (>= 3.3.6, < 4.0)
|
||||||
|
xcpretty (0.4.1)
|
||||||
|
rouge (~> 3.28.0)
|
||||||
|
xcpretty-travis-formatter (1.0.1)
|
||||||
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
arm64-darwin-25
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
fastlane
|
||||||
|
fastlane-plugin-xcodegen
|
||||||
|
|
||||||
|
CHECKSUMS
|
||||||
|
CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261
|
||||||
|
abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242
|
||||||
|
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
|
||||||
|
artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263
|
||||||
|
atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f
|
||||||
|
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
|
||||||
|
aws-partitions (1.1269.0) sha256=80beca6c8a35ddfc11b0b529424ea6167ace3da92cd7368847ce694679dedea5
|
||||||
|
aws-sdk-core (3.254.0) sha256=ee3e3220b8468a3c9e59daba18e6ec897bf5c7ce8adcc0670cfa2f1f092112fe
|
||||||
|
aws-sdk-kms (1.130.0) sha256=a2e83662ca31b77a2a19c9aa2f40a98165a67270c18c718fe1c70d0cbd7cd749
|
||||||
|
aws-sdk-s3 (1.227.0) sha256=552b23bf9a37c7db4957e9291543e61c8de886cf31d1d45ea3b429df0feea939
|
||||||
|
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
|
||||||
|
babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99
|
||||||
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
||||||
|
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
|
||||||
|
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
|
||||||
|
claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e
|
||||||
|
colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c
|
||||||
|
colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
|
||||||
|
commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9
|
||||||
|
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
|
||||||
|
declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9
|
||||||
|
digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07
|
||||||
|
domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933
|
||||||
|
dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8
|
||||||
|
emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b
|
||||||
|
excon (1.5.0) sha256=c503ad1d0123bc8ab2a062ff3789dc891ec368cb9e13765ab88a9c58c8bb6d50
|
||||||
|
faraday (1.10.6) sha256=7ff4802a6b312876a2241b3e641ce0d5045e168dd871b422c35b505e5261ad4d
|
||||||
|
faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb
|
||||||
|
faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689
|
||||||
|
faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750
|
||||||
|
faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940
|
||||||
|
faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b
|
||||||
|
faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757
|
||||||
|
faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682
|
||||||
|
faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335
|
||||||
|
faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7
|
||||||
|
faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0
|
||||||
|
faraday-retry (1.0.4) sha256=dc659233777fabf96c69c2ffe56c0a5d2c102af90321a42cc6c90157bcd716aa
|
||||||
|
faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9
|
||||||
|
fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20
|
||||||
|
fastlane (2.237.0) sha256=bb1e867bc070fb328741b5e6e7606d5ba596941a9ecca0478f60ef22d1009db9
|
||||||
|
fastlane-plugin-brew (0.1.1) sha256=c2d1c98ead82f78818621b5b1eb123e0bf9da7e1a7b3efeaac77a776725a7fbf
|
||||||
|
fastlane-plugin-xcodegen (1.1.0) sha256=9bbed6c2ea21ca6869567e350d0b62cf41770949d15e40869058d689f1a6d06d
|
||||||
|
fastlane-sirp (1.1.0) sha256=10bc94f9682efd8e1badfb31452a76dd8981f1f3a33717c765fde6d75b54d847
|
||||||
|
gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939
|
||||||
|
google-apis-androidpublisher_v3 (0.104.0) sha256=3bf7f0dee23ae71e070e279848374834853204e304831cf6aa76fa435a4d6eff
|
||||||
|
google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee
|
||||||
|
google-apis-iamcredentials_v1 (0.28.0) sha256=0a92ffe6cc39c569554af2a77a25dfc61519ed8bbb64ab04cffdd352dc5ef106
|
||||||
|
google-apis-playcustomapp_v1 (0.18.0) sha256=44b277b9dee4a59ac5e9d98be1485edc5e382d2f9d73c79ae8908a455786a254
|
||||||
|
google-apis-storage_v1 (0.64.0) sha256=75b11afa2edcee859b84c7a6972ee4456314eeef5f762827fd6cf5c5ffaf93f2
|
||||||
|
google-cloud-core (1.9.0) sha256=ab55409f51488e8deefb6edcc1ce4771dfb5da2fe7b3bc075709a030c2b682a4
|
||||||
|
google-cloud-env (2.2.2) sha256=94bed40e05a67e9468ce1cb38389fba9a90aa8fc62fc9e173204c1dca59e21e7
|
||||||
|
google-cloud-errors (1.7.0) sha256=6e682f42d89aae08689f36f28495de629d756da076bafff0003bac7f8042ebb3
|
||||||
|
google-cloud-storage (1.62.0) sha256=e2c3c08bf8fd40d50be92304084942203314d4fc0ee52028e99f9359c3ad1330
|
||||||
|
google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b
|
||||||
|
googleauth (1.17.1) sha256=0f7e6fc70e204cee1b2d71f1e1de2d3b349d432404197fe68ebf7fa23d0821b9
|
||||||
|
highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479
|
||||||
|
http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6
|
||||||
|
httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8
|
||||||
|
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
|
||||||
|
json (2.21.1) sha256=13a43df75d95641443f5702dff350f237164a9d811ff0f2c2800d4d980220583
|
||||||
|
jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7
|
||||||
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
||||||
|
mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9
|
||||||
|
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||||
|
multi_json (1.21.1) sha256=e6126a31808e3b4d19f483c775ceac34df190dffa62adfb63a165ee14ba68080
|
||||||
|
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
|
||||||
|
mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751
|
||||||
|
nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723
|
||||||
|
naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01
|
||||||
|
nkf (0.3.0) sha256=357a8dbeba38b727b75930f665146546076a394a1c243faf634ff176e3588895
|
||||||
|
optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a
|
||||||
|
os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f
|
||||||
|
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
|
||||||
|
plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42
|
||||||
|
pstore (0.2.1) sha256=03904d0f2c66579e96d1e6704cdabc0c88df7ea8ed8782d9f3569f6f6c702c1a
|
||||||
|
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
|
||||||
|
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
||||||
|
representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
|
||||||
|
retriable (3.8.0) sha256=9f2f1b0207594c7817f17f671587b8ec7587387ac6cebda6c941a802bb98a8e5
|
||||||
|
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
||||||
|
rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20
|
||||||
|
ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef
|
||||||
|
rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615
|
||||||
|
security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7
|
||||||
|
signet (0.22.0) sha256=b76d495ccb07ad35dbc89f3e920665a9d8ed717141955034005d7843dcfe4780
|
||||||
|
simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b
|
||||||
|
terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea
|
||||||
|
terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91
|
||||||
|
trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3
|
||||||
|
tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48
|
||||||
|
tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50
|
||||||
|
tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542
|
||||||
|
uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc
|
||||||
|
unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
|
||||||
|
word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7
|
||||||
|
xcodeproj (1.28.1) sha256=6f12670f00739d9817ca27ac89d6ef01cc86050e22a0bc08a3131487e5b5cddc
|
||||||
|
xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892
|
||||||
|
xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
4.0.3
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# HealthChat Changes
|
||||||
|
|
||||||
|
Release notes for HealthChat builds of Element X. Each release adds a section at the
|
||||||
|
top; the release workflow extracts the one matching the version being released and
|
||||||
|
uses it as the TestFlight "What to Test" text.
|
||||||
|
|
||||||
|
The heading format is load-bearing — `.github/workflows/release-healthchat.yml`
|
||||||
|
matches `## Changes in <version>` exactly.
|
||||||
|
|
||||||
|
Upstream's `CHANGES.md` tracks element-hq's own releases and must not be edited here.
|
||||||
|
|
||||||
|
## Changes in 26.07.3 (2026-07-15)
|
||||||
|
|
||||||
|
🙌 Improvements
|
||||||
|
|
||||||
|
- First HealthChat release built from Element X.
|
||||||
@@ -55,6 +55,16 @@ struct RunTests: AsyncParsableCommand {
|
|||||||
"com.apple.CoreSimulator.SimRuntime.iOS-\(runtime.replacingOccurrences(of: ".", with: "-"))"
|
"com.apple.CoreSimulator.SimRuntime.iOS-\(runtime.replacingOccurrences(of: ".", with: "-"))"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test identifiers to pass to `-skip-testing`, read from the `CI_SKIP_TESTS` environment variable
|
||||||
|
/// (comma-separated, e.g. "VoiceMessageCacheTests"). Lets a workflow exclude a suite that only fails in
|
||||||
|
/// its environment without touching the shared test.
|
||||||
|
private var skippedTests: [String] {
|
||||||
|
(ProcessInfo.processInfo.environment["CI_SKIP_TESTS"] ?? "")
|
||||||
|
.split(separator: ",")
|
||||||
|
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||||
|
.filter { !$0.isEmpty }
|
||||||
|
}
|
||||||
|
|
||||||
func run() async throws {
|
func run() async throws {
|
||||||
if let createName = createSimulatorName {
|
if let createName = createSimulatorName {
|
||||||
guard let createType = createSimulatorType else {
|
guard let createType = createSimulatorType else {
|
||||||
@@ -142,6 +152,10 @@ struct RunTests: AsyncParsableCommand {
|
|||||||
command += " -only-testing:\(scheme)/\(testName)"
|
command += " -only-testing:\(scheme)/\(testName)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for skippedTest in skippedTests {
|
||||||
|
command += " -skip-testing:\(scheme)/\(skippedTest)"
|
||||||
|
}
|
||||||
|
|
||||||
command += " | \(formatter)"
|
command += " | \(formatter)"
|
||||||
|
|
||||||
try await CI.run(.path("/bin/zsh"), ["-cu", command])
|
try await CI.run(.path("/bin/zsh"), ["-cu", command])
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func classicAppAccountRequiresBackup() async throws {
|
func classicAppAccountRequiresBackupFallsThroughToLogin() async throws {
|
||||||
// Given a view model with a Classic app account that requires backup before signing in.
|
// Given a view model with a Classic app account that has no usable key backup.
|
||||||
let classicAppAccount = makeClassicAppAccount()
|
let classicAppAccount = makeClassicAppAccount()
|
||||||
await setupViewModel(classicAppAccount: classicAppAccount, availableSecrets: .requiresBackup)
|
await setupViewModel(classicAppAccount: classicAppAccount, availableSecrets: .requiresBackup)
|
||||||
guard case .welcomeBack(let account) = context.viewState.classicAppMode else {
|
guard case .welcomeBack(let account) = context.viewState.classicAppMode else {
|
||||||
@@ -248,27 +248,15 @@ final class AuthenticationStartScreenViewModelTests {
|
|||||||
}
|
}
|
||||||
#expect(account.state.availableSecrets == .requiresBackup)
|
#expect(account.state.availableSecrets == .requiresBackup)
|
||||||
|
|
||||||
// When continuing with the Classic account while backup is required.
|
// When continuing with the Classic account.
|
||||||
var deferred = deferFulfillment(context.observe(\.viewState.bindings.showClassicAppBackupInstructions)) { $0 }
|
let deferred = deferFulfillment(viewModel.actions) { $0.isLoginDirectlyWithOAuth }
|
||||||
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
||||||
|
|
||||||
// Then the backup instructions should be shown.
|
// Then the flow continues to a normal sign-in rather than the backup instructions sheet.
|
||||||
|
// HealthChat replaces the Classic app in place, so those instructions could never be followed.
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
#expect(context.viewState.bindings.showClassicAppBackupInstructions == false)
|
||||||
// When the user completes the backup in the Classic app and the app returns to the foreground.
|
#expect(client.urlForOauthOauthConfigurationPromptLoginHintDeviceIdAdditionalScopesReceivedArguments?.loginHint == "mxid:\(classicAppAccount.userID)")
|
||||||
classicAppManager?.availableSecretsForReturnValue = .complete
|
|
||||||
deferred = deferFulfillment(context.observe(\.viewState.bindings.showClassicAppBackupInstructions)) { !$0 }
|
|
||||||
notificationCenter.post(name: UIApplication.didBecomeActiveNotification, object: nil)
|
|
||||||
|
|
||||||
// Then the backup instructions sheet should be dismissed.
|
|
||||||
try await deferred.fulfill()
|
|
||||||
|
|
||||||
// When the user continues with the Classic account again.
|
|
||||||
let deferredAction = deferFulfillment(viewModel.actions) { $0.isLoginDirectlyWithOAuth }
|
|
||||||
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
|
||||||
|
|
||||||
// Then the flow should continue the login process.
|
|
||||||
try await deferredAction.fulfill()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
# The legacy element-ios HealthChat's identifiers. ClassicAppManager reads the account and
|
||||||
|
# crypto-store keys from these to migrate existing users on an in-place update. Upstream's
|
||||||
|
# defaults in app.yml point at Element's own (group.im.vector / im.vector.app.*), which this
|
||||||
|
# team cannot provision — repoint, never remove. Failure here is silent.
|
||||||
|
CLASSIC_APP_GROUP_IDENTIFIER: group.ch.healthchat
|
||||||
|
CLASSIC_APP_KEYCHAIN_SERVICE_IDENTIFIER: ch.healthchat.element.encryption-manager-service
|
||||||
|
CLASSIC_APP_KEYCHAIN_ACCESS_GROUP_IDENTIFIER: "$(DEVELOPMENT_TEAM).ch.healthchat.element.keychain.shared"
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
## Project configuration
|
||||||
|
|
||||||
|
PROJECT_PATH=ElementX.xcodeproj
|
||||||
|
SCHEME=ElementX
|
||||||
|
|
||||||
|
MAIN_TARGET=ElementX
|
||||||
|
NSE_TARGET=NSE
|
||||||
|
SHARE_EXTENSION_TARGET=ShareExtension
|
||||||
|
|
||||||
|
MAIN_BUNDLE_ID=ch.healthchat.element
|
||||||
|
NSE_BUNDLE_ID=ch.healthchat.element.nse
|
||||||
|
SHARE_EXTENSION_BUNDLE_ID=ch.healthchat.element.shareextension
|
||||||
|
|
||||||
|
## Build configuration
|
||||||
|
|
||||||
|
DERIVED_DATA_PATH=build
|
||||||
|
IPA_NAME=ElementX
|
||||||
|
ARCHIVE_PATH=out/ElementX.xcarchive
|
||||||
|
BUILD_OUTPUT_DIRECTORY=out
|
||||||
|
BUILD_LOG_DIRECTORY=build/Logs/Gym/
|
||||||
|
|
||||||
|
## Provisioning profile files
|
||||||
|
|
||||||
|
PROVISIONING_PROFILES_PATH=./provisioning_profiles/
|
||||||
|
|
||||||
|
MAIN_PROVISIONING_PROFILE_FILENAME=main.mobileprovision
|
||||||
|
NSE_PROVISIONING_PROFILE_FILENAME=nse.mobileprovision
|
||||||
|
SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME=share_extension.mobileprovision
|
||||||
|
|
||||||
|
## App Store code signing
|
||||||
|
|
||||||
|
APPSTORE_CODESIGNING_IDENTITY="Apple Distribution: E-HealthSolutions GmbH (6G37K72W82)"
|
||||||
|
APPSTORE_SIGNING_CERTIFICATE="Apple Distribution: E-HealthSolutions GmbH (6G37K72W82)"
|
||||||
|
|
||||||
|
APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER="HealthChat App Store"
|
||||||
|
APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER="HealthChat NSE: App Store"
|
||||||
|
APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="HealthChat Share Extension: App Store"
|
||||||
|
|
||||||
|
## Account information
|
||||||
|
|
||||||
|
TEAM_ID=6G37K72W82
|
||||||
|
# APPLE_ID is set as a shell environment variable on the machine running fastlane.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# For more information about the Appfile, see:
|
||||||
|
# https://docs.fastlane.tools/advanced/#appfile
|
||||||
|
|
||||||
|
apple_id ENV["APPLE_ID"]
|
||||||
|
team_id ENV["TEAM_ID"]
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
default_platform :ios
|
||||||
|
|
||||||
|
# No Xcode version pin: novaloop-mac-studio-001 carries a single, globally-selected Xcode, so
|
||||||
|
# whatever xcode-select points at is by definition the one we build with. Pinning bought nothing
|
||||||
|
# and risked everything — a version the runner lacks sends xcodes into a multi-GB authenticated
|
||||||
|
# download that no CI secret can satisfy, hanging the release. Change the runner's Xcode instead.
|
||||||
|
platform :ios do
|
||||||
|
#### Public ####
|
||||||
|
|
||||||
|
desc "Builds the App Store ipa and uploads it to TestFlight (internal testers only)"
|
||||||
|
# Named release_testflight to avoid colliding with fastlane's built-in :testflight action.
|
||||||
|
lane :release_testflight do |options|
|
||||||
|
# Accept inputs via options (local) or env vars (CI — lets the workflow pass multi-line
|
||||||
|
# release notes without shell-escaping them).
|
||||||
|
marketing_version = options[:marketing_version] || ENV["HEALTHCHAT_MARKETING_VERSION"]
|
||||||
|
release_notes = options[:release_notes] || ENV["HEALTHCHAT_RELEASE_NOTES"]
|
||||||
|
|
||||||
|
UI.user_error!("'marketing_version' is required (pass the option or set HEALTHCHAT_MARKETING_VERSION)") if marketing_version.to_s.empty?
|
||||||
|
|
||||||
|
# Apple caps CFBundleShortVersionString at three period-separated integers. The workflow's
|
||||||
|
# regex is laxer and accepts markers like 26.07.3-rc1, so catch it here — otherwise the run
|
||||||
|
# dies at the upload step after ~30 minutes of signing, looking exactly like a signing failure.
|
||||||
|
unless marketing_version =~ /\A\d+(\.\d+){0,2}\z/
|
||||||
|
UI.user_error!("'marketing_version' must be 1-3 dot-separated integers (got '#{marketing_version}'). " \
|
||||||
|
"Apple rejects anything else at upload. Put markers in the tag or release notes instead.")
|
||||||
|
end
|
||||||
|
|
||||||
|
build_number = options[:build_number] || generate_build_number
|
||||||
|
|
||||||
|
build_release(marketing_version: marketing_version, build_number: build_number)
|
||||||
|
|
||||||
|
upload_to_testflight(
|
||||||
|
ipa: lane_context[SharedValues::IPA_OUTPUT_PATH],
|
||||||
|
skip_submission: true,
|
||||||
|
skip_waiting_for_build_processing: true,
|
||||||
|
changelog: release_notes,
|
||||||
|
distribute_external: false,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
#### Private ####
|
||||||
|
|
||||||
|
desc "Builds the signed App Store ipa"
|
||||||
|
private_lane :build_release do |options|
|
||||||
|
marketing_version = options[:marketing_version]
|
||||||
|
build_number = options[:build_number]
|
||||||
|
UI.user_error!("'build_number' parameter is missing") if build_number.to_s.empty?
|
||||||
|
|
||||||
|
if is_ci
|
||||||
|
%w[APPSTORECONNECT_KEY_ID APPSTORECONNECT_KEY_ISSUER_ID APPSTORECONNECT_KEY_CONTENT].each do |key|
|
||||||
|
UI.user_error!("'#{key}' environment variable should be set to use this lane") if ENV[key].to_s.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
app_store_connect_api_key(
|
||||||
|
key_id: ENV["APPSTORECONNECT_KEY_ID"],
|
||||||
|
issuer_id: ENV["APPSTORECONNECT_KEY_ISSUER_ID"],
|
||||||
|
key_content: ENV["APPSTORECONNECT_KEY_CONTENT"]
|
||||||
|
)
|
||||||
|
else
|
||||||
|
UI.user_error!("'APPLE_ID' environment variable should be set to use this lane") if ENV["APPLE_ID"].to_s.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)
|
||||||
|
UI.user_error!("Failed to fetch the team id from the Appfile") if team_id.to_s.empty?
|
||||||
|
|
||||||
|
# Inject the version at build time rather than rewriting project.yml, so no tracked file ever
|
||||||
|
# carries a release-only value. All three targets resolve CFBundleShortVersionString and
|
||||||
|
# CFBundleVersion from these settings, which keeps the app and its extensions consistent —
|
||||||
|
# App Store rejects an upload where they disagree.
|
||||||
|
xcargs_hash = {
|
||||||
|
"MARKETING_VERSION" => marketing_version,
|
||||||
|
"CURRENT_PROJECT_VERSION" => build_number,
|
||||||
|
"CODE_SIGN_STYLE" => "Manual",
|
||||||
|
}
|
||||||
|
xcargs = xcargs_hash.map { |k, v| "#{k}=#{v.to_s.shellescape}" }.join(" ")
|
||||||
|
|
||||||
|
xcodegen(spec: "project.yml")
|
||||||
|
clear_derived_data(derived_data_path: ENV["DERIVED_DATA_PATH"])
|
||||||
|
|
||||||
|
download_provisioning_profiles
|
||||||
|
disable_automatic_code_signing(path: ENV["PROJECT_PATH"])
|
||||||
|
update_project_provisioning_profiles
|
||||||
|
|
||||||
|
build_app(
|
||||||
|
clean: true,
|
||||||
|
scheme: ENV["SCHEME"],
|
||||||
|
xcargs: xcargs,
|
||||||
|
export_method: "app-store",
|
||||||
|
derived_data_path: ENV["DERIVED_DATA_PATH"],
|
||||||
|
archive_path: ENV["ARCHIVE_PATH"],
|
||||||
|
output_directory: ENV["BUILD_OUTPUT_DIRECTORY"],
|
||||||
|
output_name: "#{ENV['IPA_NAME']}.ipa",
|
||||||
|
buildlog_path: ENV["BUILD_LOG_DIRECTORY"],
|
||||||
|
codesigning_identity: ENV["APPSTORE_CODESIGNING_IDENTITY"],
|
||||||
|
skip_profile_detection: true,
|
||||||
|
export_options: {
|
||||||
|
method: "app-store",
|
||||||
|
signingStyle: "manual",
|
||||||
|
teamID: team_id,
|
||||||
|
signingCertificate: ENV["APPSTORE_SIGNING_CERTIFICATE"],
|
||||||
|
provisioningProfiles: {
|
||||||
|
ENV["MAIN_BUNDLE_ID"] => ENV["APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER"],
|
||||||
|
ENV["NSE_BUNDLE_ID"] => ENV["APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER"],
|
||||||
|
ENV["SHARE_EXTENSION_BUNDLE_ID"] => ENV["APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Downloads the three App Store provisioning profiles"
|
||||||
|
private_lane :download_provisioning_profiles do
|
||||||
|
{
|
||||||
|
ENV["MAIN_BUNDLE_ID"] => [ENV["APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER"], ENV["MAIN_PROVISIONING_PROFILE_FILENAME"]],
|
||||||
|
ENV["NSE_BUNDLE_ID"] => [ENV["APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER"], ENV["NSE_PROVISIONING_PROFILE_FILENAME"]],
|
||||||
|
ENV["SHARE_EXTENSION_BUNDLE_ID"] => [ENV["APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"], ENV["SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME"]],
|
||||||
|
}.each do |bundle_id, (profile_name, filename)|
|
||||||
|
get_provisioning_profile(
|
||||||
|
app_identifier: bundle_id,
|
||||||
|
provisioning_name: profile_name,
|
||||||
|
ignore_profiles_with_different_name: true,
|
||||||
|
adhoc: false,
|
||||||
|
skip_certificate_verification: false,
|
||||||
|
output_path: ENV["PROVISIONING_PROFILES_PATH"],
|
||||||
|
filename: filename,
|
||||||
|
readonly: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Points each target at its downloaded provisioning profile"
|
||||||
|
private_lane :update_project_provisioning_profiles do
|
||||||
|
{
|
||||||
|
ENV["MAIN_TARGET"] => ENV["MAIN_PROVISIONING_PROFILE_FILENAME"],
|
||||||
|
ENV["NSE_TARGET"] => ENV["NSE_PROVISIONING_PROFILE_FILENAME"],
|
||||||
|
ENV["SHARE_EXTENSION_TARGET"] => ENV["SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME"],
|
||||||
|
}.each do |target, filename|
|
||||||
|
update_project_provisioning(
|
||||||
|
xcodeproj: ENV["PROJECT_PATH"],
|
||||||
|
profile: "#{ENV['PROVISIONING_PROFILES_PATH']}#{filename}",
|
||||||
|
# Anchored: target_filter is a regex, and an unanchored "NSE" or "ElementX" would also
|
||||||
|
# match any future target containing that substring.
|
||||||
|
target_filter: "^#{target}$",
|
||||||
|
build_configuration: "Release",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generates a build number from the current timestamp. Monotonic across runs, so a retry never
|
||||||
|
# collides with a build number App Store Connect has already seen.
|
||||||
|
def generate_build_number
|
||||||
|
require 'date'
|
||||||
|
DateTime.now.strftime("%Y%m%d%H%M%S")
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Autogenerated by fastlane
|
||||||
|
#
|
||||||
|
# Ensure this file is checked in to source control!
|
||||||
|
|
||||||
|
gem 'fastlane-plugin-xcodegen'
|
||||||
+1
-1
@@ -68,7 +68,7 @@ include:
|
|||||||
- path: UITests/SupportingFiles/target.yml
|
- path: UITests/SupportingFiles/target.yml
|
||||||
- path: IntegrationTests/SupportingFiles/target.yml
|
- path: IntegrationTests/SupportingFiles/target.yml
|
||||||
- path: AccessibilityTests/SupportingFiles/target.yml
|
- path: AccessibilityTests/SupportingFiles/target.yml
|
||||||
# - path: MyAppVariant/override.yml
|
- path: Variants/HealthChat/healthchat.yml
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
# Element/Matrix dependencies
|
# Element/Matrix dependencies
|
||||||
|
|||||||
Reference in New Issue
Block a user