diff --git a/BookPlayer.xcodeproj/project.pbxproj b/BookPlayer.xcodeproj/project.pbxproj index 95e0c771..b38908de 100644 --- a/BookPlayer.xcodeproj/project.pbxproj +++ b/BookPlayer.xcodeproj/project.pbxproj @@ -552,6 +552,7 @@ 63B760FC2C33B77F00AA98C7 /* SupportProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B760FB2C33B77F00AA98C7 /* SupportProfileView.swift */; }; 63B9149E2EE47951000918D8 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B9149C2EE47951000918D8 /* AppEnvironment.swift */; }; 63B9149F2EE47951000918D8 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B9149C2EE47951000918D8 /* AppEnvironment.swift */; }; + 63B914A12EE49079000918D8 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B914A02EE49079000918D8 /* NetworkMonitor.swift */; }; 63B9EFA82E9A4230002361A0 /* PlayerControlsBoostVolumeSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B9EFA72E9A4230002361A0 /* PlayerControlsBoostVolumeSectionView.swift */; }; 63B9EFAA2E9A4644002361A0 /* PlayerControlsSpeedSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63B9EFA92E9A4644002361A0 /* PlayerControlsSpeedSectionView.swift */; }; 63C1A8B02B0915EE00C4B418 /* WidgetUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 418445C2258AE11E0072DD13 /* WidgetUtils.swift */; }; @@ -1459,6 +1460,7 @@ 63B760F82C32738E00AA98C7 /* StoryAccountSubscriptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryAccountSubscriptionService.swift; sourceTree = ""; }; 63B760FB2C33B77F00AA98C7 /* SupportProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportProfileView.swift; sourceTree = ""; }; 63B9149C2EE47951000918D8 /* AppEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = ""; }; + 63B914A02EE49079000918D8 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; 63B9EFA72E9A4230002361A0 /* PlayerControlsBoostVolumeSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsBoostVolumeSectionView.swift; sourceTree = ""; }; 63B9EFA92E9A4644002361A0 /* PlayerControlsSpeedSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsSpeedSectionView.swift; sourceTree = ""; }; 63C48C792E3DAC9D005FBB96 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; @@ -3290,6 +3292,7 @@ 8ADD46212CF29A02002E9C50 /* TextFieldFocused.swift */, 63C48C832E3E66D1005FBB96 /* PurchasesManager.swift */, 63344BFF2EA708F800B90DF7 /* BackupDatabaseOperation.swift */, + 63B914A02EE49079000918D8 /* NetworkMonitor.swift */, ); path = Utils; sourceTree = ""; @@ -4212,6 +4215,7 @@ 63C48C842E3E66D1005FBB96 /* PurchasesManager.swift in Sources */, 8ADD46222CF29A02002E9C50 /* TextFieldFocused.swift in Sources */, 63C48C7E2E3DB24A005FBB96 /* LoginDisclaimerSectionView.swift in Sources */, + 63B914A12EE49079000918D8 /* NetworkMonitor.swift in Sources */, 9F22DE39288CBE6A00056FCD /* FormButton.swift in Sources */, 63B4A3BE2E9187B800784A22 /* ItemListView+ConfirmationDialogs.swift in Sources */, 4151A6A826E48C3400E49DBE /* Storyboarded.swift in Sources */, diff --git a/BookPlayer/Profile/Profile/QueuedSyncTasksView.swift b/BookPlayer/Profile/Profile/QueuedSyncTasksView.swift index b4b857ee..6c7992b9 100644 --- a/BookPlayer/Profile/Profile/QueuedSyncTasksView.swift +++ b/BookPlayer/Profile/Profile/QueuedSyncTasksView.swift @@ -16,6 +16,7 @@ struct QueuedSyncTasksView: View { @State private var queuedJobs = [SyncTaskReference]() @State private var jobsCount = 0 @State private var showInfoAlert = false + @State private var networkMonitor = NetworkMonitor() @Environment(\.syncService) private var syncService @EnvironmentObject private var theme: ThemeViewModel @@ -30,7 +31,7 @@ struct QueuedSyncTasksView: View { ) } } header: { - if !allowsCellularData { + if !allowsCellularData && !networkMonitor.isConnectedViaWiFi { HStack { Spacer() Image(systemName: "wifi") diff --git a/BookPlayer/Settings/Sections/DebugFileTransferable.swift b/BookPlayer/Settings/Sections/DebugFileTransferable.swift index 0fa67a91..2bd8c8c3 100644 --- a/BookPlayer/Settings/Sections/DebugFileTransferable.swift +++ b/BookPlayer/Settings/Sections/DebugFileTransferable.swift @@ -17,9 +17,14 @@ struct DebugFileTransferable: Transferable { var remoteIdentifiers: [String]? var syncJobsInformation: String? + var syncError: String? if syncService.isActive { - remoteIdentifiers = try await syncService.fetchSyncedIdentifiers() + do { + remoteIdentifiers = try await syncService.fetchSyncedIdentifiers() + } catch { + syncError = "Error fetching remote identifiers: \(error.localizedDescription)" + } syncJobsInformation = await file.getSyncOperationsInformation() } @@ -43,6 +48,10 @@ struct DebugFileTransferable: Transferable { libraryRepresentation += syncJobsInformation } + if let syncError { + libraryRepresentation += "\n\n⚠️ Sync Error:\n\(syncError)\n" + } + return libraryRepresentation.data(using: .utf8)! } .suggestedFileName { _ in diff --git a/BookPlayer/Utils/NetworkMonitor.swift b/BookPlayer/Utils/NetworkMonitor.swift new file mode 100644 index 00000000..3c64b2fe --- /dev/null +++ b/BookPlayer/Utils/NetworkMonitor.swift @@ -0,0 +1,59 @@ +// +// NetworkMonitor.swift +// BookPlayer +// +// Created by BookPlayer on 6/12/24. +// Copyright © 2024 BookPlayer LLC. All rights reserved. +// + +import Foundation +import Network +import SwiftUI + +/// Monitors network connectivity and provides information about the current connection type +@Observable +class NetworkMonitor { + private let monitor = NWPathMonitor() + private let queue = DispatchQueue(label: "NetworkMonitor") + + var isConnected: Bool = false + var isConnectedViaWiFi: Bool = false + var isConnectedViaCellular: Bool = false + + init() { + startMonitoring() + } + + deinit { + stopMonitoring() + } + + func startMonitoring() { + monitor.pathUpdateHandler = { [weak self] path in + DispatchQueue.main.async { + self?.isConnected = path.status == .satisfied + self?.isConnectedViaWiFi = path.usesInterfaceType(.wifi) + self?.isConnectedViaCellular = path.usesInterfaceType(.cellular) + } + } + monitor.start(queue: queue) + } + + func stopMonitoring() { + monitor.cancel() + } +} + +// MARK: - Environment Key + +private struct NetworkMonitorKey: EnvironmentKey { + static let defaultValue = NetworkMonitor() +} + +extension EnvironmentValues { + var networkMonitor: NetworkMonitor { + get { self[NetworkMonitorKey.self] } + set { self[NetworkMonitorKey.self] = newValue } + } +} +