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

Release 1.192 #1057

Merged
merged 16 commits into from
Sep 30, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Course info tab news teacher mode (#1052)
* Add statistics view

* Update CourseInfoTabNewsViewModel.swift

Delete comments.

* Add badges

* Fix ReplaceTemplateUsernameRule

* Update data representation

* Delete unused code
ivan-magda authored Sep 28, 2021
commit f62fb3b30ca44d4c3d751c9d952a2bb1b1821c66
20 changes: 20 additions & 0 deletions Stepic.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -755,6 +755,7 @@
2C7E293726B05680008581F4 /* StepQuizReviewStatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7E293626B05680008581F4 /* StepQuizReviewStatusesView.swift */; };
2C7EFCED24D08CFF003A4E93 /* NewProfileSocialProfilesSkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7EFCEC24D08CFF003A4E93 /* NewProfileSocialProfilesSkeletonView.swift */; };
2C7EFCEF24D08D80003A4E93 /* NewProfileSocialProfilesSkeletonProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7EFCEE24D08D80003A4E93 /* NewProfileSocialProfilesSkeletonProfileView.swift */; };
2C7F13CC26FE1A1400866E4C /* CourseInfoTabNewsBadgesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7F13CB26FE1A1400866E4C /* CourseInfoTabNewsBadgesView.swift */; };
2C7F4160269484DA00BD5C48 /* CourseInfoTabReviewsSummaryRatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7F415F269484DA00BD5C48 /* CourseInfoTabReviewsSummaryRatingView.swift */; };
2C7F416226948CCE00BD5C48 /* CourseInfoTabReviewsSummaryDistributionProgressesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7F416126948CCE00BD5C48 /* CourseInfoTabReviewsSummaryDistributionProgressesView.swift */; };
2C7F4164269492DF00BD5C48 /* CourseInfoTabReviewsSummaryDistributionCountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7F4163269492DF00BD5C48 /* CourseInfoTabReviewsSummaryDistributionCountsView.swift */; };
@@ -784,6 +785,7 @@
2C85EC0925599AD10059EF97 /* CatalogBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C85EC0825599AD10059EF97 /* CatalogBlock.swift */; };
2C85EC0E25599E9C0059EF97 /* CatalogBlockKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C85EC0D25599E9C0059EF97 /* CatalogBlockKind.swift */; };
2C85EC162559A57D0059EF97 /* CatalogBlockAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C85EC152559A57D0059EF97 /* CatalogBlockAppearance.swift */; };
2C85FCEE26FDE60700BD6BB9 /* CourseInfoTabNewsBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C85FCED26FDE60700BD6BB9 /* CourseInfoTabNewsBadgeView.swift */; };
2C87A7A12446502900933CA4 /* UsersPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C87A7A02446502900933CA4 /* UsersPersistenceService.swift */; };
2C87A7A424465B5900933CA4 /* ProfilesPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C87A7A324465B5800933CA4 /* ProfilesPersistenceService.swift */; };
2C87A7A82446646600933CA4 /* UserActivityEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C87A7A72446646600933CA4 /* UserActivityEntity.swift */; };
@@ -1484,6 +1486,7 @@
62E9888DF0D85074AFE0092C /* StepViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98395E3AF46CA25556A98 /* StepViewController.swift */; };
62E9889E935597A0ED849B0C /* ContentLanguageSwitchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E986932B6744B0958ABE36 /* ContentLanguageSwitchViewController.swift */; };
62E988A30D23C70799B8E4C3 /* CourseInfoTabInfoInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98CF68C4ED03FB36FA4F3 /* CourseInfoTabInfoInteractor.swift */; };
62E988A3F6BFDF9F0A9777BF /* CourseInfoTabNewsStatisticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98A19D9B85303250354DA /* CourseInfoTabNewsStatisticsView.swift */; };
62E988B311FB626588A8615D /* CodeDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98F4FA1EEC6CD7841F0D5 /* CodeDetailsView.swift */; };
62E988B9C5AE71A1CC86A209 /* LessonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E987AE3DC45621DD9315B4 /* LessonViewController.swift */; };
62E988C4572F2A9C97348C83 /* CodeQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9806901565B551EF1942F /* CodeQuizView.swift */; };
@@ -2718,6 +2721,7 @@
2C7E293626B05680008581F4 /* StepQuizReviewStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizReviewStatusesView.swift; sourceTree = "<group>"; };
2C7EFCEC24D08CFF003A4E93 /* NewProfileSocialProfilesSkeletonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewProfileSocialProfilesSkeletonView.swift; sourceTree = "<group>"; };
2C7EFCEE24D08D80003A4E93 /* NewProfileSocialProfilesSkeletonProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewProfileSocialProfilesSkeletonProfileView.swift; sourceTree = "<group>"; };
2C7F13CB26FE1A1400866E4C /* CourseInfoTabNewsBadgesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseInfoTabNewsBadgesView.swift; sourceTree = "<group>"; };
2C7F415F269484DA00BD5C48 /* CourseInfoTabReviewsSummaryRatingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseInfoTabReviewsSummaryRatingView.swift; sourceTree = "<group>"; };
2C7F416126948CCE00BD5C48 /* CourseInfoTabReviewsSummaryDistributionProgressesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseInfoTabReviewsSummaryDistributionProgressesView.swift; sourceTree = "<group>"; };
2C7F4163269492DF00BD5C48 /* CourseInfoTabReviewsSummaryDistributionCountsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseInfoTabReviewsSummaryDistributionCountsView.swift; sourceTree = "<group>"; };
@@ -2748,6 +2752,7 @@
2C85EC0825599AD10059EF97 /* CatalogBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogBlock.swift; sourceTree = "<group>"; };
2C85EC0D25599E9C0059EF97 /* CatalogBlockKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogBlockKind.swift; sourceTree = "<group>"; };
2C85EC152559A57D0059EF97 /* CatalogBlockAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogBlockAppearance.swift; sourceTree = "<group>"; };
2C85FCED26FDE60700BD6BB9 /* CourseInfoTabNewsBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseInfoTabNewsBadgeView.swift; sourceTree = "<group>"; };
2C87A7A02446502900933CA4 /* UsersPersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersPersistenceService.swift; sourceTree = "<group>"; };
2C87A7A324465B5800933CA4 /* ProfilesPersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilesPersistenceService.swift; sourceTree = "<group>"; };
2C87A7A52446635E00933CA4 /* Model_user_activity_v50.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_user_activity_v50.xcdatamodel; sourceTree = "<group>"; };
@@ -3520,6 +3525,7 @@
62E989FAD86F79364CC2EF89 /* ProgressesNetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressesNetworkService.swift; sourceTree = "<group>"; };
62E989FEFDC26A706157D1AB /* Analytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = "<group>"; };
62E98A1234FE9DA8BC81202A /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
62E98A19D9B85303250354DA /* CourseInfoTabNewsStatisticsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseInfoTabNewsStatisticsView.swift; sourceTree = "<group>"; };
62E98A1A76183E4780F9343C /* FullscreenCourseListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullscreenCourseListViewController.swift; sourceTree = "<group>"; };
62E98A1F1D23C325EB6288F9 /* WriteCommentOutputProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WriteCommentOutputProtocol.swift; sourceTree = "<group>"; };
62E98A2F0861B457FAABF490 /* CourseListDataFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseListDataFlow.swift; sourceTree = "<group>"; };
@@ -4983,6 +4989,15 @@
path = SocialProfiles;
sourceTree = "<group>";
};
2C7F13CA26FE19F400866E4C /* Badge */ = {
isa = PBXGroup;
children = (
2C7F13CB26FE1A1400866E4C /* CourseInfoTabNewsBadgesView.swift */,
2C85FCED26FDE60700BD6BB9 /* CourseInfoTabNewsBadgeView.swift */,
);
path = Badge;
sourceTree = "<group>";
};
2C7F415E26947F9E00BD5C48 /* Summary */ = {
isa = PBXGroup;
children = (
@@ -6887,7 +6902,9 @@
isa = PBXGroup;
children = (
2CF68CFF26FA022400EBB023 /* CourseInfoTabNewsCellView.swift */,
62E98A19D9B85303250354DA /* CourseInfoTabNewsStatisticsView.swift */,
2CF68CFC26FA01F900EBB023 /* CourseInfoTabNewsTableViewCell.swift */,
2C7F13CA26FE19F400866E4C /* Badge */,
);
path = Cell;
sourceTree = "<group>";
@@ -10827,6 +10844,7 @@
2CBC5AF52682437C0000F2D1 /* CourseRevenueTabPurchasesViewModel.swift in Sources */,
2C79F61821873CD9004CC082 /* NotificationsRequestOnlySettingsAlertPresenter.swift in Sources */,
2CCBDBE524AF93B8001A5A83 /* NewProfileDetailsViewModel.swift in Sources */,
2C7F13CC26FE1A1400866E4C /* CourseInfoTabNewsBadgesView.swift in Sources */,
081B7E2A1BAC208200554153 /* StandardsExtensions.swift in Sources */,
2CA867D225892B050006576E /* GridSimpleCourseListWidgetView.swift in Sources */,
2C3A1F5E24F3B1D300B4070F /* FillBlanksFeedback.swift in Sources */,
@@ -11038,6 +11056,7 @@
2CE9BF4A248D09FC004F6659 /* BlocksPersistenceService.swift in Sources */,
2CCDD80724F8A7D7006644A8 /* ApplicationShortcutService.swift in Sources */,
08F485A51C57AF2E000165AA /* FreeAnswerReply.swift in Sources */,
2C85FCEE26FDE60700BD6BB9 /* CourseInfoTabNewsBadgeView.swift in Sources */,
2C0A10A7268B279A001D4023 /* CourseBenefitByMonthsAPI.swift in Sources */,
2CCB4B1A26E77CED0056C44E /* AnnouncementsAPI.swift in Sources */,
08C1FC331F41E74500E14B46 /* QuizPresenter.swift in Sources */,
@@ -12360,6 +12379,7 @@
1009EF884D5CAC070717C737 /* CourseInfoTabNewsView.swift in Sources */,
D064B6AE4E478249DCE7B7F8 /* CourseInfoTabNewsViewController.swift in Sources */,
2BF2D5AC65ACA8B668CF93A6 /* CourseInfoTabNewsInputProtocol.swift in Sources */,
62E988A3F6BFDF9F0A9777BF /* CourseInfoTabNewsStatisticsView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6 changes: 6 additions & 0 deletions Stepic/Images.xcassets/Course info news/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "course-info-news-badge-correct.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "course-info-news-badge-eye-off.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "course-info-news-badge-mail.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "course-info-news-badge-timer.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -139,6 +139,11 @@ extension User {
}
}

var shortName: String {
let firstName = self.firstName.trimmed()
return firstName.isEmpty ? "User" : firstName
}

var fullName: String {
"\(self.firstName) \(self.lastName)".trimmed()
}
Original file line number Diff line number Diff line change
@@ -37,6 +37,13 @@ struct AnnouncementPlainObject: JSONSerializable {
let estimatedStartDate: Date?
let estimatedFinishDate: Date?
let noticeDates: [Date]

var isOneTimeEvent: Bool { !self.isInfinite && !self.onEnroll }

var isActiveEvent: Bool {
self.onEnroll
|| (self.isInfinite && (self.startDate == nil || self.startDate.require() < Date()))
}
}

extension AnnouncementPlainObject {
Original file line number Diff line number Diff line change
@@ -13,24 +13,4 @@ enum AnnouncementStatus: String {
let order: [AnnouncementStatus] = [.composing, .queueing, .queued, .sending, .scheduled, .sent, .aborted]
return order.firstIndex(of: self) ?? 0
}

var isReady: Bool {
let statuses: [AnnouncementStatus] = [.queueing, .queued, .sending, .sending]
return statuses.contains(self)
}

var isInProgress: Bool {
let statuses: [AnnouncementStatus] = [.queueing, .queued, .sending]
return statuses.contains(self)
}

var isQueuedOrSending: Bool {
let statuses: [AnnouncementStatus] = [.queued, .sending]
return statuses.contains(self)
}

var isStopped: Bool {
let statuses: [AnnouncementStatus] = [.scheduled, .sent, .aborted]
return statuses.contains(self)
}
}
Original file line number Diff line number Diff line change
@@ -173,15 +173,25 @@ final class AlwaysOpenedDetailsDisclosureBoxRule: ContentProcessingRule {
}

final class ReplaceTemplateUsernameRule: ContentProcessingRule {
private let username: String
private let shortName: String
private let fullName: String

init(username: String) {
self.username = username
init(shortName: String, fullName: String) {
self.shortName = shortName
self.fullName = fullName
}

func process(content: String) -> String {
content
.replacingOccurrences(of: "{{ user_name }}", with: self.username)
.replacingOccurrences(of: "{{user_name}}", with: self.username)
guard let shortRegex = try? Regex(string: "\\{\\{\\s*user_name\\s*\\}\\}", options: [.ignoreCase]),
let fullRegex = try? Regex(string: "\\{\\{\\s*user_full_name\\s*\\}\\}", options: [.ignoreCase]) else {
return content
}

var content = content

content.replaceAll(matching: shortRegex, with: self.shortName)
content.replaceAll(matching: fullRegex, with: self.fullName)

return content
}
}
Original file line number Diff line number Diff line change
@@ -45,9 +45,7 @@ final class CourseInfoTabInfoInstructorsBlockView: UIView {
self.headerView.icon = CourseInfoTabInfoView.Block.instructors.icon
self.headerView.title = CourseInfoTabInfoView.Block.instructors.title

if !self.stackView.arrangedSubviews.isEmpty {
self.stackView.removeAllArrangedSubviews()
}
self.stackView.removeAllArrangedSubviews()

instructors.forEach { instructor in
let view = CourseInfoTabInfoInstructorView()
Original file line number Diff line number Diff line change
@@ -12,7 +12,11 @@ final class CourseInfoTabNewsPresenter: CourseInfoTabNewsPresenterProtocol {
func presentCourseNews(response: CourseInfoTabNews.NewsLoad.Response) {
switch response.result {
case .success(let data):
self.makeNewsViewModels(data.announcements, currentUser: data.currentUser).done { viewModels in
self.makeNewsViewModels(
data.announcements,
course: data.course,
currentUser: data.currentUser
).done { viewModels in
let data = CourseInfoTabNews.NewsResultData(news: viewModels, hasNextPage: data.hasNextPage)
self.viewController?.displayCourseNews(viewModel: .init(state: .result(data: data)))
}
@@ -24,7 +28,11 @@ final class CourseInfoTabNewsPresenter: CourseInfoTabNewsPresenterProtocol {
func presentNextCourseNews(response: CourseInfoTabNews.NextNewsLoad.Response) {
switch response.result {
case .success(let data):
self.makeNewsViewModels(data.announcements, currentUser: data.currentUser).done { viewModels in
self.makeNewsViewModels(
data.announcements,
course: data.course,
currentUser: data.currentUser
).done { viewModels in
let data = CourseInfoTabNews.NewsResultData(news: viewModels, hasNextPage: data.hasNextPage)
self.viewController?.displayNextCourseNews(viewModel: .init(state: .result(data: data)))
}
@@ -37,12 +45,15 @@ final class CourseInfoTabNewsPresenter: CourseInfoTabNewsPresenterProtocol {

private func makeNewsViewModels(
_ announcements: [AnnouncementPlainObject],
course: Course,
currentUser: User?
) -> Guarantee<[CourseInfoTabNewsViewModel]> {
Guarantee { seal in
DispatchQueue.global(qos: .userInitiated).async {
let contentProcessor = self.makeContentProcessor(currentUser: currentUser)
let viewModels = announcements.map { self.makeViewModel($0, contentProcessor: contentProcessor) }
let viewModels = announcements.map {
self.makeViewModel($0, course: course, contentProcessor: contentProcessor)
}

DispatchQueue.main.async {
seal(viewModels)
@@ -53,24 +64,66 @@ final class CourseInfoTabNewsPresenter: CourseInfoTabNewsPresenterProtocol {

private func makeViewModel(
_ announcement: AnnouncementPlainObject,
course: Course,
contentProcessor: ContentProcessor
) -> CourseInfoTabNewsViewModel {
let formattedDate = FormatterHelper.dateStringWithFullMonthAndYear(announcement.sentDate ?? Date())
let date: Date = { () -> Date? in
if announcement.isActiveEvent && !course.canCreateAnnouncements,
let noticeDate = announcement.noticeDates.first {
return noticeDate
}
return announcement.sentDate
}() ?? Date()
let formattedDate = FormatterHelper.dateStringWithFullMonthAndYear(date)

let processedContent = contentProcessor.processContent(announcement.text)

let badge: CourseInfoTabNewsBadgeViewModel? = {
guard course.canCreateAnnouncements,
let status = announcement.status else {
return nil
}

return CourseInfoTabNewsBadgeViewModel(
status: status,
isOneTimeEvent: announcement.isOneTimeEvent,
isActiveEvent: announcement.isActiveEvent
)
}()

let statistics: CourseInfoTabNewsStatisticsViewModel? = {
guard course.canCreateAnnouncements else {
return nil
}

return CourseInfoTabNewsStatisticsViewModel(
publishCount: announcement.publishCount ?? 0,
queueCount: announcement.queueCount ?? 0,
sentCount: announcement.sentCount ?? 0,
openCount: announcement.openCount ?? 0,
clickCount: announcement.clickCount ?? 0
)
}()

return CourseInfoTabNewsViewModel(
uniqueIdentifier: "\(announcement.id)",
formattedDate: formattedDate,
subject: announcement.subject.trimmed(),
processedContent: processedContent
processedContent: processedContent,
badge: badge,
statistics: statistics
)
}

private func makeContentProcessor(currentUser: User?) -> ContentProcessor {
var rules = ContentProcessor.defaultRules
if let currentUser = currentUser, !currentUser.fullName.isEmpty {
rules.append(ReplaceTemplateUsernameRule(username: currentUser.fullName))
rules.append(
ReplaceTemplateUsernameRule(
shortName: currentUser.shortName,
fullName: FormatterHelper.username(currentUser)
)
)
}

var injections = ContentProcessor.defaultInjections
Loading