diff --git a/EatHub/EatHub/Domain/Meal.swift b/EatHub/EatHub/Domain/Meal.swift index e9827f1..b0597a5 100644 --- a/EatHub/EatHub/Domain/Meal.swift +++ b/EatHub/EatHub/Domain/Meal.swift @@ -5,7 +5,7 @@ // Created by Kirill Prokofyev on 25.03.2025. // -struct Meal: Identifiable, Equatable { +struct Meal: Identifiable, Equatable, Hashable { let id: String let name: String let category: String? diff --git a/EatHub/EatHub/Modules/Details/DetailsView.swift b/EatHub/EatHub/Modules/Details/DetailsView.swift index fc2c28a..eb7846f 100644 --- a/EatHub/EatHub/Modules/Details/DetailsView.swift +++ b/EatHub/EatHub/Modules/Details/DetailsView.swift @@ -10,7 +10,7 @@ import SwiftUI struct DetailsView: View { private enum Constants { static let chipSpacing: CGFloat = 8 - static let closeButtonSize: CGFloat = 44 + static let backButtonSize: CGFloat = 44 static let horizontalPadding: CGFloat = 16 static let imageCornerRadius: CGFloat = 24 static let imageHeight: CGFloat = 200 @@ -19,7 +19,7 @@ struct DetailsView: View { enum Icons { static let area: String = "globe" static let category: String = "square.grid.2x2" - static let close: String = "xmark" + static let back: String = "arrow.backward" static let youtube: String = "play.rectangle.fill" } @@ -31,45 +31,35 @@ struct DetailsView: View { } @ObservedObject var viewModel: DetailsViewModel - let onClose: (() -> Void)? - let namespace: Namespace.ID + @Environment(\.dismiss) private var dismiss var body: some View { - Group { + ZStack { ScrollView { VStack(alignment: .leading, spacing: Constants.spacing) { - VerticalItemView( - viewModel: viewModel.verticalItemViewModel, - namespace: namespace - ) - .matchedGeometryEffect( - id: MatchedGeometryEffectIdentifier(.info, for: viewModel.id), - in: namespace - ) + VerticalItemView(viewModel: viewModel.verticalItemViewModel) makeTagsChipsScrollView() ingredientsSection instructionsSection } + .background(Color(.systemBackground)) } .ignoresSafeArea(edges: .top) + .safeAreaInset(edge: .top) { + HStack { + makeBackButton() + Spacer() + makeLikeButton() + } + .padding(.horizontal, Constants.spacing) + .padding(.vertical, Constants.spacing) + } } + .navigationBarHidden(true) .background(Color(.systemBackground)) - .onFirstAppear { + .onAppear { viewModel.fetchMeal() } - .overlay( - HStack(alignment: .top, spacing: .zero) { - makeLikeButton() - Spacer() - makeCloseButton() - } - .padding(.horizontal, Constants.spacing) - .padding(.top, Constants.spacing) - .transaction { transaction in - transaction.animation = nil - }, - alignment: .top - ) } } @@ -95,7 +85,6 @@ private extension DetailsView { Text(Constants.Title.ingredients) .font(.headline) .padding(.top) - ForEach(viewModel.ingredients, id: \.self) { ingredient in HStack { Text(ingredient.name) @@ -117,6 +106,9 @@ private extension DetailsView { if let instructions = viewModel.instructions { VStack { makeInstructionsContent(instructions: instructions) + if let url = viewModel.youtubeURL { + makeYoutubeLink(url: url) + } } .padding(Constants.spacing) .frame(maxWidth: .infinity) @@ -125,22 +117,26 @@ private extension DetailsView { } @ViewBuilder - func makeCloseButton() -> some View { - if !viewModel.isCloseButtonHidden { - Button(action: { onClose?() }) { - ZStack { - Circle() - .fill(Color.black.opacity(0.3)) - .shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2) - - Image(systemName: Constants.Icons.close) - .font(.title) - .symbolVariant(viewModel.isLiked ? .fill : .none) - .padding(12) - .foregroundColor(.white) - } - .frame(width: Constants.closeButtonSize, height: Constants.closeButtonSize) + func makeBackButton() -> some View { + Button(action: { dismiss() }) { + ZStack { + Circle() + .fill(Color.black.opacity(0.3)) + .shadow( + color: Color.black.opacity(0.1), + radius: 4, + x: .zero, + y: 2 + ) + Image(systemName: Constants.Icons.back) + .font(.title) + .padding(12) + .foregroundColor(.white) } + .frame( + width: Constants.backButtonSize, + height: Constants.backButtonSize + ) } } @@ -160,19 +156,13 @@ private extension DetailsView { VStack(alignment: .leading, spacing: Constants.spacing) { Text(Constants.Title.instructions) .font(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - + .frame(alignment: .leading) Text(instructions) .font(.body) .multilineTextAlignment(.leading) - .frame(maxWidth: .infinity, alignment: .leading) - - if let url = viewModel.youtubeURL { - makeYoutubeLink(url: url) - } + .frame(alignment: .leading) } .padding(Constants.spacing) - .frame(maxWidth: .infinity) .background(Color.secondary.opacity(0.1)) .cornerRadius(Constants.spacing) } @@ -185,10 +175,9 @@ private extension DetailsView { Text(Constants.Title.youtube) .bold() } - .frame(maxWidth: .infinity) .padding(Constants.spacing) - .foregroundColor(.accent) - .background(Color.white) + .foregroundColor(.white) + .background(Color.accentColor) .cornerRadius(Constants.spacing) } } @@ -197,7 +186,6 @@ private extension DetailsView { // MARK: - Preview #Preview("Meal Details") { - @Previewable @Namespace var namespace let requester = APIRequester() let mealsService = MealsService(requester: requester) let favoritesManager = FavoritesManager(store: UserDefaults.standard) @@ -228,9 +216,7 @@ private extension DetailsView { youtubeURL: URL(string: "example.com"), favoritesManager: favoritesManager, mealsService: mealsService - ), - onClose: nil, - namespace: namespace + ) ) } } diff --git a/EatHub/EatHub/Modules/Details/DetailsViewModel.swift b/EatHub/EatHub/Modules/Details/DetailsViewModel.swift index 7b308a7..97ea25b 100644 --- a/EatHub/EatHub/Modules/Details/DetailsViewModel.swift +++ b/EatHub/EatHub/Modules/Details/DetailsViewModel.swift @@ -20,7 +20,7 @@ final class DetailsViewModel: ObservableObject { @Published var ingredients: [Ingredient] @Published var youtubeURL: URL? - @Published var isCloseButtonHidden: Bool + @Published var isBackButtonHidden: Bool @Published var isSkeletonable: Bool var isLiked: Bool { @@ -53,7 +53,7 @@ final class DetailsViewModel: ObservableObject { youtubeURL: URL? = nil, favoritesManager: FavoritesManagerInterface, mealsService: MealsServiceInterface, - isCloseButtonHidden: Bool = false, + isBackButtonHidden: Bool = false, isSkeletonable: Bool = true ) { self.id = id @@ -67,7 +67,7 @@ final class DetailsViewModel: ObservableObject { self.youtubeURL = youtubeURL self.favoritesManager = favoritesManager self.mealsService = mealsService - self.isCloseButtonHidden = isCloseButtonHidden + self.isBackButtonHidden = isBackButtonHidden self.isSkeletonable = isSkeletonable } diff --git a/EatHub/EatHub/Modules/Favorite/FavoriteView.swift b/EatHub/EatHub/Modules/Favorite/FavoriteView.swift index df3a9e5..d09b35d 100644 --- a/EatHub/EatHub/Modules/Favorite/FavoriteView.swift +++ b/EatHub/EatHub/Modules/Favorite/FavoriteView.swift @@ -17,20 +17,23 @@ struct FavoriteView: View { } var body: some View { - NavigationView { - ZStack { - contentView - .background(Color(.systemGroupedBackground)) - .onAppear { - viewModel.refreshFavorites() - } - - if let selectedItem, showDetail { - openDetailsView(for: selectedItem) + NavigationStack { + contentView + .background(Color(.systemGroupedBackground)) + .onAppear { + viewModel.refreshFavorites() } - } - .navigationBarHidden(true) } + .navigationDestination(for: RecipeViewModel.self) { recipeViewModel in + let input = DetailsViewModuleInput( + id: recipeViewModel.id, + name: recipeViewModel.name, + thumbnail: recipeViewModel.thumbnail + ) + let detailsViewModel = viewModel.detailsViewModelBuilder(input) + DetailsView(viewModel: detailsViewModel) + } + .navigationTitle(viewModel.title) } } @@ -80,23 +83,20 @@ private extension FavoriteView { var favoritesList: some View { LazyVStack(spacing: 8) { ForEach(viewModel.likedRecipes) { recipeViewModel in - RecipeRow( - recipe: recipeViewModel, - onToggleFavorite: { - viewModel.toggleFavorite(for: recipeViewModel) - } - ) - .onTapGesture { - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - selectedItem = recipeViewModel - showDetail = true - } + NavigationLink(value: recipeViewModel) { + RecipeRow( + recipe: recipeViewModel, + onToggleFavorite: { + viewModel.toggleFavorite(for: recipeViewModel) + } + ) } .buttonStyle(PlainButtonStyle()) } } .padding(.top, 8) } + var emptyPlaceholder: some View { VStack(spacing: 16) { Image(systemName: "heart") @@ -114,31 +114,4 @@ private extension FavoriteView { } .frame(maxWidth: .infinity, alignment: .center) } - - @ViewBuilder - func openDetailsView(for recipeViewModel: RecipeViewModel) -> some View { - let viewModel = viewModel.detailsViewModelBuilder( - DetailsViewModuleInput( - id: recipeViewModel.id, - name: recipeViewModel.name, - thumbnail: recipeViewModel.thumbnail - ) - ) - DetailsView( - viewModel: viewModel, - onClose: { - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - showDetail = false - viewModel.isCloseButtonHidden = true - self.viewModel.refreshFavorites() - } - DispatchQueue.main.asyncAfter(deadline: .now() + Constants.animationDuration) { - selectedItem = nil - } - }, - namespace: animationNamespace - ) - .zIndex(Constants.detailZIndex) - .transition(Constants.detailTransition) - } } diff --git a/EatHub/EatHub/Modules/Favorite/Models/RecipeViewModel.swift b/EatHub/EatHub/Modules/Favorite/Models/RecipeViewModel.swift index de67fc0..3799e7c 100644 --- a/EatHub/EatHub/Modules/Favorite/Models/RecipeViewModel.swift +++ b/EatHub/EatHub/Modules/Favorite/Models/RecipeViewModel.swift @@ -18,3 +18,15 @@ class RecipeViewModel: ObservableObject, Identifiable { self.isFavorite = isFavorite } } + +extension RecipeViewModel: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} + +extension RecipeViewModel: Equatable { + static func == (lhs: RecipeViewModel, rhs: RecipeViewModel) -> Bool { + lhs.id == rhs.id + } +} diff --git a/EatHub/EatHub/Modules/Home/HomeView.swift b/EatHub/EatHub/Modules/Home/HomeView.swift index b0dd9a2..7f65743 100644 --- a/EatHub/EatHub/Modules/Home/HomeView.swift +++ b/EatHub/EatHub/Modules/Home/HomeView.swift @@ -8,82 +8,30 @@ import SwiftUI struct HomeView: View { - private enum Constants { - static let listSpacing: CGFloat = 16 - static let animationDuration: TimeInterval = 0.5 - static let mainListZIndex: Double = 0 - static let detailZIndex: Double = 1 - static let detailTransition: AnyTransition = .asymmetric( - insertion: .move(edge: .bottom), - removal: .move(edge: .bottom) - ) - } - @ObservedObject var viewModel: HomeViewModel - @Namespace private var animationNamespace - @State private var selectedMeal: Meal? - @State private var showDetail: Bool = false var body: some View { - ZStack { - + NavigationStack { ScrollView(.vertical) { - VStack(alignment: .leading, spacing: Constants.listSpacing) { - HorizontalListSection(meals: viewModel.horizontalMeals) { meal in - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - selectedMeal = meal - showDetail = true - } - } - VerticalListSection( - meals: viewModel.verticalMeals, - namespace: animationNamespace - ) { meal in - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - selectedMeal = meal - showDetail = true - } - } + VStack(alignment: .leading, spacing: 16) { + HorizontalListSection(meals: viewModel.horizontalMeals) + VerticalListSection(meals: viewModel.verticalMeals) } .padding(.vertical) } - .zIndex(Constants.mainListZIndex) - - if let meal = selectedMeal, showDetail { - openDetailsView(for: meal) + .navigationDestination(for: Meal.self) { meal in + let input = DetailsViewModuleInput( + id: meal.id, + name: meal.name, + thumbnail: meal.thumbnail + ) + let detailsViewModel = viewModel.detailsViewModelBuilder(input) + DetailsView(viewModel: detailsViewModel) + } + .onAppear { + viewModel.fetchMeals() } } - .onAppear { - viewModel.fetchMeals() - } - } -} - -private extension HomeView { - @ViewBuilder - func openDetailsView(for meal: Meal) -> some View { - let viewModel = viewModel.detailsViewModelBuilder( - DetailsViewModuleInput( - id: meal.id, - name: meal.name, - thumbnail: meal.thumbnail - ) - ) - DetailsView( - viewModel: viewModel, - onClose: { - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - showDetail = false - viewModel.isCloseButtonHidden = true - } - DispatchQueue.main.asyncAfter(deadline: .now() + Constants.animationDuration) { - selectedMeal = nil - } - }, - namespace: animationNamespace - ) - .zIndex(Constants.detailZIndex) - .transition(Constants.detailTransition) } } diff --git a/EatHub/EatHub/Modules/Random/RandomView.swift b/EatHub/EatHub/Modules/Random/RandomView.swift index c3e48c0..63de976 100644 --- a/EatHub/EatHub/Modules/Random/RandomView.swift +++ b/EatHub/EatHub/Modules/Random/RandomView.swift @@ -17,12 +17,6 @@ struct RandomView: View { static let buttonCorberRadius: CGFloat = 12 static let buttonPaddings: CGFloat = 12 - static let animationDuration: TimeInterval = 0.5 - static let detailTransition: AnyTransition = .asymmetric( - insertion: .move(edge: .bottom), - removal: .move(edge: .bottom) - ) - enum Title { static let errorText: String = "Error while fetching random item" } @@ -33,13 +27,11 @@ struct RandomView: View { } @ObservedObject var viewModel: RandomViewModel - - @Namespace private var animationNamespace @State private var selectedMeal: Meal? @State private var showDetail = false var body: some View { - ZStack { + NavigationStack { VStack(spacing: 0) { Spacer() bodyView @@ -70,9 +62,14 @@ struct RandomView: View { viewModel.fetchRandom() } } - - if let meal = selectedMeal, showDetail { - openDetailsView(for: meal) + .navigationDestination(for: Meal.self) { meal in + let input = DetailsViewModuleInput( + id: meal.id, + name: meal.name, + thumbnail: meal.thumbnail + ) + let detailsViewModel = viewModel.detailsViewModelBuilder(input) + DetailsView(viewModel: detailsViewModel) } } } @@ -85,44 +82,13 @@ extension RandomView { case .loading: RandomItemView() case .loaded(let result): - RandomItemView(item: result) - .matchedGeometryEffect(id: result.id, in: animationNamespace) - .onTapGesture { - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - selectedMeal = result - showDetail = true - } - } + NavigationLink(value: result) { + RandomItemView(item: result) + } case .error: CenteredVStaskText(text: Constants.Title.errorText) } } - - @ViewBuilder - func openDetailsView(for meal: Meal) -> some View { - let detailsVM = viewModel.detailsViewModelBuilder( - DetailsViewModuleInput( - id: meal.id, - name: meal.name, - thumbnail: meal.thumbnail - ) - ) - - DetailsView( - viewModel: detailsVM, - onClose: { - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - showDetail = false - } - DispatchQueue.main.asyncAfter(deadline: .now() + Constants.animationDuration) { - selectedMeal = nil - } - }, - namespace: animationNamespace - ) - .zIndex(1) - .transition(Constants.detailTransition) - } } #Preview { diff --git a/EatHub/EatHub/Modules/Search/SearchView.swift b/EatHub/EatHub/Modules/Search/SearchView.swift index 3e0268f..dc56584 100644 --- a/EatHub/EatHub/Modules/Search/SearchView.swift +++ b/EatHub/EatHub/Modules/Search/SearchView.swift @@ -8,9 +8,8 @@ import SwiftUI struct SearchView: View { - @ObservedObject var viewModel: SearchViewModel - @FocusState var isTextFieldFocused: Bool + @FocusState private var isTextFieldFocused: Bool @Namespace private var animationNamespace @State private var selectedMeal: Meal? @@ -54,22 +53,22 @@ struct SearchView: View { } var body: some View { - ZStack { + NavigationStack { VStack(spacing: Constants.bodySpacing) { searchBar bodyView } - .frame(maxWidth: .infinity) - .zIndex(Constants.searchListZIndex) - - if let meal = selectedMeal, showDetail { - openDetailsView(for: meal) + .navigationDestination(for: Meal.self) { meal in + let input = DetailsViewModuleInput( + id: meal.id, + name: meal.name, + thumbnail: meal.thumbnail + ) + let detailsViewModel = viewModel.detailsViewModelBuilder(input) + DetailsView(viewModel: detailsViewModel) } } } -} - -private extension SearchView { private var searchBar: some View { HStack { @@ -78,22 +77,22 @@ private extension SearchView { .fill(Constants.Colors.lightGray) .frame(height: Constants.searchBarHeight) HStack(spacing: 0) { - Button(action: { + Button { isTextFieldFocused.toggle() - }, label: { + } label: { Image(systemName: Constants.Icons.search) - }) + } .foregroundColor(Constants.Colors.darkGray) .padding(.leading, Constants.searchBarIconsPadding) TextField(Constants.searchBarTitle, text: $viewModel.searchText) .focused($isTextFieldFocused) .frame(height: Constants.searchBarHeight) - Button(action: { + Button { viewModel.searchText = "" - }, label: { + } label: { // TODO: - разобраться почему не сразу стирает Image(systemName: Constants.Icons.clear) - }) + } .foregroundColor(Constants.Colors.darkGray) .padding(.trailing, Constants.searchBarIconsPadding) } @@ -109,69 +108,22 @@ private extension SearchView { case .idle, .error, .emptyResults: CenteredVStaskText(text: viewModel.state.title) case .resultsLoaded(let results): - ZStack { - ScrollView { - VerticalListSection( - meals: results, - namespace: animationNamespace - ) { meal in - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - selectedMeal = meal - showDetail = true - } - } - .id(results.map(\.id).joined()) - .padding(.bottom, Constants.bottomScrollPadding) - } - .transition(.opacity) - .animation(.easeInOut, value: results) - topShadowToBottom + ScrollView { + VerticalListSection(meals: results) + .padding(.bottom, 41) } + .transition(.opacity) + .animation(.easeInOut, value: results) } } - private var topShadowToBottom: some View { + private func centeredMessage(_ text: String) -> some View { VStack { - Rectangle() - .fill(.clear) - .overlay( - LinearGradient( - gradient: - Gradient(colors: [.white, .clear]), - startPoint: .top, - endPoint: .bottom - ) - ) - .frame(height: Constants.searchBarShadowHeight, alignment: .top) + Spacer() + Text(text) Spacer() } } - - @ViewBuilder - func openDetailsView(for meal: Meal) -> some View { - let viewModel = viewModel.detailsViewModelBuilder( - DetailsViewModuleInput( - id: meal.id, - name: meal.name, - thumbnail: meal.thumbnail - ) - ) - DetailsView( - viewModel: viewModel, - onClose: { - withAnimation(.easeInOut(duration: Constants.animationDuration)) { - showDetail = false - viewModel.isCloseButtonHidden = true - } - DispatchQueue.main.asyncAfter(deadline: .now() + Constants.animationDuration) { - selectedMeal = nil - } - }, - namespace: animationNamespace - ) - .zIndex(Constants.detailZIndex) - .transition(Constants.detailTransition) - } } #Preview { diff --git a/EatHub/EatHub/UIComponents/HorizontalListSection/HorizontalListSection.swift b/EatHub/EatHub/UIComponents/HorizontalListSection/HorizontalListSection.swift index 02a5752..dcb36d4 100644 --- a/EatHub/EatHub/UIComponents/HorizontalListSection/HorizontalListSection.swift +++ b/EatHub/EatHub/UIComponents/HorizontalListSection/HorizontalListSection.swift @@ -9,16 +9,12 @@ import SwiftUI struct HorizontalListSection: View { let meals: [Meal] - var onSelect: (Meal) -> Void var body: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 16) { ForEach(meals, id: \.id) { meal in HorizontalItemView(meal: meal) - .makeTappable { - onSelect(meal) - } } } .padding(.horizontal) diff --git a/EatHub/EatHub/UIComponents/VerticalListSection/Cell/VerticalItemView.swift b/EatHub/EatHub/UIComponents/VerticalListSection/Cell/VerticalItemView.swift index 7f58e50..5a007bf 100644 --- a/EatHub/EatHub/UIComponents/VerticalListSection/Cell/VerticalItemView.swift +++ b/EatHub/EatHub/UIComponents/VerticalListSection/Cell/VerticalItemView.swift @@ -23,20 +23,10 @@ struct VerticalItemView: View { } @ObservedObject var viewModel: VerticalItemViewModel - let namespace: Namespace.ID - - init(viewModel: VerticalItemViewModel, namespace: Namespace.ID) { - self.viewModel = viewModel - self.namespace = namespace - } var body: some View { VStack(alignment: .leading, spacing: Constants.spacing) { makeMealImage() - .matchedGeometryEffect( - id: MatchedGeometryEffectIdentifier(.image, for: viewModel.id), - in: namespace - ) infoSection } .padding(.bottom, Constants.spacing) @@ -51,10 +41,6 @@ struct VerticalItemView: View { Text(name) .font(.title) .bold() - .matchedGeometryEffect( - id: MatchedGeometryEffectIdentifier(.title, for: viewModel.id), - in: namespace - ) } HStack { makeCategoryIfNeeded() @@ -101,10 +87,6 @@ struct VerticalItemView: View { Label(area, systemImage: Constants.Icons.area) .font(.subheadline) .foregroundColor(.secondary) - .matchedGeometryEffect( - id: MatchedGeometryEffectIdentifier(.area, for: viewModel.id), - in: namespace - ) } } } diff --git a/EatHub/EatHub/UIComponents/VerticalListSection/VerticalListSection.swift b/EatHub/EatHub/UIComponents/VerticalListSection/VerticalListSection.swift index 91baa6f..c004559 100644 --- a/EatHub/EatHub/UIComponents/VerticalListSection/VerticalListSection.swift +++ b/EatHub/EatHub/UIComponents/VerticalListSection/VerticalListSection.swift @@ -9,34 +9,27 @@ import SwiftUI struct VerticalListSection: View { let meals: [Meal] - let namespace: Namespace.ID - var onSelect: (Meal) -> Void var body: some View { VStack(spacing: 12) { ForEach(meals, id: \.id) { meal in - let viewModel = VerticalItemViewModel( - id: meal.id, - name: meal.name, - thumbnail: meal.thumbnail, - category: meal.category, - area: meal.area - ) - VerticalItemView(viewModel: viewModel, namespace: namespace) - .id(viewModel.id) - .transition( - .asymmetric( - insertion: .move(edge: .top).combined(with: .opacity), - removal: .move(edge: .top).combined(with: .opacity) - ) - ) - .matchedGeometryEffect( - id: MatchedGeometryEffectIdentifier(.info, for: meal.id), - in: namespace + NavigationLink(value: meal) { + let viewModel = VerticalItemViewModel( + id: meal.id, + name: meal.name, + thumbnail: meal.thumbnail, + category: meal.category, + area: meal.area ) - .makeTappable { - onSelect(meal) - } + VerticalItemView(viewModel: viewModel) + .transition( + .asymmetric( + insertion: .move(edge: .top).combined(with: .opacity), + removal: .move(edge: .top).combined(with: .opacity) + ) + ) + } + .buttonStyle(PlainButtonStyle()) } } }