diff --git a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift index 80f3c37..10f7ad2 100644 --- a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift +++ b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift @@ -27,6 +27,10 @@ struct SessionDetailView: View { .safeAreaInset(edge: .bottom) { actionArea } .navigationTitle(summary?.title ?? "Session") .navigationBarTitleDisplayMode(.inline) + // With a session open the floating chat bar owns the bottom edge; the tab bar below it + // would be a redundant second bar (and clip the glass action area), so hide it while + // this detail is pushed. A no-op in the iPad split, which has no tab bar. + .toolbar(.hidden, for: .tabBar) .toolbar { ToolbarItem(placement: .topBarTrailing) { sessionMenu } } diff --git a/NucleicRemote/NucleicRemote/Views/SessionsView.swift b/NucleicRemote/NucleicRemote/Views/SessionsView.swift index 81ad3bb..5597625 100644 --- a/NucleicRemote/NucleicRemote/Views/SessionsView.swift +++ b/NucleicRemote/NucleicRemote/Views/SessionsView.swift @@ -108,28 +108,31 @@ struct SessionsView: View { } } } - .presentationDetents([.medium, .large]) + // The composer needs only a fraction of the screen — a compact height by default, + // still expandable if the advanced options are opened. + .presentationDetents([.height(360), .large]) + .presentationDragIndicator(.visible) } .safeAreaInset(edge: .bottom) { ConnectionChip().padding(.bottom, 8) } } } - /// The circular plus button, styled to match the transcript's jump-to-bottom FAB: an accent - /// disc with a soft shadow, floated clear of the connection chip. + /// The circular plus button, on Liquid Glass rather than a solid teal disc — an accent-tinted + /// glyph over a translucent glass circle so it reads as a floating control, not a stamped + /// button. Sits low over the list, clear of the connection chip. private var newChatButton: some View { Button { showNewChat = true } label: { Image(systemName: "plus") .font(.system(size: 22, weight: .semibold)) - .foregroundStyle(.white) + .foregroundStyle(Palette.accent) .frame(width: 56, height: 56) - .background(Palette.accent, in: .circle) - .shadow(color: .black.opacity(0.25), radius: 6, y: 2) + .glassCircle() } .buttonStyle(.plain) .padding(.trailing, 20) - .padding(.bottom, 20) + .padding(.bottom, 6) .accessibilityLabel("New chat") } diff --git a/NucleicRemote/NucleicRemote/Views/Theme.swift b/NucleicRemote/NucleicRemote/Views/Theme.swift index 1417988..d6a3285 100644 --- a/NucleicRemote/NucleicRemote/Views/Theme.swift +++ b/NucleicRemote/NucleicRemote/Views/Theme.swift @@ -354,6 +354,22 @@ struct GlassSurface: ViewModifier { } } +/// A circular Liquid Glass backing for a floating round control (the New-chat FAB). Real +/// interactive `glassEffect` on iOS 26; an ultra-thin material disc with a hairline edge and a +/// soft shadow on earlier systems so it still lifts off the list. +struct GlassCircle: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 26.0, *) { + content.glassEffect(.regular.interactive(), in: .circle) + } else { + content + .background(.ultraThinMaterial, in: .circle) + .overlay(Circle().strokeBorder(Color.primary.opacity(0.08), lineWidth: 1)) + .shadow(color: .black.opacity(0.2), radius: 6, y: 2) + } + } +} + /// A lifted card surface with a hairline border — the mobile echo of `AppTheme.surface`. struct CardBackground: ViewModifier { var padding: CGFloat = 14 @@ -373,6 +389,9 @@ extension View { func glassSurface(cornerRadius: CGFloat = 24) -> some View { modifier(GlassSurface(cornerRadius: cornerRadius)) } + /// Back a circular control (the floating New-chat FAB) with interactive Liquid Glass — the + /// round analogue of `glassSurface`, falling back to an ultra-thin material disc pre-iOS 26. + func glassCircle() -> some View { modifier(GlassCircle()) } /// Apply the teal accent app-wide. func nucleicTint() -> some View { tint(Palette.accent) } /// Give a text field a way to release the keyboard. Without this, iOS leaves a focused