iOS client for Grain grain.social
ios photography atproto
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: glass input pill for story comment sheet

- Fix TupleView safeAreaInset bug by wrapping commentList in VStack
- Wire Done button via @Environment(\.dismiss) so it works without onDismiss callback
- Replace manual ultraThinMaterial pill with GlassEffectContainer matching iMessage-style input from PR #13

+50 -40
+50 -40
Grain/Views/Stories/StoryCommentSheet.swift
··· 1 1 import SwiftUI 2 2 3 3 struct StoryCommentSheet: View { 4 + @Environment(\.dismiss) private var dismiss 4 5 @Environment(AuthManager.self) private var auth 5 6 @Environment(StoryStatusCache.self) private var storyStatusCache 6 7 @Environment(ViewedStoryStorage.self) private var viewedStories ··· 26 27 27 28 var body: some View { 28 29 NavigationStack { 29 - commentList 30 - .safeAreaInset(edge: .bottom) { 31 - VStack(spacing: 0) { 32 - MentionSuggestionOverlay(state: mentionState) { suggestion in 33 - mentionState.complete(handle: suggestion.handle, in: &commentText) 34 - } 35 - glassInputPill 30 + VStack(spacing: 0) { 31 + commentList 32 + } 33 + .safeAreaInset(edge: .bottom) { 34 + VStack(spacing: 0) { 35 + MentionSuggestionOverlay(state: mentionState) { suggestion in 36 + mentionState.complete(handle: suggestion.handle, in: &commentText) 36 37 } 38 + glassInputPill 37 39 } 38 - .navigationTitle("Comments") 39 - .navigationBarTitleDisplayMode(.inline) 40 - .toolbar { 41 - ToolbarItem(placement: .cancellationAction) { 42 - Button("Done") { 43 - onDismiss?() 44 - } 40 + } 41 + .navigationTitle("Comments") 42 + .navigationBarTitleDisplayMode(.inline) 43 + .toolbar { 44 + ToolbarItem(placement: .cancellationAction) { 45 + Button("Done") { 46 + onDismiss?() 47 + dismiss() 45 48 } 46 49 } 50 + } 47 51 } 48 52 .presentationDetents([.medium, .large]) 49 53 .task { ··· 117 121 replyingTo = nil 118 122 } label: { 119 123 Image(systemName: "xmark.circle.fill") 124 + .font(.caption) 120 125 .foregroundStyle(.secondary) 121 126 } 122 127 } 123 - .padding(.horizontal, 14) 124 - .padding(.top, 10) 125 - .padding(.bottom, 4) 128 + .padding(.horizontal, 16) 129 + .padding(.top, 4) 126 130 } 127 131 128 - HStack(alignment: .bottom, spacing: 8) { 129 - TextField(replyingTo != nil ? "Reply..." : "Add a comment...", text: $commentText, axis: .vertical) 130 - .textFieldStyle(.plain) 131 - .font(.subheadline) 132 - .focused($commentFocused) 133 - .lineLimit(1 ... 5) 134 - .onChange(of: commentText) { mentionState.update(text: commentText) } 132 + GlassEffectContainer(spacing: 8) { 133 + HStack(alignment: .bottom, spacing: 10) { 134 + TextField(replyingTo != nil ? "Reply..." : "Add a comment...", text: $commentText, axis: .vertical) 135 + .textFieldStyle(.plain) 136 + .font(.body) 137 + .focused($commentFocused) 138 + .lineLimit(1 ... 5) 139 + .onChange(of: commentText) { mentionState.update(text: commentText) } 140 + .padding(.horizontal, 18) 141 + .padding(.vertical, 12) 142 + .glassEffect(.regular, in: .capsule) 135 143 136 - Button { 137 - Task { await postComment() } 138 - } label: { 139 - Image(systemName: "arrow.up.circle.fill") 140 - .font(.title2) 141 - .foregroundStyle(commentText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? .secondary : Color("AccentColor")) 144 + let isEmpty = commentText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty 145 + if !isEmpty { 146 + Button { 147 + Task { await postComment() } 148 + } label: { 149 + Image(systemName: "arrow.up.circle.fill") 150 + .font(.system(size: 28)) 151 + .foregroundStyle(Color("AccentColor")) 152 + .frame(width: 44, height: 44) 153 + } 154 + .glassEffect(.regular.interactive(), in: .circle) 155 + .disabled(viewModel.isPostingComment) 156 + .transition(.scale.combined(with: .opacity)) 157 + } 142 158 } 143 - .disabled(commentText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.isPostingComment) 159 + .animation(.spring(response: 0.3, dampingFraction: 0.8), value: commentText.isEmpty) 160 + .padding(.horizontal, 12) 161 + .padding(.vertical, 10) 144 162 } 145 - .padding(.horizontal, 14) 146 - .padding(.vertical, 10) 147 163 } 148 - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 22, style: .continuous)) 149 - .overlay { 150 - RoundedRectangle(cornerRadius: 22, style: .continuous) 151 - .strokeBorder(.white.opacity(0.15), lineWidth: 0.5) 152 - } 153 - .shadow(color: .black.opacity(0.12), radius: 8, y: 2) 154 - .padding(.horizontal, 16) 164 + .padding(.horizontal, 12) 155 165 .padding(.bottom, 8) 156 166 } 157 167