From a86a1e8e7ce065962f202765bb71ce648c99f83b Mon Sep 17 00:00:00 2001 From: Jayana Nanayakkara Date: Mon, 29 Sep 2025 18:54:24 -0700 Subject: [PATCH 1/5] added app rating and review prompt --- berkeley-mobile.xcodeproj/project.pbxproj | 4 ++ berkeley-mobile/Common/ReviewPrompter.swift | 58 +++++++++++++++++++ .../Events/Campus/CampusEventDetailView.swift | 2 + .../EventDataSource/EventsViewModel.swift | 1 + .../Fitness/GymDetailViewController.swift | 1 + berkeley-mobile/Home/HomeView.swift | 7 ++- .../LibraryDetailViewController.swift | 1 + .../Resources/ResourcesSectionDropdown.swift | 3 + 8 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 berkeley-mobile/Common/ReviewPrompter.swift diff --git a/berkeley-mobile.xcodeproj/project.pbxproj b/berkeley-mobile.xcodeproj/project.pbxproj index e2bec2ef6..d9bb9f260 100644 --- a/berkeley-mobile.xcodeproj/project.pbxproj +++ b/berkeley-mobile.xcodeproj/project.pbxproj @@ -181,6 +181,7 @@ E8FCD6842C374A81004B66A3 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = E8FCD6832C374A81004B66A3 /* SwiftSoup */; }; E8FCD6882C38AEC9004B66A3 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FCD6872C38AEC9004B66A3 /* String+Extension.swift */; }; E8FCD68A2C3A382F004B66A3 /* EventScrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8FCD6892C3A382F004B66A3 /* EventScrapper.swift */; }; + FAFF001C2E84C62100551C44 /* ReviewPrompter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAFF001B2E84C61B00551C44 /* ReviewPrompter.swift */; }; FD44FE6125EB0EAD00F713EB /* AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD44FE6025EB0EAD00F713EB /* AlertView.swift */; }; FDD7946C260FE09D00ABE60E /* Colors+AlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD7946B260FE09D00ABE60E /* Colors+AlertView.swift */; }; /* End PBXBuildFile section */ @@ -378,6 +379,7 @@ E8EBE7612CEC2FD700A220BB /* SafetyLogDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafetyLogDetailView.swift; sourceTree = ""; }; E8FCD6872C38AEC9004B66A3 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; E8FCD6892C3A382F004B66A3 /* EventScrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventScrapper.swift; sourceTree = ""; }; + FAFF001B2E84C61B00551C44 /* ReviewPrompter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewPrompter.swift; sourceTree = ""; }; FD44FE6025EB0EAD00F713EB /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = ""; }; FDD7946B260FE09D00ABE60E /* Colors+AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Colors+AlertView.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -536,6 +538,7 @@ 1396012D23865E2E005E4788 /* Common */ = { isa = PBXGroup; children = ( + FAFF001B2E84C61B00551C44 /* ReviewPrompter.swift */, 2913595524B135B600DE9AD6 /* DetailView */, 13B39AB123E777AC0039FBA2 /* FilterView */, 29481560253274CA00D113B6 /* Images */, @@ -1155,6 +1158,7 @@ E8C2BDA72D912B3A00165554 /* CalendarView.swift in Sources */, E83B6DA52D7A85D500AA9422 /* GymOccupancyScrapper.swift in Sources */, E8472B062DE453D6005C24EA /* BMAddedCalendarStatusOverlayView.swift in Sources */, + FAFF001C2E84C62100551C44 /* ReviewPrompter.swift in Sources */, 13EA64D02399D50C00FD8E13 /* DataManager.swift in Sources */, E80330EB2CE431C200DC9574 /* DepthButtonStyle.swift in Sources */, 1336A326241D9B9900949F32 /* DiningHall.swift in Sources */, diff --git a/berkeley-mobile/Common/ReviewPrompter.swift b/berkeley-mobile/Common/ReviewPrompter.swift new file mode 100644 index 000000000..ccbb20bb0 --- /dev/null +++ b/berkeley-mobile/Common/ReviewPrompter.swift @@ -0,0 +1,58 @@ +// +// ReviewPrompter.swift +// berkeley-mobile +// +// Created by Jayana Nanayakkara on 9/24/25. +// Copyright © 2025 ASUC OCTO. All rights reserved. +// + +import Foundation +import SwiftUI +final class ReviewPrompter { + static let shared = ReviewPrompter() + + private let successfulEventKey = "successfulEventCount" + private let lastPromptDateKey = "lastPromptDate" + private let promptCountPastYearKey = "promptCountPastYear" + + private let successfulEventThreshold = 3 + private let yearlyPromptLimit = 3 + private let minSecondsBetweenPrompts: TimeInterval = 86_400 + + private var successfulEventCount: Int { + get { UserDefaults.standard.integer(forKey: successfulEventKey) } + set { UserDefaults.standard.set(newValue, forKey: successfulEventKey) } + } + + private var lastPromptDate: Date? { + get { UserDefaults.standard.object(forKey: lastPromptDateKey) as? Date} + set { UserDefaults.standard.set(newValue, forKey: lastPromptDateKey) } + } + + private var promptCountPastYear: Int { + get { UserDefaults.standard.integer(forKey: promptCountPastYearKey) } + set { UserDefaults.standard.set(newValue, forKey: promptCountPastYearKey) } + } + + func incSuccessfulEvent() { + successfulEventCount += 1 + } + +// func resetForTesting() { +// UserDefaults.standard.removeObject(forKey: successfulEventKey) +// UserDefaults.standard.removeObject(forKey: lastPromptDateKey) +// UserDefaults.standard.removeObject(forKey: promptCountPastYearKey) +// } + + func shouldPromptForReview() -> Bool { + if successfulEventCount >= successfulEventThreshold && + (lastPromptDate == nil || Date().timeIntervalSince(lastPromptDate!) > minSecondsBetweenPrompts) && + promptCountPastYear < yearlyPromptLimit{ + lastPromptDate = Date() + promptCountPastYear += 1 + successfulEventCount = 0 + return true + } + return false + } +} diff --git a/berkeley-mobile/Events/Campus/CampusEventDetailView.swift b/berkeley-mobile/Events/Campus/CampusEventDetailView.swift index b11ab9701..318406908 100644 --- a/berkeley-mobile/Events/Campus/CampusEventDetailView.swift +++ b/berkeley-mobile/Events/Campus/CampusEventDetailView.swift @@ -91,9 +91,11 @@ struct CampusEventDetailView: View { withoutAnimation { eventsViewModel.alert = BMAlert(title: "Open in Safari?", message: message, type: .action) { UIApplication.shared.open(url) + } alertType = nil } + ReviewPrompter.shared.incSuccessfulEvent() } } diff --git a/berkeley-mobile/Events/EventDataSource/EventsViewModel.swift b/berkeley-mobile/Events/EventDataSource/EventsViewModel.swift index 4a66b6620..a5692ea70 100644 --- a/berkeley-mobile/Events/EventDataSource/EventsViewModel.swift +++ b/berkeley-mobile/Events/EventDataSource/EventsViewModel.swift @@ -50,6 +50,7 @@ class EventsViewModel: ObservableObject { do { try await BMEventManager.shared.addEventToCalendar(calendarEvent: event) presentAlertWithoutAnimation(BMAlert(title: "", message: "Successfully added to calendar!", type: .notice)) + ReviewPrompter.shared.incSuccessfulEvent() } catch { if let bmError = error as? BMError, bmError == .mayExistedInCalendarAlready { presentAlertWithoutAnimation(BMAlert(title: "Successfully added to calendar!", message: error.localizedDescription, type: .notice)) diff --git a/berkeley-mobile/Home/Fitness/GymDetailViewController.swift b/berkeley-mobile/Home/Fitness/GymDetailViewController.swift index 245ce4785..f462e49c0 100644 --- a/berkeley-mobile/Home/Fitness/GymDetailViewController.swift +++ b/berkeley-mobile/Home/Fitness/GymDetailViewController.swift @@ -59,6 +59,7 @@ class GymDetailViewController: UIViewController { @objc private func moreButtonClicked(sender: UIButton) { guard let url = gym.website else { return } UIApplication.shared.open(url, options: [:]) + ReviewPrompter.shared.incSuccessfulEvent() } var scrollingStackView: ScrollingStackView = { diff --git a/berkeley-mobile/Home/HomeView.swift b/berkeley-mobile/Home/HomeView.swift index 75db27df7..87468d273 100644 --- a/berkeley-mobile/Home/HomeView.swift +++ b/berkeley-mobile/Home/HomeView.swift @@ -13,7 +13,7 @@ struct HomeView: View { @State private var tabSelectedIndex = 0 @State private var selectedDetent: PresentationDetent = .fraction(0.45) - + @Environment(\.requestReview) private var requestReview private var mapViewController: MapViewController init(mapViewController: MapViewController) { @@ -40,6 +40,11 @@ struct HomeView: View { .onChange(of: homeViewModel.isShowingDrawer) { _ in homeViewModel.drawerViewState = .medium } + .onAppear { + if ReviewPrompter.shared.shouldPromptForReview(){ + requestReview() + } + } } } diff --git a/berkeley-mobile/Home/Libraries/LibraryDetailViewController.swift b/berkeley-mobile/Home/Libraries/LibraryDetailViewController.swift index 19acb66de..faa9ba423 100644 --- a/berkeley-mobile/Home/Libraries/LibraryDetailViewController.swift +++ b/berkeley-mobile/Home/Libraries/LibraryDetailViewController.swift @@ -52,6 +52,7 @@ class LibraryDetailViewController: UIViewController { @objc private func bookButtonClicked(sender: UIButton) { guard let url = URL(string: kBookingURL) else { return } UIApplication.shared.open(url, options: [:]) + ReviewPrompter.shared.incSuccessfulEvent() } var scrollingStackView: ScrollingStackView = { diff --git a/berkeley-mobile/Resources/ResourcesSectionDropdown.swift b/berkeley-mobile/Resources/ResourcesSectionDropdown.swift index cf64870a9..f6d580806 100644 --- a/berkeley-mobile/Resources/ResourcesSectionDropdown.swift +++ b/berkeley-mobile/Resources/ResourcesSectionDropdown.swift @@ -7,6 +7,7 @@ // import SwiftUI +import StoreKit struct ResourcesSectionDropdown: View { let title: String @@ -130,6 +131,8 @@ struct ResourceItemView: View { VStack(alignment: .leading, spacing: 8) { Button(action: { isPresentingWebView.toggle() + ReviewPrompter.shared.incSuccessfulEvent() +// ReviewPrompter.shared.resetForTesting() }) { HStack { Image(systemName: "globe") From 1a644ee3879bd3dfacb89162e05532c93d946484 Mon Sep 17 00:00:00 2001 From: Jayana Nanayakkara Date: Sun, 5 Oct 2025 18:24:54 -0700 Subject: [PATCH 2/5] modified app rating prompting frequency --- berkeley-mobile/AppDelegate.swift | 2 + berkeley-mobile/Common/ReviewPrompter.swift | 54 +++++-------------- .../Events/Campus/CampusEventDetailView.swift | 1 - .../EventDataSource/EventsViewModel.swift | 1 - .../Fitness/GymDetailViewController.swift | 1 - berkeley-mobile/Home/HomeView.swift | 5 +- .../LibraryDetailViewController.swift | 1 - .../MainContainerViewController.swift | 5 +- .../Resources/ResourcesSectionDropdown.swift | 2 - .../Utils/UserDefaults+Extension.swift | 5 ++ 10 files changed, 26 insertions(+), 51 deletions(-) diff --git a/berkeley-mobile/AppDelegate.swift b/berkeley-mobile/AppDelegate.swift index 2a99430a7..47a26eaff 100644 --- a/berkeley-mobile/AppDelegate.swift +++ b/berkeley-mobile/AppDelegate.swift @@ -12,6 +12,7 @@ import FirebaseMessaging import GoogleSignIn import UIKit import UserNotifications +import StoreKit @UIApplicationMain @@ -19,6 +20,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() + UserDefaults.standard.increment(forKey: UserDefaultsKeys.numAppLaunchForAppStoreReview) self.checkForUpdate() DataManager.shared.fetchAll() BMLocationManager.shared.requestLocation() diff --git a/berkeley-mobile/Common/ReviewPrompter.swift b/berkeley-mobile/Common/ReviewPrompter.swift index ccbb20bb0..fe5701dae 100644 --- a/berkeley-mobile/Common/ReviewPrompter.swift +++ b/berkeley-mobile/Common/ReviewPrompter.swift @@ -8,51 +8,25 @@ import Foundation import SwiftUI +import StoreKit +import UIKit + final class ReviewPrompter { static let shared = ReviewPrompter() - - private let successfulEventKey = "successfulEventCount" - private let lastPromptDateKey = "lastPromptDate" - private let promptCountPastYearKey = "promptCountPastYear" - - private let successfulEventThreshold = 3 - private let yearlyPromptLimit = 3 - private let minSecondsBetweenPrompts: TimeInterval = 86_400 - - private var successfulEventCount: Int { - get { UserDefaults.standard.integer(forKey: successfulEventKey) } - set { UserDefaults.standard.set(newValue, forKey: successfulEventKey) } - } - - private var lastPromptDate: Date? { - get { UserDefaults.standard.object(forKey: lastPromptDateKey) as? Date} - set { UserDefaults.standard.set(newValue, forKey: lastPromptDateKey) } - } - - private var promptCountPastYear: Int { - get { UserDefaults.standard.integer(forKey: promptCountPastYearKey) } - set { UserDefaults.standard.set(newValue, forKey: promptCountPastYearKey) } - } - - func incSuccessfulEvent() { - successfulEventCount += 1 - } - -// func resetForTesting() { -// UserDefaults.standard.removeObject(forKey: successfulEventKey) -// UserDefaults.standard.removeObject(forKey: lastPromptDateKey) -// UserDefaults.standard.removeObject(forKey: promptCountPastYearKey) -// } - func shouldPromptForReview() -> Bool { - if successfulEventCount >= successfulEventThreshold && - (lastPromptDate == nil || Date().timeIntervalSince(lastPromptDate!) > minSecondsBetweenPrompts) && - promptCountPastYear < yearlyPromptLimit{ - lastPromptDate = Date() - promptCountPastYear += 1 - successfulEventCount = 0 + let launches = UserDefaults.standard.integer(forKey: UserDefaultsKeys.numAppLaunchForAppStoreReview) + if launches > 30 { + UserDefaults.standard.set(0, forKey: UserDefaultsKeys.numAppLaunchForAppStoreReview.rawValue) return true } return false } + func presentReviewIfNeeded() { + guard shouldPromptForReview() else { return } + + if let scene = UIApplication.shared.connectedScenes + .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { + SKStoreReviewController.requestReview(in: scene) + } + } } diff --git a/berkeley-mobile/Events/Campus/CampusEventDetailView.swift b/berkeley-mobile/Events/Campus/CampusEventDetailView.swift index 318406908..beefbdc8a 100644 --- a/berkeley-mobile/Events/Campus/CampusEventDetailView.swift +++ b/berkeley-mobile/Events/Campus/CampusEventDetailView.swift @@ -95,7 +95,6 @@ struct CampusEventDetailView: View { } alertType = nil } - ReviewPrompter.shared.incSuccessfulEvent() } } diff --git a/berkeley-mobile/Events/EventDataSource/EventsViewModel.swift b/berkeley-mobile/Events/EventDataSource/EventsViewModel.swift index a5692ea70..4a66b6620 100644 --- a/berkeley-mobile/Events/EventDataSource/EventsViewModel.swift +++ b/berkeley-mobile/Events/EventDataSource/EventsViewModel.swift @@ -50,7 +50,6 @@ class EventsViewModel: ObservableObject { do { try await BMEventManager.shared.addEventToCalendar(calendarEvent: event) presentAlertWithoutAnimation(BMAlert(title: "", message: "Successfully added to calendar!", type: .notice)) - ReviewPrompter.shared.incSuccessfulEvent() } catch { if let bmError = error as? BMError, bmError == .mayExistedInCalendarAlready { presentAlertWithoutAnimation(BMAlert(title: "Successfully added to calendar!", message: error.localizedDescription, type: .notice)) diff --git a/berkeley-mobile/Home/Fitness/GymDetailViewController.swift b/berkeley-mobile/Home/Fitness/GymDetailViewController.swift index f462e49c0..245ce4785 100644 --- a/berkeley-mobile/Home/Fitness/GymDetailViewController.swift +++ b/berkeley-mobile/Home/Fitness/GymDetailViewController.swift @@ -59,7 +59,6 @@ class GymDetailViewController: UIViewController { @objc private func moreButtonClicked(sender: UIButton) { guard let url = gym.website else { return } UIApplication.shared.open(url, options: [:]) - ReviewPrompter.shared.incSuccessfulEvent() } var scrollingStackView: ScrollingStackView = { diff --git a/berkeley-mobile/Home/HomeView.swift b/berkeley-mobile/Home/HomeView.swift index 87468d273..d795fd732 100644 --- a/berkeley-mobile/Home/HomeView.swift +++ b/berkeley-mobile/Home/HomeView.swift @@ -13,7 +13,6 @@ struct HomeView: View { @State private var tabSelectedIndex = 0 @State private var selectedDetent: PresentationDetent = .fraction(0.45) - @Environment(\.requestReview) private var requestReview private var mapViewController: MapViewController init(mapViewController: MapViewController) { @@ -41,9 +40,7 @@ struct HomeView: View { homeViewModel.drawerViewState = .medium } .onAppear { - if ReviewPrompter.shared.shouldPromptForReview(){ - requestReview() - } + } } } diff --git a/berkeley-mobile/Home/Libraries/LibraryDetailViewController.swift b/berkeley-mobile/Home/Libraries/LibraryDetailViewController.swift index faa9ba423..19acb66de 100644 --- a/berkeley-mobile/Home/Libraries/LibraryDetailViewController.swift +++ b/berkeley-mobile/Home/Libraries/LibraryDetailViewController.swift @@ -52,7 +52,6 @@ class LibraryDetailViewController: UIViewController { @objc private func bookButtonClicked(sender: UIButton) { guard let url = URL(string: kBookingURL) else { return } UIApplication.shared.open(url, options: [:]) - ReviewPrompter.shared.incSuccessfulEvent() } var scrollingStackView: ScrollingStackView = { diff --git a/berkeley-mobile/MainContainerViewController.swift b/berkeley-mobile/MainContainerViewController.swift index f50b40d34..0c49016d2 100644 --- a/berkeley-mobile/MainContainerViewController.swift +++ b/berkeley-mobile/MainContainerViewController.swift @@ -10,7 +10,7 @@ import UIKit import SwiftUI class MainContainerViewController: UIViewController, MainDrawerViewDelegate { - + // MainDrawerViewDelegate properties var drawerStack: [DrawerViewDelegate] = [] var positions: [DrawerState?] = [] @@ -50,6 +50,9 @@ class MainContainerViewController: UIViewController, MainDrawerViewDelegate { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) attemptShowFeedbackForm() + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + ReviewPrompter.shared.presentReviewIfNeeded() + } } private func attemptShowFeedbackForm() { diff --git a/berkeley-mobile/Resources/ResourcesSectionDropdown.swift b/berkeley-mobile/Resources/ResourcesSectionDropdown.swift index f6d580806..7c71612c9 100644 --- a/berkeley-mobile/Resources/ResourcesSectionDropdown.swift +++ b/berkeley-mobile/Resources/ResourcesSectionDropdown.swift @@ -131,8 +131,6 @@ struct ResourceItemView: View { VStack(alignment: .leading, spacing: 8) { Button(action: { isPresentingWebView.toggle() - ReviewPrompter.shared.incSuccessfulEvent() -// ReviewPrompter.shared.resetForTesting() }) { HStack { Image(systemName: "globe") diff --git a/berkeley-mobile/Utils/UserDefaults+Extension.swift b/berkeley-mobile/Utils/UserDefaults+Extension.swift index f2de226d0..3b62bfa95 100644 --- a/berkeley-mobile/Utils/UserDefaults+Extension.swift +++ b/berkeley-mobile/Utils/UserDefaults+Extension.swift @@ -10,6 +10,7 @@ import Foundation enum UserDefaultsKeys: String { case numAppLaunchForFeedbackForm = "numAppLaunchForFeedbackForm" + case numAppLaunchForAppStoreReview = "numAppLaunchForAppStoreReview" case academicEventsLastSavedDate = "academicEventsLastSavedDate" case campuswideEventsLastSavedDate = "campuswideEventsLastSavedDate" case recentSearches = "recentSearches" @@ -27,4 +28,8 @@ extension UserDefaults { func data(forKey key: UserDefaultsKeys) -> Data? { data(forKey: key.rawValue) } + + func increment(forKey key: UserDefaultsKeys) { + set(integer(forKey: key) + 1, forKey: key) + } } From 9791af8780769309f4ef9b8601b080f28cd7074e Mon Sep 17 00:00:00 2001 From: Jayana Nanayakkara Date: Mon, 13 Oct 2025 18:36:02 -0700 Subject: [PATCH 3/5] implemented suggested edits for pr --- berkeley-mobile/AppDelegate.swift | 1 - berkeley-mobile/Common/ReviewPrompter.swift | 8 +++++--- berkeley-mobile/Home/HomeView.swift | 4 +--- berkeley-mobile/Home/Map/MapViewController.swift | 6 ++++++ berkeley-mobile/Resources/ResourcesSectionDropdown.swift | 1 - 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/berkeley-mobile/AppDelegate.swift b/berkeley-mobile/AppDelegate.swift index 47a26eaff..fec40e5f4 100644 --- a/berkeley-mobile/AppDelegate.swift +++ b/berkeley-mobile/AppDelegate.swift @@ -12,7 +12,6 @@ import FirebaseMessaging import GoogleSignIn import UIKit import UserNotifications -import StoreKit @UIApplicationMain diff --git a/berkeley-mobile/Common/ReviewPrompter.swift b/berkeley-mobile/Common/ReviewPrompter.swift index fe5701dae..8c7f787be 100644 --- a/berkeley-mobile/Common/ReviewPrompter.swift +++ b/berkeley-mobile/Common/ReviewPrompter.swift @@ -7,22 +7,24 @@ // import Foundation -import SwiftUI import StoreKit import UIKit final class ReviewPrompter { + private let numLaunchesForReview: Int = 30 static let shared = ReviewPrompter() func shouldPromptForReview() -> Bool { let launches = UserDefaults.standard.integer(forKey: UserDefaultsKeys.numAppLaunchForAppStoreReview) - if launches > 30 { + if launches > numLaunchesForReview { UserDefaults.standard.set(0, forKey: UserDefaultsKeys.numAppLaunchForAppStoreReview.rawValue) return true } return false } func presentReviewIfNeeded() { - guard shouldPromptForReview() else { return } + guard shouldPromptForReview() else { + return + } if let scene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { diff --git a/berkeley-mobile/Home/HomeView.swift b/berkeley-mobile/Home/HomeView.swift index c06f504cb..01fab5df8 100644 --- a/berkeley-mobile/Home/HomeView.swift +++ b/berkeley-mobile/Home/HomeView.swift @@ -13,6 +13,7 @@ struct HomeView: View { @State private var tabSelectedIndex = 0 @State private var selectedDetent: PresentationDetent = .fraction(0.45) + private var mapViewController: MapViewController init(mapViewController: MapViewController) { @@ -39,9 +40,6 @@ struct HomeView: View { .onChange(of: homeViewModel.isShowingDrawer) { _ in homeViewModel.drawerViewState = .medium } - .onAppear { - - } } } diff --git a/berkeley-mobile/Home/Map/MapViewController.swift b/berkeley-mobile/Home/Map/MapViewController.swift index 9237de425..92aed5e31 100644 --- a/berkeley-mobile/Home/Map/MapViewController.swift +++ b/berkeley-mobile/Home/Map/MapViewController.swift @@ -138,6 +138,12 @@ class MapViewController: UIViewController, SearchDrawerViewDelegate { self.createMapMarkerDropdownButton() self.showSelectedMapMarkerTypeAnnotations(forType: types.first!) + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + if self.mainContainer?.presentedViewController == nil { + ReviewPrompter.shared.presentReviewIfNeeded() + } + } } } diff --git a/berkeley-mobile/Resources/ResourcesSectionDropdown.swift b/berkeley-mobile/Resources/ResourcesSectionDropdown.swift index 13f348582..40008ab43 100644 --- a/berkeley-mobile/Resources/ResourcesSectionDropdown.swift +++ b/berkeley-mobile/Resources/ResourcesSectionDropdown.swift @@ -7,7 +7,6 @@ // import SwiftUI -import StoreKit struct ResourcesSectionDropdown: View { let title: String From 7b1dcd9895844dde408fe5b44de7a4cd290843d5 Mon Sep 17 00:00:00 2001 From: Jayana Nanayakkara Date: Mon, 20 Oct 2025 21:27:43 -0700 Subject: [PATCH 4/5] implemented suggested edits for pr (round 2) --- berkeley-mobile/Common/ReviewPrompter.swift | 29 ++++++++++--------- .../Home/Map/MapViewController.swift | 2 +- .../MainContainerViewController.swift | 3 -- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/berkeley-mobile/Common/ReviewPrompter.swift b/berkeley-mobile/Common/ReviewPrompter.swift index 8c7f787be..085673a91 100644 --- a/berkeley-mobile/Common/ReviewPrompter.swift +++ b/berkeley-mobile/Common/ReviewPrompter.swift @@ -10,25 +10,28 @@ import Foundation import StoreKit import UIKit -final class ReviewPrompter { - private let numLaunchesForReview: Int = 30 - static let shared = ReviewPrompter() - func shouldPromptForReview() -> Bool { - let launches = UserDefaults.standard.integer(forKey: UserDefaultsKeys.numAppLaunchForAppStoreReview) - if launches > numLaunchesForReview { - UserDefaults.standard.set(0, forKey: UserDefaultsKeys.numAppLaunchForAppStoreReview.rawValue) +enum ReviewPrompter { + private static let numLaunchesForReview: Int = 30 + + private static func shouldPromptForReview() -> Bool { + let key = UserDefaultsKeys.numAppLaunchForAppStoreReview.rawValue + let launches = UserDefaults.standard.integer(forKey: key) + print("DEBUG: ",launches) + if launches == numLaunchesForReview { + UserDefaults.standard.set(0, forKey: key) return true } return false } - func presentReviewIfNeeded() { - guard shouldPromptForReview() else { - return - } - + + @MainActor + static func presentReviewIfNeeded() { + guard shouldPromptForReview() else { return } + if let scene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { - SKStoreReviewController.requestReview(in: scene) + AppStore.requestReview(in: scene) + } } } diff --git a/berkeley-mobile/Home/Map/MapViewController.swift b/berkeley-mobile/Home/Map/MapViewController.swift index 92aed5e31..00e1fbdfd 100644 --- a/berkeley-mobile/Home/Map/MapViewController.swift +++ b/berkeley-mobile/Home/Map/MapViewController.swift @@ -141,7 +141,7 @@ class MapViewController: UIViewController, SearchDrawerViewDelegate { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { if self.mainContainer?.presentedViewController == nil { - ReviewPrompter.shared.presentReviewIfNeeded() + ReviewPrompter.presentReviewIfNeeded() } } } diff --git a/berkeley-mobile/MainContainerViewController.swift b/berkeley-mobile/MainContainerViewController.swift index 0c49016d2..c80372cf6 100644 --- a/berkeley-mobile/MainContainerViewController.swift +++ b/berkeley-mobile/MainContainerViewController.swift @@ -50,9 +50,6 @@ class MainContainerViewController: UIViewController, MainDrawerViewDelegate { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) attemptShowFeedbackForm() - DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { - ReviewPrompter.shared.presentReviewIfNeeded() - } } private func attemptShowFeedbackForm() { From 82eca924eee3183f4a1b04e4ffb3b36cfb691263 Mon Sep 17 00:00:00 2001 From: Jayana Nanayakkara Date: Mon, 10 Nov 2025 00:16:11 -0800 Subject: [PATCH 5/5] Addressed missed changes --- berkeley-mobile/Common/ReviewPrompter.swift | 5 +++-- berkeley-mobile/Events/EventDetailView.swift | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/berkeley-mobile/Common/ReviewPrompter.swift b/berkeley-mobile/Common/ReviewPrompter.swift index 085673a91..21bb9ed9a 100644 --- a/berkeley-mobile/Common/ReviewPrompter.swift +++ b/berkeley-mobile/Common/ReviewPrompter.swift @@ -16,7 +16,6 @@ enum ReviewPrompter { private static func shouldPromptForReview() -> Bool { let key = UserDefaultsKeys.numAppLaunchForAppStoreReview.rawValue let launches = UserDefaults.standard.integer(forKey: key) - print("DEBUG: ",launches) if launches == numLaunchesForReview { UserDefaults.standard.set(0, forKey: key) return true @@ -26,7 +25,9 @@ enum ReviewPrompter { @MainActor static func presentReviewIfNeeded() { - guard shouldPromptForReview() else { return } + guard shouldPromptForReview() else { + return + } if let scene = UIApplication.shared.connectedScenes .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { diff --git a/berkeley-mobile/Events/EventDetailView.swift b/berkeley-mobile/Events/EventDetailView.swift index a3ee1d584..f9aaebc92 100644 --- a/berkeley-mobile/Events/EventDetailView.swift +++ b/berkeley-mobile/Events/EventDetailView.swift @@ -91,7 +91,6 @@ struct EventDetailView: View { withoutAnimation { eventsViewModel.alert = BMAlert(title: "Open in Safari?", message: message, type: .action) { UIApplication.shared.open(url) - } alertType = nil }