Astradial iOS App (astradial-ios)¶
Fork of BelledonneCommunications/linphone-iphone (Linphone 6.x, full-SwiftUI rewrite). We keep the engine and the iOS-platform plumbing; we replace the UI with a native iOS Phone-app clone per the iOS UI Spec.
- Local checkout:
~/AstradialDevelopment/Mobile App/astradial-ios - Cloned: June 2026 from upstream
master - Upstream remote: keep
originpointed at our fork once created; add upstream asupstreamto pull SDK/feature updates:git remote add upstream https://github.com/BelledonneCommunications/linphone-iphone
What the codebase gives us¶
Linphone 6 is a 2024+ ground-up rewrite: SwiftUI throughout, and the SIP engine consumed as a Swift Package Manager dependency (linphone-sdk-swift-ios from gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git). No CocoaPods, no submodule SDK builds — open LinphoneApp.xcodeproj in Xcode, SPM resolves the SDK, build and run.
Source map (what to keep vs. replace)¶
| Path | What it is | Astradial action |
|---|---|---|
Linphone/Core/ (CoreContext.swift, CorePreferences.swift) | liblinphone Core lifecycle, config, iterate loop | Keep — this is the engine binding |
Linphone/TelecomManager/ (TelecomManager.swift, ProviderDelegate.swift, EarlyPushkitDelegate.swift) | CallKit provider + PushKit VoIP push handling | Keep — hardest iOS plumbing, already solved |
Linphone/Contacts/ContactsManager.swift | CNContactStore ↔ Linphone Friend bridge | Keep, extend for stock-Contacts UI |
Linphone/UI/Main/History/ | Call history (Recents) views + view models | Keep view models, replace views with stock-Recents UI |
Linphone/UI/Main/Contacts/ | Contacts views + view models | Keep view models, replace views |
Linphone/UI/Call/ (CallView.swift, fragments, view models) | In-call UI | Keep logic; restyle per spec (CallKit native screen does most of the work) |
Linphone/UI/Main/ContentView.swift | Root tab/navigation structure | Replace with the 5-tab Phone-app TabView (Favourites, Recents, Contacts, Keypad, Voicemail) |
Linphone/UI/Assistant/, UI/Welcome/ | Linphone onboarding/assistant | Replace with Astradial QR provisioning + manual SIP entry |
Linphone/UI/Main/Conversations/, Meetings/, Recordings/, Help/, Settings/ | Chat, meetings, etc. | Hide/remove from UI for v1 (engine support stays) |
Linphone/Assets.xcassets, Fonts/, branding, GoogleService-Info.plist | Linphone branding, Firebase config | Replace with Astradial branding; our own push/Firebase config |
msgNotificationService/, linphoneExtension/, intentsExtension/ | Notification service + share + Siri intents extensions | Review per-extension; keep what calling needs |
*.lproj/, Linphone/Localizable/ | Translations | English only for v1; prune later |
Build prerequisites¶
- Mac with Xcode (15+), Apple developer account for device builds (CallKit/PushKit do not work in the simulator for real calls).
- Open
LinphoneApp.xcodeproj, let SPM resolvelinphone-sdk-swift-ios, select a team, build to a device. - A local SDK build is possible (see upstream
README.md, "Using a local Linphone SDK") but not needed unless we patch the SDK.
First build — verified June 2026 (what it took)¶
Verified on Xcode 26.5 (17F42), scheme Linphone, iPhone 17 Pro simulator (iOS 26.5). App builds, installs, launches to the Linphone welcome screen. Three gotchas, all fixed on the astradial branch (commit 1f3e740):
- Upstream's SDK pin was broken. The project tracked the
betabranch oflinphone-sdk-swift-ios, whose binary XCFrameworks live underdownload.linphone.org/snapshots/…— Belledonne purges those, so all 17 zips 404'd and package resolution failed. Fix: point the package requirement at thestablebranch (→ SDK 5.5.1 release artifacts, which persist). If resolution ever 404s again after an upstream merge, check which branch the pbxproj pins. - One API mismatch. App code on
masterwas written against the beta SDK's renamedConference.isMe(uri:); stable 5.5.1 expectsisMe(URI:). 10 call sites inCallViewModel.swift. - Don't build with
CODE_SIGNING_ALLOWED=NO. It strips the app-group entitlement (group.belledonne-communications.linphone), liblinphone then can't create its data directory and the app exits at startup ("Create data path directory error"). Default simulator signing (no team needed) works.
Useful CLI (when xcode-select points at CommandLineTools, prefix with DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer):
xcodebuild -project LinphoneApp.xcodeproj -scheme Linphone \
-configuration Debug -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build
Download note: the SDK + Firebase binaries are ~1.3 GB on first resolve; linphone.xcframework and mediastreamer2.xcframework are the big ones and can time out — just re-run, completed downloads are cached.
App flow (implemented June 2026)¶
Onboarding (replaces Linphone Welcome/Assistant): [Sign In | Skip login] → Firebase email/password (same auth as the web dashboard) → native "Connect Your Line" SIP step with QR scan (editor's Zoiper-style payload) or manual entry; signed-in owners land on the Analytics (Pulse) tab. Main UI: Analytics · Recents · Contacts · Keypad · Tickets (open-count badge on tab + app icon). In-call: native stock-Phone screen (NativeCallUI.swift) — mute / DTMF pad / speaker / hold / record, replacing Linphone's CallView. Display name is now Astradial (bundle id still org.linphone.phone until the Apple-portal rebrand, task #3).
Rebrand checklist (before any TestFlight build)¶
- New bundle identifier (
com.astradial.phone— decide and register in the Apple Developer portal) + app name, icon, splash. - Replace
GoogleService-Info.plistwith an Astradial Firebase project (or drop Firebase if only used for Linphone's push/crash). - Entitlements: our App Group, our Push/VoIP certificates.
- Strip Linphone default account creation (their assistant points at sip.linphone.org and Flexisip account services).
- Default
linphonerc/CorePreferences pointed at Astradial: transports, codecs (opus/PCMU/PCMA to match our Asterisk), STUN/TURN if needed.
Astradial integration¶
Same model as the previous softphone (the server side already exists):
- SIP: register to
devsip.astradial.com:5060(prod) /stagesip.astradial.com:5080(staging) over UDP/TCP/TLS with the user'sasterisk_endpoint+ secret. No WebRTC/WSS requirement — liblinphone is a classic SIP UA, so the Flutter-erawebrtc=yes/admin-only restriction does not apply. - Provisioning: scan the editor's SIP QR (
Users → SIP icon; format documented in Softphone App → QR provisioning). Parse the Zoiper-style payload, create the LinphoneAccountfrom it. liblinphone also supports remote provisioning via a config URL — consider serving a per-userlinphonercfrom the API later.
Open questions¶
| # | Question | Notes |
|---|---|---|
| 1 | VoIP push for inbound calls when app is killed. | iOS kills background SIP registrations. Linphone solves this with Flexisip proxy sending PushKit pushes. Our Asterisk does not push. Options: (a) run a push gateway that subscribes/forks INVITEs to APNs VoIP push, (b) Flexisip in front of Asterisk, (c) v1 ships foreground/recent-background calling only. Must be decided before v1 "always reachable" claims. |
| 2 | GPLv3 vs commercial licence. | linphone-iphone app code is GPLv3; SDK is dual-licensed. Closed-source App Store distribution needs either GPL compliance (publish our source) or a paid Belledonne licence. Business decision — track before submission. |
| 3 | Visual voicemail backend. | Stock-style Voicemail tab needs message list + audio from Asterisk app_voicemail. No API for this today. v1 fallback: native-looking empty state + "Call Voicemail" feature-code dial. |
| 4 | Per-user push credentials / Firebase. | Upstream ships Linphone's GoogleService-Info.plist; we must not ship it. Decide our crash-reporting + push stack. |
Suggested branch strategy¶
mastertracks upstream (pull fromupstream/masterto take SDK bumps).astradialis our main development branch; all UI replacement work lands there. Rebases onto upstream are deliberate, occasional, and tested — upstream moves fast and touches the same UI files we are rewriting, so prefer cherry-picking engine-relevant fixes over blind merges.
Milestones (v1)¶
- M0 — builds & registers: rebranded shell builds on device, registers against staging via QR provisioning, can place/receive a call with CallKit (current Linphone UI still visible underneath is acceptable here).
- M1 — Keypad + outbound: stock-Phone tab bar + Keypad screen per UI spec, outbound calls.
- M2 — Recents + Contacts: call history and address-book screens per spec, info/detail pages, tap-to-call.
- M3 — Favourites + in-call polish: favourites CRUD, in-call screen parity, multi-call/transfer/DTMF verified against Asterisk (on-call features).
- M4 — Voicemail tab + push decision implemented, TestFlight beta to internal users.
Acceptance bar for every UI milestone: the side-by-side test in iOS UI Spec → Acceptance test.