diff --git a/SMHS.xcodeproj/project.pbxproj b/SMHS.xcodeproj/project.pbxproj index c1c1414..5f6fdba 100644 --- a/SMHS.xcodeproj/project.pbxproj +++ b/SMHS.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ 621F5CA3266AAB0F006EBE8F /* CalendarEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621F5CA2266AAB0F006EBE8F /* CalendarEvent.swift */; }; 621F5CA5266AAB35006EBE8F /* CalendarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621F5CA4266AAB35006EBE8F /* CalendarManager.swift */; }; 621F5CA7266ACF05006EBE8F /* SelectedDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621F5CA6266ACF05006EBE8F /* SelectedDateView.swift */; }; + 6225B4D128A1C166008999D3 /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 6225B4D028A1C166008999D3 /* FirebaseDatabase */; }; 622C8D7C288CFB98001A94D3 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622C8D7B288CFB98001A94D3 /* Comparable.swift */; }; 622C8D7D288CFB98001A94D3 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622C8D7B288CFB98001A94D3 /* Comparable.swift */; }; 622CC3902851613D003F33D6 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62D1E512263DF778004DBFAD /* WidgetKit.framework */; }; @@ -148,8 +149,6 @@ 629BE8BD2636444400BC49AD /* ScheduleWeek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629BE8BC2636444400BC49AD /* ScheduleWeek.swift */; }; 629BE8BF2636489600BC49AD /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629BE8BE2636489600BC49AD /* Array.swift */; }; 629EC8352645D50900E236DC /* TodayViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629EC8342645D50900E236DC /* TodayViewViewModel.swift */; }; - 62A11A7B26E6A105007F7241 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 62382B5A26E69FE600BF8918 /* GoogleService-Info.plist */; }; - 62A11A7C26E6A106007F7241 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 62382B5A26E69FE600BF8918 /* GoogleService-Info.plist */; }; 62A11A7F26E6CBDE007F7241 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A11A7E26E6CBDE007F7241 /* Constants.swift */; }; 62A11A8926EE9F4A007F7241 /* TeamsJoinBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A11A8826EE9F4A007F7241 /* TeamsJoinBanner.swift */; }; 62A11A8E26F99FA4007F7241 /* OnboardingFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A11A8D26F99FA4007F7241 /* OnboardingFeature.swift */; }; @@ -259,6 +258,13 @@ 62FCFFF128161DB200D3E1CA /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F43CC7268902380009CD34 /* Endpoint.swift */; }; 62FCFFF3281CCF5600D3E1CA /* DeveloperTapGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FCFFF2281CCF5600D3E1CA /* DeveloperTapGesture.swift */; }; 62FCFFF4281CCF5600D3E1CA /* DeveloperTapGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FCFFF2281CCF5600D3E1CA /* DeveloperTapGesture.swift */; }; + F203BBFB28A1AF960002DAD9 /* BannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203BBF628A1AF960002DAD9 /* BannerView.swift */; }; + F203BBFC28A1AF960002DAD9 /* BannerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203BBF728A1AF960002DAD9 /* BannerDetailView.swift */; }; + F203BBFD28A1AF960002DAD9 /* BannerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203BBF828A1AF960002DAD9 /* BannerExtensions.swift */; }; + F203BBFE28A1AF960002DAD9 /* BannersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203BBF928A1AF960002DAD9 /* BannersView.swift */; }; + F203BBFF28A1AF960002DAD9 /* Banner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203BBFA28A1AF960002DAD9 /* Banner.swift */; }; + F2297A4A28A56D690032EF99 /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = F2297A4928A56D690032EF99 /* FirebaseDatabase */; }; + F2297A4C28A56EC90032EF99 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F2297A4B28A56EC90032EF99 /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -325,7 +331,6 @@ 62328C9526476E42000F26ED /* ProgressRingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressRingView.swift; sourceTree = ""; }; 62353A512638894500207C15 /* ScheduleListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleListView.swift; sourceTree = ""; }; 62382B5426E6994D00BF8918 /* AnnoucementBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnoucementBanner.swift; sourceTree = ""; }; - 62382B5A26E69FE600BF8918 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 623A7F17264EEBF700FF3F28 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; 623A7F1A264EED2700FF3F28 /* InformationCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InformationCard.swift; sourceTree = ""; }; 623A7F22264F595400FF3F28 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; @@ -466,6 +471,12 @@ 62F5FCFE26B4876600E821D5 /* LoadingAnimatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingAnimatable.swift; sourceTree = ""; }; 62F5FD0026B49A2D00E821D5 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; 62FCFFF2281CCF5600D3E1CA /* DeveloperTapGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperTapGesture.swift; sourceTree = ""; }; + F203BBF628A1AF960002DAD9 /* BannerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BannerView.swift; sourceTree = ""; }; + F203BBF728A1AF960002DAD9 /* BannerDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BannerDetailView.swift; sourceTree = ""; }; + F203BBF828A1AF960002DAD9 /* BannerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BannerExtensions.swift; sourceTree = ""; }; + F203BBF928A1AF960002DAD9 /* BannersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BannersView.swift; sourceTree = ""; }; + F203BBFA28A1AF960002DAD9 /* Banner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Banner.swift; sourceTree = ""; }; + F2297A4B28A56EC90032EF99 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -501,6 +512,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 6225B4D128A1C166008999D3 /* FirebaseDatabase in Frameworks */, 623D596B27024D43001D05F7 /* Kingfisher in Frameworks */, 62382B5926E69FCC00BF8918 /* FirebaseRemoteConfig in Frameworks */, 6261DF2527121E3D009F6234 /* FirebaseMessaging in Frameworks */, @@ -797,7 +809,7 @@ isa = PBXGroup; children = ( 6261DF2827122329009F6234 /* SMHSSchedule (iOS).entitlements */, - 62382B5A26E69FE600BF8918 /* GoogleService-Info.plist */, + F2297A4B28A56EC90032EF99 /* GoogleService-Info.plist */, 6263E83026BCEA69009EC880 /* ElegantCalendar.xcassets */, 62D1E55B263F17E9004DBFAD /* Settings.bundle */, 62C9E887263BD2AC00CCA5C5 /* Assets */, @@ -867,6 +879,7 @@ 626A2293260037C400903C39 /* Views */ = { isa = PBXGroup; children = ( + F203BBF528A1AF960002DAD9 /* Banners */, 6287EA6226548BA7007B41CB /* Components */, 629BE8B8263642BC00BC49AD /* ScheduleListView.swift */, 626A2294260037D000903C39 /* TodayView */, @@ -1204,6 +1217,18 @@ path = JSON; sourceTree = ""; }; + F203BBF528A1AF960002DAD9 /* Banners */ = { + isa = PBXGroup; + children = ( + F203BBF628A1AF960002DAD9 /* BannerView.swift */, + F203BBF728A1AF960002DAD9 /* BannerDetailView.swift */, + F203BBF828A1AF960002DAD9 /* BannerExtensions.swift */, + F203BBF928A1AF960002DAD9 /* BannersView.swift */, + F203BBFA28A1AF960002DAD9 /* Banner.swift */, + ); + path = Banners; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1294,6 +1319,7 @@ 6261DF20271124DF009F6234 /* FirebaseCrashlytics */, 6261DF2427121E3D009F6234 /* FirebaseMessaging */, 62CCF9D4288F1C0400FA85C7 /* SwiftUIX */, + 6225B4D028A1C166008999D3 /* FirebaseDatabase */, ); productName = "SMHS Schedule"; productReference = 626A2265260035F100903C39 /* SMHS.app */; @@ -1430,7 +1456,6 @@ 62D1E55C263F17E9004DBFAD /* Settings.bundle in Resources */, 626183AB2637168E001C4AD4 /* Assets.xcassets in Resources */, 6263E83126BCEA69009EC880 /* ElegantCalendar.xcassets in Resources */, - 62A11A7C26E6A106007F7241 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1441,9 +1466,9 @@ 626A2270260035F200903C39 /* Preview Assets.xcassets in Resources */, 6260B0FD2832D85F007DBD3E /* JsonResponses in Resources */, 62D1E55D263F17E9004DBFAD /* Settings.bundle in Resources */, - 62A11A7B26E6A105007F7241 /* GoogleService-Info.plist in Resources */, 6263E83226BCEA69009EC880 /* ElegantCalendar.xcassets in Resources */, 626A226D260035F200903C39 /* Assets.xcassets in Resources */, + F2297A4C28A56EC90032EF99 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1647,6 +1672,7 @@ 623D595726FAF3D9001D05F7 /* GradesDetailViewModel.swift in Sources */, 62B227DA265D9A430023A7E6 /* FeaturesStatementView.swift in Sources */, 6201D9E72810C89B00EBC375 /* GradesRubric.swift in Sources */, + F203BBFC28A1AF960002DAD9 /* BannerDetailView.swift in Sources */, 62626390264DC5E3004146CE /* DeveloperSettingsView.swift in Sources */, 624F79F4264FA13500B93C63 /* NewsSelectionButtons.swift in Sources */, 62FCFFF028161DB100D3E1CA /* Endpoint.swift in Sources */, @@ -1698,6 +1724,7 @@ 624F7A002651A4E600B93C63 /* HapticsManager.swift in Sources */, 6287EA6A26558081007B41CB /* CustomScheduleView.swift in Sources */, 62583EA5286D214700349962 /* Match.swift in Sources */, + F203BBFF28A1AF960002DAD9 /* Banner.swift in Sources */, 62D3211C2649F2BD00C99C42 /* NewsDetailedView.swift in Sources */, 62D32122264A4C6700C99C42 /* ClassPeriod.swift in Sources */, 626183EF263718FC001C4AD4 /* UserDefaultWrapper.swift in Sources */, @@ -1705,7 +1732,9 @@ 621F5C8D2669FBA0006EBE8F /* SchoolMapDirections.swift in Sources */, 621385EF265C60D500F4806C /* InformationCardItem.swift in Sources */, 62583EA4286D214700349962 /* Utils.swift in Sources */, + F203BBFB28A1AF960002DAD9 /* BannerView.swift in Sources */, 62626393264DCFA0004146CE /* PeriodEditSettingsView.swift in Sources */, + F203BBFE28A1AF960002DAD9 /* BannersView.swift in Sources */, 623A7F18264EEBF700FF3F28 /* SearchView.swift in Sources */, 62328C9826476ED9000F26ED /* ProgressRingView.swift in Sources */, 6211915F27EEF144008BD312 /* Calendar.swift in Sources */, @@ -1715,6 +1744,7 @@ 62A11A8926EE9F4A007F7241 /* TeamsJoinBanner.swift in Sources */, 624F79EF264F87BE00B93C63 /* SearchResultView.swift in Sources */, 621F5C842669F2D5006EBE8F /* TodayHeroView.swift in Sources */, + F203BBFD28A1AF960002DAD9 /* BannerExtensions.swift in Sources */, 62B227C2265CC1D20023A7E6 /* WhyStatementView.swift in Sources */, 626A229C260039AB00903C39 /* ScheduleDetailView.swift in Sources */, 626183DF263718CA001C4AD4 /* ScheduleDay.swift in Sources */, @@ -2426,6 +2456,11 @@ package = 621F5C8E266A9479006EBE8F /* XCRemoteSwiftPackageReference "ElegantCalendar" */; productName = ElegantCalendar; }; + 6225B4D028A1C166008999D3 /* FirebaseDatabase */ = { + isa = XCSwiftPackageProductDependency; + package = 624B06DA26D3521B001C2EAE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseDatabase; + }; 62382B5826E69FCC00BF8918 /* FirebaseRemoteConfig */ = { isa = XCSwiftPackageProductDependency; package = 624B06DA26D3521B001C2EAE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; @@ -2511,6 +2546,11 @@ package = 62E3BF04264270AE00E1FF88 /* XCRemoteSwiftPackageReference "swiftui-visual-effects" */; productName = SwiftUIVisualEffects; }; + F2297A4928A56D690032EF99 /* FirebaseDatabase */ = { + isa = XCSwiftPackageProductDependency; + package = 624B06DA26D3521B001C2EAE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseDatabase; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 626A225D260035F100903C39 /* Project object */; diff --git a/Sources/SMHS (iOS)/Views/Banners/Banner.swift b/Sources/SMHS (iOS)/Views/Banners/Banner.swift new file mode 100644 index 0000000..738017a --- /dev/null +++ b/Sources/SMHS (iOS)/Views/Banners/Banner.swift @@ -0,0 +1,84 @@ +// +// Banner.swift +// SMHS (iOS) +// +// Created by Lampeh on 7.08.2022. +// + +import Foundation +import SwiftUI +import FirebaseDatabase +import Alamofire + +struct Banner: Identifiable, Codable, Equatable { + let headline: String + let title: String + let footnote: String + let externalLink: URL + let image: URL + let isActive: Bool + let paragraphs: [String] + + let requirements: [Requirement] + + enum Requirement: String, Codable { + case name, phoneNumber, email, school, grade + } + + let email: String + + let id = UUID() + + static func fetch() async throws -> [Banner] { + let db = Database.database().reference() + + let snapshot = try await db.getData() + + let data = try JSONSerialization.data(withJSONObject: snapshot.value as Any) + + let banners = try JSONDecoder().decode([Banner].self, from: data) + + return banners + } + + func submit(name: String, phoneNumber: String, email: String, school: String, grade: String) async throws { + let endpoint = Endpoint.submit(name: name, phoneNumber: phoneNumber, email: email, school: school, grade: grade, sendEmail: self.email) + + AF.request(endpoint.request) + .response(completionHandler: { result in + let res = result.response + + let ok = (200...300).contains(res?.statusCode ?? 0) + + if ok { + print("Successfully sent email.") + + Task { + let db = Database.database().reference() + guard let data = try? await db.getData() else { return } + + let index = Array(data.children).firstIndex { child in + let snapshot = child as! DataSnapshot + + let dict = snapshot.value as! [String: Any] + + return dict["title"] as? String == title + } + + guard let index = index else { return } + + let ref = db.child("\(index)/submissions").childByAutoId() + + try await ref.setValue([ + "name": name, + "phone_number": phoneNumber, + "email": email, + "school": school, + "grade": grade + ]) + } + } + }) + .resume() + } +} diff --git a/Sources/SMHS (iOS)/Views/Banners/BannerDetailView.swift b/Sources/SMHS (iOS)/Views/Banners/BannerDetailView.swift new file mode 100644 index 0000000..1173c67 --- /dev/null +++ b/Sources/SMHS (iOS)/Views/Banners/BannerDetailView.swift @@ -0,0 +1,139 @@ +// +// BannerDetailView.swift +// SMHS (iOS) +// +// Created by Lampeh on 7.08.2022. +// + +import SwiftUI +import SwiftUIVisualEffects + +struct BannerDetailView: View { + let banner: Banner + @Binding var selected: Banner? + var animate: Namespace.ID + + @State var name: String = "" + @State var phoneNumber: String = "" + @State var email: String = "" + @State var school: String = "" + @State var grade: String = "" + + var body: some View { + ZStack(alignment: .topTrailing) { + ScrollView(showsIndicators: false) { + VStack { + ZStack(alignment: .topLeading) { + BannerImage(url: banner.image, selected: $selected) + .ignoresSafeArea() + VStack(alignment: .leading, spacing: 5) { + Text(banner.headline) + .bannerHeadline() + Text(banner.title) + .bannerTitle() + Spacer() + Text(banner.footnote) + .bannerFootnote() + } + .padding(15) + .padding(.top, 45) // 20 + } + .ignoresSafeArea() + .matchedGeometryEffect(id: banner.id, in: animate, isSource: selected?.id == banner.id) + + VStack(alignment: .leading, spacing: 20) { + ForEach(banner.paragraphs, id: \.self) { paragraph in + Text(paragraph) + .foregroundColor(.secondary) + .font(.headline) + } + } + .padding() + VStack(spacing: 30) { + Text("Sign up") + .font(.headline) + VStack(spacing: 15) { + if banner.requirements.contains(.name) { + TextField("Name", text: $name) + .textFieldStyle(.roundedBorder) + } + if banner.requirements.contains(.phoneNumber) { + TextField("Phone number", text: $phoneNumber) + .textFieldStyle(.roundedBorder) + } + + if banner.requirements.contains(.email) { + TextField("Email", text: $email) + .textFieldStyle(.roundedBorder) + } + if banner.requirements.contains(.school) { + TextField("School", text: $school) + .textFieldStyle(.roundedBorder) + } + } + + if banner.requirements.contains(.grade) { + Picker("Grade level", selection: $grade) { + Text("Freshman") + .tag("freshman") + Text("Sophomore") + .tag("sophomore") + Text("Junior") + .tag("junior") + Text("Senior") + .tag("senior") + } + .pickerStyle(.segmented) + } + + Button("Submit") { + Task { + do { + let sent = try await banner.submit(name: name, + phoneNumber: phoneNumber, + email: email, + school: school, + grade: grade) + // print(sent ? "Sent email" : "Could not send email") + } + catch { + print("Submit error: \(error)") + } + } + } + .padding(.horizontal, 48) + .padding(.vertical, 12) + // .background(.thickMaterial) + .cornerRadius(8) + Spacer() + .frame(height: 300) + } + .padding() + } + } + .ignoresSafeArea() + Button(action: { + withAnimation(.spring()) { + selected = nil + } + }) { + BlurEffect() + .frame(height: UIDevice.hasTopNotch ? 35 : 20) + .clipShape(Circle()) + .blurEffectStyle(.systemThinMaterial) + .frame(width: 30, height: 30) + .overlay { + Image(systemName: "xmark") + } + } + .padding(.horizontal) + } + // .statusBarHidden(selected != nil) + } +} + +//struct BannerDetailView_Previews: PreviewProvider { +// static var previews: some View { +// BannerDetailView() +// } +//} diff --git a/Sources/SMHS (iOS)/Views/Banners/BannerExtensions.swift b/Sources/SMHS (iOS)/Views/Banners/BannerExtensions.swift new file mode 100644 index 0000000..3797aba --- /dev/null +++ b/Sources/SMHS (iOS)/Views/Banners/BannerExtensions.swift @@ -0,0 +1,115 @@ +// +// BottomBlur.swift +// SMHS (iOS) +// +// Created by Lampeh on 7.08.2022. +// + +import SwiftUI +import Kingfisher + +//struct WebpProcessor: ImageProcessor { +// +// // `identifier` should be the same for processors with the same properties/functionality +// // It will be used when storing and retrieving the image to/from cache. +// let identifier = "com.yourdomain.webpprocessor" +// +// // Convert input data/image to target image and return it. +// func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { +// switch item { +// case .image(let image): +// return image +// case .data(_): +// return nil +// } +// } +//} + +struct BannerImage: View { + let url: URL + @Binding var selected: Banner? + + var body: some View { + KFImage(url) + .placeholder { + Color(UIColor.systemGray) + } + .retry(maxCount: 3, interval: .seconds(3)) + .onFailure { + #if DEBUG + print("failure: \($0)") + #endif + } + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 3.0/4.0 * UIScreen.screenWidth, + height: 3.0/4.0 * UIScreen.screenWidth) + .clipped() + .overlay( + KFImage(url) + .setProcessor(BlurImageProcessor(blurRadius: 65)) + .aspectRatio(contentMode: .fill) + .frame(width: 3.0/4.0 * UIScreen.screenWidth, + height: 3.0/4.0 * UIScreen.screenWidth) + .clipped() + .mask { + LinearGradient(stops: [.init(color: .black, location: 0), .init(color: .black, location: 0.1), + .init(color: .clear, location: 0.4), .init(color: .clear, location: 0.8), .init(color: .black, location: 0.95), .init(color: .black, location: 1)], startPoint: .bottom, endPoint: .top) + } + .opacity(selected == nil ? 1 : 0) + + ) + .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous)) + .shadow(color: Color.appPrimary + .makeColor(componentDelta: -0.5) + .opacity(0.3), + radius: 15, x: 5, y: 5) + } +} + +extension Text { + func bannerTitle() -> some View { + self + .font(.title) + .fontWeight(.heavy) + .foregroundColor(.white) + .multilineTextAlignment(.leading) + } + func bannerHeadline() -> some View { + self + .font(.body) + .fontWeight(.semibold) + .textCase(.uppercase) + .foregroundColor(.white) + .multilineTextAlignment(.leading) + .opacity(0.6) + } + + func bannerFootnote() -> some View { + self + .foregroundColor(.white) + .multilineTextAlignment(.leading) + } +} + +extension KFImage { + func bottomBlurred(level: Int) -> some View { + return self + .overlay { + ForEach(0.. Endpoint { + let body = [ + "name": name, + "phone_number": phoneNumber, + "email": email, + "school": school, + "grade": grade, + "send_email": sendEmail + ] + + return Endpoint(host: c.SmhsApiPath.host, + path: c.SmhsApiPath.main + c.SmhsApiPath.submit, + requestBody: body, + httpMethod: .POST, + isApplicationJson: true, + jsonEncode: true) + } + } diff --git a/Sources/Shared/Utility/Extensions/Colors.swift b/Sources/Shared/Utility/Extensions/Colors.swift index ad91348..b2e74d0 100644 --- a/Sources/Shared/Utility/Extensions/Colors.swift +++ b/Sources/Shared/Utility/Extensions/Colors.swift @@ -227,4 +227,16 @@ extension Color { Color(hexadecimal: Constants.Color.fetchedSecondary ?? Constants.Color.secondaryHex) } + + func makeColor(componentDelta: Double) -> Color { + if let components = self.cgColor?.components { + return Color(red: components[0] + componentDelta, + green: components[1] + componentDelta, + blue: components[2] + componentDelta) + } + else { + return self + } + + } } diff --git a/Sources/Shared/Views/NewsView/NewsViewViewModel.swift b/Sources/Shared/Views/NewsView/NewsViewViewModel.swift index 615a4e4..e6679c4 100644 --- a/Sources/Shared/Views/NewsView/NewsViewViewModel.swift +++ b/Sources/Shared/Views/NewsView/NewsViewViewModel.swift @@ -9,6 +9,7 @@ import Foundation import Combine import SwiftyXMLParser import SwiftSoup +import FirebaseRemoteConfig final class NewsViewViewModel: ObservableObject { private var cancellable: AnyCancellable? diff --git a/Sources/Shared/Views/ScheduleView/Components/AnimatedBlurBackground.swift b/Sources/Shared/Views/ScheduleView/Components/AnimatedBlurBackground.swift index a594dc3..a450854 100644 --- a/Sources/Shared/Views/ScheduleView/Components/AnimatedBlurBackground.swift +++ b/Sources/Shared/Views/ScheduleView/Components/AnimatedBlurBackground.swift @@ -27,33 +27,33 @@ struct AnimatedBlurBackground: View { .blurEffectStyle(.systemUltraThinMaterial) } else { - ZStack { - makeBackgroundImage(geo) - .if(dynamicBlurred, transform: { - $0 - .blur(radius: 2, opaque: true) - - }, elseThen: { - $0 - .blurEffect() - .blurEffectStyle(.systemMaterial) - }) - - if dynamicBlurred { - makeBackgroundImage(geo) - .blur(radius: 60, opaque: true) - .mask ( - LinearGradient(stops: [.init(color: .clear, location: 0), - .init(color: .clear, location: gradientTopLocation), - .init(color: .black, location: gradientBottomLocation), - .init(color: .black, location: 1)], startPoint: .top, endPoint: .bottom) - ) - } - } - .if(dynamicBlurred) { - $0 - .drawingGroup() - } +// ZStack { +// makeBackgroundImage(geo) +// .if(dynamicBlurred, transform: { +// $0 +// .blur(radius: 2, opaque: true) +// +// }, elseThen: { +// $0 +// .blurEffect() +// .blurEffectStyle(.systemMaterial) +// }) +// +// if dynamicBlurred { +// makeBackgroundImage(geo) +// .blur(radius: 60, opaque: true) +// .mask ( +// LinearGradient(stops: [.init(color: .clear, location: 0), +// .init(color: .clear, location: gradientTopLocation), +// .init(color: .black, location: gradientBottomLocation), +// .init(color: .black, location: 1)], startPoint: .top, endPoint: .bottom) +// ) +// } +// } +// .if(dynamicBlurred) { +// $0 +// .drawingGroup() +// } } } diff --git a/Sources/Shared/Views/ScheduleView/Components/PeriodBlockItem.swift b/Sources/Shared/Views/ScheduleView/Components/PeriodBlockItem.swift index 55a04aa..bfac818 100644 --- a/Sources/Shared/Views/ScheduleView/Components/PeriodBlockItem.swift +++ b/Sources/Shared/Views/ScheduleView/Components/PeriodBlockItem.swift @@ -63,6 +63,7 @@ struct PeriodBlockItem: View { .fontWeight(.semibold) .textAlign(.leading) .font(.headline) + .lineLimit(1) .if(isBlurred) { $0.vibrancyEffectStyle(.label) } diff --git a/Sources/Shared/Views/ScheduleView/Components/ScheduleDetailView.swift b/Sources/Shared/Views/ScheduleView/Components/ScheduleDetailView.swift index 69d1509..5ecd1d9 100644 --- a/Sources/Shared/Views/ScheduleView/Components/ScheduleDetailView.swift +++ b/Sources/Shared/Views/ScheduleView/Components/ScheduleDetailView.swift @@ -41,40 +41,45 @@ struct ScheduleDetailView: View { var scheduleDay: ScheduleDay? //Periods before lunch, 1st out of 3 UI sections var preLunchPeriods: [ClassPeriod] { - let firstIndex = scheduleDay?.periods.firstIndex{$0.periodCategory.isLunchRevolving} - if let firstIndex = firstIndex, - let scheduleDay = scheduleDay { - return Array(scheduleDay.periods[0.. [ClassPeriod] { + func parseClassPeriods() -> [ClassPeriod]? { //Will be returned for value of this variable var classPeriods: [ClassPeriod] = [ClassPeriod]() @@ -27,22 +27,22 @@ extension ScheduleDay { //Normal start/end time format case guard let startTime: Substring = c.startTimePattern.findFirst(in: String(line))?.matched.dropLast(), //Optional might be nil because some lines do not contain schedule - let endTime: Substring = c.endTimePattern.findFirst(in: String(line))?.matched.dropFirst(), + var endTime: Substring = c.endTimePattern.findFirst(in: String(line))?.matched.dropFirst(), "[a-zA-Z]".r!.matches(String(line)) else - { - //Handle 1st/2nd nutrition schedule case - if let nutritionIndex = c.lunchPattern.findFirst(in: String(line))?.range.lowerBound, - let period: Match = c.periodPattern.findFirst(in: String(line)) { - - let block = parseNutritionPeriodLines(textLines, - lineNum: lineNum, - nutritionIndex: nutritionIndex, - period: period) - classPeriods.append(contentsOf: block) - } - continue - } - + { + //Handle 1st/2nd nutrition schedule case + if let nutritionIndex = c.lunchPattern.findFirst(in: String(line))?.range.lowerBound, + let period: Match = c.periodPattern.findFirst(in: String(line)) { + + let block = parseNutritionPeriodLines(textLines, + lineNum: lineNum, + nutritionIndex: nutritionIndex, + period: period) + classPeriods.append(contentsOf: block) + } + continue + } + endTime = Substring(endTime.replacingOccurrences(of: "noon", with: "12:00")) guard let timeIndex = line.index(of: startTime) else { continue } @@ -168,10 +168,10 @@ extension ScheduleDay { let p8Days = Constants.period8Days, p8Days.contains(self.dayOfTheWeek), - // Make sure is not school holiday + // Make sure is not school holiday !Constants.noSchoolIdentifier.contains(dayTitle ?? ""), - let times = Constants.period8Times + let times = Constants.period8Times else { return periods } var periods = periods diff --git a/Sources/Shared/Views/ScheduleView/Model/ScheduleDay.swift b/Sources/Shared/Views/ScheduleView/Model/ScheduleDay.swift index 698b56e..a65b96e 100644 --- a/Sources/Shared/Views/ScheduleView/Model/ScheduleDay.swift +++ b/Sources/Shared/Views/ScheduleView/Model/ScheduleDay.swift @@ -15,14 +15,12 @@ struct ScheduleDay: Hashable, Identifiable, Codable { } internal init(date: Date, scheduleText: String, dayTitle: String) { - self.date = date - self.scheduleText = scheduleText + self.init(date: date, scheduleText: scheduleText) self.dayTitle = dayTitle } internal init(date: Date, scheduleText: String) { - self.date = date - self.scheduleText = scheduleText + self.init(date: date, scheduleText: scheduleText, mockDate: nil) } var mockDate: Date? //Mock representation of current date, for testing @@ -42,6 +40,7 @@ struct ScheduleDay: Hashable, Identifiable, Codable { let appended = appendOptionalPeriod8(periods: parsedPeriods) return appended } + var atheleticsInfo: String { guard dayOfTheWeek != 6 && dayOfTheWeek != 0 else { @@ -161,15 +160,6 @@ struct ScheduleDay: Hashable, Identifiable, Codable { } extension ScheduleDay { - subscript(periodIndex: Int) -> ClassPeriod { - get { - periods[periodIndex] - } - set { - customPeriods[periodIndex] = newValue - } - } - static let schoolDay = DateFormatter().serverTimeFormat("2022-07-22T04:24:14+0000")! static let sampleScheduleDay = ScheduleDay(date: schoolDay, scheduleText: "Period 6 8:00-9:05\nPeriod 7 9:12-10:22\n(5 minutes for announcements)\nNutrition Period 1\n10:22-11:02 10:29-11:34\nPeriod 1 Nutrition\n11:09-12:14 11:34-12:14\nPeriod 2 12:21-1:26\nOffice Hours 1:33-2:30\n-------------------------------\nAP French Lang/Culture & Modern World Hist 8:00\nAP Macroeconomics 12:00\nB FS Golf vs MD 3:30\nB JV Golf @ JSerra 3:00\nB JV Tennis vs Servite 3:15\nB JV/V LAX @ JSerra 7:00/5:30\nB V Tennis @ Servite 2:30\nB V Vball @ JSerra 3:00\nG JV/V LAX @ Orange Luth 7:00/5:30\nG V Golf vs Rosary 4:30\nPossible G Soccer CIF\nSenior Graduation Ticket Distribution\n\nV Wrestling vs Aliso Niguel 1:30\n") }