diff --git a/apple/Omnivore.xcodeproj/project.pbxproj b/apple/Omnivore.xcodeproj/project.pbxproj index ffe6b398c..3eb20ea5f 100644 --- a/apple/Omnivore.xcodeproj/project.pbxproj +++ b/apple/Omnivore.xcodeproj/project.pbxproj @@ -2043,7 +2043,7 @@ repositoryURL = "https://github.com/intercom/intercom-ios"; requirement = { kind = exactVersion; - version = 11.1.2; + version = 17.1.2; }; }; 048F592A2790EAF800E0B494 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { diff --git a/apple/Omnivore.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apple/Omnivore.xcworkspace/xcshareddata/swiftpm/Package.resolved index c75c8d8a0..bf37d1aa9 100644 --- a/apple/Omnivore.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apple/Omnivore.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nathantannar4/Engine", "state" : { - "revision" : "0d2d5647921473be4aac40cb70ad13900f56ed2b", - "version" : "1.8.1" + "revision" : "391d0da0282f51327b5d2c0e5de64f75e59cbae7", + "version" : "1.8.3" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/intercom/intercom-ios", "state" : { - "revision" : "3345d9e7599141d7c844981423a7e5409f2bfb81", - "version" : "11.1.2" + "revision" : "ad789a51b350dffe804dbf083430e0d6e91ac4af", + "version" : "17.1.2" } }, { @@ -239,8 +239,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nathantannar4/Transmission", "state" : { - "revision" : "6b3e6b46f34d0d18715d27c46e0053f1eda1bcd1", - "version" : "1.3.1" + "revision" : "9cbf1f6fec330e25c615182f3de483b129576819", + "version" : "1.3.2" } }, { diff --git a/apple/OmnivoreKit/Package.swift b/apple/OmnivoreKit/Package.swift index 5c3b96b5e..1c43ffde9 100644 --- a/apple/OmnivoreKit/Package.swift +++ b/apple/OmnivoreKit/Package.swift @@ -71,9 +71,7 @@ var dependencies: [Package.Dependency] { .package(url: "https://github.com/google/GoogleSignIn-iOS", from: "6.2.2"), .package(url: "https://github.com/gonzalezreal/swift-markdown-ui", from: "2.0.0"), .package(url: "https://github.com/PostHog/posthog-ios.git", from: "2.0.0"), -// .package(url: "https://github.com/nathantannar4/Engine", exact: "1.0.1"), -// .package(url: "https://github.com/nathantannar4/Turbocharger", exact: "1.1.4"), - .package(url: "https://github.com/nathantannar4/Transmission", exact: "1.3.1") + .package(url: "https://github.com/nathantannar4/Transmission", exact: "1.3.2") ] // Comment out following line for macOS build deps.append(.package(url: "https://github.com/PSPDFKit/PSPDFKit-SP", from: "13.7.0")) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index e43d8341d..5d27bc54e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -546,7 +546,6 @@ struct AnimatingCellHeight: AnimatableModifier { if presentingItem.isPDF { PDFContainerView(item: presentingItem) } else { - let too = print("$viewModel.linkIsActive", viewModel.linkIsActive) WebReaderContainerView(item: presentingItem) } } else { @@ -804,9 +803,40 @@ struct AnimatingCellHeight: AnimatableModifier { } } + @State private var isAnimating = false + + var progress: some View { + GeometryReader { geometry in + VStack { + Spacer() + Rectangle() + .fill(Color.yellow) + .frame(height: 2) + .offset(x: self.isAnimating ? geometry.size.width - 40 : 0) + .frame(width: self.isAnimating ? geometry.size.width : 40) + .animation(Animation.linear(duration: 2).repeatForever(autoreverses: true)) + Spacer() + } + .onAppear { + self.isAnimating = true + } + .frame(height: 2) + } + .background(.clear) + .edgesIgnoringSafeArea(.all) + } + var body: some View { VStack(spacing: 0) { - Color.systemBackground.frame(height: 1) + if viewModel.showLoadingBar == .simple { + progress + .frame(height: 2) + .frame(maxWidth: .infinity) + .listRowSeparator(.hidden, edges: .all) + .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0)) + } else { + Color.systemBackground.frame(height: 2) + } ScrollViewReader { reader in List(selection: $selection) { Section(content: { @@ -843,15 +873,7 @@ struct AnimatingCellHeight: AnimatableModifier { } } - if viewModel.showLoadingBar == .redacted || viewModel.showLoadingBar == .simple { - VStack { - ProgressView() - } - .frame(minHeight: 400) - .frame(maxWidth: .infinity) - .padding() - .listRowSeparator(.hidden, edges: .all) - } else if viewModel.isEmptyingTrash { + if viewModel.isEmptyingTrash { VStack { Text("Emptying trash") ProgressView() diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 29ec1c998..f5356bb90 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -219,7 +219,7 @@ enum LoadingBarStyle { func loadItems(dataService: DataService, isRefresh: Bool, forceRemote: Bool = false, loadingBarStyle: LoadingBarStyle? = nil) async { isLoading = true - showLoadingBar = isRefresh ? loadingBarStyle ?? .redacted : .none + showLoadingBar = .simple // isRefresh ? loadingBarStyle ?? .redacted : .none if let filterState = filterState { await fetcher.loadItems( diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionView.swift index 4b6fd3fdd..845db8f8e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionView.swift @@ -23,72 +23,18 @@ var body: some View { Group { Form { - if language.key == "en" { - if viewModel.waitingForRealisticVoices { - HStack { - Text(LocalText.texttospeechBetaSignupInProcess) - Spacer() - ProgressView() + if showLanguageChanger { + Section("Language") { + NavigationLink(destination: TextToSpeechLanguageView().navigationTitle("Language")) { + Text(audioController.currentVoiceLanguage.name) } - } else { - Toggle("Use Ultra Realistic Voices", isOn: $viewModel.realisticVoicesToggle) - .accentColor(Color.green) - } - - if !viewModel.waitingForRealisticVoices, !audioController.ultraRealisticFeatureKey.isEmpty { - Text(LocalText.texttospeechBetaRealisticVoiceLimit) - .multilineTextAlignment(.leading) - } else if audioController.ultraRealisticFeatureRequested { - Text(LocalText.texttospeechBetaRequestReceived) - .multilineTextAlignment(.leading) - } else { - Text(LocalText.texttospeechBetaWaitlist) - .multilineTextAlignment(.leading) } } - - if viewModel.realisticVoicesToggle, !audioController.ultraRealisticFeatureKey.isEmpty { - if showLanguageChanger { - Section("Language") { - NavigationLink(destination: TextToSpeechLanguageView().navigationTitle("Language")) { - Text(audioController.currentVoiceLanguage.name) - } - } - } - ultraRealisticVoices - } else { - if showLanguageChanger { - Section("Language") { - NavigationLink(destination: TextToSpeechLanguageView().navigationTitle("Language")) { - Text(audioController.currentVoiceLanguage.name) - } - } - } - standardVoices - } + standardVoices } } .navigationTitle("Choose a Voice") - .onAppear { - // swiftlint:disable:next line_length - viewModel.realisticVoicesToggle = (audioController.useUltraRealisticVoices && !audioController.ultraRealisticFeatureKey.isEmpty) - } - .onChange(of: viewModel.realisticVoicesToggle) { value in - if value, audioController.ultraRealisticFeatureKey.isEmpty { - // User wants to sign up - viewModel.waitingForRealisticVoices = true - Task { - await viewModel.requestUltraRealisticFeatureAccess( - dataService: self.dataService, - audioController: audioController - ) - } - } else if value, !audioController.ultraRealisticFeatureKey.isEmpty { - audioController.useUltraRealisticVoices = true - } else if !value { - audioController.useUltraRealisticVoices = false - } - }.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in dismiss() } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionViewModel.swift index 7595513bb..0e988d6a2 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/TextToSpeechVoiceSelectionViewModel.swift @@ -15,47 +15,8 @@ @MainActor final class TextToSpeechVoiceSelectionViewModel: ObservableObject { @Published var playbackSample: String? - @Published var realisticVoicesToggle: Bool = false - @Published var waitingForRealisticVoices: Bool = false @Published var showSnackbar: Bool = false var snackbarMessage: String? - - func requestUltraRealisticFeatureAccess( - dataService: DataService, - audioController: AudioController - ) async { - do { - let feature = try await dataService.optInFeature(name: "ultra-realistic-voice") - DispatchQueue.main.async { - if let feature = feature { - audioController.useUltraRealisticVoices = true - audioController.ultraRealisticFeatureRequested = true - audioController.ultraRealisticFeatureKey = feature.granted ? feature.token : "" - if feature.granted, !Voices.isUltraRealisticVoice(audioController.currentVoice) { - // Attempt to set to an ultra voice - if let voice = Voices.UltraPairs.first { - audioController.currentVoice = voice.firstKey - } - } - self.realisticVoicesToggle = true - } else { - audioController.useUltraRealisticVoices = false - audioController.ultraRealisticFeatureKey = "" - audioController.ultraRealisticFeatureRequested = false - self.realisticVoicesToggle = false - } - self.waitingForRealisticVoices = false - } - } catch { - print("ERROR OPTING INTO FEATURE", error) - audioController.useUltraRealisticVoices = false - realisticVoicesToggle = false - waitingForRealisticVoices = false - audioController.ultraRealisticFeatureRequested = false - snackbarMessage = "Error signing up for beta. Please try again." - showSnackbar = true - } - } } #endif diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift b/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift index c639057d9..c3f66937e 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/Authenticator.swift @@ -44,6 +44,7 @@ public final class Authenticator: ObservableObject { isLoggedIn = false } + @MainActor public func logout(dataService: DataService, isAccountDeletion: Bool = false) { dataService.resetLocalStorage() diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift b/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift index 89324e140..651e5d66e 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/GoogleAuth.swift @@ -10,6 +10,7 @@ public enum GoogleAuthResponse { } extension Authenticator { + @MainActor public func handleGoogleAuth(presentingVC: PlatformViewController?) async -> GoogleAuthResponse { let idToken = await withCheckedContinuation { continuation in googleSignIn(presenting: presentingVC) { continuation.resume(returning: $0) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/OptIntoFeature.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/OptIntoFeature.swift index 35f427da0..68ce7fa65 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/OptIntoFeature.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/OptIntoFeature.swift @@ -50,6 +50,7 @@ public extension DataService { case let .error(errorCode: errorCode): if errorCode == .ineligible { continuation.resume(throwing: IneligibleError.message(messageText: "You are not eligible for this feature.")) + return } continuation.resume(throwing: BasicError.message(messageText: errorCode.rawValue)) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift index c386b991d..38223fe41 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift @@ -82,7 +82,6 @@ public struct ViewerInternal { do { try context.save() - EventTracker.registerUser(userID: userID) logger.debug("Viewer saved succesfully") } catch { context.rollback() @@ -90,5 +89,8 @@ public struct ViewerInternal { throw error } } + DispatchQueue.main.async { + EventTracker.registerUser(userID: userID) + } } } diff --git a/apple/OmnivoreKit/Sources/Utils/EventTracking/EventTracker.swift b/apple/OmnivoreKit/Sources/Utils/EventTracking/EventTracker.swift index 245cd8094..57439b61c 100644 --- a/apple/OmnivoreKit/Sources/Utils/EventTracking/EventTracker.swift +++ b/apple/OmnivoreKit/Sources/Utils/EventTracking/EventTracker.swift @@ -4,6 +4,7 @@ import Foundation import PostHog #endif +@MainActor public enum EventTracker { #if os(iOS) public static var posthog: PHGPostHog? = { diff --git a/apple/Sources/AppDelegate.swift b/apple/Sources/AppDelegate.swift index 3082ddc91..0a9f3941b 100644 --- a/apple/Sources/AppDelegate.swift +++ b/apple/Sources/AppDelegate.swift @@ -47,9 +47,11 @@ private let logger = Logger(subsystem: "app.omnivore", category: "app-delegate") Intercom.setApiKey(intercomKeys.apiKey, forAppId: intercomKeys.appID) if let userId = UserDefaults.standard.string(forKey: Keys.userIdKey) { - Intercom.registerUser(withUserId: userId) + let userAttributes = ICMUserAttributes() + userAttributes.userId = userId + Intercom.loginUser(with: userAttributes) } else { - Intercom.registerUnidentifiedUser() + Intercom.loginUnidentifiedUser() } } diff --git a/apple/Sources/MainApp.swift b/apple/Sources/MainApp.swift index 8f7151a84..535e3157a 100644 --- a/apple/Sources/MainApp.swift +++ b/apple/Sources/MainApp.swift @@ -29,10 +29,14 @@ struct MainApp: App { WindowGroup { RootView( intercomProvider: AppKeys.sharedInstance?.intercom != nil ? IntercomProvider( - registerIntercomUser: { Intercom.registerUser(withUserId: $0) }, + registerIntercomUser: { userId in + let userAttributes = ICMUserAttributes() + userAttributes.userId = userId + Intercom.loginUser(with: userAttributes) + }, setIntercomUserHash: { Intercom.setUserHash($0) }, unregisterIntercomUser: Intercom.logout, - showIntercomMessenger: Intercom.presentMessenger + showIntercomMessenger: Intercom.present ) : nil ) }