From 465c66bf77a8b91ab3199d6c6c86a285189f2ab8 Mon Sep 17 00:00:00 2001 From: Ivan Stepanok Date: Fri, 15 Sep 2023 08:52:34 +0300 Subject: [PATCH 1/4] Depricate iOS 14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove isIOS14 in ViewExtension
 Change deployment target to iOS 15
 Add a new RefreshableScrollView for better pull to refresh animation on any iOS update loading logic in DiscoveryViewModel, ThreadViewModel, and PostsViewModel for a smooth refreshing --- .../Authorization.xcodeproj/project.pbxproj | 32 +- Core/Core.xcodeproj/project.pbxproj | 20 +- Core/Core/Extensions/ViewExtension.swift | 8 - .../View/Base/RefreshableScrollView.swift | 593 +++++++----------- .../Base/RefreshableScrollViewCompat.swift | 40 -- Course/Course.xcodeproj/project.pbxproj | 32 +- .../Details/CourseDetailsView.swift | 13 +- .../Outline/CourseOutlineView.swift | 11 +- Dashboard/Dashboard.xcodeproj/project.pbxproj | 32 +- .../Presentation/DashboardView.swift | 105 ++-- Discovery/Discovery.xcodeproj/project.pbxproj | 32 +- .../Presentation/DiscoveryView.swift | 92 +-- .../Presentation/DiscoveryViewModel.swift | 6 +- .../Discussion.xcodeproj/project.pbxproj | 32 +- .../Comments/Responses/ResponsesView.swift | 245 ++++---- .../Comments/Thread/ThreadView.swift | 12 +- .../Comments/Thread/ThreadViewModel.swift | 6 +- .../DiscussionTopicsView.swift | 19 +- .../Presentation/Posts/PostsView.swift | 20 +- .../Presentation/Posts/PostsViewModel.swift | 156 +++-- OpenEdX.xcodeproj/project.pbxproj | 12 +- Profile/Profile.xcodeproj/project.pbxproj | 32 +- .../Presentation/Profile/ProfileView.swift | 26 +- 23 files changed, 722 insertions(+), 854 deletions(-) delete mode 100644 Core/Core/View/Base/RefreshableScrollViewCompat.swift diff --git a/Authorization/Authorization.xcodeproj/project.pbxproj b/Authorization/Authorization.xcodeproj/project.pbxproj index b4c539d33..fba945920 100644 --- a/Authorization/Authorization.xcodeproj/project.pbxproj +++ b/Authorization/Authorization.xcodeproj/project.pbxproj @@ -583,7 +583,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -611,7 +611,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AuthorizationTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -694,7 +694,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -721,7 +721,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AuthorizationTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -739,7 +739,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AuthorizationTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -757,7 +757,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AuthorizationTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -775,7 +775,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AuthorizationTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -793,7 +793,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AuthorizationTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -811,7 +811,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AuthorizationTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -829,7 +829,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.AuthorizationTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -918,7 +918,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1011,7 +1011,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1109,7 +1109,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1202,7 +1202,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1358,7 +1358,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1393,7 +1393,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 680eb2cf4..a9463cdcc 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -32,7 +32,6 @@ 024D865E28F02C6B0077E0A0 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024D865D28F02C6B0077E0A0 /* WebView.swift */; }; 024FCD0028EF1CD300232339 /* WebBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024FCCFF28EF1CD300232339 /* WebBrowser.swift */; }; 02512FF0299533DF0024D438 /* CoreDataHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02512FEF299533DE0024D438 /* CoreDataHandlerProtocol.swift */; }; - 0251ED0C299D16BD00E70450 /* RefreshableScrollViewCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0251ED0B299D16BC00E70450 /* RefreshableScrollViewCompat.swift */; }; 0255D5582936283A004DBC1A /* UploadBodyEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0255D55729362839004DBC1A /* UploadBodyEncoding.swift */; }; 0259104A29C4A5B6004B5A55 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0259104929C4A5B6004B5A55 /* UserSettings.swift */; }; 025B36752A13B7D5001A640E /* UnitButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025B36742A13B7D5001A640E /* UnitButtonView.swift */; }; @@ -152,7 +151,6 @@ 024D865D28F02C6B0077E0A0 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; 024FCCFF28EF1CD300232339 /* WebBrowser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebBrowser.swift; sourceTree = ""; }; 02512FEF299533DE0024D438 /* CoreDataHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataHandlerProtocol.swift; sourceTree = ""; }; - 0251ED0B299D16BC00E70450 /* RefreshableScrollViewCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshableScrollViewCompat.swift; sourceTree = ""; }; 0255D55729362839004DBC1A /* UploadBodyEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadBodyEncoding.swift; sourceTree = ""; }; 0259104929C4A5B6004B5A55 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; 025B36742A13B7D5001A640E /* UnitButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitButtonView.swift; sourceTree = ""; }; @@ -521,7 +519,6 @@ 02F6EF3A28D9B8EC00835477 /* CourseCellView.swift */, 024FCCFF28EF1CD300232339 /* WebBrowser.swift */, 028CE96829858ECC00B6B1C3 /* FlexibleKeyboardInputView.swift */, - 0251ED0B299D16BC00E70450 /* RefreshableScrollViewCompat.swift */, 023A4DD3299E66BD006C0E48 /* OfflineSnackBarView.swift */, 024D865D28F02C6B0077E0A0 /* WebView.swift */, 02C2DC0729B63D6200F4445D /* WebViewHTML.swift */, @@ -802,7 +799,6 @@ 027BD3B42909475900392132 /* KeyboardState.swift in Sources */, 027BD3922907D88F00392132 /* Data_RegistrationFields.swift in Sources */, 07460FE3294B72D700F70538 /* Notification.swift in Sources */, - 0251ED0C299D16BD00E70450 /* RefreshableScrollViewCompat.swift in Sources */, 0727877F28D25B24002E9142 /* Alamofire+Error.swift in Sources */, 02A4833829B8A8F900D33F33 /* CoreDataModel.xcdatamodeld in Sources */, 0259104A29C4A5B6004B5A55 /* UserSettings.swift in Sources */, @@ -951,7 +947,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1064,7 +1060,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1302,7 +1298,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1395,7 +1391,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1493,7 +1489,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1586,7 +1582,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1742,7 +1738,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1777,7 +1773,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Core/Core/Extensions/ViewExtension.swift b/Core/Core/Extensions/ViewExtension.swift index 3beb78550..d6584cbcf 100644 --- a/Core/Core/Extensions/ViewExtension.swift +++ b/Core/Core/Extensions/ViewExtension.swift @@ -166,14 +166,6 @@ public extension View { ) } - var isIOS14: Bool { - if #available(iOS 15.0, *) { - return false - } else { - return true - } - } - func onFirstAppear(_ action: @escaping () -> Void) -> some View { modifier(FirstAppear(action: action)) } diff --git a/Core/Core/View/Base/RefreshableScrollView.swift b/Core/Core/View/Base/RefreshableScrollView.swift index 3a942d130..d72580b5e 100644 --- a/Core/Core/View/Base/RefreshableScrollView.swift +++ b/Core/Core/View/Base/RefreshableScrollView.swift @@ -6,418 +6,249 @@ // import SwiftUI +import Combine -// There are two type of positioning views - one that scrolls with the content, -// and one that stays fixed -private enum PositionType { - case fixed, moving -} - -// This struct is the currency of the Preferences, and has a type -// (fixed or moving) and the actual Y-axis value. -// It's Equatable because Swift requires it to be. -private struct Position: Equatable { - let type: PositionType - let y: CGFloat -} - -// This might seem weird, but it's necessary due to the funny nature of -// how Preferences work. We can't just store the last position and merge -// it with the next one - instead we have a queue of all the latest positions. -private struct PositionPreferenceKey: PreferenceKey { - typealias Value = [Position] - - static var defaultValue = [Position]() - - static func reduce(value: inout [Position], nextValue: () -> [Position]) { - value.append(contentsOf: nextValue()) - } -} - -private struct PositionIndicator: View { - let type: PositionType - - var body: some View { - GeometryReader { proxy in - // the View itself is an invisible Shape that fills as much as possible - Color.clear - // Compute the top Y position and emit it to the Preferences queue - .preference(key: PositionPreferenceKey.self, value: [Position(type: type, y: proxy.frame(in: .global).minY)]) - } - } -} - -// Callback that'll trigger once refreshing is done -public typealias RefreshComplete = () -> Void - -// The actual refresh action that's called once refreshing starts. It has the -// RefreshComplete callback to let the refresh action let the View know -// once it's done refreshing. -public typealias OnRefresh = (@escaping RefreshComplete) -> Void - -// The offset threshold. 68 is a good number, but you can play -// with it to your liking. -public let defaultRefreshThreshold: CGFloat = 68 - -// Tracks the state of the RefreshableScrollView - it's either: -// 1. waiting for a scroll to happen -// 2. has been primed by pulling down beyond THRESHOLD -// 3. is doing the refreshing. -public enum RefreshState { - case waiting, primed, loading -} - -// ViewBuilder for the custom progress View, that may render itself -// based on the current RefreshState. -public typealias RefreshProgressBuilder = (RefreshState) -> Progress - -// Default color of the rectangle behind the progress spinner -public let defaultLoadingViewBackgroundColor = Color(UIColor.clear) - -public struct RefreshableScrollView: View where Progress: View, Content: View { - let showsIndicators: Bool // if the ScrollView should show indicators - let shouldTriggerHapticFeedback: Bool // if key actions should trigger haptic feedback - let loadingViewBackgroundColor: Color - let threshold: CGFloat // what height do you have to pull down to trigger the refresh - let onRefresh: OnRefresh // the refreshing action - let progress: RefreshProgressBuilder // custom progress view - let content: () -> Content // the ScrollView content - @State private var offset: CGFloat = 0 - @State private var state = RefreshState.waiting // the current state - // Haptic Feedback - let finishedReloadingFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium) - let primedFeedbackGenerator = UIImpactFeedbackGenerator(style: .heavy) - - // We use a custom constructor to allow for usage of a @ViewBuilder for the content - public init(showsIndicators: Bool = true, - shouldTriggerHapticFeedback: Bool = false, - loadingViewBackgroundColor: Color = defaultLoadingViewBackgroundColor, - threshold: CGFloat = defaultRefreshThreshold, - onRefresh: @escaping OnRefresh, - @ViewBuilder progress: @escaping RefreshProgressBuilder, - @ViewBuilder content: @escaping () -> Content) { - self.showsIndicators = showsIndicators - self.shouldTriggerHapticFeedback = shouldTriggerHapticFeedback - self.loadingViewBackgroundColor = loadingViewBackgroundColor - self.threshold = threshold - self.onRefresh = onRefresh - self.progress = progress - self.content = content - } - - public var body: some View { - // The root view is a regular ScrollView - ScrollView(showsIndicators: showsIndicators) { - // The ZStack allows us to position the PositionIndicator, - // the content and the loading view, all on top of each other. - ZStack(alignment: .top) { - // The moving positioning indicator, that sits at the top - // of the ScrollView and scrolls down with the content - PositionIndicator(type: .moving) - .frame(height: 0) - - // Your ScrollView content. If we're loading, we want - // to keep it below the loading view, hence the alignmentGuide. - content() - .alignmentGuide(.top, computeValue: { _ in - (state == .loading) ? -threshold + max(0, offset) : 0 - }) - - // The loading view. It's offset to the top of the content unless we're loading. - ZStack { - Rectangle() - .foregroundColor(loadingViewBackgroundColor) - .frame(height: threshold) - progress(state) - }.offset(y: (state == .loading) ? -max(0, offset) : -threshold) - } - } - // Put a fixed PositionIndicator in the background so that we have - // a reference point to compute the scroll offset. - .background(PositionIndicator(type: .fixed)) - // Once the scrolling offset changes, we want to see if there should - // be a state change. - .onPreferenceChange(PositionPreferenceKey.self) { values in - DispatchQueue.main.async { - // Compute the offset between the moving and fixed PositionIndicators - let movingY = values.first { $0.type == .moving }?.y ?? 0 - let fixedY = values.first { $0.type == .fixed }?.y ?? 0 - offset = movingY - fixedY - if state != .loading { // If we're already loading, ignore everything - // Map the preference change action to the UI thread - // If the user pulled down below the threshold, prime the view - if offset > threshold && state == .waiting { - state = .primed - if shouldTriggerHapticFeedback { - self.primedFeedbackGenerator.impactOccurred() - } - - // If the view is primed and we've crossed the threshold again on the - // way back, trigger the refresh - } else if offset < threshold && state == .primed { - state = .loading - onRefresh { // trigger the refreshing callback - // once refreshing is done, smoothly move the loading view - // back to the offset position - withAnimation { - self.state = .waiting - } - if shouldTriggerHapticFeedback { - self.finishedReloadingFeedbackGenerator.impactOccurred() - } - } - } - } - } - } - } -} - -// Extension that uses default RefreshActivityIndicator so that you don't have to -// specify it every time. -public extension RefreshableScrollView where Progress == RefreshActivityIndicator { - init(showsIndicators: Bool = true, - loadingViewBackgroundColor: Color = defaultLoadingViewBackgroundColor, - threshold: CGFloat = defaultRefreshThreshold, - onRefresh: @escaping OnRefresh, - @ViewBuilder content: @escaping () -> Content) { - self.init(showsIndicators: showsIndicators, - loadingViewBackgroundColor: loadingViewBackgroundColor, - threshold: threshold, - onRefresh: onRefresh, - progress: { state in - RefreshActivityIndicator(isAnimating: state == .loading) { - $0.hidesWhenStopped = false - } - }, - content: content) - } -} - -// Wraps a UIActivityIndicatorView as a loading spinner that works on all SwiftUI versions. -public struct RefreshActivityIndicator: UIViewRepresentable { - public typealias UIView = UIActivityIndicatorView - public var isAnimating: Bool = true - public var configuration = { (indicator: UIView) in } - - public init(isAnimating: Bool, configuration: ((UIView) -> Void)? = nil) { - self.isAnimating = isAnimating - if let configuration = configuration { - self.configuration = configuration - } - } - - public func makeUIView(context: UIViewRepresentableContext) -> UIView { - UIView() - } - - public func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext) { - isAnimating ? uiView.startAnimating() : uiView.stopAnimating() - configuration(uiView) - } -} - -#if compiler(>=5.5) -// Allows using RefreshableScrollView with an async block. -@available(iOS 15.0, *) -public extension RefreshableScrollView { - init(showsIndicators: Bool = true, - loadingViewBackgroundColor: Color = defaultLoadingViewBackgroundColor, - threshold: CGFloat = defaultRefreshThreshold, - action: @escaping @Sendable () async -> Void, - @ViewBuilder progress: @escaping RefreshProgressBuilder, - @ViewBuilder content: @escaping () -> Content) { - self.init(showsIndicators: showsIndicators, - loadingViewBackgroundColor: loadingViewBackgroundColor, - threshold: threshold, - onRefresh: { refreshComplete in - Task { - await action() - refreshComplete() - } - }, - progress: progress, - content: content) - } -} -#endif - -public struct RefreshableCompat: ViewModifier where Progress: View { +public struct RefreshableScrollView: View { + @StateObject private var viewModel = RefreshableScrollViewModel() + + private let content: () -> Content private let showsIndicators: Bool - private let loadingViewBackgroundColor: Color - private let threshold: CGFloat - private let onRefresh: OnRefresh - private let progress: RefreshProgressBuilder - - public init(showsIndicators: Bool = true, - loadingViewBackgroundColor: Color = defaultLoadingViewBackgroundColor, - threshold: CGFloat = defaultRefreshThreshold, - onRefresh: @escaping OnRefresh, - @ViewBuilder progress: @escaping RefreshProgressBuilder) { + private let onRefresh: () async -> Void + + public init(showsIndicators: Bool = true, + @ViewBuilder content: @escaping () -> Content, + onRefresh: @escaping () async -> Void) { + self.content = content self.showsIndicators = showsIndicators - self.loadingViewBackgroundColor = loadingViewBackgroundColor - self.threshold = threshold self.onRefresh = onRefresh - self.progress = progress } - - public func body(content: Content) -> some View { - RefreshableScrollView(showsIndicators: showsIndicators, - loadingViewBackgroundColor: loadingViewBackgroundColor, - threshold: threshold, - onRefresh: onRefresh, - progress: progress) { - content + + private var topGeometryReader: some View { + GeometryReader { geometry in + Color.clear + .framePreferenceKey(geometry.frame(in: .global)) { frame in + self.viewModel.update(topFrame: frame) + } } } -} - -#if compiler(>=5.5) -@available(iOS 15.0, *) -public extension List { - @ViewBuilder func refreshableCompat(showsIndicators: Bool = true, - loadingViewBackgroundColor: - Color = defaultLoadingViewBackgroundColor, - threshold: CGFloat = defaultRefreshThreshold, - onRefresh: @escaping OnRefresh, - @ViewBuilder progress: - @escaping RefreshProgressBuilder) -> some View { - if #available(iOS 15.0, macOS 12.0, *) { - self.refreshable { - await withCheckedContinuation { cont in - onRefresh { - cont.resume() - } + + private var scrollViewGeometryReader: some View { + GeometryReader { geometry in + Color.clear + .framePreferenceKey(geometry.frame(in: .global)) { frame in + self.viewModel.update(scrollFrame: frame) + } + } + } + + public var body: some View { + VStack() { +// ProgressView() +// .progressViewStyle(.circular) +// .opacity(self.viewModel.isRefreshing ? 1 : 0) +// Activity + ActivityIndicator(size: self.$viewModel.progressViewHeight, isAnimating: self.$viewModel.isRefreshing) + .frame(width: self.viewModel.progressViewHeight, height: self.viewModel.progressViewHeight) + .background { self.topGeometryReader } + + ScrollView(.vertical, showsIndicators: self.showsIndicators) { + self.content() + .background { self.scrollViewGeometryReader } + } + } + .onChange(of: self.viewModel.isRefreshing) { isRefreshing in + guard isRefreshing else { return } + + Task { + await self.onRefresh() + + // In case the async method returns quickly. + // We want to keep it refreshing for some time so it is smooth. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.viewModel.endRefreshing() } } - } else { - self.modifier(RefreshableCompat(showsIndicators: showsIndicators, - loadingViewBackgroundColor: loadingViewBackgroundColor, - threshold: threshold, - onRefresh: onRefresh, - progress: progress)) } } } -#endif -public extension View { - @ViewBuilder func refreshableCompat(showsIndicators: Bool = true, - loadingViewBackgroundColor: - Color = defaultLoadingViewBackgroundColor, - threshold: CGFloat = defaultRefreshThreshold, - onRefresh: @escaping OnRefresh, - @ViewBuilder progress: - @escaping RefreshProgressBuilder) -> some View { - self.modifier(RefreshableCompat(showsIndicators: showsIndicators, - loadingViewBackgroundColor: loadingViewBackgroundColor, - threshold: threshold, - onRefresh: onRefresh, - progress: progress)) +struct RefreshableScrollView_Previews: PreviewProvider { + static var previews: some View { + RefreshableScrollView(showsIndicators: true) { + Text("Hi") + Text("World") + Text("Hello") + } onRefresh: { + print("Refreshing") + } } } -public struct RefreshableScrollViewIOS14: View { - public let onRefresh: OnRefresh // the refreshing action - public let content: Content // the ScrollView content - private let THRESHOLD: CGFloat = 100 +final class RefreshableScrollViewModel: ObservableObject { + @Published var progressViewHeight: CGFloat = 0 + @Published var isRefreshing = false - @State private var state = RefreshState.waiting // the current state + let progressViewMaxHeight: CGFloat + private let scrollPositionSubject = CurrentValueSubject(0) + private let closingAnimationDuration: Double = 0.15 + private var subscriptions: Set = [] - // We use a custom constructor to allow for usage of a @ViewBuilder for the content - public init(onRefresh: @escaping OnRefresh, @ViewBuilder content: () -> Content) { - self.onRefresh = onRefresh - self.content = content() + private var topYValue: CGFloat? + private var scrollYValue: CGFloat? + private var startingDistance: CGFloat? + private var isClosing = false + + /// - Parameter activityIndicatorStyle: Used to derive the size of the indicator. Might be better to get in another way. In case Apple changes the sizes + init(activityIndicatorStyle: UIActivityIndicatorView.Style = .medium) { + self.progressViewMaxHeight = activityIndicatorStyle == .large ? 35 : 27 + self.reactToScrollEnding() } - public var body: some View { - // The root view is a regular ScrollView - ScrollView { - // The ZStack allows us to position the PositionIndicator, - // the content and the loading view, all on top of each other. - ZStack(alignment: .top) { - // The moving positioning indicator, that sits at the top - // of the ScrollView and scrolls down with the content - PositionIndicator(type: .moving) - .frame(height: 0) - - // Your ScrollView content. If we're loading, we want - // to keep it below the loading view, hence the alignmentGuide. - content - .alignmentGuide(.top, computeValue: { _ in - (state == .loading) ? -THRESHOLD : 0 - }) + private func reactToScrollEnding() { + self.scrollPositionSubject + .debounce(for: 0.1, scheduler: RunLoop.main, options: nil) + .sink { [weak self] _ in + guard self?.progressViewHeight != 0, + self?.isRefreshing != true + else { return } - // The loading view. It's offset to the top of the content unless we're loading. - ZStack { - Rectangle() - .foregroundColor(.clear) - .frame(height: THRESHOLD) - - ActivityIndicator(isAnimating: state == .loading) { - $0.hidesWhenStopped = false - } - }.offset(y: (state == .loading) ? 0 : -THRESHOLD) + self?.reset() } + .store(in: &self.subscriptions) + } + + /// Updates the progressViewHeight and progressViewIsAnimating properties based on the given topFrame and any existing scrollYValue, if any + /// - Parameter topFrame: CGRect + func update(topFrame: CGRect) { + let topY = topFrame.minY + self.topYValue = topY + guard let scrollY = self.scrollYValue else { return } + + self.update(topY: topY, scrollY: scrollY) + } + + /// Updates the progressViewHeight and progressViewIsAnimating properties based on the given scrollFrame and any existing topYValue, if any + /// - Parameter scrollFrame: CGRect + func update(scrollFrame: CGRect) { + let scrollY = scrollFrame.minY + self.scrollYValue = scrollY + self.scrollPositionSubject.send(scrollY) + guard let topY = self.topYValue else { return } + + self.update(topY: topY, scrollY: scrollY) + } + + /// Stops refreshing and hides the progress view + func endRefreshing() { + self.reset() + + DispatchQueue.main.asyncAfter(deadline: .now() + self.closingAnimationDuration) { + self.isRefreshing = false } - // Put a fixed PositionIndicator in the background so that we have - // a reference point to compute the scroll offset. - .background(PositionIndicator(type: .fixed)) - // Once the scrolling offset changes, we want to see if there should - // be a state change. - .onPreferenceChange(PositionPreferenceKey.self) { values in - if state != .loading { // If we're already loading, ignore everything - // Map the preference change action to the UI thread - DispatchQueue.main.async { - // Compute the offset between the moving and fixed PositionIndicators - let movingY = values.first { $0.type == .moving }?.y ?? 0 - let fixedY = values.first { $0.type == .fixed }?.y ?? 0 - let offset = movingY - fixedY - - // If the user pulled down below the threshold, prime the view - if offset > THRESHOLD && state == .waiting { - state = .primed - - // If the view is primed and we've crossed the threshold again on the - // way back, trigger the refresh - } else if offset < THRESHOLD && state == .primed { - state = .loading - self.state = .waiting - - onRefresh { // trigger the refreshing callback - // once refreshing is done, smoothly move the loading view - // back to the offset position - // withAnimation { - // self.state = .waiting - // } - } - } - } - } + } + + private func reset() { + self.isClosing = true + let topY = self.topYValue ?? 0 + let startDistance = self.startingDistance ?? 0 + let startingScrollYValue = topY + startDistance + self.scrollYValue = startingScrollYValue + + withAnimation(.linear(duration: self.closingAnimationDuration)) { + self.progressViewHeight = 0 + } + + DispatchQueue.main.asyncAfter(deadline: .now() + self.closingAnimationDuration) { + self.isClosing = false + } + } + + private func update(topY: CGFloat, scrollY: CGFloat) { + // Don't react to updates while animating closed + guard !self.isClosing else { return } + + let newDistance = max(scrollY - topY, 0) + + if self.startingDistance == nil { + self.startingDistance = newDistance + } + + let differenceFromStart = newDistance - self.startingDistance! + let constrainedDifference = min(max(differenceFromStart, 0), self.progressViewMaxHeight) + + // Don't change the height of the progress view if we are refreshing + guard !isRefreshing else { return } + + DispatchQueue.main.async { + self.progressViewHeight = constrainedDifference + self.isRefreshing = constrainedDifference == self.progressViewMaxHeight } } } +struct FramePreferenceKey: PreferenceKey { + static var defaultValue: CGRect = .zero + + static func reduce(value: inout CGRect, nextValue: () -> CGRect) { + value = nextValue() + } +} + +extension View { + func framePreferenceKey(_ value: CGRect, onFrameChange: @escaping (CGRect) -> Void) -> some View { + self + .preference(key: FramePreferenceKey.self, value: value) + .onPreferenceChange(FramePreferenceKey.self, perform: onFrameChange) + } +} + struct ActivityIndicator: UIViewRepresentable { - public typealias UIView = UIActivityIndicatorView - public var isAnimating: Bool = true - public var configuration = { (indicator: UIView) in } + @Binding var size: CGFloat + @Binding var isAnimating: Bool + private let style: UIActivityIndicatorView.Style - public init(isAnimating: Bool, configuration: ((UIView) -> Void)? = nil) { - self.isAnimating = isAnimating - if let configuration = configuration { - self.configuration = configuration - } + init(style: UIActivityIndicatorView.Style = .medium, size: Binding, isAnimating: Binding) { + self._size = size + self._isAnimating = isAnimating + self.style = style } - public func makeUIView(context: UIViewRepresentableContext) -> UIView { - let uiView = UIView() - uiView.startAnimating() - return uiView + func makeUIView(context: Context) -> UIView { + let activityIndicator = UIActivityIndicatorView(style: self.style) + activityIndicator.hidesWhenStopped = false + + if self.isAnimating { + activityIndicator.startAnimating() + } + + let containerView = UIView() + containerView.layer.cornerRadius = self.size / 2 + containerView.clipsToBounds = true + + containerView.addSubview(activityIndicator) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator + .centerXAnchor + .constraint(equalTo: containerView.centerXAnchor) + .isActive = true + activityIndicator + .centerYAnchor + .constraint(equalTo: containerView.centerYAnchor) + .isActive = true + + return containerView } - public func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext) { -// isAnimating ? uiView.startAnimating() : uiView.stopAnimating() - configuration(uiView) + func updateUIView(_ uiView: UIView, context: Context) { + uiView.layer.cornerRadius = self.size / 2 + + guard let activityIndicator = uiView.subviews.first(where: { $0 is UIActivityIndicatorView }) as? UIActivityIndicatorView + else { return } + + if self.isAnimating { + activityIndicator.startAnimating() + } else { + activityIndicator.stopAnimating() + } } - } +} diff --git a/Core/Core/View/Base/RefreshableScrollViewCompat.swift b/Core/Core/View/Base/RefreshableScrollViewCompat.swift deleted file mode 100644 index 446e472be..000000000 --- a/Core/Core/View/Base/RefreshableScrollViewCompat.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// RefreshableScrollViewCompat.swift -// Core -// -// Created by  Stepanok Ivan on 15.02.2023. -// - -import SwiftUI - -public struct RefreshableScrollViewCompat: View where Content: View { - private let content: () -> Content - private let action: () async -> Void - - public init(action: @escaping () async -> Void, @ViewBuilder content: @escaping () -> Content) { - self.action = action - self.content = content - } - - public var body: some View { - if #available(iOS 15.0, *) { - return RefreshableScrollView(onRefresh: { done in - Task { - await action() - done() - } - }) { - content() - } - } else { - return RefreshableScrollViewIOS14(onRefresh: { done in - Task { - await action() - done() - } - }) { - content() - } - } - } -} diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index 910e26671..714caf8b7 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -752,7 +752,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseTests; @@ -773,7 +773,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseTests; @@ -794,7 +794,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseTests; @@ -815,7 +815,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseTests; @@ -836,7 +836,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseTests; @@ -857,7 +857,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseTests; @@ -1007,7 +1007,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1042,7 +1042,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1140,7 +1140,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1239,7 +1239,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1332,7 +1332,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1424,7 +1424,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1522,7 +1522,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1550,7 +1550,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseTests; @@ -1636,7 +1636,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1663,7 +1663,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.CourseTests; diff --git a/Course/Course/Presentation/Details/CourseDetailsView.swift b/Course/Course/Presentation/Details/CourseDetailsView.swift index f8f9ebf0c..5c5edada2 100644 --- a/Course/Course/Presentation/Details/CourseDetailsView.swift +++ b/Course/Course/Presentation/Details/CourseDetailsView.swift @@ -47,9 +47,7 @@ public struct CourseDetailsView: View { .padding(.horizontal) }.frame(width: proxy.size.width) } else { - RefreshableScrollViewCompat(action: { - await viewModel.getCourseDetail(courseID: courseID, withProgress: isIOS14) - }) { + RefreshableScrollView { VStack(alignment: .leading) { if let courseDetails = viewModel.courseDetails { @@ -133,7 +131,12 @@ public struct CourseDetailsView: View { } } } - }.frameLimit() + } onRefresh: { + Task { + await viewModel.getCourseDetail(courseID: courseID, withProgress: false) + } + }.coordinateSpace(name: "pullToRefresh") + .frameLimit() .onRightSwipeGesture { viewModel.router.back() } @@ -154,7 +157,7 @@ public struct CourseDetailsView: View { // MARK: - Offline mode SnackBar OfflineSnackBarView(connectivity: viewModel.connectivity, reloadAction: { - await viewModel.getCourseDetail(courseID: courseID, withProgress: isIOS14) + await viewModel.getCourseDetail(courseID: courseID, withProgress: false) }) // MARK: - Error Alert diff --git a/Course/Course/Presentation/Outline/CourseOutlineView.swift b/Course/Course/Presentation/Outline/CourseOutlineView.swift index a0c38d78c..c07019c96 100644 --- a/Course/Course/Presentation/Outline/CourseOutlineView.swift +++ b/Course/Course/Presentation/Outline/CourseOutlineView.swift @@ -37,9 +37,7 @@ public struct CourseOutlineView: View { GeometryReader { proxy in VStack(alignment: .center) { // MARK: - Page Body - RefreshableScrollViewCompat(action: { - await viewModel.getCourseBlocks(courseID: courseID, withProgress: isIOS14) - }) { + RefreshableScrollView { VStack(alignment: .leading) { ZStack { // MARK: - Course Banner @@ -136,7 +134,10 @@ public struct CourseOutlineView: View { } Spacer(minLength: 84) } - }.frameLimit() + } onRefresh: { + await viewModel.getCourseBlocks(courseID: courseID, withProgress: false) + }.coordinateSpace(name: "pullToRefresh") + .frameLimit() .onRightSwipeGesture { viewModel.router.back() } @@ -146,7 +147,7 @@ public struct CourseOutlineView: View { OfflineSnackBarView( connectivity: viewModel.connectivity, reloadAction: { - await viewModel.getCourseBlocks(courseID: courseID, withProgress: isIOS14) + await viewModel.getCourseBlocks(courseID: courseID, withProgress: false) } ) diff --git a/Dashboard/Dashboard.xcodeproj/project.pbxproj b/Dashboard/Dashboard.xcodeproj/project.pbxproj index 7f943cdf7..9eb3ade8c 100644 --- a/Dashboard/Dashboard.xcodeproj/project.pbxproj +++ b/Dashboard/Dashboard.xcodeproj/project.pbxproj @@ -488,7 +488,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DashboardTests; @@ -509,7 +509,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DashboardTests; @@ -530,7 +530,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DashboardTests; @@ -551,7 +551,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DashboardTests; @@ -572,7 +572,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DashboardTests; @@ -593,7 +593,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DashboardTests; @@ -685,7 +685,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -713,7 +713,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DashboardTests; @@ -799,7 +799,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -826,7 +826,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DashboardTests; @@ -976,7 +976,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1011,7 +1011,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1109,7 +1109,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1202,7 +1202,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1300,7 +1300,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1393,7 +1393,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Dashboard/Dashboard/Presentation/DashboardView.swift b/Dashboard/Dashboard/Presentation/DashboardView.swift index d4e63949c..59747da5d 100644 --- a/Dashboard/Dashboard/Presentation/DashboardView.swift +++ b/Dashboard/Dashboard/Presentation/DashboardView.swift @@ -33,63 +33,68 @@ public struct DashboardView: View { // MARK: - Page body VStack(alignment: .center) { - RefreshableScrollViewCompat(action: { - await viewModel.getMyCourses(page: 1, refresh: true) - }) { - if viewModel.courses.isEmpty && !viewModel.fetchInProgress { - EmptyPageIcon() - } else { - LazyVStack(spacing: 0) { - HStack { - dashboardCourses + RefreshableScrollView { + Group { + if viewModel.courses.isEmpty && !viewModel.fetchInProgress { + EmptyPageIcon() + } else { + LazyVStack(spacing: 0) { + HStack { + dashboardCourses + .padding(.horizontal, 20) + .padding(.bottom, 20) + Spacer() + }.padding(.leading, 10) + ForEach(Array(viewModel.courses.enumerated()), + id: \.offset) { index, course in + + CourseCellView( + model: course, + type: .dashboard, + index: index, + cellsCount: viewModel.courses.count + ) .padding(.horizontal, 20) - .padding(.bottom, 20) - Spacer() - }.padding(.leading, 10) - ForEach(Array(viewModel.courses.enumerated()), - id: \.offset) { index, course in - - CourseCellView( - model: course, - type: .dashboard, - index: index, - cellsCount: viewModel.courses.count - ) - .padding(.horizontal, 20) - .listRowBackground(Color.clear) - .onAppear { - Task { - await viewModel.getMyCoursesPagination(index: index) + .listRowBackground(Color.clear) + .onAppear { + Task { + await viewModel.getMyCoursesPagination(index: index) + } + } + .onTapGesture { + viewModel.trackDashboardCourseClicked( + courseID: course.courseID, + courseName: course.name + ) + router.showCourseScreens( + courseID: course.courseID, + isActive: course.isActive, + courseStart: course.courseStart, + courseEnd: course.courseEnd, + enrollmentStart: course.enrollmentStart, + enrollmentEnd: course.enrollmentEnd, + title: course.name + ) } } - .onTapGesture { - viewModel.trackDashboardCourseClicked( - courseID: course.courseID, - courseName: course.name - ) - router.showCourseScreens( - courseID: course.courseID, - isActive: course.isActive, - courseStart: course.courseStart, - courseEnd: course.courseEnd, - enrollmentStart: course.enrollmentStart, - enrollmentEnd: course.enrollmentEnd, - title: course.name - ) + // MARK: - ProgressBar + if viewModel.nextPage <= viewModel.totalPages { + VStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + .padding(.top, 20) + }.frame(maxWidth: .infinity, + maxHeight: .infinity) } + VStack {}.frame(height: 40) } - // MARK: - ProgressBar - if viewModel.nextPage <= viewModel.totalPages { - VStack(alignment: .center) { - ProgressBar(size: 40, lineWidth: 8) - .padding(.top, 20) - }.frame(maxWidth: .infinity, - maxHeight: .infinity) - } - VStack {}.frame(height: 40) } } - }.frameLimit() + } onRefresh: { + Task { + await viewModel.getMyCourses(page: 1, refresh: true) + } + }.coordinateSpace(name: "pullToRefresh") + .frameLimit() }.padding(.top, 8) // MARK: - Offline mode SnackBar diff --git a/Discovery/Discovery.xcodeproj/project.pbxproj b/Discovery/Discovery.xcodeproj/project.pbxproj index 632cd869e..05974ad4f 100644 --- a/Discovery/Discovery.xcodeproj/project.pbxproj +++ b/Discovery/Discovery.xcodeproj/project.pbxproj @@ -514,7 +514,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscoveryUnitTests; @@ -535,7 +535,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscoveryUnitTests; @@ -556,7 +556,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscoveryUnitTests; @@ -577,7 +577,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscoveryUnitTests; @@ -598,7 +598,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscoveryUnitTests; @@ -619,7 +619,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscoveryUnitTests; @@ -711,7 +711,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -739,7 +739,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscoveryUnitTests; @@ -825,7 +825,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -852,7 +852,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscoveryUnitTests; @@ -1002,7 +1002,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1037,7 +1037,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1135,7 +1135,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1234,7 +1234,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1327,7 +1327,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1419,7 +1419,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Discovery/Discovery/Presentation/DiscoveryView.swift b/Discovery/Discovery/Presentation/DiscoveryView.swift index 7341e8684..2aab73fcf 100644 --- a/Discovery/Discovery/Presentation/DiscoveryView.swift +++ b/Discovery/Discovery/Presentation/DiscoveryView.swift @@ -66,54 +66,56 @@ public struct DiscoveryView: View { .padding(.bottom, 20) ZStack { - RefreshableScrollViewCompat(action: { - viewModel.courses = [] - viewModel.totalPages = 1 - viewModel.nextPage = 1 - await viewModel.discovery(page: 1) - }) { - LazyVStack(spacing: 0) { - HStack { - discoveryNew - .padding(.horizontal, 20) - .padding(.bottom, 20) - Spacer() - }.padding(.leading, 10) - ForEach(Array(viewModel.courses.enumerated()), id: \.offset) { index, course in - CourseCellView( - model: course, - type: .discovery, - index: index, - cellsCount: viewModel.courses.count - ).padding(.horizontal, 24) - .onAppear { - Task { - await viewModel.getDiscoveryCourses(index: index) + RefreshableScrollView { + LazyVStack(spacing: 0) { + HStack { + discoveryNew + .padding(.horizontal, 20) + .padding(.bottom, 20) + Spacer() + }.padding(.leading, 10) + ForEach(Array(viewModel.courses.enumerated()), id: \.offset) { index, course in + CourseCellView( + model: course, + type: .discovery, + index: index, + cellsCount: viewModel.courses.count + ).padding(.horizontal, 24) + .onAppear { + Task { + await viewModel.getDiscoveryCourses(index: index) + } } - } - .onTapGesture { - viewModel.discoveryCourseClicked( - courseID: course.courseID, - courseName: course.name - ) - router.showCourseDetais( - courseID: course.courseID, - title: course.name - ) - } - } - - // MARK: - ProgressBar - if viewModel.nextPage <= viewModel.totalPages { - VStack(alignment: .center) { - ProgressBar(size: 40, lineWidth: 8) - .padding(.top, 20) - }.frame(maxWidth: .infinity, - maxHeight: .infinity) + .onTapGesture { + viewModel.discoveryCourseClicked( + courseID: course.courseID, + courseName: course.name + ) + router.showCourseDetais( + courseID: course.courseID, + title: course.name + ) + } + } + + // MARK: - ProgressBar + if viewModel.nextPage <= viewModel.totalPages { + VStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + .padding(.top, 20) + }.frame(maxWidth: .infinity, + maxHeight: .infinity) + } + VStack {}.frame(height: 40) } - VStack {}.frame(height: 40) + } onRefresh: { + viewModel.totalPages = 1 + viewModel.nextPage = 1 + Task { + await viewModel.discovery(page: 1, withProgress: false) } }.frameLimit() + .coordinateSpace(name: "pullToRefresh") } }.padding(.top, 8) @@ -124,7 +126,7 @@ public struct DiscoveryView: View { viewModel.courses = [] viewModel.totalPages = 1 viewModel.nextPage = 1 - await viewModel.discovery(page: 1, withProgress: isIOS14) + await viewModel.discovery(page: 1, withProgress: false) }) // MARK: - Error Alert diff --git a/Discovery/Discovery/Presentation/DiscoveryViewModel.swift b/Discovery/Discovery/Presentation/DiscoveryViewModel.swift index 568a37a9b..08a588191 100644 --- a/Discovery/Discovery/Presentation/DiscoveryViewModel.swift +++ b/Discovery/Discovery/Presentation/DiscoveryViewModel.swift @@ -60,7 +60,11 @@ public class DiscoveryViewModel: ObservableObject { fetchInProgress = withProgress do { if connectivity.isInternetAvaliable { - await courses += try interactor.discovery(page: page) + if page == 1 { + await courses = try interactor.discovery(page: page) + } else { + await courses += try interactor.discovery(page: page) + } self.nextPage += 1 if !courses.isEmpty { totalPages = courses[0].numPages diff --git a/Discussion/Discussion.xcodeproj/project.pbxproj b/Discussion/Discussion.xcodeproj/project.pbxproj index cfd6abaff..9a659e4a7 100644 --- a/Discussion/Discussion.xcodeproj/project.pbxproj +++ b/Discussion/Discussion.xcodeproj/project.pbxproj @@ -882,7 +882,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -916,7 +916,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1013,7 +1013,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1111,7 +1111,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1203,7 +1203,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1294,7 +1294,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1321,7 +1321,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscussionTests; @@ -1342,7 +1342,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscussionTests; @@ -1363,7 +1363,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscussionTests; @@ -1384,7 +1384,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscussionTests; @@ -1405,7 +1405,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscussionTests; @@ -1426,7 +1426,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscussionTests; @@ -1517,7 +1517,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1545,7 +1545,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscussionTests; @@ -1630,7 +1630,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1657,7 +1657,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.DiscussionTests; diff --git a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift index 2be19cf06..b079cb94c 100644 --- a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift +++ b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift @@ -36,145 +36,148 @@ public struct ResponsesView: View { public var body: some View { ZStack(alignment: .top) { - // MARK: - Page Body - ScrollViewReader { scroll in - VStack { - ZStack(alignment: .top) { - RefreshableScrollViewCompat(action: { - viewModel.comments = [] - _ = await viewModel.getComments(commentID: commentID, - parentComment: parentComment, page: 1) - }) { - VStack { - if let comments = viewModel.postComments { - ParentCommentView( - comments: comments, - isThread: false, + // MARK: - Page Body + ScrollViewReader { scroll in + VStack { + ZStack(alignment: .top) { + RefreshableScrollView { + VStack { + if let comments = viewModel.postComments { + ParentCommentView( + comments: comments, + isThread: false, + onLikeTap: { + Task { + if await viewModel.vote( + id: parentComment.commentID, + isThread: false, + voted: comments.voted, + index: nil + ) { + viewModel.sendThreadLikeState() + } + } + }, + onReportTap: { + Task { + if await viewModel.flag( + id: parentComment.commentID, + isThread: false, + abuseFlagged: comments.abuseFlagged, + index: nil + ) { + viewModel.sendThreadReportState() + } + + } + }, + onFollowTap: {} + ) + HStack { + Text("\(viewModel.itemsCount)") + Text(DiscussionLocalization.commentsCount(viewModel.itemsCount)) + Spacer() + }.padding(.top, 40) + .padding(.bottom, 14) + .padding(.leading, 24) + .font(Theme.Fonts.titleMedium) + ForEach( + Array(comments.comments.enumerated()), id: \.offset + ) { index, comment in + CommentCell( + comment: comment, + addCommentAvailable: false, leftLineEnabled: true, onLikeTap: { Task { - if await viewModel.vote( - id: parentComment.commentID, + await viewModel.vote( + id: comment.commentID, isThread: false, - voted: comments.voted, - index: nil - ) { - viewModel.sendThreadLikeState() - } + voted: comment.voted, + index: index + ) } }, onReportTap: { Task { - if await viewModel.flag( - id: parentComment.commentID, + await viewModel.flag( + id: comment.commentID, isThread: false, - abuseFlagged: comments.abuseFlagged, - index: nil - ) { - viewModel.sendThreadReportState() - } - + abuseFlagged: comment.abuseFlagged, + index: index + ) } }, - onFollowTap: {} - ) - HStack { - Text("\(viewModel.itemsCount)") - Text(DiscussionLocalization.commentsCount(viewModel.itemsCount)) - Spacer() - }.padding(.top, 40) - .padding(.bottom, 14) - .padding(.leading, 24) - .font(Theme.Fonts.titleMedium) - ForEach( - Array(comments.comments.enumerated()), id: \.offset - ) { index, comment in - CommentCell( - comment: comment, - addCommentAvailable: false, leftLineEnabled: true, - onLikeTap: { - Task { - await viewModel.vote( - id: comment.commentID, - isThread: false, - voted: comment.voted, - index: index - ) - } - }, - onReportTap: { - Task { - await viewModel.flag( - id: comment.commentID, - isThread: false, - abuseFlagged: comment.abuseFlagged, - index: index - ) - } - }, - onCommentsTap: {}, - onFetchMore: { - Task { - await viewModel.fetchMorePosts( - commentID: commentID, - parentComment: parentComment, - index: index - ) - } + onCommentsTap: {}, + onFetchMore: { + Task { + await viewModel.fetchMorePosts( + commentID: commentID, + parentComment: parentComment, + index: index + ) } - ) - .id(index) - .padding(.bottom, -8) - } - if viewModel.nextPage <= viewModel.totalPages { - VStack(alignment: .center) { - ProgressBar(size: 40, lineWidth: 8) - .padding(.top, 20) - }.frame(maxWidth: .infinity, - maxHeight: .infinity) - } + } + ) + .id(index) + .padding(.bottom, -8) + } + if viewModel.nextPage <= viewModel.totalPages { + VStack(alignment: .center) { + ProgressBar(size: 40, lineWidth: 8) + .padding(.top, 20) + }.frame(maxWidth: .infinity, + maxHeight: .infinity) } - Spacer(minLength: 84) - } - .onRightSwipeGesture { - viewModel.router.back() } - }.frameLimit() - - if !parentComment.closed { - FlexibleKeyboardInputView( - hint: DiscussionLocalization.Response.addComment, - sendText: { commentText in - if let threadID = viewModel.postComments?.threadID { - Task { - await viewModel.postComment( - threadID: threadID, - rawBody: commentText, - parentID: commentID - ) - } + Spacer(minLength: 84) + } + .onRightSwipeGesture { + viewModel.router.back() + } + } onRefresh: { + viewModel.comments = [] + Task { + _ = await viewModel.getComments(commentID: commentID, + parentComment: parentComment, page: 1) + } + }.coordinateSpace(name: "pullToRefresh") + .frameLimit() + + if !parentComment.closed { + FlexibleKeyboardInputView( + hint: DiscussionLocalization.Response.addComment, + sendText: { commentText in + if let threadID = viewModel.postComments?.threadID { + Task { + await viewModel.postComment( + threadID: threadID, + rawBody: commentText, + parentID: commentID + ) } } - ) - } + } + ) } } - .onReceive(viewModel.addPostSubject, perform: { newComment in - guard let newComment else { return } - viewModel.sendThreadPostsCountState() - if viewModel.nextPage - 1 == viewModel.totalPages { - viewModel.addNewPost(newComment) - withAnimation { - guard let count = viewModel.postComments?.comments.count else { return } - scroll.scrollTo(count - 2, anchor: .top) - } - } else { - viewModel.alertMessage = DiscussionLocalization.Response.Alert.commentAdded - viewModel.showAlert = true + } + .onReceive(viewModel.addPostSubject, perform: { newComment in + guard let newComment else { return } + viewModel.sendThreadPostsCountState() + if viewModel.nextPage - 1 == viewModel.totalPages { + viewModel.addNewPost(newComment) + withAnimation { + guard let count = viewModel.postComments?.comments.count else { return } + scroll.scrollTo(count - 2, anchor: .top) } - }) - .frame(maxWidth: .infinity, maxHeight: .infinity) - }.scrollAvoidKeyboard(dismissKeyboardByTap: true) - .padding(.top, 8) + } else { + viewModel.alertMessage = DiscussionLocalization.Response.Alert.commentAdded + viewModel.showAlert = true + } + }) + .frame(maxWidth: .infinity, maxHeight: .infinity) + }.scrollAvoidKeyboard(dismissKeyboardByTap: true) + .padding(.top, 8) // MARK: - Error Alert if viewModel.showError { VStack { diff --git a/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift b/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift index f942a4b6c..de192751d 100644 --- a/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift +++ b/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift @@ -34,10 +34,7 @@ public struct ThreadView: View { ScrollViewReader { scroll in VStack { ZStack(alignment: .top) { - RefreshableScrollViewCompat(action: { - viewModel.comments = [] - _ = await viewModel.getPosts(thread: thread, page: 1) - }) { + RefreshableScrollView { VStack { if let comments = viewModel.postComments { ParentCommentView( @@ -143,7 +140,12 @@ public struct ThreadView: View { onBackTapped() viewModel.sendUpdateUnreadState() } - } + } onRefresh: { +// viewModel.comments = [] + Task { + _ = await viewModel.getPosts(thread: thread, page: 1) + } + }.coordinateSpace(name: "pullToRefresh") if !thread.closed { FlexibleKeyboardInputView( hint: DiscussionLocalization.Thread.addResponse, diff --git a/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift b/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift index bd8edf468..35870b6ee 100644 --- a/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift @@ -135,7 +135,11 @@ public class ThreadViewModel: BaseResponsesViewModel, ObservableObject { .getQuestionComments(threadID: thread.id, page: page) self.totalPages = pagination.numPages self.itemsCount = pagination.count - self.comments += comments + if page == 1 { + self.comments = comments + } else { + self.comments += comments + } postComments = generateComments(comments: self.comments, thread: thread) case .discussion: let (comments, pagination) = try await interactor diff --git a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsView.swift b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsView.swift index 73ac31b33..4629b9956 100644 --- a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsView.swift +++ b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsView.swift @@ -53,11 +53,8 @@ public struct DiscussionTopicsView: View { // MARK: - Page Body VStack { ZStack(alignment: .top) { - RefreshableScrollViewCompat(action: { - await viewModel.getTopics(courseID: self.courseID, withProgress: isIOS14) - }) { + RefreshableScrollView { VStack { - if let topics = viewModel.discussionTopics { HStack { Text(DiscussionLocalization.Topics.mainCategories) @@ -128,10 +125,16 @@ public struct DiscussionTopicsView: View { } Spacer(minLength: 84) } - }.frameLimit() - .onRightSwipeGesture { - router.back() - } + } onRefresh: { + Task { + await viewModel.getTopics(courseID: self.courseID, withProgress: false) + } + }.coordinateSpace(name: "pullToRefresh") + .frameLimit() + .onRightSwipeGesture { + router.back() + } + } }.frame(maxWidth: .infinity) }.padding(.top, 8) diff --git a/Discussion/Discussion/Presentation/Posts/PostsView.swift b/Discussion/Discussion/Presentation/Posts/PostsView.swift index 49d90aab2..55f0d09ab 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsView.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsView.swift @@ -83,13 +83,7 @@ public struct PostsView: View { Divider().offset(y: -8) } .frameLimit() - RefreshableScrollViewCompat(action: { - listAnimation = nil - viewModel.resetPosts() - _ = await viewModel.getPosts(courseID: courseID, - pageNumber: 1, - withProgress: isIOS14) - }) { + RefreshableScrollView { let posts = Array(viewModel.filteredPosts.enumerated()) if posts.count >= 1 { LazyVStack { @@ -174,7 +168,15 @@ public struct PostsView: View { .padding(.top, 100) } } - } + } onRefresh: { + listAnimation = nil + viewModel.resetPosts() + Task { + _ = await viewModel.getPosts(courseID: courseID, + pageNumber: 1, + withProgress: false) + } + }.coordinateSpace(name: "pullToRefresh") }.frameLimit() .animation(listAnimation) .onRightSwipeGesture { @@ -217,7 +219,7 @@ public struct PostsView: View { viewModel.resetPosts() _ = await viewModel.getPosts(courseID: courseID, pageNumber: 1, - withProgress: isIOS14) + withProgress: false) onSuccess() } } diff --git a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift index a618bdff9..47adce086 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift @@ -109,11 +109,11 @@ public class PostsViewModel: ObservableObject { } public func resetPosts() { - filteredPosts = [] - discussionPosts = [] - threads.threads = [] - nextPage = 1 - totalPages = 1 +// filteredPosts = [] +// discussionPosts = [] +// threads.threads = [] +// nextPage = 1 +// totalPages = 1 } public func generateButtons(type: ButtonType) { @@ -188,59 +188,112 @@ public class PostsViewModel: ObservableObject { } } + // swiftlint:disable function_body_length @MainActor public func getPosts(courseID: String, pageNumber: Int, withProgress: Bool = true) async -> Bool { fetchInProgress = true isShowProgress = withProgress do { - switch type { - case .allPosts: - threads.threads += try await interactor - .getThreadsList(courseID: courseID, - type: .allPosts, - sort: sortTitle, - filter: filterTitle, - page: pageNumber).threads - if threads.threads.indices.contains(0) { - self.totalPages = threads.threads[0].numPages - self.nextPage += 1 - } - case .followingPosts: - threads.threads += try await interactor - .getThreadsList(courseID: courseID, - type: .followingPosts, - sort: sortTitle, - filter: filterTitle, - page: pageNumber).threads - if threads.threads.indices.contains(0) { - self.totalPages = threads.threads[0].numPages - self.nextPage += 1 - } - case .nonCourseTopics: - threads.threads += try await interactor - .getThreadsList(courseID: courseID, - type: .nonCourseTopics, - sort: sortTitle, - filter: filterTitle, - page: pageNumber).threads - if threads.threads.indices.contains(0) { - self.totalPages = threads.threads[0].numPages - self.nextPage += 1 + if pageNumber == 1 { + switch type { + case .allPosts: + threads.threads = try await interactor + .getThreadsList(courseID: courseID, + type: .allPosts, + sort: sortTitle, + filter: filterTitle, + page: pageNumber).threads + if threads.threads.indices.contains(0) { + self.totalPages = threads.threads[0].numPages + self.nextPage = 2 + } + case .followingPosts: + threads.threads = try await interactor + .getThreadsList(courseID: courseID, + type: .followingPosts, + sort: sortTitle, + filter: filterTitle, + page: pageNumber).threads + if threads.threads.indices.contains(0) { + self.totalPages = threads.threads[0].numPages + self.nextPage = 2 + } + case .nonCourseTopics: + threads.threads = try await interactor + .getThreadsList(courseID: courseID, + type: .nonCourseTopics, + sort: sortTitle, + filter: filterTitle, + page: pageNumber).threads + if threads.threads.indices.contains(0) { + self.totalPages = threads.threads[0].numPages + self.nextPage = 2 + } + case .courseTopics(topicID: let topicID): + threads.threads = try await interactor + .getThreadsList(courseID: courseID, + type: .courseTopics(topicID: topicID), + sort: sortTitle, + filter: filterTitle, + page: pageNumber).threads + if threads.threads.indices.contains(0) { + self.totalPages = threads.threads[0].numPages + self.nextPage = 2 + } + case .none: + isShowProgress = false + return false } - case .courseTopics(topicID: let topicID): - threads.threads += try await interactor - .getThreadsList(courseID: courseID, - type: .courseTopics(topicID: topicID), - sort: sortTitle, - filter: filterTitle, - page: pageNumber).threads - if threads.threads.indices.contains(0) { - self.totalPages = threads.threads[0].numPages - self.nextPage += 1 + } else { + switch type { + case .allPosts: + threads.threads += try await interactor + .getThreadsList(courseID: courseID, + type: .allPosts, + sort: sortTitle, + filter: filterTitle, + page: pageNumber).threads + if threads.threads.indices.contains(0) { + self.totalPages = threads.threads[0].numPages + self.nextPage += 1 + } + case .followingPosts: + threads.threads += try await interactor + .getThreadsList(courseID: courseID, + type: .followingPosts, + sort: sortTitle, + filter: filterTitle, + page: pageNumber).threads + if threads.threads.indices.contains(0) { + self.totalPages = threads.threads[0].numPages + self.nextPage += 1 + } + case .nonCourseTopics: + threads.threads += try await interactor + .getThreadsList(courseID: courseID, + type: .nonCourseTopics, + sort: sortTitle, + filter: filterTitle, + page: pageNumber).threads + if threads.threads.indices.contains(0) { + self.totalPages = threads.threads[0].numPages + self.nextPage += 1 + } + case .courseTopics(topicID: let topicID): + threads.threads += try await interactor + .getThreadsList(courseID: courseID, + type: .courseTopics(topicID: topicID), + sort: sortTitle, + filter: filterTitle, + page: pageNumber).threads + if threads.threads.indices.contains(0) { + self.totalPages = threads.threads[0].numPages + self.nextPage += 1 + } + case .none: + isShowProgress = false + return false } - case .none: - isShowProgress = false - return false } discussionPosts = generatePosts(threads: threads) filteredPosts = discussionPosts @@ -259,6 +312,7 @@ public class PostsViewModel: ObservableObject { return false } } + // swiftlint:enable function_body_length private func updateUnreadCommentsCount(id: String) { var threads = threads.threads diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 715431a35..8aecb5c84 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -511,7 +511,7 @@ INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -600,7 +600,7 @@ INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -695,7 +695,7 @@ INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -784,7 +784,7 @@ INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -933,7 +933,7 @@ INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -968,7 +968,7 @@ INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index ef60fd5ab..3ba3e0531 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -737,7 +737,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -772,7 +772,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -869,7 +869,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -967,7 +967,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1059,7 +1059,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1150,7 +1150,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1177,7 +1177,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.ProfileTests; @@ -1198,7 +1198,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.ProfileTests; @@ -1219,7 +1219,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.ProfileTests; @@ -1240,7 +1240,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.ProfileTests; @@ -1261,7 +1261,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.ProfileTests; @@ -1282,7 +1282,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.ProfileTests; @@ -1373,7 +1373,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1401,7 +1401,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.ProfileTests; @@ -1486,7 +1486,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1513,7 +1513,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = L8PG7LC3Y3; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = org.openedx.ProfileTests; diff --git a/Profile/Profile/Presentation/Profile/ProfileView.swift b/Profile/Profile/Presentation/Profile/ProfileView.swift index e1b961bf5..fca2e192f 100644 --- a/Profile/Profile/Presentation/Profile/ProfileView.swift +++ b/Profile/Profile/Presentation/Profile/ProfileView.swift @@ -22,9 +22,7 @@ public struct ProfileView: View { public var body: some View { ZStack(alignment: .top) { // MARK: - Page Body - RefreshableScrollViewCompat(action: { - await viewModel.getMyProfile(withProgress: isIOS14) - }) { + RefreshableScrollView { VStack { if viewModel.isShowProgress { ProgressBar(size: 40, lineWidth: 8) @@ -79,16 +77,17 @@ public struct ProfileView: View { .padding(.horizontal, 24) .font(Theme.Fonts.labelLarge) VStack(alignment: .leading, spacing: 27) { - HStack { Button(action: { viewModel.trackProfileVideoSettingsClicked() viewModel.router.showSettings() }, label: { + HStack { Text(ProfileLocalization.settingsVideo) Spacer() Image(systemName: "chevron.right") + } }) - } + }.cardStyle( bgColor: Theme.Colors.textInputUnfocusedBackground, strokeColor: .clear @@ -156,7 +155,6 @@ public struct ProfileView: View { // MARK: - Log out VStack { - HStack { Button(action: { viewModel.router.presentView(transitionStyle: .crossDissolve) { AlertView( @@ -175,12 +173,15 @@ public struct ProfileView: View { ) } }, label: { + HStack { Text(ProfileLocalization.logout) Spacer() Image(systemName: "rectangle.portrait.and.arrow.right") + } }) - } - }.foregroundColor(Theme.Colors.alert) + + } + .foregroundColor(Theme.Colors.alert) .cardStyle(bgColor: Theme.Colors.textInputUnfocusedBackground, strokeColor: .clear) .padding(.top, 24) @@ -189,7 +190,12 @@ public struct ProfileView: View { Spacer() } } - }.frameLimit(sizePortrait: 420) + } onRefresh: { + Task { + await viewModel.getMyProfile(withProgress: false) + } + }.coordinateSpace(name: "pullToRefresh") + .frameLimit(sizePortrait: 420) .padding(.top, 8) .onChange(of: settingsTapped, perform: { _ in if let userModel = viewModel.userModel { @@ -214,7 +220,7 @@ public struct ProfileView: View { // MARK: - Offline mode SnackBar OfflineSnackBarView(connectivity: viewModel.connectivity, reloadAction: { - await viewModel.getMyProfile(withProgress: isIOS14) + await viewModel.getMyProfile(withProgress: false) }) // MARK: - Error Alert From e9a06a9327df0d9d64901aa21f360f80a606b9a4 Mon Sep 17 00:00:00 2001 From: Ivan Stepanok Date: Fri, 15 Sep 2023 10:02:58 +0300 Subject: [PATCH 2/4] update resetPosts() --- .../Discussion/Presentation/Posts/PostsViewModel.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift index 47adce086..f8baeba06 100644 --- a/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift +++ b/Discussion/Discussion/Presentation/Posts/PostsViewModel.swift @@ -109,11 +109,11 @@ public class PostsViewModel: ObservableObject { } public func resetPosts() { -// filteredPosts = [] -// discussionPosts = [] -// threads.threads = [] -// nextPage = 1 -// totalPages = 1 + filteredPosts = [] + discussionPosts = [] + threads.threads = [] + nextPage = 1 + totalPages = 1 } public func generateButtons(type: ButtonType) { From bb6c4a0fa5337ff44766c822afa2cc248e4c9911 Mon Sep 17 00:00:00 2001 From: Ivan Stepanok Date: Fri, 15 Sep 2023 11:34:11 +0300 Subject: [PATCH 3/4] minor fixes --- Discovery/Discovery/Presentation/DiscoveryView.swift | 3 --- Discovery/Discovery/Presentation/DiscoveryViewModel.swift | 2 ++ .../Discussion/Presentation/Comments/Thread/ThreadView.swift | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Discovery/Discovery/Presentation/DiscoveryView.swift b/Discovery/Discovery/Presentation/DiscoveryView.swift index 2aab73fcf..e8f6f52d4 100644 --- a/Discovery/Discovery/Presentation/DiscoveryView.swift +++ b/Discovery/Discovery/Presentation/DiscoveryView.swift @@ -123,9 +123,6 @@ public struct DiscoveryView: View { OfflineSnackBarView( connectivity: viewModel.connectivity, reloadAction: { - viewModel.courses = [] - viewModel.totalPages = 1 - viewModel.nextPage = 1 await viewModel.discovery(page: 1, withProgress: false) }) diff --git a/Discovery/Discovery/Presentation/DiscoveryViewModel.swift b/Discovery/Discovery/Presentation/DiscoveryViewModel.swift index 08a588191..37514275b 100644 --- a/Discovery/Discovery/Presentation/DiscoveryViewModel.swift +++ b/Discovery/Discovery/Presentation/DiscoveryViewModel.swift @@ -62,6 +62,8 @@ public class DiscoveryViewModel: ObservableObject { if connectivity.isInternetAvaliable { if page == 1 { await courses = try interactor.discovery(page: page) + self.totalPages = 1 + self.nextPage = 1 } else { await courses += try interactor.discovery(page: page) } diff --git a/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift b/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift index de192751d..7bbbdae0a 100644 --- a/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift +++ b/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift @@ -141,7 +141,6 @@ public struct ThreadView: View { viewModel.sendUpdateUnreadState() } } onRefresh: { -// viewModel.comments = [] Task { _ = await viewModel.getPosts(thread: thread, page: 1) } From 3ce2a405eea48c52ca70724049c1ce6306c066cd Mon Sep 17 00:00:00 2001 From: Ivan Stepanok Date: Fri, 15 Sep 2023 11:44:48 +0300 Subject: [PATCH 4/4] rename CourseEndpoint file --- Course/Course.xcodeproj/project.pbxproj | 8 ++++---- .../{CourseDetailsEndpoint.swift => CourseEndpoint.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename Course/Course/Data/Network/{CourseDetailsEndpoint.swift => CourseEndpoint.swift} (100%) diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index 714caf8b7..c80d63032 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -44,7 +44,7 @@ 02B6B3B228E1C49400232911 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 02B6B3B428E1C49400232911 /* Localizable.strings */; }; 02B6B3B728E1D11E00232911 /* CourseInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3B628E1D11E00232911 /* CourseInteractor.swift */; }; 02B6B3BC28E1D14F00232911 /* CourseRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3BB28E1D14F00232911 /* CourseRepository.swift */; }; - 02B6B3BE28E1D15C00232911 /* CourseDetailsEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3BD28E1D15C00232911 /* CourseDetailsEndpoint.swift */; }; + 02B6B3BE28E1D15C00232911 /* CourseEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3BD28E1D15C00232911 /* CourseEndpoint.swift */; }; 02B6B3C128E1DBA100232911 /* Data_CourseDetailsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3C028E1DBA100232911 /* Data_CourseDetailsResponse.swift */; }; 02B6B3C328E1DCD100232911 /* CourseDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3C228E1DCD100232911 /* CourseDetails.swift */; }; 02B6B3C928E1E68100232911 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B6B3C828E1E68100232911 /* Core.framework */; }; @@ -115,7 +115,7 @@ 02B6B3B328E1C49400232911 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 02B6B3B628E1D11E00232911 /* CourseInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseInteractor.swift; sourceTree = ""; }; 02B6B3BB28E1D14F00232911 /* CourseRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseRepository.swift; sourceTree = ""; }; - 02B6B3BD28E1D15C00232911 /* CourseDetailsEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetailsEndpoint.swift; sourceTree = ""; }; + 02B6B3BD28E1D15C00232911 /* CourseEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseEndpoint.swift; sourceTree = ""; }; 02B6B3C028E1DBA100232911 /* Data_CourseDetailsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data_CourseDetailsResponse.swift; sourceTree = ""; }; 02B6B3C228E1DCD100232911 /* CourseDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetails.swift; sourceTree = ""; }; 02B6B3C828E1E68100232911 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -278,7 +278,7 @@ 02B6B3B928E1D13500232911 /* Network */ = { isa = PBXGroup; children = ( - 02B6B3BD28E1D15C00232911 /* CourseDetailsEndpoint.swift */, + 02B6B3BD28E1D15C00232911 /* CourseEndpoint.swift */, ); path = Network; sourceTree = ""; @@ -715,7 +715,7 @@ 022F8E162A1DFBC6008EFAB9 /* YouTubeVideoPlayerViewModel.swift in Sources */, 02E685BE28E4B60A000AE015 /* CourseDetailsView.swift in Sources */, 02F175372A4DAFD20019CD70 /* CourseAnalytics.swift in Sources */, - 02B6B3BE28E1D15C00232911 /* CourseDetailsEndpoint.swift in Sources */, + 02B6B3BE28E1D15C00232911 /* CourseEndpoint.swift in Sources */, 02B6B3C328E1DCD100232911 /* CourseDetails.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Course/Course/Data/Network/CourseDetailsEndpoint.swift b/Course/Course/Data/Network/CourseEndpoint.swift similarity index 100% rename from Course/Course/Data/Network/CourseDetailsEndpoint.swift rename to Course/Course/Data/Network/CourseEndpoint.swift