Skip to content

feat: Dates Tab integration with Calendar - #347

Merged
shafqat-muneer merged 9 commits into
openedx:developfrom
shafqat-muneer:Shafqat/LEARNER-9665
Mar 26, 2024
Merged

feat: Dates Tab integration with Calendar#347
shafqat-muneer merged 9 commits into
openedx:developfrom
shafqat-muneer:Shafqat/LEARNER-9665

Conversation

@shafqat-muneer

@shafqat-muneer shafqat-muneer commented Mar 17, 2024

Copy link
Copy Markdown

Jira Tickets:

  • LEARNER-9665: iOS - Dates Tab integration with Calendar
  • LEARNER-9855: iOS - Calendar Integration deep links implementation in local calendar events

Github Issues:

Light Mode Dark Mode
Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 36 45 Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 37 36
Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 36 51 Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 37 47
Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 36 55 Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 37 52
Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 37 11 Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 38 09
Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 37 18 Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 38 15
Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 37 21 Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 38 19
Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 39 38 Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 40 13
Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 41 29 Simulator Screenshot - iPhone 15 - 2024-03-15 at 12 40 30
Simulator Screenshot - iPhone 15 - 2024-03-18 at 01 06 21 Simulator Screenshot - iPhone 15 - 2024-03-18 at 01 06 54
Simulator Screenshot - iPhone 15 - 2024-03-18 at 01 06 25 Simulator Screenshot - iPhone 15 - 2024-03-18 at 01 06 58
Simulator Screenshot - iPhone 15 - 2024-03-18 at 01 06 31 Simulator Screenshot - iPhone 15 - 2024-03-18 at 01 07 32

Demos

Light Mode: Sync and un-sync events in calendar application:

Simulator.Screen.Recording.-.iPhone.15.-.2024-03-15.at.12.42.44.mp4

Light Mode: Update out dated calendar:

Simulator.Screen.Recording.-.iPhone.15.-.2024-03-15.at.12.49.21.mp4

Light Mode: Deep Linking:

RPReplay_Final1710750802.mp4

Dark Mode: Sync and un-sync events in calendar application:

Simulator.Screen.Recording.-.iPhone.15.-.2024-03-15.at.12.43.27.mp4

Dark Mode: Update out dated calendar:

Simulator.Screen.Recording.-.iPhone.15.-.2024-03-15.at.12.50.38.mp4

@volodymyr-chekyrta

Copy link
Copy Markdown
Contributor

Hey @shafqat-muneer, thanks, looks great! 🚀
Do you know why the deep link from the calendar app didn't take the user to the specific course page?
I thought it would work after @eyatsenkoperpetio introduced #329
Do we need to develop anything else to make it work like that?

RPReplay_Final1710750802.mp4

Comment thread OpenEdX/Info.plist Outdated
Comment on lines +34 to +37
<key>NSCalendarsUsageDescription</key>
<string>edX would like to use your calendar list to subscribe to your personalized edX calendar for this course.</string>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>edX would like to use your calendar list to subscribe to your personalized edX calendar for this course.</string>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please not hardcode the name of the edX app?
Ideally, we should use platformName or something similar from the config.yaml.

@shafqat-muneer

Copy link
Copy Markdown
Author

Hey @shafqat-muneer, thanks, looks great! 🚀 Do you know why the deep link from the calendar app didn't take the user to the specific course page? I thought it would work after @eyatsenkoperpetio introduced #329 Do we need to develop anything else to make it work like that?

RPReplay_Final1710750802.mp4

Hi @volodymyr-chekyrta, thanks for the review.
I've just finished implementing the generation of deep link URLs for the calendar in this pull request. That's why it hasn't been included in #329. I'll proceed to open another pull request to direct the user to the specific course page.

@volodymyr-chekyrta

Copy link
Copy Markdown
Contributor

Hey @shafqat-muneer, thanks, looks great! 🚀 Do you know why the deep link from the calendar app didn't take the user to the specific course page? I thought it would work after @eyatsenkoperpetio introduced #329 Do we need to develop anything else to make it work like that?
RPReplay_Final1710750802.mp4

Hi @volodymyr-chekyrta, thanks for the review. I've just finished implementing the generation of deep link URLs for the calendar in this pull request. That's why it hasn't been included in #329. I'll proceed to open another pull request to direct the user to the specific course page.

Makes sense to me, thank you! 👍

rnr
rnr previously approved these changes Mar 19, 2024

@rnr rnr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, some small changes

import Core
import BranchSDK

