Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ability to shift courses dates if deadlines have been missed #288

Merged
merged 15 commits into from
Mar 1, 2024

Conversation

shafqat-muneer
Copy link
Contributor

@shafqat-muneer shafqat-muneer commented Feb 18, 2024

Jira Ticket: LEARNER-9781: iOS - Ability to shift courses dates if deadlines have been missed

Github Issues: #161

Light Mode Dark Mode
Simulator Screenshot - iPhone 15 - 2024-02-19 at 01 34 40 Simulator Screenshot - iPhone 15 - 2024-02-19 at 11 00 18
Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 27 33 Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 27 56
Simulator Screenshot - iPhone 15 - 2024-02-19 at 01 34 47 Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 28 00
Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 35 36 Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 35 47
Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 40 02 Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 40 08
Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 42 49 Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 42 55
Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 31 57 Simulator Screenshot - iPhone 15 - 2024-02-19 at 10 32 05

@saeedbashir saeedbashir linked an issue Feb 19, 2024 that may be closed by this pull request
Copy link
Contributor

@saeedbashir saeedbashir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Getting response validation failed error for course course-v1:USMx+PGPM661.3x+2T2022 while getting course dates status.
  • It would be nice if dates status info view appear with animation like resume course.
  • Please remove all the temp files pushed like [.mocky3C3F2BD4-1ED2-461C-96E3-33CAE5082F47/.config.yml.tmp](https://github.com/openedx/openedx-app-ios/pull/288/files#diff-ae688431c3fdb1778a03a28b5d0af9281947494db8d4c254fe74bcce1cf97637)

import Combine
import Theme

public struct SuccessViewWithButton: View {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming it to DatesShiftedSuccessView Thoughts?

Comment on lines 111 to 114
"COURSE_DATES.TOAST_SUCCESS_MESSAGE" = "Your due dates have been successfully shifted to help you stay on track.";
"COURSE_DATES.VIEW_ALL_DATES" = "View all dates";

"COURSE_DATES.RESET_DATE.RESET_DATE_BANNER.BODY" = "Don't worry - shift our suggested schedule to complete past due assignments without losing any progress.";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if they can move these to course module as they are related to course.

@@ -81,6 +84,7 @@ public class CourseContainerViewModel: BaseCourseViewModel {
super.init(manager: manager)

addObservers()
addNotificationObserver()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to define new method, new observers can be added to addObservers.

@@ -47,6 +49,7 @@ public class CourseContainerViewModel: BaseCourseViewModel {
private let authInteractor: AuthInteractorProtocol
private let analytics: CourseAnalytics
private(set) var storage: CourseStorage
private var shiftCourseDatesObserver: NSObjectProtocol?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this observer declaration isn't needed and work can be done by NotificationCenter.default.addObserver(.....

Comment on lines 479 to 481
let center = NotificationCenter.default
guard let shiftCourseDatesObserver = self.shiftCourseDatesObserver else { return }
center.removeObserver(shiftCourseDatesObserver)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to remove a specific observer, you can simply call NotificationCenter.default.removeObserver(self).

Comment on lines 130 to 132
guard let strongSelf = self else {
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can be updated to guard let self.

guard let strongSelf = self else {
return
}
Task {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we can define addNotificationObserver as MainActor so we don't have to define tasks here and even we can omit DispatchQueue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we designate it with @MainActor and eliminate the Task closure, we would have to utilize an async function here. However, this change leads to certain exceptions. To resolve this issue, we may consider employing addObserver(forName:object:queue:using:). Currently, this method is not being utilized for notifications in the application.

Comment on lines 497 to 502
Task {
await strongSelf.getCourseBlocks(courseID: courseID, withProgress: true)
await strongSelf.getCourseDeadlineInfo(courseID: courseID, withProgress: true)
DispatchQueue.main.async {
strongSelf.dueDatesShifted = true
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about defining addNotificationObserver as MainActor so we don't have to define Task here and even can omit DispatchQueue


@State private var openCertificateView: Bool = false
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }

@State private var showingDownloads: Bool = false
@State private var showingVideoDownloadQuality: Bool = false
@State private var showingNoWifiMessage: Bool = false
@Binding private var selection: Int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming it to some more meaning name like selectedTabIndex or something similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ScrollSlidingTabBar also employs an identical naming convention and logic for consistency purposes.

Podfile.lock Outdated
@@ -182,4 +182,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 544edab2f9ecc4ac18973fb8865f1d0613ec8a28

COCOAPODS: 1.15.0
COCOAPODS: 1.14.3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to push files generated with a lower version.

@shafqat-muneer
Copy link
Contributor Author

  • Getting response validation failed error for course course-v1:USMx+PGPM661.3x+2T2022 while getting course dates status.
  • It would be nice if dates status info view appear with animation like resume course.
  • Please remove all the temp files pushed like [.mocky3C3F2BD4-1ED2-461C-96E3-33CAE5082F47/.config.yml.tmp](https://github.com/openedx/openedx-app-ios/pull/288/files#diff-ae688431c3fdb1778a03a28b5d0af9281947494db8d4c254fe74bcce1cf97637)

Done with 2nd and 3rd point.
The first one pertains to error handling and should be addressed in a distinct pull request, as it is unrelated to the modifications made in this pull request.

@volodymyr-chekyrta
Copy link
Contributor

I haven't finished the review yet, but most of the code looks good.

I have a slight concern about this view.
It doesn't look like a notification or alert from the Open edX mobile design system.
Button with no rounded corners, the X button paddings.
image
@marcotuts @sdaitzman, could you please take a look from a design perspective?

rnr
rnr previously approved these changes Feb 26, 2024
Copy link
Contributor

@rnr rnr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@@ -224,9 +237,6 @@ public class CourseRepository: CourseRepositoryProtocol {
}
}

// Mark - For testing and SwiftUI preview
// swiftlint:disable all
#if DEBUG
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shafqat-muneer I don't think we need to remove this condition for Mock purposes

}
}

struct SuccessViewWithButton_Previews: PreviewProvider {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add #if DEBUG

}

#if DEBUG
struct AdjustScheduleView_Previews: PreviewProvider {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if you can rename the preview class as per the class name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for highlighting it; It was skipped during struct rename.

}

#if DEBUG
struct SuccessViewWithButton_Previews: PreviewProvider {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if you could rename the preview as per the class name.

Comment on lines 80 to 81
"COURSE_DATES.TOMORROW" = "Tomorrow";
"COURSE_DATES.YESTERDAY" = "Yesterday";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to duplicate these keys, we can use them from the Core.

@moiz994
Copy link

moiz994 commented Feb 26, 2024

I haven't finished the review yet, but most of the code looks good.
I have a slight concern about this view.
It doesn't look like a notification or alert from the Open edX mobile design system.
Button with no rounded corners, the X button paddings.
image
@marcotuts @sdaitzman, could you please take a look from a design perspective?

@volodymyr-chekyrta Regarding the above message, we are trying to display a message in the form of a snack bar similar to Adroid's native snack bar. Currently, it's replicating what the live application shows and I don't think such a component exists in the schema design system. Let me reach out to marco and sam on slack to finalize the approach for this.

@volodymyr-chekyrta
Copy link
Contributor

I haven't finished the review yet, but most of the code looks good.
I have a slight concern about this view.
It doesn't look like a notification or alert from the Open edX mobile design system.
Button with no rounded corners, the X button paddings.
image
@marcotuts @sdaitzman, could you please take a look from a design perspective?

@volodymyr-chekyrta Regarding the above message, we are trying to display a message in the form of a snack bar similar to Adroid's native snack bar. Currently, it's replicating what the live application shows and I don't think such a component exists in the schema design system. Let me reach out to marco and sam on slack to finalize the approach for this.

@moiz994 We have this type of view in the profile and a few other screens.
Paddings are different, and all buttons have rounded corners.
We can discuss it tomorrow at the Mobile Design Weekly meeting.

image

@moiz994
Copy link

moiz994 commented Feb 26, 2024

I haven't finished the review yet, but most of the code looks good.
I have a slight concern about this view.
It doesn't look like a notification or alert from the Open edX mobile design system.
Button with no rounded corners, the X button paddings.
image
@marcotuts @sdaitzman, could you please take a look from a design perspective?

@volodymyr-chekyrta Regarding the above message, we are trying to display a message in the form of a snack bar similar to Adroid's native snack bar. Currently, it's replicating what the live application shows and I don't think such a component exists in the schema design system. Let me reach out to marco and sam on slack to finalize the approach for this.

@moiz994 We have this type of view in the profile and a few other screens. Paddings are different, and all buttons have rounded corners. We can discuss it tomorrow at the Mobile Design Weekly meeting.

image

Thanks for sharing, @volodymyr-chekyrta! I can't find these components in the figma design file but I think the team is updating the snack bar to match this styling.

@sdaitzman
Copy link

sdaitzman commented Feb 26, 2024

Thanks for flagging this for design review. I added this to our mobile design weekly agenda for tomorrow, and can add the missing snack bar component in Figma. I also think the snack bar background in dark mode should be darker to improve contrast and match the rest of the app, will make sure to include that change in Figma:

image image

I also wonder if the upgrade suggestion messages should include an inline upgrade button to offer a clear path to upgrades?

image

Everything else looks good to me!

@shafqat-muneer
Copy link
Contributor Author

@sdaitzman I have implemented the snackbarInfoAlert background to accommodate both darker and lighter modes of alerts. Please assess whether the changes appear to be an enhancement.
Light Mode:
image
Dark Mode:
image

Regarding second point of inline upgrade button, it's intentional because:

Mobile payments are not implemented yet and to avoid breaking appstore guidelines, upgrade button is hidden, which leads user to payments

@sdaitzman
Copy link

Thanks, @shafqat-muneer. Makes sense about mobile payments.

Unfortunately, using light text on the info background actually has a worse contrast ratio.

image

I've created an early rough draft of this bottom dialog in Figma this morning, screenshots attached below. I want to run this by the mobile design weekly group tomorrow morning, then will follow up with any design updates to get this unblocked quickly.

image

@volodymyr-chekyrta
Copy link
Contributor

@shafqat-muneer, at the Mobile Design Weekly meeting, we discussed and agreed on a design for the "Due Dates Shifted" view/snackbar.
It's coming soon; @sdaitzman is working on it.

@sdaitzman
Copy link

sdaitzman commented Feb 27, 2024

@shafqat-muneer here is the proposed design, attached and viewable in Figma. When the user shifts due dates from within the Course tab, the dialog should include the button to View All Dates. When the user shifts due dates from within the CTA inside the Dates tab, the dialog should not include a View All Dates button.

Also want to make sure to note the wider dialog, which helps to distinguish this snack bar view from the content underneath.

With View All Dates, for within the Course tab
image

Without View All Dates, for within the Dates tab
image

In-context within Course tab (Figma link) In-context within Dates tab (Figma link)
image image

@shafqat-muneer
Copy link
Contributor Author

@sdaitzman Thank you for the design review and for providing all necessary details. 👏 I will address the change and share details.

@shafqat-muneer
Copy link
Contributor Author

@sdaitzman @volodymyr-chekyrta snackbar design has been updated.

Light Mode Course Tab Dark Mode Course Tab
Simulator Screenshot - iPhone 15 - 2024-02-28 at 16 53 42 Simulator Screenshot - iPhone 15 - 2024-02-28 at 16 53 50
Light Mode Dates Tab Dark Mode Dates Tab
Simulator Screenshot - iPhone 15 - 2024-02-28 at 16 54 13 Simulator Screenshot - iPhone 15 - 2024-02-28 at 16 54 10

@sdaitzman
Copy link

Thanks @shafqat-muneer, this looks great!

Comment on lines +503 to +504
func resetDueDatesShiftedFlag() {
dueDatesShifted = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a method just to change one variable? Can we directly change it's value as the variable isn't private.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable cannot be set as private since it is a @Published property and is being displayed in various views. The reason for creating a method to set its default value is to ensure that the view model is responsible for updating the property value, maintaining a single source of truth.

@MainActor
func shiftDueDates(courseID: String, withProgress: Bool = true) async {
isShowProgress = withProgress
dueDatesShifted = false
Copy link
Contributor

@saeedbashir saeedbashir Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to set it to false, as it will be false by default and even after dates shift?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I utilized it specifically for generating mock data. The initializer won't be invoked again if we remain on the same screen and trigger the shift due dates request within a time span of 5 seconds. The snackbar will not be hidden before the next API call. Indeed, you're correct, in a real-world scenario, this situation is unlikely to occur. Therefore, I am just removing it.

await self.getCourseDeadlineInfo(courseID: courseID, withProgress: true)
}
DispatchQueue.main.async {
self.dueDatesShifted = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using weak self here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would even ask you not to use Concurrency and GCD frameworks at the same time.
Please use MainActor.run or similar approach to switch to the main thread.

@objc private func handleShiftDueDates(_ notification: Notification) {
if let courseID = notification.object as? String {
Task {
await self.getCourseDates(courseID: courseID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self is redundant.

Comment on lines 134 to 135
guard let self else { return }
self.dueDatesShifted = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check wether the self exists or not, can't self?.dueDatesShifted = true be ignored if the self will be nil?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, please do not combine Swift Concurrency and GCD frameworks.
Use the Main Actor instead.

Comment on lines 272 to 281
do {
let courseDates = try
await getCourseDates(courseID: courseID)
return CourseDateBanner(
datesBannerInfo: courseDates.datesBannerInfo,
hasEnded: courseDates.hasEnded
)
} catch {
throw error
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to catch an error just to throw it further?
Seems like we can simplify by removing the do-catch block.

}

// Cases are defied according to this link
// https://2u-internal.atlassian.net/browse/LEARNER-7724?focusedCommentId=479226
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This private link does not make sense for the Open edX repo and community.
Better to describe the entire flow here or move it to the documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cases are self explanatory and I believe we can remove this reference link.

@@ -37,6 +39,10 @@ enum CourseEndpoint: EndPointType {
return url
case .getCourseDates(let courseID):
return "/api/course_home/v1/dates/\(courseID)"
case .getCourseDeadlineInfo(courseID: let courseID):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case .getCourseDeadlineInfo(courseID: let courseID):
can be simplified:
case .getCourseDeadlineInfo(let courseID):


NotificationCenter.default.addObserver(
self,
selector: #selector(handleShiftDueDates(_:)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not mistaken, just #selector(handleShiftDueDates) is enough.

private func addObservers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleShiftDueDates(_:)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#selector(handleShiftDueDates)

@volodymyr-chekyrta
Copy link
Contributor

👍 🚀 🔥

@shafqat-muneer
Copy link
Contributor Author

@volodymyr-chekyrta @saeedbashir Feedback addressed 🎉

Copy link
Contributor

@volodymyr-chekyrta volodymyr-chekyrta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Copy link
Contributor

@saeedbashir saeedbashir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@shafqat-muneer shafqat-muneer merged commit be22a55 into openedx:develop Mar 1, 2024
3 checks passed
@shafqat-muneer shafqat-muneer deleted the Shafqat/LEARNER-9781 branch March 1, 2024 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[iOS] Option "Shift due date"
6 participants