mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Use more Liquid Glass in the room/space screen toolbars (#4965)
* Add a glass style to the Room/Space header view on iOS 26. * Update snapshots and fix the join call button.
This commit is contained in:
@@ -52,7 +52,7 @@ extension View {
|
||||
if #available(iOS 26, *) {
|
||||
// `.glassProminent` breaks our preview tests so we need to disable it when running tests.
|
||||
// https://github.com/pointfreeco/swift-snapshot-testing/issues/1029#issuecomment-3366942138
|
||||
if ProcessInfo.isRunningTests {
|
||||
if ProcessInfo.isRunningUnitTests {
|
||||
self
|
||||
} else {
|
||||
buttonStyle(.glassProminent)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations Ltd.
|
||||
// Copyright 2023-2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ElementCallButtonStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.padding(.horizontal, 16.0)
|
||||
.padding(.vertical, 4.0)
|
||||
.foregroundColor(.compound.bgCanvasDefault)
|
||||
.background(Color.compound.iconAccentTertiary)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations Ltd.
|
||||
// Copyright 2023-2025 New Vector Ltd.
|
||||
//
|
||||
// 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
|
||||
|
||||
struct JoinCallButton: View {
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 26, *) {
|
||||
glassButton
|
||||
} else {
|
||||
customButton
|
||||
}
|
||||
}
|
||||
|
||||
var glassButton: some View {
|
||||
Button(action: action) {
|
||||
Text(L10n.a11yJoinCall)
|
||||
.font(.compound.bodyLG.weight(.medium))
|
||||
.foregroundStyle(.compound.textOnSolidPrimary)
|
||||
}
|
||||
.tint(.compound.bgAccentRest)
|
||||
.backportButtonStyleGlassProminent()
|
||||
}
|
||||
|
||||
var customButton: some View {
|
||||
Button(action: action) {
|
||||
Label(L10n.actionJoin, icon: \.videoCallSolid)
|
||||
.labelStyle(.titleAndIcon)
|
||||
}
|
||||
.buttonStyle(CustomStyle())
|
||||
.accessibilityLabel(L10n.a11yJoinCall)
|
||||
}
|
||||
|
||||
struct CustomStyle: ButtonStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.padding(.horizontal, 16.0)
|
||||
.padding(.vertical, 4.0)
|
||||
.foregroundColor(.compound.bgCanvasDefault)
|
||||
.background(Color.compound.iconAccentTertiary)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct JoinCallButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
Color.clear
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
JoinCallButton { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,24 @@ struct RoomHeaderView: View {
|
||||
|
||||
let mediaProvider: MediaProviderProtocol?
|
||||
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 26.0, *) {
|
||||
// On iOS 26+ we use the toolbarRole(.editor) to leading align.
|
||||
content
|
||||
Button(action: action) {
|
||||
// On iOS 26+ we use the toolbarRole(.editor) to leading align.
|
||||
content
|
||||
}
|
||||
.backportButtonStyleGlass()
|
||||
} else {
|
||||
// On iOS 18 and lower, the editor role causes an animation glitch with the back button whenever
|
||||
// you push a screen whilst the large title is visible on the room screen.
|
||||
content
|
||||
// So take up as much space as possible, with a leading alignment for use in the default principal toolbar position
|
||||
.frame(idealWidth: .greatestFiniteMagnitude, maxWidth: .infinity, alignment: .leading)
|
||||
// Using a button stops it from getting truncated in the navigation bar
|
||||
.contentShape(.rect)
|
||||
.onTapGesture(perform: action)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +106,7 @@ struct RoomHeaderView_Previews: PreviewProvider, TestablePreview {
|
||||
name: "Some Room Name",
|
||||
avatarURL: avatarURL),
|
||||
dmRecipientVerificationState: verificationState,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
mediaProvider: MediaProviderMock(configuration: .init())) { }
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,6 @@ enum TestablePreviewsDictionary {
|
||||
"TimelineReplyView_Previews" : TimelineReplyView_Previews.self,
|
||||
"TimelineStartRoomTimelineView_Previews" : TimelineStartRoomTimelineView_Previews.self,
|
||||
"TimelineThreadSummaryView_Previews" : TimelineThreadSummaryView_Previews.self,
|
||||
"TimelineView_Previews" : TimelineView_Previews.self,
|
||||
"TombstonedAvatarImage_Previews" : TombstonedAvatarImage_Previews.self,
|
||||
"ToolbarButton_Previews" : ToolbarButton_Previews.self,
|
||||
"TypingIndicatorView_Previews" : TypingIndicatorView_Previews.self,
|
||||
|
||||
@@ -320,7 +320,9 @@ struct JoinRoomScreen: View {
|
||||
if let avatar = context.viewState.avatar {
|
||||
RoomHeaderView(roomName: context.viewState.title,
|
||||
roomAvatar: avatar,
|
||||
mediaProvider: context.mediaProvider)
|
||||
mediaProvider: context.mediaProvider) {
|
||||
// There is no action but the iOS 26 designs have it looking like a button.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,12 +159,9 @@ struct RoomScreen: View {
|
||||
RoomHeaderView(roomName: context.viewState.roomTitle,
|
||||
roomAvatar: context.viewState.roomAvatar,
|
||||
dmRecipientVerificationState: context.viewState.dmRecipientVerificationState,
|
||||
mediaProvider: context.mediaProvider)
|
||||
// Using a button stops it from getting truncated in the navigation bar
|
||||
.contentShape(.rect)
|
||||
.onTapGesture {
|
||||
context.send(viewAction: .displayRoomDetails)
|
||||
}
|
||||
mediaProvider: context.mediaProvider) {
|
||||
context.send(viewAction: .displayRoomDetails)
|
||||
}
|
||||
}
|
||||
|
||||
if !ProcessInfo.processInfo.isiOSAppOnMac {
|
||||
@@ -180,14 +177,9 @@ struct RoomScreen: View {
|
||||
@ViewBuilder
|
||||
private var callButton: some View {
|
||||
if context.viewState.hasOngoingCall {
|
||||
Button {
|
||||
JoinCallButton {
|
||||
context.send(viewAction: .displayCall)
|
||||
} label: {
|
||||
Label(L10n.actionJoin, icon: \.videoCallSolid)
|
||||
.labelStyle(.titleAndIcon)
|
||||
}
|
||||
.buttonStyle(ElementCallButtonStyle())
|
||||
.accessibilityLabel(L10n.a11yJoinCall)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.joinCall)
|
||||
} else {
|
||||
Button {
|
||||
|
||||
@@ -72,14 +72,12 @@ struct SpaceScreen: View {
|
||||
ToolbarItem(placement: .principal) {
|
||||
RoomHeaderView(roomName: context.viewState.space.name,
|
||||
roomAvatar: context.viewState.space.avatar,
|
||||
mediaProvider: context.mediaProvider)
|
||||
.contentShape(.rect)
|
||||
.onTapGesture {
|
||||
if context.viewState.isSpaceManagementEnabled,
|
||||
let roomProxy = context.viewState.roomProxy {
|
||||
context.send(viewAction: .spaceSettings(roomProxy: roomProxy))
|
||||
}
|
||||
mediaProvider: context.mediaProvider) {
|
||||
if context.viewState.isSpaceManagementEnabled,
|
||||
let roomProxy = context.viewState.roomProxy {
|
||||
context.send(viewAction: .spaceSettings(roomProxy: roomProxy))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should really use a ToolbarItemGroup(placement: .secondaryAction), however it
|
||||
|
||||
@@ -65,9 +65,9 @@ struct ThreadTimelineScreen: View {
|
||||
roomSubtitle: context.viewState.roomTitle,
|
||||
roomAvatar: context.viewState.roomAvatar,
|
||||
dmRecipientVerificationState: context.viewState.dmRecipientVerificationState,
|
||||
mediaProvider: context.mediaProvider)
|
||||
// Using a button stops it from getting truncated in the navigation bar
|
||||
.contentShape(.rect)
|
||||
mediaProvider: context.mediaProvider) {
|
||||
// There is no action but the iOS 26 designs have it looking like a button.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ struct TimelineViewRepresentable: UIViewControllerRepresentable {
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct TimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
struct TimelineView_Previews: PreviewProvider { // Not testable as this preview is built the same way as RoomScreen.
|
||||
static let roomProxyMock = JoinedRoomProxyMock(.init(id: "stable_id",
|
||||
name: "Preview room"))
|
||||
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
||||
|
||||
Reference in New Issue
Block a user