redesign bottom bar

This commit is contained in:
2026-05-03 21:06:20 -07:00
parent 53a3b722ec
commit ee8a93a8c4
3 changed files with 79 additions and 28 deletions

View File

@@ -6,6 +6,7 @@ struct SybilChatTranscriptView: View {
var isLoading: Bool var isLoading: Bool
var isSending: Bool var isSending: Bool
var topContentInset: CGFloat = 0 var topContentInset: CGFloat = 0
var bottomContentInset: CGFloat = 0
@State private var hasHandledInitialTranscriptScroll = false @State private var hasHandledInitialTranscriptScroll = false
private var hasPendingAssistant: Bool { private var hasPendingAssistant: Bool {
@@ -50,7 +51,7 @@ struct SybilChatTranscriptView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 14) .padding(.horizontal, 14)
.padding(.top, 18 + topContentInset) .padding(.top, 18 + topContentInset)
.padding(.bottom, 18) .padding(.bottom, 18 + bottomContentInset)
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.scrollDismissesKeyboard(.interactively) .scrollDismissesKeyboard(.interactively)

View File

@@ -7,6 +7,7 @@ struct SybilSearchResultsView: View {
var isRunning: Bool var isRunning: Bool
var isStartingChat: Bool = false var isStartingChat: Bool = false
var topContentInset: CGFloat = 0 var topContentInset: CGFloat = 0
var bottomContentInset: CGFloat = 0
var onStartChat: (() -> Void)? = nil var onStartChat: (() -> Void)? = nil
var body: some View { var body: some View {
@@ -100,7 +101,7 @@ struct SybilSearchResultsView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 14) .padding(.horizontal, 14)
.padding(.top, 20 + topContentInset) .padding(.top, 20 + topContentInset)
.padding(.bottom, 20) .padding(.bottom, 20 + bottomContentInset)
} }
.scrollDismissesKeyboard(.interactively) .scrollDismissesKeyboard(.interactively)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)

View File

@@ -35,6 +35,7 @@ struct SybilWorkspaceView: View {
@State private var newChatSwipeFeedbackGenerator: UIImpactFeedbackGenerator? @State private var newChatSwipeFeedbackGenerator: UIImpactFeedbackGenerator?
private let customWorkspaceNavigationContentInset: CGFloat = 96 private let customWorkspaceNavigationContentInset: CGFloat = 96
private let composerOverlayContentInset: CGFloat = 112
private var isSettingsSelected: Bool { private var isSettingsSelected: Bool {
if case .settings = viewModel.selectedItem { if case .settings = viewModel.selectedItem {
@@ -149,7 +150,7 @@ struct SybilWorkspaceView: View {
.overlay(SybilTheme.border) .overlay(SybilTheme.border)
} }
Group { ZStack(alignment: .bottom) {
if isSettingsSelected { if isSettingsSelected {
SybilSettingsView(viewModel: viewModel) SybilSettingsView(viewModel: viewModel)
} else if viewModel.isSearchMode { } else if viewModel.isSearchMode {
@@ -158,7 +159,8 @@ struct SybilWorkspaceView: View {
isLoading: viewModel.isLoadingSelection, isLoading: viewModel.isLoadingSelection,
isRunning: viewModel.isSending, isRunning: viewModel.isSending,
isStartingChat: viewModel.isCreatingSearchChat, isStartingChat: viewModel.isCreatingSearchChat,
topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0 topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0,
bottomContentInset: viewModel.showsComposer ? composerOverlayContentInset : 0
) { ) {
Task { Task {
await viewModel.startChatFromSelectedSearch() await viewModel.startChatFromSelectedSearch()
@@ -169,10 +171,15 @@ struct SybilWorkspaceView: View {
messages: viewModel.displayedMessages, messages: viewModel.displayedMessages,
isLoading: viewModel.isLoadingSelection, isLoading: viewModel.isLoadingSelection,
isSending: viewModel.isSending, isSending: viewModel.isSending,
topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0 topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0,
bottomContentInset: viewModel.showsComposer ? composerOverlayContentInset : 0
) )
.id(transcriptScrollContextID) .id(transcriptScrollContextID)
} }
if viewModel.showsComposer {
composerBar
}
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background { .background {
@@ -193,12 +200,6 @@ struct SybilWorkspaceView: View {
} }
) )
} }
if viewModel.showsComposer {
Divider()
.overlay(SybilTheme.border)
composerBar
}
} }
} }
@@ -501,7 +502,7 @@ struct SybilWorkspaceView: View {
} }
TextField( TextField(
viewModel.isSearchMode ? "Search the web" : "Message Sybil", viewModel.isSearchMode ? "Search the web" : "Enter Prompt",
text: $viewModel.composer, text: $viewModel.composer,
axis: .vertical axis: .vertical
) )
@@ -518,10 +519,7 @@ struct SybilWorkspaceView: View {
.background( .background(
RoundedRectangle(cornerRadius: 12) RoundedRectangle(cornerRadius: 12)
.fill(SybilTheme.composerGradient) .fill(SybilTheme.composerGradient)
.overlay( .opacity(0.98)
RoundedRectangle(cornerRadius: 12)
.stroke(SybilTheme.primary.opacity(0.34), lineWidth: 1)
)
) )
.foregroundStyle(SybilTheme.text) .foregroundStyle(SybilTheme.text)
@@ -546,23 +544,19 @@ struct SybilWorkspaceView: View {
} }
} }
.padding(.horizontal, 14) .padding(.horizontal, 14)
.padding(.vertical, 12) .padding(.top, 34)
.background( .padding(.bottom, 12)
LinearGradient( .background(alignment: .bottom) {
colors: [ SybilComposerFadeBackground()
SybilTheme.background.opacity(0.18), .allowsHitTesting(false)
SybilTheme.background.opacity(0.96) }
],
startPoint: .top,
endPoint: .bottom
)
)
.overlay { .overlay {
if isComposerDropTargeted && !viewModel.isSearchMode { if isComposerDropTargeted && !viewModel.isSearchMode {
RoundedRectangle(cornerRadius: 18) RoundedRectangle(cornerRadius: 18)
.stroke(SybilTheme.accent.opacity(0.78), style: StrokeStyle(lineWidth: 1.5, dash: [7, 5])) .stroke(SybilTheme.accent.opacity(0.78), style: StrokeStyle(lineWidth: 1.5, dash: [7, 5]))
.padding(.horizontal, 14) .padding(.horizontal, 14)
.padding(.vertical, 10) .padding(.top, 32)
.padding(.bottom, 10)
} }
} }
.onDrop(of: [UTType.fileURL.identifier, UTType.image.identifier], isTargeted: $isComposerDropTargeted) { providers in .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 { private struct SybilNavigationIcon: View {
var systemImage: String var systemImage: String
@@ -1024,6 +1072,7 @@ private struct SybilNavigationFadeBackground: View {
.blendMode(.screen) .blendMode(.screen)
.offset(x: -44, y: -46) .offset(x: -44, y: -46)
} }
.frame(height: 200.0)
.ignoresSafeArea(edges: .top) .ignoresSafeArea(edges: .top)
} }
} }