enum DeepLinkType: String {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@shafqat-muneer maybe we could re-use DeepLinkType and DeepLinkKeys defined in DeepLink class to avoid duplication?
Thank you

courseID: courseID,
title: calendarName,
isOn: true,
modalPresented: false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you move the bracket to the next line to respect the "parameter on its own line" style?


if viewModel.dueDatesShifted {
DatesShiftedSuccessView(selectedTab: .dates, courseDatesViewModel: viewModel)
DatesSuccessView(title: CourseLocalization.CourseDates.toastSuccessTitle,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"parameter on its own line" style please

CoreAssets.syncToCalendar.swiftUIImage
Toggle(
CourseLocalization.CourseDates.syncToCalendar,
isOn: $viewModel.isOn)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"parameter on its own line" style please


if viewModel.dueDatesShifted && !isVideo {
DatesShiftedSuccessView(selectedTab: .course, courseContainerViewModel: viewModel) {
DatesSuccessView(title: CourseLocalization.CourseDates.toastSuccessTitle,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"parameter on its own line" style please

@saeedbashir saeedbashir left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. On adding the second calendar the syncing calendar keeps on screen and never goes away.
  2. On selecting cancel remove on remove calendar alert, the switch should turns back on.

Comment on lines +53 to +63
DatesSuccessView(
title: CourseLocalization.CourseDates.calendarEvents,
message: CourseLocalization.CourseDates.calendarEventsAdded,
selectedTab: .dates
) {
Task {
await MainActor.run {
viewModel.calendarEventsAdded = false
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This code is being shared between the following three conditions calendarEventsAdded, calendarEventsRemoved, and calendarEventsUpdated. How about moving it to a function and use that. Even that func can be used for dueDatesShifted with an additional param.

Comment on lines +19 to +21
@Published var calendarEventsAdded: Bool = false
@Published var calendarEventsRemoved: Bool = false
@Published var calendarEventsUpdated: Bool = false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Instead of using a boolean for each condition, how about using an enum for these cases?

},
okTapped: {
self.router.dismiss(animated: true)
self.removeCourseCalendar { [weak self] success in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

success is unused here, so it can be replaced with _.

positiveAction: CourseLocalization.CourseDates.calendarShiftPromptUpdateNow,
onCloseTapped: {
// Remove course calendar
self.router.dismiss(animated: true)

Copy link
Copy Markdown
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 in closures? if so please update it for all alert functions.

get {
if let calendarEntry = calendarEntry,
let localCalendar = localCalendar {
if calendarEntry.identifier == localCalendar.calendarIdentifier {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it possible to move this if condition to where clause of if let?

@shafqat-muneer

Copy link
Copy Markdown
Author

@saeedbashir I have made the recommended adjustments. Kindly review them.

Comment on lines +102 to +103
} else {
if let localCalendar = localCalendar {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess else and il let can be merged like else if let

),
positiveAction: CoreLocalization.Alert.accept,
onCloseTapped: { [weak self] in
guard let self else { return }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems strong reference if self isn't required in this type of blocks because if the self will be nill, by using the optional self will not crash the app and following code will automatically be ignored.

@shafqat-muneer

Copy link
Copy Markdown
Author

@saeedbashir I have addressed requested changes.

@volodymyr-chekyrta Please also review this pull request. Thank you.

saeedbashir
saeedbashir previously approved these changes Mar 25, 2024

@saeedbashir saeedbashir left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 👍

rnr
rnr previously approved these changes Mar 25, 2024

@volodymyr-chekyrta volodymyr-chekyrta left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 👍
Just one question

Comment on lines +102 to +112
return DatesSuccessView(
title: title,
message: message,
selectedTab: .dates
) {
Task {
await MainActor.run {
viewModel.eventState = CourseDatesViewModel.EventState.none
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can it be simplified just to something like this?

return DatesSuccessView(
                title: title,
                message: message,
                selectedTab: .dates
            ) {
                viewModel.eventState = CourseDatesViewModel.EventState.none
            }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems a bit odd. We switch to a Task only to switch back to the MainActor.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@volodymyr-chekyrta Thank you for bringing that to my attention. I've streamlined the logic for simplification.

Explanation:
The DatesSuccessView struct was previously invoking action closures asynchronously on a background thread. Upon receiving the closure action, we had to switch back to the main thread to avoid warnings such as:

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

I've refactored this logic so that the action closures in DatesSuccessView are no longer asynchronous.

@shafqat-muneer
shafqat-muneer merged commit d02b303 into openedx:develop Mar 26, 2024
@shafqat-muneer
shafqat-muneer deleted the Shafqat/LEARNER-9665 branch March 26, 2024 12:39
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.

4 participants