nvrsion: Add: Chat: Chat View Refinement to modernize session chat interface by removing vertical space for selectors, Fix: Chat: Remove Face ID Permission Text, Refactor: Chat: Live Activity Content Density to open app directly into waiting sessions when tapped, Refactor: Chat: Live Activity Content Density to refresh live activity info significantly faster, Fix: Chat: Live Activity Content Density to refresh live activity info significantly faster, Fix: Chat: Live Activity Content Density to refresh live activity info significantly faster, Fix: Chat: Network Type Transition Optimization to reduce delay during network type switches, Feature Enhancement Planning: All devices in a mesh group should see and be aware of each other; “pair” reframed into “join”; scanning, Fix: Chat: Session Transcript Sync to show only current frame’s transcript when opening a session on another Mac, Build: Push-update path to stay fresh while locked.
Nucleic-Promote: 1 Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -282,6 +282,7 @@ final class RemoteStore: ObservableObject {
|
||||
// MARK: - Lifecycle
|
||||
|
||||
func onAppear() {
|
||||
setupLiveActivityBridge()
|
||||
if demoMode { seedDemo(); return }
|
||||
// Re-plan every connection the moment the network path changes (left Wi-Fi, joined a
|
||||
// network, cellular⇄Wi-Fi) — the proactive half of "immediate switchover".
|
||||
@@ -489,6 +490,9 @@ final class RemoteStore: ObservableObject {
|
||||
if hostID == self.activeHostID { self.mirrorActive() }
|
||||
self.refreshAggregate()
|
||||
self.flushPendingNotificationDecision()
|
||||
// A host that just connected (or reconnected) needs the current Live Activity token
|
||||
// so it can push while the phone is away.
|
||||
self.syncLiveActivityRegistration()
|
||||
}
|
||||
cb.openSnapshot = { [weak self] snap in
|
||||
guard let self, hostID == self.activeHostID, snap.summary.sessionID == self.openSessionID else { return }
|
||||
@@ -834,6 +838,49 @@ final class RemoteStore: ObservableObject {
|
||||
activeConnection?.send(msg)
|
||||
}
|
||||
|
||||
// MARK: - Live Activity push registration (UX_IOS §5.3)
|
||||
|
||||
/// The current Live Activity's APNS update token + id, shipped to every capable Mac so it can
|
||||
/// refresh the lock-screen glance over APNs while the phone is backgrounded and its socket is
|
||||
/// suspended. Sent to *all* connected hosts (not just the active one) — while the phone is
|
||||
/// away, whichever Mac has work to report should be able to push.
|
||||
private var liveActivityReg: (token: String, activityID: String)?
|
||||
/// Host ids that already have the current token (re-sent to a host that (re)connects, and
|
||||
/// re-sent to everyone when the token rotates).
|
||||
private var liveActivitySentTo: Set<String> = []
|
||||
|
||||
private func setupLiveActivityBridge() {
|
||||
LiveActivityManager.shared.onPushToken = { [weak self] token, activityID in
|
||||
guard let self, self.liveActivityReg?.token != token else { return }
|
||||
self.liveActivityReg = (token, activityID)
|
||||
self.liveActivitySentTo.removeAll() // a fresh token must reach every host again
|
||||
self.syncLiveActivityRegistration()
|
||||
}
|
||||
LiveActivityManager.shared.onActivityEnded = { [weak self] activityID in
|
||||
guard let self else { return }
|
||||
self.liveActivityReg = nil
|
||||
self.liveActivitySentTo.removeAll()
|
||||
for conn in self.connections.values where conn.capabilities.canPushLiveActivity {
|
||||
conn.send(.endLiveActivity(activityID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the current Live Activity token to every live, capable host that hasn't got it yet.
|
||||
/// Called when the token changes and on every connection update, so a host that just connected
|
||||
/// — or reconnected while the phone was away — learns the token it needs to push.
|
||||
private func syncLiveActivityRegistration() {
|
||||
guard let reg = liveActivityReg else { return }
|
||||
// A host that dropped should re-register when it returns.
|
||||
liveActivitySentTo = liveActivitySentTo.filter { connections[$0]?.connectivity.isLive == true }
|
||||
for (id, conn) in connections {
|
||||
guard conn.connectivity.isLive, conn.capabilities.canPushLiveActivity,
|
||||
!liveActivitySentTo.contains(id) else { continue }
|
||||
conn.send(.registerLiveActivity(reg.token, reg.activityID))
|
||||
liveActivitySentTo.insert(id)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Demo simulator (offline, interactive)
|
||||
//
|
||||
// In demo mode there's no host, so writes can't go over the wire. Instead they mutate the
|
||||
@@ -887,7 +934,11 @@ final class RemoteStore: ObservableObject {
|
||||
case .setSessionShipBranch(let id, let branch):
|
||||
demoUpdateSession(id) { $0.demoCopy(shipBranch: .some(branch)) }
|
||||
case .hello, .listSessions, .listDashboard, .subscribe, .unsubscribe,
|
||||
.ping, .cancelQueuedMessage, .fetchDiff, .listPeers, .addressUpdate, .meshRoster,
|
||||
.ping, .cancelQueuedMessage, .fetchDiff, .fetchTranscript, .listPeers,
|
||||
.addressUpdate, .meshRoster,
|
||||
// Live Activity push registration is a real-connection concern (there's no host to
|
||||
// push in demo), so it's inert here.
|
||||
.registerLiveActivity, .endLiveActivity,
|
||||
// Session transfer (mesh P5) is a Mac↔Mac flow — the phone never originates these,
|
||||
// and demo has no peer Macs, so they're inert here.
|
||||
.transferOffer, .transferChunk, .transferCommit, .transferCancel:
|
||||
|
||||
Reference in New Issue
Block a user