import ComposableArchitecture import Preferences import SwiftUI extension View { @ViewBuilder func animation( featureFlag: KeyPath, _ animation: Animation?, value: V ) -> some View { let isOn = UserDefaults.shared.value(for: featureFlag) if isOn { self.animation(animation, value: value) } else { self } } } struct SharedPanelView: View { var store: StoreOf struct OverallState: Equatable { var isPanelDisplayed: Bool var opacity: Double var colorScheme: ColorScheme var alignTopToAnchor: Bool } var body: some View { WithPerceptionTracking { VStack(spacing: 0) { if !store.alignTopToAnchor { Spacer() .frame(minHeight: 0, maxHeight: .infinity) .allowsHitTesting(false) } DynamicContent(store: store) .frame(maxWidth: .infinity, maxHeight: Style.panelHeight) .fixedSize(horizontal: false, vertical: true) .allowsHitTesting(store.isPanelDisplayed) .frame(maxWidth: .infinity) if store.alignTopToAnchor { Spacer() .frame(minHeight: 0, maxHeight: .infinity) .allowsHitTesting(false) } } .preferredColorScheme(store.colorScheme) .opacity(store.opacity) .animation( featureFlag: \.animationBCrashSuggestion, .easeInOut(duration: 0.2), value: store.isPanelDisplayed ) .frame(maxWidth: Style.panelWidth, maxHeight: Style.panelHeight) } } struct DynamicContent: View { let store: StoreOf @AppStorage(\.suggestionPresentationMode) var suggestionPresentationMode var body: some View { WithPerceptionTracking { ZStack(alignment: .topLeading) { if let errorMessage = store.content.error { error(errorMessage) } else if let _ = store.content.promptToCode { promptToCode() } else if let suggestionProvider = store.content.suggestion { suggestion(suggestionProvider) } } } } @ViewBuilder func error(_ error: String) -> some View { ErrorPanel(description: error) { store.send( .errorMessageCloseButtonTapped, animation: .easeInOut(duration: 0.2) ) } } @ViewBuilder func promptToCode() -> some View { if let store = store.scope( state: \.content.promptToCodeGroup.activePromptToCode, action: \.promptToCodeGroup.activePromptToCode ) { PromptToCodePanel(store: store) } } @ViewBuilder func suggestion(_ suggestion: CodeSuggestionProvider) -> some View { switch suggestionPresentationMode { case .nearbyTextCursor: EmptyView() case .floatingWidget: CodeBlockSuggestionPanel(suggestion: suggestion, firstLineIndent: 0, lineHeight: 12, isPanelDisplayed: true) } } } } struct CommandButtonStyle: ButtonStyle { var color: Color var cornerRadius: Double = 4 func makeBody(configuration: Configuration) -> some View { configuration.label .padding(.vertical, 4) .padding(.horizontal, 8) .foregroundColor(.white) .background( RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) .fill(color.opacity(configuration.isPressed ? 0.8 : 1)) .animation(.easeOut(duration: 0.1), value: configuration.isPressed) ) .overlay { RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) .stroke(Color.white.opacity(0.2), style: .init(lineWidth: 1)) } } } // MARK: - Previews struct SharedPanelView_Error_Preview: PreviewProvider { static var previews: some View { SharedPanelView(store: .init( initialState: .init( content: .init(error: "This is an error\nerror"), colorScheme: .light, isPanelDisplayed: true ), reducer: { SharedPanelFeature() } )) .frame(width: 450, height: 200) } } struct SharedPanelView_Both_DisplayingSuggestion_Preview: PreviewProvider { static var previews: some View { SharedPanelView(store: .init( initialState: .init( content: .init( suggestion: .init( code: """ - (void)addSubview:(UIView *)view { [self addSubview:view]; } """, language: "objective-c", startLineIndex: 8, suggestionCount: 2, currentSuggestionIndex: 0 ) ), colorScheme: .dark, isPanelDisplayed: true ), reducer: { SharedPanelFeature() } )) .frame(width: 450, height: 200) .background { HStack { Color.red Color.green Color.blue } } } }