diff --git a/Core/Core/Data/Persistence/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/Core/Core/Data/Persistence/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index ed12a0c8a..3dc8a8e03 100644 --- a/Core/Core/Data/Persistence/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/Core/Core/Data/Persistence/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -1,10 +1,9 @@ - + - diff --git a/Core/Core/Data/Persistence/CorePersistenceProtocol.swift b/Core/Core/Data/Persistence/CorePersistenceProtocol.swift index e7256850c..f250fc49c 100644 --- a/Core/Core/Data/Persistence/CorePersistenceProtocol.swift +++ b/Core/Core/Data/Persistence/CorePersistenceProtocol.swift @@ -15,7 +15,7 @@ public protocol CorePersistenceProtocol { func getNextBlockForDownloading() -> DownloadData? func getDownloadsForCourse(_ courseId: String) -> [DownloadData] func downloadData(by blockId: String) -> DownloadData? - func updateDownloadState(id: String, state: DownloadState, path: String?, resumeData: Data?) + func updateDownloadState(id: String, state: DownloadState, resumeData: Data?) func deleteDownloadData(id: String) throws func saveDownloadData(data: DownloadData) } diff --git a/Core/Core/Network/DownloadManager.swift b/Core/Core/Network/DownloadManager.swift index 4b8ed6df8..0fdfba105 100644 --- a/Core/Core/Network/DownloadManager.swift +++ b/Core/Core/Network/DownloadManager.swift @@ -24,7 +24,6 @@ public struct DownloadData { public let id: String public let courseId: String public let url: String - public let path: String? public let fileName: String public let progress: Double public let resumeData: Data? @@ -35,7 +34,6 @@ public struct DownloadData { id: String, courseId: String, url: String, - path: String?, fileName: String, progress: Double, resumeData: Data?, @@ -45,7 +43,6 @@ public struct DownloadData { self.id = id self.courseId = courseId self.url = url - self.path = path self.fileName = fileName self.progress = progress self.resumeData = resumeData @@ -148,28 +145,25 @@ public class DownloadManager: DownloadManagerProtocol { persistence.updateDownloadState( id: download.id, state: .inProgress, - path: nil, resumeData: download.resumeData ) self.isDownloadingInProgress = true - let fileName = url.lastPathComponent if let resumeData = download.resumeData { downloadRequest = AF.download(resumingWith: resumeData) } else { downloadRequest = AF.download(url) } -// downloadRequest?.downloadProgress { prog in -// let completed = Double(prog.fractionCompleted * 100) -// print(">>>>> Downloading", download.url, completed, "%") -// } + downloadRequest?.downloadProgress { prog in + let completed = Double(prog.fractionCompleted * 100) + print(">>>>> Downloading", download.url, completed, "%") + } downloadRequest?.responseData(completionHandler: { [weak self] data in guard let self else { return } if let data = data.value, let url = self.videosFolderUrl() { - let fileUrl = self.saveFile(fileName: fileName, data: data, folderURL: url) + self.saveFile(fileName: download.fileName, data: data, folderURL: url) self.persistence.updateDownloadState( id: download.id, state: .finished, - path: fileUrl?.absoluteString, resumeData: nil ) try? self.newDownload() @@ -188,7 +182,6 @@ public class DownloadManager: DownloadManagerProtocol { self.persistence.updateDownloadState( id: currentDownload.id, state: .paused, - path: nil, resumeData: resumeData ) }) @@ -196,14 +189,13 @@ public class DownloadManager: DownloadManagerProtocol { public func deleteFile(blocks: [CourseBlock]) { for block in blocks { - let downloadData = persistence.downloadData(by: block.id) - guard let path = persistence.downloadData(by: block.id)?.path, - let fileUrl = URL(string: path) else { return } - do { + print(">>>>>", block.displayName, block.id) try persistence.deleteDownloadData(id: block.id) - try FileManager.default.removeItem(at: fileUrl) - print("File deleted successfully") + if let fileUrl = fileUrl(for: block.id) { + try FileManager.default.removeItem(at: fileUrl) + print("File deleted successfully") + } } catch { print("Error deleting file: \(error.localizedDescription)") } @@ -213,7 +205,7 @@ public class DownloadManager: DownloadManagerProtocol { public func deleteAllFiles() { let downloadData = persistence.getAllDownloadData() downloadData.forEach { - if let path = $0.path, let fileURL = URL(string: path) { + if let fileURL = fileUrl(for: $0.id) { do { try FileManager.default.removeItem(at: fileURL) } catch { @@ -227,8 +219,9 @@ public class DownloadManager: DownloadManagerProtocol { guard let data = persistence.downloadData(by: blockId), data.url.count > 0, data.state == .finished else { return nil } - - return URL(string: data.path ?? "") + let path = videosFolderUrl() + let fileName = data.fileName + return path?.appendingPathComponent(fileName) } private func videosFolderUrl() -> URL? { @@ -252,15 +245,13 @@ public class DownloadManager: DownloadManagerProtocol { } } - private func saveFile(fileName: String, data: Data, folderURL: URL) -> URL? { + private func saveFile(fileName: String, data: Data, folderURL: URL) { let fileURL = folderURL.appendingPathComponent(fileName) do { try data.write(to: fileURL) - return fileURL } catch { print("SaveFile Error", error.localizedDescription) } - return nil } } diff --git a/Core/Core/View/Base/WebBrowser.swift b/Core/Core/View/Base/WebBrowser.swift index 3c0ae6ec4..ffea4335f 100644 --- a/Core/Core/View/Base/WebBrowser.swift +++ b/Core/Core/View/Base/WebBrowser.swift @@ -22,28 +22,28 @@ public struct WebBrowser: View { public var body: some View { ZStack(alignment: .top) { - + CoreAssets.background.swiftUIColor.ignoresSafeArea() // MARK: - Page name VStack(alignment: .center) { + NavigationBar(title: pageTitle, + leftButtonAction: { presentationMode.wrappedValue.dismiss() }) + // MARK: - Page Body VStack { ZStack(alignment: .top) { - NavigationView { +// NavigationView { WebView( viewModel: .init(url: url, baseURL: ""), isLoading: $isShowProgress, refreshCookies: {} ) - .navigationBarTitle(Text("")) // Needed for hide navBar on ios 14, 15 - .navigationBarHidden(true) - .ignoresSafeArea() - } - } + +// } + }.navigationBarTitle(Text("")) // Needed for hide navBar on ios 14, 15 + .navigationBarHidden(true) + .ignoresSafeArea() } } - .navigationBarHidden(false) - .navigationBarBackButtonHidden(false) - .navigationTitle(pageTitle) } } } diff --git a/Course/Course/Presentation/Handouts/HandoutsUpdatesDetailView.swift b/Course/Course/Presentation/Handouts/HandoutsUpdatesDetailView.swift index eaf467484..efc4b2e3d 100644 --- a/Course/Course/Presentation/Handouts/HandoutsUpdatesDetailView.swift +++ b/Course/Course/Presentation/Handouts/HandoutsUpdatesDetailView.swift @@ -68,6 +68,8 @@ public struct HandoutsUpdatesDetailView: View { public var body: some View { ZStack(alignment: .top) { + Theme.Colors.background + .ignoresSafeArea() GeometryReader { reader in // MARK: - Page Body @@ -121,10 +123,10 @@ public struct HandoutsUpdatesDetailView: View { } Spacer(minLength: 84) - .background( - Theme.Colors.background - .ignoresSafeArea() - ) +// .background( +// Theme.Colors.background +// .ignoresSafeArea() +// ) } } diff --git a/Course/Course/Presentation/Handouts/HandoutsView.swift b/Course/Course/Presentation/Handouts/HandoutsView.swift index 81cd06f6a..d6563b46f 100644 --- a/Course/Course/Presentation/Handouts/HandoutsView.swift +++ b/Course/Course/Presentation/Handouts/HandoutsView.swift @@ -12,7 +12,7 @@ struct HandoutsView: View { private let courseID: String - @ObservedObject + @StateObject private var viewModel: HandoutsViewModel public init( @@ -20,7 +20,8 @@ struct HandoutsView: View { viewModel: HandoutsViewModel ) { self.courseID = courseID - self.viewModel = viewModel +// self.viewModel = viewModel + self._viewModel = StateObject(wrappedValue: { viewModel }()) } public var body: some View { diff --git a/Course/Course/Presentation/Outline/CourseOutlineView.swift b/Course/Course/Presentation/Outline/CourseOutlineView.swift index f58b2438e..19a6fa413 100644 --- a/Course/Course/Presentation/Outline/CourseOutlineView.swift +++ b/Course/Course/Presentation/Outline/CourseOutlineView.swift @@ -71,6 +71,7 @@ public struct CourseOutlineView: View { ) .frame(width: 141) .padding(.top, 8) + .fullScreenCover( isPresented: $openCertificateView, content: { diff --git a/Course/Course/Presentation/Unit/CourseUnitView.swift b/Course/Course/Presentation/Unit/CourseUnitView.swift index 1d9637440..e9afacbc1 100644 --- a/Course/Course/Presentation/Unit/CourseUnitView.swift +++ b/Course/Course/Presentation/Unit/CourseUnitView.swift @@ -45,7 +45,6 @@ public struct CourseUnitView: View { GeometryReader { reader in VStack(spacing: 0) { VStack {}.frame(height: 100) - if viewModel.connectivity.isInternetAvaliable { LazyVStack(spacing: 0) { let data = Array(viewModel.verticals[viewModel.verticalIndex].childs.enumerated()) ForEach(data, id: \.offset) { index, block in @@ -54,40 +53,58 @@ public struct CourseUnitView: View { switch LessonType.from(block) { // MARK: YouTube case let .youtube(url, blockID): - YouTubeView( - name: block.displayName, - url: url, - courseID: viewModel.courseID, - blockID: blockID, - playerStateSubject: playerStateSubject, - languages: block.subtitles ?? [], - isOnScreen: index == viewModel.index - ).frameLimit() - Spacer(minLength: 100) + if viewModel.connectivity.isInternetAvaliable { + YouTubeView( + name: block.displayName, + url: url, + courseID: viewModel.courseID, + blockID: blockID, + playerStateSubject: playerStateSubject, + languages: block.subtitles ?? [], + isOnScreen: index == viewModel.index + ).frameLimit() + Spacer(minLength: 100) + } else { + NoInternetView(playerStateSubject: playerStateSubject) + } // MARK: Encoded Video case let .video(encodedUrl, blockID): - EncodedVideoView( - name: block.displayName, - url: viewModel.urlForVideoFileOrFallback( - blockId: blockID, - url: encodedUrl - ), - courseID: viewModel.courseID, - blockID: blockID, - playerStateSubject: playerStateSubject, - languages: block.subtitles ?? [], - isOnScreen: index == viewModel.index - ).frameLimit() - Spacer(minLength: 100) + let url = viewModel.urlForVideoFileOrFallback( + blockId: blockID, + url: encodedUrl + ) + if viewModel.connectivity.isInternetAvaliable || url?.isFileURL == true { + EncodedVideoView( + name: block.displayName, + url: url, + courseID: viewModel.courseID, + blockID: blockID, + playerStateSubject: playerStateSubject, + languages: block.subtitles ?? [], + isOnScreen: index == viewModel.index + ).frameLimit() + Spacer(minLength: 100) + } else { + NoInternetView(playerStateSubject: playerStateSubject) + } // MARK: Web case .web(let url): - WebView(url: url, viewModel: viewModel) + if viewModel.connectivity.isInternetAvaliable { + WebView(url: url, viewModel: viewModel) + } else { + NoInternetView(playerStateSubject: playerStateSubject) + } // MARK: Unknown case .unknown(let url): + if viewModel.connectivity.isInternetAvaliable { UnknownView(url: url, viewModel: viewModel) Spacer() + } else { + NoInternetView(playerStateSubject: playerStateSubject) + } // MARK: Discussion case let .discussion(blockID, blockKey, title): + if viewModel.connectivity.isInternetAvaliable { VStack { if showDiscussion { DiscussionView( @@ -104,6 +121,9 @@ public struct CourseUnitView: View { } } }.frameLimit() + } else { + NoInternetView(playerStateSubject: playerStateSubject) + } } } else { EmptyView() @@ -126,21 +146,7 @@ public struct CourseUnitView: View { } }) - } else { - - // MARK: No internet view - VStack(spacing: 28) { - Image(systemName: "wifi").resizable() - .scaledToFit() - .frame(width: 100) - Text(CourseLocalization.Error.noInternet) - .multilineTextAlignment(.center) - .padding(.horizontal, 20) - UnitButtonView(type: .reload, action: { - playerStateSubject.send(VideoPlayerState.kill) - }).frame(width: 100) - }.frame(maxWidth: .infinity, maxHeight: .infinity) - } + }.frame(maxWidth: .infinity) .clipped() @@ -334,3 +340,22 @@ struct CourseUnitView_Previews: PreviewProvider { } //swiftlint:enable all #endif + +struct NoInternetView: View { + + let playerStateSubject: CurrentValueSubject + + var body: some View { + VStack(spacing: 28) { + Image(systemName: "wifi").resizable() + .scaledToFit() + .frame(width: 100) + Text(CourseLocalization.Error.noInternet) + .multilineTextAlignment(.center) + .padding(.horizontal, 20) + UnitButtonView(type: .reload, action: { + playerStateSubject.send(VideoPlayerState.kill) + }).frame(width: 100) + }.frame(maxWidth: .infinity, maxHeight: .infinity) + } +} diff --git a/OpenEdX/Data/CorePersistence.swift b/OpenEdX/Data/CorePersistence.swift index 38605c97b..75b136eb5 100644 --- a/OpenEdX/Data/CorePersistence.swift +++ b/OpenEdX/Data/CorePersistence.swift @@ -49,7 +49,6 @@ public class CorePersistence: CorePersistenceProtocol { id: $0.id ?? "", courseId: $0.courseId ?? "", url: $0.url ?? "", - path: $0.path, fileName: $0.fileName ?? "", progress: $0.progress, resumeData: $0.resumeData, @@ -65,7 +64,9 @@ public class CorePersistence: CorePersistenceProtocol { request.predicate = NSPredicate(format: "id = %@", block.id) guard (try? context.fetch(request).first) == nil else { continue } guard let url = block.videoUrl, - let fileName = URL(string: url)?.lastPathComponent else { continue } + let fileExtension = URL(string: url)?.pathExtension + else { continue } + let fileName = "\(block.id).\(fileExtension)" context.performAndWait { let newDownloadData = CDDownloadData(context: context) context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump @@ -90,7 +91,6 @@ public class CorePersistence: CorePersistenceProtocol { id: data.id ?? "", courseId: data.courseId ?? "", url: data.url ?? "", - path: data.path, fileName: data.fileName ?? "", progress: data.progress, resumeData: data.resumeData, @@ -108,7 +108,6 @@ public class CorePersistence: CorePersistenceProtocol { id: $0.id ?? "", courseId: $0.courseId ?? "", url: $0.url ?? "", - path: $0.path, fileName: $0.fileName ?? "", progress: $0.progress, resumeData: $0.resumeData, @@ -126,7 +125,6 @@ public class CorePersistence: CorePersistenceProtocol { id: downloadData.id ?? "", courseId: downloadData.courseId ?? "", url: downloadData.url ?? "", - path: downloadData.path, fileName: downloadData.fileName ?? "", progress: downloadData.progress, resumeData: downloadData.resumeData, @@ -135,13 +133,12 @@ public class CorePersistence: CorePersistenceProtocol { ) } - public func updateDownloadState(id: String, state: DownloadState, path: String?, resumeData: Data?) { + public func updateDownloadState(id: String, state: DownloadState, resumeData: Data?) { context.performAndWait { let request = CDDownloadData.fetchRequest() request.predicate = NSPredicate(format: "id = %@", id) guard let downloadData = try? context.fetch(request).first else { return } downloadData.state = state.rawValue - downloadData.path = path downloadData.resumeData = resumeData do { try context.save() diff --git a/OpenEdX/Environment.swift b/OpenEdX/Environment.swift index e89c0bb88..53b45124b 100644 --- a/OpenEdX/Environment.swift +++ b/OpenEdX/Environment.swift @@ -28,9 +28,9 @@ class BuildConfiguration { var baseURL: String { switch environment { case .debugDev, .releaseDev: - return "https://example-dev.com" + return "https://lms-rg-app-ios-dev.raccoongang.com" case .debugStage, .releaseStage: - return "https://example-stage.com" + return "https://lms-rg-app-ios-stage.raccoongang.com" case .debugProd, .releaseProd: return "https://example.com" } @@ -39,9 +39,9 @@ class BuildConfiguration { var clientId: String { switch environment { case .debugDev, .releaseDev: - return "DEV_CLIENT_ID" + return "T7od4OFlYni7hTMnepfQuF1XUoqsESjEClltL40T" case .debugStage, .releaseStage: - return "STAGE_CLIENT_ID" + return "kHDbLaYlc1lpY1obmyAAEp9dX9qPqeDrBiVGQFIy" case .debugProd, .releaseProd: return "PROD_CLIENT_ID" } @@ -50,23 +50,23 @@ class BuildConfiguration { var firebaseOptions: FirebaseOptions { switch environment { case .debugDev, .releaseDev: - let firebaseOptions = FirebaseOptions(googleAppID: "", - gcmSenderID: "") - firebaseOptions.apiKey = "" - firebaseOptions.projectID = "" - firebaseOptions.bundleID = "" - firebaseOptions.clientID = "" - firebaseOptions.storageBucket = "" + let firebaseOptions = FirebaseOptions(googleAppID: "1:60657986297:ios:bf0254060e5c3779581028", + gcmSenderID: "60657986297") + firebaseOptions.apiKey = "AIzaSyCKj--Dfkq08r4P1d2q7Tz36gu9SQ9Apbs" + firebaseOptions.projectID = "openedxmobile-dev" + firebaseOptions.bundleID = "com.raccoongang.NewEdX.dev" + firebaseOptions.clientID = "60657986297-t6utefrq6tt0tscr85igqh0ni9gtis8l.apps.googleusercontent.com" + firebaseOptions.storageBucket = "openedxmobile-dev.appspot.com" return firebaseOptions case .debugStage, .releaseStage: - let firebaseOptions = FirebaseOptions(googleAppID: "", - gcmSenderID: "") - firebaseOptions.apiKey = "" - firebaseOptions.projectID = "" - firebaseOptions.bundleID = "" - firebaseOptions.clientID = "" - firebaseOptions.storageBucket = "" + let firebaseOptions = FirebaseOptions(googleAppID: "1:156114692773:ios:8058bca851a8bc7c187b4c", + gcmSenderID: "156114692773") + firebaseOptions.apiKey = "AIzaSyCKAIXDLM7pnX43P_viTsfgbxrLBOaJwGo" + firebaseOptions.projectID = "openedxmobile-stage" + firebaseOptions.bundleID = "com.raccoongang.NewEdX.stage" + firebaseOptions.clientID = "156114692773-r5pgdcdjqq7sup75fdla4lk3q3kjc6m8.apps.googleusercontent.com" + firebaseOptions.storageBucket = "openedxmobile-stage.appspot.com" return firebaseOptions case .debugProd, .releaseProd: diff --git a/Profile/Profile/Presentation/EditProfile/EditProfileView.swift b/Profile/Profile/Presentation/EditProfile/EditProfileView.swift index 3a1b79761..f0a439d48 100644 --- a/Profile/Profile/Presentation/EditProfile/EditProfileView.swift +++ b/Profile/Profile/Presentation/EditProfile/EditProfileView.swift @@ -199,9 +199,18 @@ public struct EditProfileView: View { } } .navigationBarHidden(false) - .navigationBarBackButtonHidden(false) + .navigationBarBackButtonHidden(true) .navigationTitle(ProfileLocalization.editProfile) .toolbar { + ToolbarItem(placement: .navigationBarLeading, content: { + Button(action: { + viewModel.backButtonTapped() + }, label: { + CoreAssets.arrowLeft.swiftUIImage + .renderingMode(.template) + .foregroundColor(Theme.Colors.accentColor) + }).opacity(viewModel.isChanged ? 1 : 0.3) + }) ToolbarItem(placement: .navigationBarTrailing, content: { Button(action: { if viewModel.isChanged { @@ -212,7 +221,8 @@ public struct EditProfileView: View { } }, label: { HStack(spacing: 2) { - CoreAssets.done.swiftUIImage + CoreAssets.done.swiftUIImage.renderingMode(.template) + .foregroundColor(Theme.Colors.accentColor) Text(CoreLocalization.done) .font(Theme.Fonts.labelLarge) .foregroundColor(Theme.Colors.accentColor)