diff --git a/PennMobile.xcodeproj/project.pbxproj b/PennMobile.xcodeproj/project.pbxproj index 4860c04ed..9d59acbee 100644 --- a/PennMobile.xcodeproj/project.pbxproj +++ b/PennMobile.xcodeproj/project.pbxproj @@ -239,6 +239,7 @@ 890D15062AB76C3600672FFE /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890D15052AB76C3600672FFE /* MainTabView.swift */; }; 890DDBC62AA2E4B6006815A3 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890DDBC42AA2E499006815A3 /* ViewExtensions.swift */; }; 8910678B2B64715E00748FC6 /* SublettingModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891067892B64715E00748FC6 /* SublettingModels.swift */; }; + 891591ED2BA778AE00BC230F /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891591EC2BA778AE00BC230F /* SafariView.swift */; }; 89325390290F98E7006EE62C /* ConfigurationRepresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8932538E290F98BD006EE62C /* ConfigurationRepresenting.swift */; }; 89325393291025A8006EE62C /* WidgetBackgroundTypeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893253912910249B006EE62C /* WidgetBackgroundTypeExtensions.swift */; }; 8932693528FC75A5003D4BF9 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8932693428FC75A5003D4BF9 /* WidgetKit.framework */; }; @@ -667,6 +668,7 @@ 890DDBC42AA2E499006815A3 /* ViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtensions.swift; sourceTree = ""; }; 891067892B64715E00748FC6 /* SublettingModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SublettingModels.swift; sourceTree = ""; }; 8910678A2B64715E00748FC6 /* SublettingAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SublettingAPI.swift; sourceTree = ""; }; + 891591EC2BA778AE00BC230F /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; 8932538E290F98BD006EE62C /* ConfigurationRepresenting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationRepresenting.swift; sourceTree = ""; }; 893253912910249B006EE62C /* WidgetBackgroundTypeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBackgroundTypeExtensions.swift; sourceTree = ""; }; 8932693328FC75A5003D4BF9 /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1456,6 +1458,7 @@ 6CAE434F253370B200BD0200 /* AlertModifier.swift */, 6C84D9E426293E680039C57F /* UIKit Views.swift */, E7DA7CDC2B64619E00CA2A60 /* CustomPopupView.swift */, + 891591EC2BA778AE00BC230F /* SafariView.swift */, ); path = "SwiftUI Views"; sourceTree = ""; @@ -2641,6 +2644,7 @@ C11DFA31219F90E5000FC573 /* CalendarEvent.swift in Sources */, 42CC49D02AAE38C6008C41EE /* PollsViewController.swift in Sources */, 6CC88D6B27B1BF51006896F6 /* DiningVenueView.swift in Sources */, + 891591ED2BA778AE00BC230F /* SafariView.swift in Sources */, 42D9237529E0CCFB00E9E18E /* FitnessView.swift in Sources */, 6C11C08026F842CF00407C04 /* HomeUpdateCellItem.swift in Sources */, 890D15062AB76C3600672FFE /* MainTabView.swift in Sources */, @@ -3326,7 +3330,7 @@ repositoryURL = "https://github.com/pennlabs/PennForms"; requirement = { kind = revision; - revision = ce182635ad259e5013841e23b2eb641f03c64e91; + revision = aff2ed4611e71bc5c7b16dfae9636789e06993b7; }; }; F213CCE023C3EE3E000AD90F /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/PennMobile/General/SwiftUI Views/SafariView.swift b/PennMobile/General/SwiftUI Views/SafariView.swift new file mode 100644 index 000000000..225fcb906 --- /dev/null +++ b/PennMobile/General/SwiftUI Views/SafariView.swift @@ -0,0 +1,42 @@ +// +// SafariView.swift +// PennMobile +// +// Created by Anthony Li on 3/17/24. +// Copyright © 2024 PennLabs. All rights reserved. +// + +import SwiftUI +import SafariServices + +struct SafariView: UIViewControllerRepresentable { + let url: URL + + func makeUIViewController(context: Context) -> SFSafariViewController { + SFSafariViewController(url: url) + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} +} + +struct SafariModifier: ViewModifier { + @Binding var isPresented: Bool + var url: URL? + + func body(content: Content) -> some View { + if let url { + AnyView(content.sheet(isPresented: $isPresented) { + SafariView(url: url) + .ignoresSafeArea() + }) + } else { + AnyView(content) + } + } +} + +extension View { + func safari(isPresented: Binding, url: URL?) -> some View { + modifier(SafariModifier(isPresented: isPresented, url: url)) + } +} diff --git a/PennMobile/Subletting/Listings/NewListingForm.swift b/PennMobile/Subletting/Listings/NewListingForm.swift index 60c63451a..6e2a7771c 100644 --- a/PennMobile/Subletting/Listings/NewListingForm.swift +++ b/PennMobile/Subletting/Listings/NewListingForm.swift @@ -39,6 +39,7 @@ struct NewListingForm: View { @State var images: [UIImage] = [] @State var existingImages: [String] = [] @State var progress: Double? + @State var showValidationErrors = false init() { self.isNew = true @@ -59,6 +60,8 @@ struct NewListingForm: View { self._selectedAmenities = State(initialValue: OrderedSet(subletDraft.amenities)) self._images = State(initialValue: subletDraft.images) self._existingImages = State(initialValue: []) + + self.showValidationErrors = true } init(sublet: Sublet) { @@ -72,6 +75,8 @@ struct NewListingForm: View { self._selectedAmenities = State(initialValue: OrderedSet(sublet.amenities)) self._images = State(initialValue: []) self._existingImages = State(initialValue: sublet.images.map { $0.imageUrl }) + + self.showValidationErrors = true } var body: some View { @@ -171,10 +176,13 @@ struct NewListingForm: View { } .padding(.top, 30) Button(action: { - guard let negotiable, let price, let startDate, let endDate else { + guard formState.isValid, images.count + existingImages.count > 0 else { + showValidationErrors = true return } - if images.count + existingImages.count == 0 { + + guard let negotiable, let price, let startDate, let endDate else { + showValidationErrors = true return } @@ -287,7 +295,7 @@ struct NewListingForm: View { ) } .padding(.top, 30) - .disabled(!formState.isValid) + .disabled(showValidationErrors && !formState.isValid) } } } @@ -349,6 +357,7 @@ struct NewListingForm: View { UploadingOverlay(progress: progress, title: "Uploading...", message: "Your listing is being uploaded to the marketplace. Please wait a moment.") } } + .environment(\.showValidationErrors, showValidationErrors) } } diff --git a/PennMobile/Subletting/SubletDetailView.swift b/PennMobile/Subletting/SubletDetailView.swift index 8747e597c..cf4a08f8d 100644 --- a/PennMobile/Subletting/SubletDetailView.swift +++ b/PennMobile/Subletting/SubletDetailView.swift @@ -61,10 +61,8 @@ struct SubletDetailView: View { .navigationTitle(selectedTab) .navigationBarTitleDisplayMode(.inline) .toolbar { - if selectedTab == "Details" { - ToolbarItem(placement: .topBarTrailing) { - SubletDetailToolbar(sublet: sublet, showExternalLink: $showExternalLink) - } + ToolbarItem(placement: .topBarTrailing) { + SubletDetailToolbar(sublet: sublet, showExternalLink: $showExternalLink) } } .task { @@ -72,9 +70,7 @@ struct SubletDetailView: View { sublettingViewModel.updateSublet(sublet: sublet) } } - .sheet(isPresented: $showExternalLink) { - WebView(url: URL(string: sublet.data.externalLink!)!) - } + .safari(isPresented: $showExternalLink, url: sublet.data.externalLink.flatMap { URL(string: $0) }) } } @@ -335,7 +331,6 @@ struct SubletDetailToolbar: View { NavigationLink(value: SublettingPage.subletInterestForm(sublet)) { Image(systemName: "ellipsis.message") } - .buttonStyle(.plain) Button(action: { Task { @@ -348,16 +343,15 @@ struct SubletDetailToolbar: View { }) { Image(systemName: isSaved ? "heart.fill" : "heart") } - .buttonStyle(.plain) .animation(.spring(response: 0.3, dampingFraction: 0.5, blendDuration: 0.5), value: isSaved) } - if sublet.data.externalLink != nil { + + if let link = sublet.data.externalLink, URL(string: link) != nil { Button(action: { showExternalLink = true }) { Image(systemName: "link") } - .buttonStyle(.plain) } } } diff --git a/PennMobile/Subletting/SubletMapView.swift b/PennMobile/Subletting/SubletMapView.swift index f9ded49dd..6829ed631 100644 --- a/PennMobile/Subletting/SubletMapView.swift +++ b/PennMobile/Subletting/SubletMapView.swift @@ -21,9 +21,7 @@ struct SubletMapView: View { SubletDetailToolbar(sublet: sublet, showExternalLink: $showExternalLink) } } - .sheet(isPresented: $showExternalLink) { - WebView(url: URL(string: sublet.data.externalLink!)!) - } + .safari(isPresented: $showExternalLink, url: sublet.data.externalLink.flatMap { URL(string: $0) }) } }