diff --git a/Tumble.xcodeproj/project.pbxproj b/Tumble.xcodeproj/project.pbxproj index cc3dd53c..b030fc5a 100644 --- a/Tumble.xcodeproj/project.pbxproj +++ b/Tumble.xcodeproj/project.pbxproj @@ -626,9 +626,9 @@ D40E84392981C84700C2CF2E /* Account */ = { isa = PBXGroup; children = ( + D4E83EBF299960EB002E5F88 /* Login */, D4E83ECC2999B01C002E5F88 /* User */, D40E843A2981C85400C2CF2E /* Account.swift */, - D4E83EBF299960EB002E5F88 /* Login */, ); path = Account; sourceTree = ""; @@ -1842,7 +1842,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.3; + MARKETING_VERSION = 3.4; NETWORK_SETTINGS = testing; NEW_SETTING = ""; PRODUCT_BUNDLE_IDENTIFIER = com.example.tumble; @@ -2003,7 +2003,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.3; + MARKETING_VERSION = 3.4; NETWORK_SETTINGS = testing; NEW_SETTING = ""; PRODUCT_BUNDLE_IDENTIFIER = com.example.tumble; @@ -2047,7 +2047,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.3; + MARKETING_VERSION = 3.4; NETWORK_SETTINGS = production; NEW_SETTING = ""; PRODUCT_BUNDLE_IDENTIFIER = com.example.tumble; diff --git a/Tumble/Data/DataSource/Kronox/KronoxManager.swift b/Tumble/Data/DataSource/Kronox/KronoxManager.swift index dfee3c7d..9626713e 100644 --- a/Tumble/Data/DataSource/Kronox/KronoxManager.swift +++ b/Tumble/Data/DataSource/Kronox/KronoxManager.swift @@ -35,6 +35,7 @@ enum KronoxManagerError: LocalizedError { class KronoxManager { private let urlRequestUtils = NetworkUtilities.shared private let session = URLSession.shared + private let decoder = JSONDecoder() func get( _ endpoint: Endpoint, @@ -83,7 +84,6 @@ class KronoxManager { if statusCode == 200 { do { - let decoder = JSONDecoder() let decodedData = try decoder.decode(NetworkResponse.self, from: data) return decodedData } catch { diff --git a/Tumble/Extensions/Presentation/View/TextExtensions.swift b/Tumble/Extensions/Presentation/View/TextExtensions.swift index fd2d953c..b74c4890 100644 --- a/Tumble/Extensions/Presentation/View/TextExtensions.swift +++ b/Tumble/Extensions/Presentation/View/TextExtensions.swift @@ -19,14 +19,27 @@ extension Text { font(.system(size: 14, weight: .medium)) } - func info() -> some View { + func infoBodyMedium(opacity: Double = 1.0) -> some View { font(.system(size: 20)) + .fontWeight(.semibold) + .foregroundColor(.onBackground.opacity(opacity)) + .padding(.bottom, 25) + .padding([.leading, .trailing], 15) + } + + func infoHeaderSmall() -> some View { + font(.system(size: 24)) .fontWeight(.semibold) .foregroundColor(.onBackground) .padding(.bottom, 25) .padding([.leading, .trailing], 15) } + func infoHeaderMedium() -> some View { + font(.system(size: 34, weight: .semibold)) + .foregroundColor(.onBackground) + } + func programmeTitle() -> some View { font(.system(size: 18, weight: .semibold)) .multilineTextAlignment(.leading) diff --git a/Tumble/Observables/Controllers/AppController.swift b/Tumble/Observables/Controllers/AppController.swift index 247cfb9f..c36de362 100644 --- a/Tumble/Observables/Controllers/AppController.swift +++ b/Tumble/Observables/Controllers/AppController.swift @@ -17,5 +17,5 @@ class AppController: ObservableObject { @Published var eventSheet: EventDetailsSheetModel? @Published var selectedAppTab: TabbarTabType = .home - @Published var updatingBookmarks: Bool = false + @Published var isUpdatingBookmarks: Bool = false } diff --git a/Tumble/Observables/ViewModels/AccountViewModel.swift b/Tumble/Observables/ViewModels/AccountViewModel.swift index faed31d4..73e5261d 100644 --- a/Tumble/Observables/ViewModels/AccountViewModel.swift +++ b/Tumble/Observables/ViewModels/AccountViewModel.swift @@ -33,6 +33,7 @@ final class AccountViewModel: ObservableObject { private var resourceSectionDataTask: URLSessionDataTask? = nil private var eventSectionDataTask: URLSessionDataTask? = nil private let popupFactory: PopupFactory = PopupFactory.shared + private var registeredForExams: Bool = false var userDisplayName: String? { @@ -59,11 +60,6 @@ final class AccountViewModel: ObservableObject { init() { setupPublishers() - Task { - if self.userController.autoSignup { - await self.registerAutoSignup() - } - } } private func setupPublishers() { @@ -71,10 +67,16 @@ final class AccountViewModel: ObservableObject { let authSchoolIdPublisher = preferenceService.$authSchoolId.receive(on: RunLoop.main) Publishers.CombineLatest(authStatusPublisher, authSchoolIdPublisher) .sink { [weak self] authStatus, authSchoolId in - DispatchQueue.main.async { - self?.authStatus = authStatus - self?.authSchoolId = authSchoolId - } + guard let self else { return } + DispatchQueue.main.async { + self.authStatus = authStatus + self.authSchoolId = authSchoolId + } + if authStatus == .authorized && !self.registeredForExams { + Task.detached(priority: .userInitiated) { + await self.registerAutoSignup() + } + } } .store(in: &cancellables) } @@ -292,6 +294,7 @@ final class AccountViewModel: ObservableObject { } let _: Response.KronoxEventRegistration? = try await kronoxManager.put(request, refreshToken: refreshToken.value, body: Request.Empty()) + self.registeredForExams = true } catch { AppLogger.shared.error("Failed to sign up for exams: \(error)") } diff --git a/Tumble/Observables/ViewModels/BookmarksViewModel.swift b/Tumble/Observables/ViewModels/BookmarksViewModel.swift index 6f4d8446..9c7fbfd0 100644 --- a/Tumble/Observables/ViewModels/BookmarksViewModel.swift +++ b/Tumble/Observables/ViewModels/BookmarksViewModel.swift @@ -42,7 +42,7 @@ final class BookmarksViewModel: ObservableObject { } private func setupPublishers() { - let updatingBookmarksPublisher = appController.$updatingBookmarks.receive(on: RunLoop.main) + let updatingBookmarksPublisher = appController.$isUpdatingBookmarks.receive(on: RunLoop.main) updatingBookmarksPublisher.sink { [weak self] updatingBookmarks in if !updatingBookmarks { self?.setupRealmListener() diff --git a/Tumble/Observables/ViewModels/HomeViewModel.swift b/Tumble/Observables/ViewModels/HomeViewModel.swift index 005ad660..f4563c5f 100644 --- a/Tumble/Observables/ViewModels/HomeViewModel.swift +++ b/Tumble/Observables/ViewModels/HomeViewModel.swift @@ -22,29 +22,30 @@ final class HomeViewModel: ObservableObject { @Published var todaysEventsCards: [WeekEventCardModel] = .init() @Published var nextClass: Event? = nil - private var fetchedNewsDuringSession: Bool = false + private var initialisedSession: Bool = false private let viewModelFactory: ViewModelFactory = .shared + private let appController: AppController = .shared private var schedulesToken: NotificationToken? - private var cancellable: AnyCancellable? = nil + private var cancellables = Set() init() { - setupRealmListener() - setupNetworkPublisher() + setupPublishers() } - /// Listen for change in network connection, if connected attempt - /// to fetch latest news from server - private func setupNetworkPublisher() { + private func setupPublishers() { let networkConnectionPublisher = networkController.$connected.receive(on: RunLoop.main) - cancellable = networkConnectionPublisher - .sink { [weak self] connected in + let isUpdatingBookmarksPublisher = appController.$isUpdatingBookmarks.receive(on: RunLoop.main) + Publishers.CombineLatest(networkConnectionPublisher, isUpdatingBookmarksPublisher) + .sink { [weak self] connected, isUpdating in guard let self else { return } - if connected && !self.fetchedNewsDuringSession { + if connected && !self.initialisedSession && !isUpdating { Task.detached(priority: .userInitiated) { await self.fetchNews() } } + self.setupRealmListener() } + .store(in: &cancellables) } /// Initializes a listener that performs updates on the @@ -126,7 +127,7 @@ final class HomeViewModel: ObservableObject { } deinit { - cancellable?.cancel() + cancellables.forEach({ $0.cancel() }) } } diff --git a/Tumble/Observables/ViewModels/ParentViewModel.swift b/Tumble/Observables/ViewModels/ParentViewModel.swift index d8ddc674..348dc595 100644 --- a/Tumble/Observables/ViewModels/ParentViewModel.swift +++ b/Tumble/Observables/ViewModels/ParentViewModel.swift @@ -73,18 +73,20 @@ final class ParentViewModel: ObservableObject { Publishers.CombineLatest3(authSchoolIdPublisher, onBoardingPublisher, networkConnectionPublisher) .sink { [weak self] authSchoolId, userOnBoarded, connected in - guard let self else { return } + guard let self = self else { return } self.userNotOnBoarded = !userOnBoarded self.authSchoolId = authSchoolId - - if connected && updateShouldOccur() { + + if connected && self.updateShouldOccur() && !appController.isUpdatingBookmarks { + AppLogger.shared.debug("Updating all bookmarks ...") self.updateRealmSchedules() } } .store(in: &cancellables) + } - func updateShouldOccur() -> Bool { + private func updateShouldOccur() -> Bool { if let lastUpdate = preferenceService.getLastUpdated() { if let threeHoursAgo = Calendar.current.date(byAdding: .hour, value: -3, to: Date()) { if lastUpdate <= threeHoursAgo { @@ -102,9 +104,9 @@ final class ParentViewModel: ObservableObject { @MainActor func updateBookmarks(scheduleIds: [String]) async { - appController.updatingBookmarks = true - + appController.isUpdatingBookmarks = true defer { self.preferenceService.setLastUpdated(time: Date()) } + var updatedSchedules = 0 let scheduleCount: Int = scheduleIds.count @@ -122,7 +124,7 @@ final class ParentViewModel: ObservableObject { } AppLogger.shared.debug("Finished updating schedules") - appController.updatingBookmarks = false + appController.isUpdatingBookmarks = false } diff --git a/Tumble/Presentation/Views/Account/Login/LoginHeader.swift b/Tumble/Presentation/Views/Account/Login/LoginHeader.swift index 28d30a3d..ebdea0c3 100644 --- a/Tumble/Presentation/Views/Account/Login/LoginHeader.swift +++ b/Tumble/Presentation/Views/Account/Login/LoginHeader.swift @@ -11,11 +11,9 @@ struct LoginHeader: View { var body: some View { VStack(alignment: .center, spacing: 10) { Text(NSLocalizedString("Log in", comment: "")) - .font(.system(size: 30, weight: .semibold)) - .foregroundColor(.onBackground) + .infoHeaderMedium() Text(NSLocalizedString("Please log in to continue", comment: "")) - .font(.system(size: 18, weight: .regular)) - .foregroundColor(.onBackground.opacity(0.75)) + .infoBodyMedium(opacity: 0.75) } .frame(maxWidth: .infinity, alignment: .center) .padding(.bottom, 35) diff --git a/Tumble/Presentation/Views/Bookmarks/List/BookmarkListView.swift b/Tumble/Presentation/Views/Bookmarks/List/BookmarkListView.swift index 720c9927..d5f2b51b 100644 --- a/Tumble/Presentation/Views/Bookmarks/List/BookmarkListView.swift +++ b/Tumble/Presentation/Views/Bookmarks/List/BookmarkListView.swift @@ -43,6 +43,16 @@ struct BookmarkListView: View { var body: some View { ScrollViewReader { proxy in + if showSearchField { + SearchField( + search: nil, + clearSearch: nil, + title: "Search events", + searchBarText: $searchText, + searching: $searching, + disabled: .constant(false) + ).onChange(of: searching, perform: onChangeSearch) + } ScrollView { VStack { switch bookmarksListModel.state { @@ -107,35 +117,12 @@ struct BookmarkListView: View { appController.eventSheet = EventDetailsSheetModel(event: event) } - fileprivate func handleButtonAnimation() { - if -bookmarksListModel.scrollViewOffset > 450 { - withAnimation(.spring()) { - bookmarksListModel.buttonOffsetX = .zero - } - } else if -bookmarksListModel.scrollViewOffset < 450 { - withAnimation(.spring()) { - bookmarksListModel.buttonOffsetX = 200 - } - } - } - fileprivate func handleSearchFieldVisibility() { - if -bookmarksListModel.scrollViewOffset > 100 && !searching { - withAnimation(.easeInOut) { - showSearchField = false - } - } else if -bookmarksListModel.scrollViewOffset < 100 && !searching { - withAnimation(.easeInOut) { - showSearchField = true - } - } - } - - fileprivate func handleScrollOffset(value: CGFloat) { - if bookmarksListModel.startOffset == 0 { - bookmarksListModel.startOffset = value + fileprivate func onChangeSearch(searching: Bool) { + if searching { + bookmarksListModel.state = .searching + } else { + bookmarksListModel.state = .notSearching } - let offset = value - bookmarksListModel.scrollViewOffset = offset - bookmarksListModel.startOffset } } diff --git a/Tumble/Presentation/Views/Bookmarks/Week/BookmarkWeekView.swift b/Tumble/Presentation/Views/Bookmarks/Week/BookmarkWeekView.swift index 4a61ccc7..6410f6f9 100644 --- a/Tumble/Presentation/Views/Bookmarks/Week/BookmarkWeekView.swift +++ b/Tumble/Presentation/Views/Bookmarks/Week/BookmarkWeekView.swift @@ -46,7 +46,7 @@ private struct WeekPage: View { VStack { Text(NSLocalizedString("No events for this week..", comment: "")) .foregroundColor(.onBackground) - .info() + .infoBodyMedium() Image("GirlRelaxing") .resizable() .scaledToFit() diff --git a/Tumble/Presentation/Views/General/Info.swift b/Tumble/Presentation/Views/General/Info.swift index a63c4500..ad1573d1 100644 --- a/Tumble/Presentation/Views/General/Info.swift +++ b/Tumble/Presentation/Views/General/Info.swift @@ -19,7 +19,7 @@ struct Info: View { .padding(.bottom, 15) } Text(title) - .info() + .infoBodyMedium() .multilineTextAlignment(.center) } .frame( diff --git a/Tumble/Presentation/Views/General/InfoLoading.swift b/Tumble/Presentation/Views/General/InfoLoading.swift index b0d9af30..d05af117 100644 --- a/Tumble/Presentation/Views/General/InfoLoading.swift +++ b/Tumble/Presentation/Views/General/InfoLoading.swift @@ -14,7 +14,7 @@ struct InfoLoading: View { CustomProgressIndicator() .padding(.bottom, 15) Text(title) - .info() + .infoBodyMedium() .multilineTextAlignment(.center) } .frame( diff --git a/Tumble/Presentation/Views/Search/SchoolPill.swift b/Tumble/Presentation/Views/Search/SchoolPill.swift index b8a90a46..ea988199 100644 --- a/Tumble/Presentation/Views/Search/SchoolPill.swift +++ b/Tumble/Presentation/Views/Search/SchoolPill.swift @@ -53,7 +53,7 @@ struct SchoolPill: View, Pill { } var fontSize: CGFloat { - isSelected() ? 16 : 14 + isSelected() ? 18 : 16 } func isSelected() -> Bool { diff --git a/Tumble/Presentation/Views/Search/SearchInfo.swift b/Tumble/Presentation/Views/Search/SearchInfo.swift index da564a99..942dc5c7 100644 --- a/Tumble/Presentation/Views/Search/SearchInfo.swift +++ b/Tumble/Presentation/Views/Search/SearchInfo.swift @@ -19,7 +19,7 @@ struct SearchInfo: View { Text(NSLocalizedString( "Choose a university to begin your search", comment: "" )) - .info() + .infoHeaderSmall() .multilineTextAlignment(.leading) .padding(.bottom, 10) }