From ee8a93a8c41b8d6a54fd7117f6d821843a4af33c Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sun, 3 May 2026 21:06:20 -0700 Subject: [PATCH] redesign bottom bar --- .../Sybil/SybilChatTranscriptView.swift | 3 +- .../Sybil/SybilSearchResultsView.swift | 3 +- .../Sources/Sybil/SybilWorkspaceView.swift | 101 +++++++++++++----- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/ios/Packages/Sybil/Sources/Sybil/SybilChatTranscriptView.swift b/ios/Packages/Sybil/Sources/Sybil/SybilChatTranscriptView.swift index ede49c1..728265b 100644 --- a/ios/Packages/Sybil/Sources/Sybil/SybilChatTranscriptView.swift +++ b/ios/Packages/Sybil/Sources/Sybil/SybilChatTranscriptView.swift @@ -6,6 +6,7 @@ struct SybilChatTranscriptView: View { var isLoading: Bool var isSending: Bool var topContentInset: CGFloat = 0 + var bottomContentInset: CGFloat = 0 @State private var hasHandledInitialTranscriptScroll = false private var hasPendingAssistant: Bool { @@ -50,7 +51,7 @@ struct SybilChatTranscriptView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 14) .padding(.top, 18 + topContentInset) - .padding(.bottom, 18) + .padding(.bottom, 18 + bottomContentInset) } .frame(maxWidth: .infinity, alignment: .leading) .scrollDismissesKeyboard(.interactively) diff --git a/ios/Packages/Sybil/Sources/Sybil/SybilSearchResultsView.swift b/ios/Packages/Sybil/Sources/Sybil/SybilSearchResultsView.swift index b40d222..3a9c083 100644 --- a/ios/Packages/Sybil/Sources/Sybil/SybilSearchResultsView.swift +++ b/ios/Packages/Sybil/Sources/Sybil/SybilSearchResultsView.swift @@ -7,6 +7,7 @@ struct SybilSearchResultsView: View { var isRunning: Bool var isStartingChat: Bool = false var topContentInset: CGFloat = 0 + var bottomContentInset: CGFloat = 0 var onStartChat: (() -> Void)? = nil var body: some View { @@ -100,7 +101,7 @@ struct SybilSearchResultsView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 14) .padding(.top, 20 + topContentInset) - .padding(.bottom, 20) + .padding(.bottom, 20 + bottomContentInset) } .scrollDismissesKeyboard(.interactively) .frame(maxWidth: .infinity, alignment: .leading) diff --git a/ios/Packages/Sybil/Sources/Sybil/SybilWorkspaceView.swift b/ios/Packages/Sybil/Sources/Sybil/SybilWorkspaceView.swift index d828a02..8ba8bde 100644 --- a/ios/Packages/Sybil/Sources/Sybil/SybilWorkspaceView.swift +++ b/ios/Packages/Sybil/Sources/Sybil/SybilWorkspaceView.swift @@ -35,6 +35,7 @@ struct SybilWorkspaceView: View { @State private var newChatSwipeFeedbackGenerator: UIImpactFeedbackGenerator? private let customWorkspaceNavigationContentInset: CGFloat = 96 + private let composerOverlayContentInset: CGFloat = 112 private var isSettingsSelected: Bool { if case .settings = viewModel.selectedItem { @@ -149,7 +150,7 @@ struct SybilWorkspaceView: View { .overlay(SybilTheme.border) } - Group { + ZStack(alignment: .bottom) { if isSettingsSelected { SybilSettingsView(viewModel: viewModel) } else if viewModel.isSearchMode { @@ -158,7 +159,8 @@ struct SybilWorkspaceView: View { isLoading: viewModel.isLoadingSelection, isRunning: viewModel.isSending, isStartingChat: viewModel.isCreatingSearchChat, - topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0 + topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0, + bottomContentInset: viewModel.showsComposer ? composerOverlayContentInset : 0 ) { Task { await viewModel.startChatFromSelectedSearch() @@ -169,10 +171,15 @@ struct SybilWorkspaceView: View { messages: viewModel.displayedMessages, isLoading: viewModel.isLoadingSelection, isSending: viewModel.isSending, - topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0 + topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0, + bottomContentInset: viewModel.showsComposer ? composerOverlayContentInset : 0 ) .id(transcriptScrollContextID) } + + if viewModel.showsComposer { + composerBar + } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background { @@ -193,12 +200,6 @@ struct SybilWorkspaceView: View { } ) } - - if viewModel.showsComposer { - Divider() - .overlay(SybilTheme.border) - composerBar - } } } @@ -501,7 +502,7 @@ struct SybilWorkspaceView: View { } TextField( - viewModel.isSearchMode ? "Search the web" : "Message Sybil", + viewModel.isSearchMode ? "Search the web" : "Enter Prompt", text: $viewModel.composer, axis: .vertical ) @@ -518,10 +519,7 @@ struct SybilWorkspaceView: View { .background( RoundedRectangle(cornerRadius: 12) .fill(SybilTheme.composerGradient) - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke(SybilTheme.primary.opacity(0.34), lineWidth: 1) - ) + .opacity(0.98) ) .foregroundStyle(SybilTheme.text) @@ -546,23 +544,19 @@ struct SybilWorkspaceView: View { } } .padding(.horizontal, 14) - .padding(.vertical, 12) - .background( - LinearGradient( - colors: [ - SybilTheme.background.opacity(0.18), - SybilTheme.background.opacity(0.96) - ], - startPoint: .top, - endPoint: .bottom - ) - ) + .padding(.top, 34) + .padding(.bottom, 12) + .background(alignment: .bottom) { + SybilComposerFadeBackground() + .allowsHitTesting(false) + } .overlay { if isComposerDropTargeted && !viewModel.isSearchMode { RoundedRectangle(cornerRadius: 18) .stroke(SybilTheme.accent.opacity(0.78), style: StrokeStyle(lineWidth: 1.5, dash: [7, 5])) .padding(.horizontal, 14) - .padding(.vertical, 10) + .padding(.top, 32) + .padding(.bottom, 10) } } .onDrop(of: [UTType.fileURL.identifier, UTType.image.identifier], isTargeted: $isComposerDropTargeted) { providers in @@ -982,6 +976,60 @@ private extension UIView { } } +private struct SybilComposerFadeBackground: View { + var body: some View { + ZStack(alignment: .bottomLeading) { + LinearGradient( + colors: [ + Color.clear, + SybilTheme.background.opacity(0.30), + SybilTheme.background.opacity(0.86), + SybilTheme.background.opacity(0.86), + SybilTheme.background.opacity(0.98) + ], + startPoint: .top, + endPoint: .bottom + ) + + LinearGradient( + colors: [ + SybilTheme.primary.opacity(0.18), + SybilTheme.surface.opacity(0.16), + SybilTheme.accent.opacity(0.08) + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .mask( + LinearGradient( + colors: [ + Color.clear, + Color.black.opacity(0.42), + Color.black + ], + startPoint: .top, + endPoint: .bottom + ) + ) + .blendMode(.screen) + + RadialGradient( + colors: [ + SybilTheme.primary.opacity(0.28), + SybilTheme.primary.opacity(0.08), + Color.clear + ], + center: .bottomLeading, + startRadius: 8, + endRadius: 180 + ) + .blendMode(.screen) + .offset(x: -42, y: 42) + } + .ignoresSafeArea(edges: .bottom) + } +} + private struct SybilNavigationIcon: View { var systemImage: String @@ -1024,6 +1072,7 @@ private struct SybilNavigationFadeBackground: View { .blendMode(.screen) .offset(x: -44, y: -46) } + .frame(height: 200.0) .ignoresSafeArea(edges: .top) } }