iPhone: grid fixture, tile backdrop, landscape bar test, upright snapshots
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 files changed,
+29
−8
native/ios/Sources/CallsPhone.swift+6 −2native/ios/Sources/PhoneCallFixture.swift+2 −1native/ios/Sources/PhoneFullScreenCall.swift+5 −4native/ios/UITests/CallsPhoneUITests.swift+16 −1
diff --git a/native/ios/Sources/CallsPhone.swift b/native/ios/Sources/CallsPhone.swift| index d1a42e9..0a8521c 100644 |
| --- a/native/ios/Sources/CallsPhone.swift |
| +++ b/native/ios/Sources/CallsPhone.swift |
| @@ -128,12 +128,13 @@ struct PhoneCall: View { |
| init( |
| call: Call, leave: @escaping () -> Void, style: CallLayoutStyle? = nil, |
| - controlsShown: Bool? = nil, showingMore: Bool = false |
| + controlsShown: Bool? = nil, showingMore: Bool = false, grid: Bool = false |
| ) { |
| self.call = call |
| self.leave = leave |
| self.style = style |
| _showingMore = State(initialValue: showingMore) |
| + _grid = State(initialValue: grid) |
| _chrome = StateObject(wrappedValue: ControlsVisibilityModel(input: .touch, pinned: controlsShown)) |
| } |
| @@ -389,9 +390,12 @@ struct PhoneVideoTile: View { |
| var cornerRadius: CGFloat = 18 |
| var compact = false |
| var labelInsets = EdgeInsets() |
| + /// Behind a picture that is not there: a shade lighter than the stage |
| + /// for a tile among others, so it reads as a tile. |
| + var backdrop = Color.black |
| var body: some View { |
| ZStack { |
| - Color.black |
| + backdrop |
| if let track, !cameraOff, placeholder == nil { |
| PhoneVideo(track: track, fit: screen).scaleEffect(x: mirrored ? -1 : 1, y: 1) |
| } else { |
diff --git a/native/ios/Sources/PhoneCallFixture.swift b/native/ios/Sources/PhoneCallFixture.swift| index a128b46..1bcffdc 100644 |
| --- a/native/ios/Sources/PhoneCallFixture.swift |
| +++ b/native/ios/Sources/PhoneCallFixture.swift |
| @@ -24,7 +24,8 @@ |
| PhoneCall( |
| call: call, leave: {}, |
| style: defaults.bool(forKey: "CallsFixtureClassic") ? .classic : .fullWindow, |
| - controlsShown: pinned, showingMore: defaults.bool(forKey: "CallsFixtureMore")) |
| + controlsShown: pinned, showingMore: defaults.bool(forKey: "CallsFixtureMore"), |
| + grid: fixture == .grid) |
| } |
| .tint(CallsAppearance.accent) |
| .preferredColorScheme(defaults.bool(forKey: "CallsFixtureDark") ? .dark : .light) |
diff --git a/native/ios/Sources/PhoneFullScreenCall.swift b/native/ios/Sources/PhoneFullScreenCall.swift| index f215e40..d904ca1 100644 |
| --- a/native/ios/Sources/PhoneFullScreenCall.swift |
| +++ b/native/ios/Sources/PhoneFullScreenCall.swift |
| @@ -14,6 +14,7 @@ extension PhoneCall { |
| /// The round buttons' size, and the bar's height with its margin. |
| static let button: CGFloat = 56 |
| static let barHeight: CGFloat = 56 + 16 |
| + static let tileBackdrop = Color(white: 0.13) |
| var fullScreen: some View { |
| GeometryReader { geometry in |
| @@ -137,21 +138,21 @@ extension PhoneCall { |
| ForEach(call.screens, id: \.self) { peer in |
| PhoneVideoTile( |
| track: call.remoteScreens[peer], label: screenCaption(call.name(of: peer)), |
| - screen: true, cornerRadius: 8, compact: true |
| + screen: true, cornerRadius: 8, compact: true, backdrop: Self.tileBackdrop |
| ) |
| .frame(width: tile.width, height: tile.height) |
| } |
| PhoneVideoTile( |
| track: call.localTrack, label: "You", muted: call.muted, mirrored: true, |
| cameraOff: call.cameraOff, placeholder: call.selfPlaceholder, cornerRadius: 8, |
| - compact: true |
| + compact: true, backdrop: Self.tileBackdrop |
| ) |
| .frame(width: tile.width, height: tile.height) |
| ForEach(peers) { peer in |
| PhoneVideoTile( |
| track: call.remoteTracks[peer.peer], label: peer.name, |
| muted: call.isMuted(peer.peer), cameraOff: call.isCameraOff(peer.peer), |
| - cornerRadius: 8, compact: true |
| + cornerRadius: 8, compact: true, backdrop: Self.tileBackdrop |
| ) |
| .frame(width: tile.width, height: tile.height) |
| } |
| @@ -178,7 +179,7 @@ extension PhoneCall { |
| return PhoneVideoTile( |
| track: call.localTrack, label: "You", muted: call.muted, mirrored: true, |
| cameraOff: call.cameraOff, placeholder: call.selfPlaceholder, cornerRadius: 14, |
| - compact: true |
| + compact: true, backdrop: Self.tileBackdrop |
| ) |
| .frame(width: pip.width, height: pip.height) |
| .overlay(RoundedRectangle(cornerRadius: 14).strokeBorder(.white.opacity(0.2))) |
diff --git a/native/ios/UITests/CallsPhoneUITests.swift b/native/ios/UITests/CallsPhoneUITests.swift| index 536da5e..8fde901 100644 |
| --- a/native/ios/UITests/CallsPhoneUITests.swift |
| +++ b/native/ios/UITests/CallsPhoneUITests.swift |
| @@ -142,8 +142,11 @@ final class CallsPhoneUITests: XCTestCase { |
| ?? "/tmp/immersive-call") |
| } |
| + /// The screen as the device draws it: upright for portrait, and turned |
| + /// a quarter for landscape (straightened afterwards with `sips -r 270`; |
| + /// the app's own screenshot crops landscape wrongly on iOS 26). |
| @MainActor private func snapshot(_ app: XCUIApplication, _ name: String) throws { |
| - let shot = app.screenshot() |
| + let shot = XCUIScreen.main.screenshot() |
| let attachment = XCTAttachment(screenshot: shot) |
| attachment.name = name |
| attachment.lifetime = .keepAlways |
| @@ -239,4 +242,16 @@ final class CallsPhoneUITests: XCTestCase { |
| try snapshot(classic, "classic-portrait-\(scheme)") |
| } |
| } |
| + |
| + /// Landscape keeps both bar buttons on screen, clear of the Dynamic |
| + /// Island's side. |
| + @MainActor func testLandscapeKeepsTheBarOnScreen() throws { |
| + XCUIDevice.shared.orientation = .landscapeLeft |
| + defer { XCUIDevice.shared.orientation = .portrait } |
| + let app = launchCall("focus", ["-CallsFixtureControls", "shown"]) |
| + XCTAssertTrue(app.buttons["leave"].waitForExistence(timeout: 15)) |
| + XCTAssertTrue(app.buttons["leave"].isHittable) |
| + XCTAssertTrue(app.buttons["moreControls"].isHittable) |
| + XCTAssertLessThan(app.buttons["moreControls"].frame.midX, app.buttons["leave"].frame.midX) |
| + } |
| } |