Skip to content

Commit 54a7a01

Browse files
committed
Revert unrequested mobile composer WIP in GaryxMobileComposerViews
Restores the pre-feature composer view (blob b13df22), dropping a half-finished draft-API rename (model.draft / isSelectedThreadVisiblyRunning, never defined on the model) and a glass-card restyle that left the iOS app build broken. The per-thread draft store (activeComposerDraft / setComposerDraft) and the provider / agent (Trae) + service-tier work in other files are unaffected.
1 parent a0fc688 commit 54a7a01

1 file changed

Lines changed: 53 additions & 45 deletions

File tree

mobile/garyx-mobile/App/GaryxMobile/GaryxMobileComposerViews.swift

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,18 @@ private enum GaryxComposerLayout {
5050

5151

5252
struct GaryxComposer: View {
53+
@Environment(\.isEnabled) private var isEnabled
5354
@EnvironmentObject private var model: GaryxMobileModel
5455
let isFocused: FocusState<Bool>.Binding
5556
@State private var draftText = ""
5657
@State private var draftContextVersion = 0
58+
/// Identity generation for the draft field. While a CJK keyboard still
59+
/// reports an input session, SwiftUI can skip pushing a programmatic
60+
/// clear into the focused vertical-axis `TextField`, leaving the sent
61+
/// text visible until the next layout pass. Re-assigning the same empty
62+
/// string cannot dirty state, so the only deterministic flush is
63+
/// recreating the field when the draft context resets.
64+
@State private var draftFieldGeneration = 0
5765
@State private var isPickingAttachments = false
5866
@State private var isPickingPhotos = false
5967
@State private var selectedPhotoItems: [PhotosPickerItem] = []
@@ -68,7 +76,7 @@ struct GaryxComposer: View {
6876
}
6977

7078
private var showsSendButton: Bool {
71-
!model.isSelectedThreadVisiblyRunning || hasLocalPayload
79+
!model.isSelectedThreadSending || hasLocalPayload
7280
}
7381

7482
private var canChangeWorkspaceMode: Bool {
@@ -79,16 +87,13 @@ struct GaryxComposer: View {
7987
}
8088

8189
private var showsWorkspaceModeStrip: Bool {
82-
canChangeWorkspaceMode && model.newThreadWorkspaceCanUseWorktree
90+
canChangeWorkspaceMode
8391
}
8492

8593
var body: some View {
8694
GaryxAdaptiveGlassContainer(spacing: GaryxComposerLayout.composerSpacing) {
8795
composerStack
8896
}
89-
// No drop shadow: the input box is a bare glass card, defined only by its
90-
// thin glass-edge border. Nothing extends below the card to clip against
91-
// the home-indicator safe area.
9297
.padding(.horizontal, 12)
9398
.padding(.top, 10)
9499
.padding(.bottom, 6)
@@ -134,18 +139,32 @@ struct GaryxComposer: View {
134139
}
135140
.onAppear {
136141
draftContextVersion = model.composerContextVersion
137-
draftText = model.draft
142+
draftText = model.activeComposerDraft
138143
#if DEBUG
139144
presentDebugWorkspaceModeSheetIfNeeded()
140145
#endif
141146
}
142147
.onChange(of: model.composerContextVersion) { _, newValue in
143148
draftContextVersion = newValue
144-
draftText = model.draft
149+
draftText = model.activeComposerDraft
150+
let wasFocused = isFocused.wrappedValue
151+
draftFieldGeneration &+= 1
152+
if wasFocused {
153+
// The recreated field starts unfocused; re-attach the
154+
// keyboard on the next runloop so sending keeps the
155+
// composer ready for a follow-up message.
156+
DispatchQueue.main.async {
157+
isFocused.wrappedValue = true
158+
}
159+
}
145160
}
146-
.onChange(of: model.draft) { _, newValue in
147-
guard newValue != draftText else { return }
148-
draftText = newValue
161+
.onChange(of: draftText) { _, newValue in
162+
// Bind the live text to the thread it is being typed in, so a thread
163+
// switch preserves it. The store is not @Published, so this is cheap
164+
// per keystroke. Skip the brief window before a context reload so the
165+
// outgoing thread's text is never written onto the incoming one.
166+
guard draftContextVersion == model.composerContextVersion else { return }
167+
model.setComposerDraft(newValue)
149168
}
150169
.onChange(of: model.sidebarVisible) { _, visible in
151170
if visible {
@@ -174,9 +193,7 @@ struct GaryxComposer: View {
174193
#endif
175194
.onDisappear {
176195
guard draftContextVersion == model.composerContextVersion else { return }
177-
if model.draft != draftText {
178-
model.draft = draftText
179-
}
196+
model.setComposerDraft(draftText)
180197
}
181198
}
182199

@@ -205,48 +222,34 @@ struct GaryxComposer: View {
205222
}
206223

207224
private var newThreadComposerCard: some View {
208-
// Keep the card above the workspace strip in the overlapping deck.
209225
composerCard
210226
.zIndex(1)
227+
.shadow(color: GaryxComposerLayout.composerShadow, radius: 22, x: 0, y: 12)
228+
.shadow(color: GaryxComposerLayout.composerLiftShadow, radius: 12, x: 0, y: 7)
229+
.shadow(color: Color.black.opacity(0.035), radius: 2, x: 0, y: 1)
211230
}
212231

213232
private var composerCard: some View {
214233
composerCardContent
215234
.frame(maxWidth: .infinity, alignment: .leading)
216-
// Opaque backing in all cases so the skirt never bleeds through, then
217-
// the real Liquid Glass material on top — genuine glassification of
218-
// the input box rather than a faux border.
235+
.background(GaryxComposerLayout.composerMaterialTint, in: composerCardShape)
219236
.background(GaryxComposerLayout.composerOcclusionFill, in: composerCardShape)
220237
.garyxAdaptiveGlass(.regular, isInteractive: false, fallbackMaterial: .ultraThinMaterial, in: composerCardShape)
221-
}
222-
223-
private var composerGlassEdgeGradient: LinearGradient {
224-
LinearGradient(
225-
colors: [
226-
Color.primary.opacity(0.13),
227-
Color.primary.opacity(0.045),
228-
],
229-
startPoint: .top,
230-
endPoint: .bottom
231-
)
238+
.overlay {
239+
composerCardShape
240+
.stroke(GaryxComposerLayout.composerMaterialHighlight, lineWidth: 0.7)
241+
.blendMode(.plusLighter)
242+
}
243+
.overlay {
244+
composerCardShape
245+
.stroke(GaryxComposerLayout.composerMaterialStroke, lineWidth: 0.7)
246+
}
232247
}
233248

234249
private var composerCardShape: RoundedRectangle {
235250
RoundedRectangle(cornerRadius: GaryxComposerLayout.composerCornerRadius, style: .continuous)
236251
}
237252

238-
private var composerTopEdgeMask: some View {
239-
LinearGradient(
240-
stops: [
241-
.init(color: .white, location: 0),
242-
.init(color: .white, location: 0.45),
243-
.init(color: .clear, location: 0.82),
244-
],
245-
startPoint: .top,
246-
endPoint: .bottom
247-
)
248-
}
249-
250253
private var composerCardContent: some View {
251254
VStack(spacing: 0) {
252255
if !model.composerAttachments.isEmpty {
@@ -259,8 +262,8 @@ struct GaryxComposer: View {
259262
}
260263

261264
private var workspaceModeStrip: some View {
262-
// The gray strip is a non-interactive backdrop; only the leading "Local"
263-
// control is tappable, not the whole apron width.
265+
// The gray apron is a non-interactive backdrop; only the leading Local
266+
// select control (icon + label + chevron) is tappable, not the whole strip.
264267
HStack(spacing: 0) {
265268
Button {
266269
guard canChangeWorkspaceMode else { return }
@@ -292,6 +295,7 @@ struct GaryxComposer: View {
292295
.padding(.top, GaryxComposerLayout.workspaceBaseTopPadding)
293296
.padding(.bottom, GaryxComposerLayout.workspaceBaseBottomPadding)
294297
.background(GaryxComposerLayout.workspaceBaseFill, in: workspaceBaseShape)
298+
.garyxAdaptiveGlass(.regular, isInteractive: false, fallbackMaterial: .ultraThinMaterial, in: workspaceBaseShape)
295299
.overlay {
296300
workspaceBaseShape
297301
.stroke(GaryxComposerLayout.workspaceBaseHighlight, lineWidth: 0.6)
@@ -314,6 +318,7 @@ struct GaryxComposer: View {
314318
.clipShape(workspaceBaseShape)
315319
.allowsHitTesting(false)
316320
}
321+
.shadow(color: Color.black.opacity(0.07), radius: 28, x: 0, y: 10)
317322
}
318323

319324
private var workspaceBaseShape: UnevenRoundedRectangle {
@@ -366,7 +371,7 @@ struct GaryxComposer: View {
366371
}
367372

368373
TextField("", text: $draftText, axis: .vertical)
369-
.id(GaryxComposerLayout.draftFieldIdentity)
374+
.id("\(GaryxComposerLayout.draftFieldIdentity)-\(draftFieldGeneration)")
370375
.font(GaryxFont.subheadline())
371376
.foregroundStyle(.primary)
372377
.focused(isFocused)
@@ -382,6 +387,9 @@ struct GaryxComposer: View {
382387
.padding(.bottom, GaryxComposerLayout.inputBottomPadding)
383388
.contentShape(Rectangle())
384389
.onTapGesture {
390+
// `.onTapGesture` ignores `.disabled`; do not pop the keyboard
391+
// from a finger-up while the drawer drag has content disabled.
392+
guard isEnabled else { return }
385393
isFocused.wrappedValue = true
386394
}
387395
}
@@ -396,7 +404,7 @@ struct GaryxComposer: View {
396404

397405
Spacer(minLength: 0)
398406

399-
if model.isSelectedThreadVisiblyRunning {
407+
if model.isSelectedThreadSending {
400408
Button {
401409
Task { await model.interruptActiveRun() }
402410
} label: {
@@ -480,7 +488,7 @@ struct GaryxComposer: View {
480488
private func insertSlashCommand(_ command: GaryxSlashCommand) {
481489
let normalizedName = command.name.hasPrefix("/") ? command.name : "/\(command.name)"
482490
draftText = normalizedName + " "
483-
model.draft = draftText
491+
model.setComposerDraft(draftText)
484492
isFocused.wrappedValue = true
485493
}
486494

0 commit comments

Comments
 (0)