diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt
index 78014b3eae..545a533a27 100644
--- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt
+++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/fragment/StudyPlanFragment.kt
@@ -202,6 +202,9 @@ class StudyPlanFragment :
)
)
}
+ is StudyPlanScreenFeature.Action.ViewAction.NotificationDailyStudyReminderWidgetViewAction -> {
+ // TODO: ALTAPPS-1347 handle notification daily study reminder widget view actions
+ }
}
}
diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml
index e86114ef5e..a0229bac58 100644
--- a/config/detekt/baseline.xml
+++ b/config/detekt/baseline.xml
@@ -178,6 +178,8 @@
MaxLineLength:ExpandableTextView.kt$ExpandableTextView$*
MaxLineLength:HtmlText.kt$text
MaxLineLength:LinearProgressIndicator.kt$*
+ MaxLineLength:NotificationDailyStudyReminderWidgetComponentImpl.kt$NotificationDailyStudyReminderWidgetComponentImpl$override
+ MaxLineLength:NotificationDailyStudyReminderWidgetComponentImpl.kt$NotificationDailyStudyReminderWidgetComponentImpl$private
MaxLineLength:ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticEvent.kt$ProjectSelectionDetailsClickedSelectThisProjectHyperskillAnalyticEvent$*
MaxLineLength:RepositoryCacheProxy.kt$RepositoryCacheProxy$*
MaxLineLength:StepComponentImpl.kt$StepComponentImpl$nextLearningActivityStateRepository = appGraph.stateRepositoriesComponent.nextLearningActivityStateRepository
diff --git a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
index aee7473de5..5a407b33f9 100644
--- a/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
+++ b/iosHyperskillApp/iosHyperskillApp.xcodeproj/project.pbxproj
@@ -307,6 +307,7 @@
2C80D4FD288C4D0D00B2CD1E /* StepQuizCodeFullScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C80D4FC288C4D0D00B2CD1E /* StepQuizCodeFullScreenViewModel.swift */; };
2C80D4FF288C4D4400B2CD1E /* StepQuizCodeFullScreenOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C80D4FE288C4D4400B2CD1E /* StepQuizCodeFullScreenOutputProtocol.swift */; };
2C80D503288C5EBB00B2CD1E /* StepQuizCodeNavigationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C80D502288C5EBB00B2CD1E /* StepQuizCodeNavigationState.swift */; };
+ 2C8118292CA3BEB600DCE9A8 /* NotificationDailyStudyReminderWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8118282CA3BEB600DCE9A8 /* NotificationDailyStudyReminderWidgetView.swift */; };
2C829B912B88583300765335 /* StepQuizUnsupportedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C829B902B88583300765335 /* StepQuizUnsupportedView.swift */; };
2C82BA322844B01D004C9013 /* PlaceholderView+Configurations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C82BA312844B01D004C9013 /* PlaceholderView+Configurations.swift */; };
2C83FBBE2B177633007AD7E2 /* LeaderboardTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C83FBBD2B177633007AD7E2 /* LeaderboardTab.swift */; };
@@ -1118,6 +1119,7 @@
2C80D4FC288C4D0D00B2CD1E /* StepQuizCodeFullScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeFullScreenViewModel.swift; sourceTree = ""; };
2C80D4FE288C4D4400B2CD1E /* StepQuizCodeFullScreenOutputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeFullScreenOutputProtocol.swift; sourceTree = ""; };
2C80D502288C5EBB00B2CD1E /* StepQuizCodeNavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizCodeNavigationState.swift; sourceTree = ""; };
+ 2C8118282CA3BEB600DCE9A8 /* NotificationDailyStudyReminderWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationDailyStudyReminderWidgetView.swift; sourceTree = ""; };
2C829B902B88583300765335 /* StepQuizUnsupportedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepQuizUnsupportedView.swift; sourceTree = ""; };
2C82BA312844B01D004C9013 /* PlaceholderView+Configurations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlaceholderView+Configurations.swift"; sourceTree = ""; };
2C83FBBD2B177633007AD7E2 /* LeaderboardTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderboardTab.swift; sourceTree = ""; };
@@ -1937,6 +1939,7 @@
2CB0ADE92B04AC9E0089D557 /* HomeSubmodules */,
E6992F3BBF430924F32DC178 /* Leaderboard */,
1B096FE500BA52CA6E56B26D /* ManageSubscription */,
+ 2C8118272CA3BE9900DCE9A8 /* NotificationDailyStudyReminderWidget */,
B58361EACE24BF4B761F10BA /* NotificationsOnboarding */,
2C9320F32B68F13000999992 /* Paywall */,
2CE3F5BA2BD7AE7C000B51A4 /* ProblemsLimitInfo */,
@@ -2950,6 +2953,14 @@
path = Cells;
sourceTree = "";
};
+ 2C8118272CA3BE9900DCE9A8 /* NotificationDailyStudyReminderWidget */ = {
+ isa = PBXGroup;
+ children = (
+ 2C8118282CA3BEB600DCE9A8 /* NotificationDailyStudyReminderWidgetView.swift */,
+ );
+ path = NotificationDailyStudyReminderWidget;
+ sourceTree = "";
+ };
2C82BA302844AFED004C9013 /* PlaceholderView */ = {
isa = PBXGroup;
children = (
@@ -5378,6 +5389,7 @@
2C9AA3F52C24611300F5170E /* WelcomeOnboardingChooseProgrammingLanguageView.swift in Sources */,
2C20FBA8284F193A006D879E /* ContentProcessingRule.swift in Sources */,
E9AB311429DED7FE00645376 /* StudyPlanSectionItemIconView.swift in Sources */,
+ 2C8118292CA3BEB600DCE9A8 /* NotificationDailyStudyReminderWidgetView.swift in Sources */,
2C7CB66B2ADFB947006F78DA /* StepQuizFillBlanksAssembly.swift in Sources */,
2CBC97CA2A5553330078E445 /* StageImplementStageCompletedModalViewController.swift in Sources */,
2C20FBC9284F6F97006D879E /* UnitConverters.swift in Sources */,
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift
index 5b799f3d31..b8c400e5af 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Models/Constants/Strings.swift
@@ -1,3 +1,4 @@
+// swiftlint:disable all
import Foundation
import shared
@@ -279,6 +280,11 @@ enum Strings {
static let subtitle = sharedStrings.users_interview_widget_subtitle.localized()
}
+ enum NotificationDailyStudyReminderWidget {
+ static let title = sharedStrings.notification_daily_study_reminder_widget_title.localized()
+ static let subtitle = sharedStrings.notification_daily_study_reminder_widget_subtitle.localized()
+ }
+
// MARK: - Topics widget -
enum TopicsWidget {
@@ -673,3 +679,4 @@ enum Strings {
static let showMoreButton = sharedStrings.comments_show_more_btn.localized()
}
}
+// swiftlint:enable all
\ No newline at end of file
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NotificationDailyStudyReminderWidget/NotificationDailyStudyReminderWidgetView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NotificationDailyStudyReminderWidget/NotificationDailyStudyReminderWidgetView.swift
new file mode 100644
index 0000000000..c110a03406
--- /dev/null
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/NotificationDailyStudyReminderWidget/NotificationDailyStudyReminderWidgetView.swift
@@ -0,0 +1,88 @@
+import SwiftUI
+
+extension NotificationDailyStudyReminderWidgetView {
+ struct Appearance {
+ let illustrationSize = CGSize(width: 89, height: 86)
+
+ let spacing = LayoutInsets.defaultInset
+ let interitemSpacing = LayoutInsets.smallInset
+ }
+}
+
+struct NotificationDailyStudyReminderWidgetView: View {
+ private(set) var appearance = Appearance()
+
+ var onCallToAction: () -> Void
+ var onClose: () -> Void
+ var onViewedEvent: () -> Void
+
+ var body: some View {
+ ZStack {
+ UIViewControllerEventsWrapper(onViewDidAppear: onViewedEvent)
+
+ Button(
+ action: onCallToAction,
+ label: {
+ ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)) {
+ HStack(alignment: .center, spacing: appearance.spacing) {
+ textConent
+ illustration
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding([.leading, .vertical])
+
+ closeButton
+ }
+ .foregroundColor(.white)
+ .background(backgroundGradient)
+ }
+ )
+ .buttonStyle(BounceButtonStyle())
+ }
+ }
+
+ private var textConent: some View {
+ VStack(alignment: .leading, spacing: appearance.interitemSpacing) {
+ Text(Strings.NotificationDailyStudyReminderWidget.title)
+ .font(.headline)
+ Text(Strings.NotificationDailyStudyReminderWidget.subtitle)
+ .font(.subheadline)
+ }
+ }
+
+ private var illustration: some View {
+ Image(.usersInterviewWidgetIllustration)
+ .renderingMode(.original)
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(size: appearance.illustrationSize)
+ }
+
+ private var closeButton: some View {
+ Button(
+ action: onClose,
+ label: {
+ Image(systemName: "xmark.circle.fill")
+ .padding(.all, appearance.interitemSpacing)
+ }
+ )
+ }
+
+ private var backgroundGradient: some View {
+ Image(.usersInterviewWidgetGradient)
+ .renderingMode(.original)
+ .resizable()
+ .addBorder(color: .clear, width: 0)
+ }
+}
+
+#if DEBUG
+#Preview {
+ NotificationDailyStudyReminderWidgetView(
+ onCallToAction: {},
+ onClose: {},
+ onViewedEvent: {}
+ )
+ .padding()
+}
+#endif
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/Toolbar/ToolbarProgress/SpacebotWowAnimationView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/Toolbar/ToolbarProgress/SpacebotWowAnimationView.swift
index 661a5a4f3a..b512487e1a 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/Toolbar/ToolbarProgress/SpacebotWowAnimationView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/Step/Views/Toolbar/ToolbarProgress/SpacebotWowAnimationView.swift
@@ -42,12 +42,12 @@ final class SpacebotWowAnimationView: UIView {
animationView.play { [weak self] completed in
completion?(completed)
- guard let self else {
+ guard let strongSelf = self else {
return
}
- self.animationView.alpha = self.appearance.animationViewAlphaHidden
- self.animationView.currentProgress = 0
+ strongSelf.animationView.alpha = strongSelf.appearance.animationViewAlphaHidden
+ strongSelf.animationView.currentProgress = 0
}
}
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizMatching/StepQuizMatchingViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizMatching/StepQuizMatchingViewModel.swift
index b721d2b292..9276c258cc 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizMatching/StepQuizMatchingViewModel.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StepQuizSubmodules/StepQuizMatching/StepQuizMatchingViewModel.swift
@@ -52,24 +52,24 @@ final class StepQuizMatchingViewModel: ObservableObject, StepQuizChildQuizInputP
selectedColumnsIDs: matchItem.option != nil ? [matchItem.option.require().id] : [],
isMultipleChoice: false,
onColumnsSelected: { [weak self] selectedColumnsIDs in
- guard let self,
+ guard let strongSelf = self,
let selectedColumnID = selectedColumnsIDs.first,
matchItem.option?.id != selectedColumnID,
- let currentItemIndex = self.viewData.items.firstIndex(of: matchItem) else {
+ let currentItemIndex = strongSelf.viewData.items.firstIndex(of: matchItem) else {
return
}
- if let swappingIndex = self.viewData.items.firstIndex(where: { $0.option?.id == selectedColumnID }) {
- let tmp = self.viewData.items[currentItemIndex].option
- self.viewData.items[currentItemIndex].option = self.viewData.items[swappingIndex].option
- self.viewData.items[swappingIndex].option = tmp
+ if let swapIndex = strongSelf.viewData.items.firstIndex(where: { $0.option?.id == selectedColumnID }) {
+ let tmp = strongSelf.viewData.items[currentItemIndex].option
+ strongSelf.viewData.items[currentItemIndex].option = strongSelf.viewData.items[swapIndex].option
+ strongSelf.viewData.items[swapIndex].option = tmp
} else {
- self.viewData.items[currentItemIndex].option = self.options.first(
+ strongSelf.viewData.items[currentItemIndex].option = strongSelf.options.first(
where: { $0.id == selectedColumnID }
)
}
- self.outputCurrentReply()
+ strongSelf.outputCurrentReply()
}
)
}
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanAssembly.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanAssembly.swift
index 7131fe2e94..619bab60ed 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanAssembly.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanAssembly.swift
@@ -5,6 +5,7 @@ final class StudyPlanAssembly: UIKitAssembly {
let studyPlanScreenComponent = AppGraphBridge.sharedAppGraph.buildStudyPlanScreenComponent()
let viewModel = StudyPlanViewModel(
+ notificationsRegistrationService: .shared,
feature: studyPlanScreenComponent.studyPlanScreenFeature
)
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift
index 3e2b868c35..461f3d90c5 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/StudyPlanViewModel.swift
@@ -11,9 +11,22 @@ final class StudyPlanViewModel: FeatureViewModel<
var studyPlanWidgetStateKs: StudyPlanWidgetViewStateKs { .init(state.studyPlanWidgetViewState) }
var gamificationToolbarViewStateKs: GamificationToolbarFeatureViewStateKs { .init(state.toolbarViewState) }
- var usersInterviewWidgetFeatureStateKs: UsersInterviewWidgetFeatureStateKs {
+ var usersInterviewWidgetStateKs: UsersInterviewWidgetFeatureStateKs {
.init(state.usersInterviewWidgetState)
}
+ var notificationDailyStudyReminderWidgetViewStateKs: NotificationDailyStudyReminderWidgetFeatureViewStateKs {
+ .init(state.notificationDailyStudyReminderWidgetViewState)
+ }
+
+ private let notificationsRegistrationService: NotificationsRegistrationService
+
+ init(
+ notificationsRegistrationService: NotificationsRegistrationService,
+ feature: Presentation_reduxFeature
+ ) {
+ self.notificationsRegistrationService = notificationsRegistrationService
+ super.init(feature: feature)
+ }
override func shouldNotifyStateDidChange(
oldState: StudyPlanScreenFeature.ViewState,
@@ -24,10 +37,12 @@ final class StudyPlanViewModel: FeatureViewModel<
func doLoadStudyPlan() {
onNewMessage(StudyPlanScreenFeatureMessageInitialize())
+ initializeNotificationDailyStudyReminderWidgetFeature()
}
func doRetryContentLoading() {
onNewMessage(StudyPlanScreenFeatureMessageRetryContentLoading())
+ initializeNotificationDailyStudyReminderWidgetFeature()
}
func doScreenBecomesActive() {
@@ -136,6 +151,66 @@ final class StudyPlanViewModel: FeatureViewModel<
}
}
+// MARK: - StudyPlanViewModel (NotificationDailyStudyReminderWidget) -
+
+extension StudyPlanViewModel {
+ func doNotificationDailyStudyReminderWidgetCallToAction() {
+ onNewMessage(
+ StudyPlanScreenFeatureMessageNotificationDailyStudyReminderWidgetMessage(
+ message: NotificationDailyStudyReminderWidgetFeatureMessageWidgetClicked()
+ )
+ )
+ }
+
+ func doNotificationDailyStudyReminderWidgetCloseAction() {
+ onNewMessage(
+ StudyPlanScreenFeatureMessageNotificationDailyStudyReminderWidgetMessage(
+ message: NotificationDailyStudyReminderWidgetFeatureMessageCloseClicked()
+ )
+ )
+ }
+
+ func doNotificationDailyStudyReminderWidgetRequestNotificationPermission() {
+ Task(priority: .userInitiated) {
+ let isGranted = await notificationsRegistrationService.requestAuthorizationIfNeeded()
+
+ await MainActor.run {
+ onNewMessage(
+ StudyPlanScreenFeatureMessageNotificationDailyStudyReminderWidgetMessage(
+ message: NotificationDailyStudyReminderWidgetFeatureMessageNotificationPermissionRequestResult(
+ isPermissionGranted: isGranted
+ )
+ )
+ )
+ }
+ }
+ }
+
+ func logNotificationDailyStudyReminderWidgetViewedEvent() {
+ onNewMessage(
+ StudyPlanScreenFeatureMessageNotificationDailyStudyReminderWidgetMessage(
+ message: NotificationDailyStudyReminderWidgetFeatureMessageViewedEventMessage()
+ )
+ )
+ }
+
+ private func initializeNotificationDailyStudyReminderWidgetFeature() {
+ Task(priority: .userInitiated) {
+ let isNotificationPermissionGranted = await NotificationPermissionStatus.current.isRegistered
+
+ await MainActor.run {
+ onNewMessage(
+ StudyPlanScreenFeatureMessageNotificationDailyStudyReminderWidgetMessage(
+ message: NotificationDailyStudyReminderWidgetFeatureMessageInitialize(
+ isNotificationPermissionGranted: isNotificationPermissionGranted
+ )
+ )
+ )
+ }
+ }
+ }
+}
+
// MARK: - StudyPlanViewModel: StageImplementUnsupportedModalViewControllerDelegate -
extension StudyPlanViewModel: StageImplementUnsupportedModalViewControllerDelegate {
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift
index 448525e9fc..e9c9e4f30f 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/StudyPlan/Views/StudyPlanView.swift
@@ -89,7 +89,7 @@ struct StudyPlanView: View {
.padding(.bottom, appearance.trackTitleBottomPadding)
}
- let usersInterviewWidgetFeatureStateKs = viewModel.usersInterviewWidgetFeatureStateKs
+ let usersInterviewWidgetFeatureStateKs = viewModel.usersInterviewWidgetStateKs
if usersInterviewWidgetFeatureStateKs != .hidden {
UsersInterviewWidgetAssembly(
stateKs: usersInterviewWidgetFeatureStateKs,
@@ -98,6 +98,14 @@ struct StudyPlanView: View {
.makeModule()
}
+ if viewModel.notificationDailyStudyReminderWidgetViewStateKs != .hidden {
+ NotificationDailyStudyReminderWidgetView(
+ onCallToAction: viewModel.doNotificationDailyStudyReminderWidgetCallToAction,
+ onClose: viewModel.doNotificationDailyStudyReminderWidgetCloseAction,
+ onViewedEvent: viewModel.logNotificationDailyStudyReminderWidgetViewedEvent
+ )
+ }
+
if data.isPaywallBannerShown {
StudyPlanPaywallBanner(
action: viewModel.doPaywallBannerAction
@@ -152,6 +160,10 @@ private extension StudyPlanView {
handleUsersInterviewWidgetViewAction(
usersInterviewWidgetViewAction.viewAction
)
+ case .notificationDailyStudyReminderWidgetViewAction(let notificationDailyStudyReminderWidgetViewAction):
+ handleNotificationDailyStudyReminderWidgetViewAction(
+ notificationDailyStudyReminderWidgetViewAction.viewAction
+ )
}
}
@@ -236,4 +248,13 @@ private extension StudyPlanView {
)
}
}
+
+ func handleNotificationDailyStudyReminderWidgetViewAction(
+ _ viewAction: NotificationDailyStudyReminderWidgetFeatureActionViewAction
+ ) {
+ switch NotificationDailyStudyReminderWidgetFeatureActionViewActionKs(viewAction) {
+ case .requestNotificationPermission:
+ viewModel.doNotificationDailyStudyReminderWidgetRequestNotificationPermission()
+ }
+ }
}
diff --git a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/WelcomeOnboarding/Root/WelcomeOnboardingViewModel.swift b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/WelcomeOnboarding/Root/WelcomeOnboardingViewModel.swift
index 386ddc0629..1c3cc41814 100644
--- a/iosHyperskillApp/iosHyperskillApp/Sources/Modules/WelcomeOnboarding/Root/WelcomeOnboardingViewModel.swift
+++ b/iosHyperskillApp/iosHyperskillApp/Sources/Modules/WelcomeOnboarding/Root/WelcomeOnboardingViewModel.swift
@@ -18,11 +18,9 @@ final class WelcomeOnboardingViewModel: FeatureViewModel<
self.objectWillChangeSubscription = objectWillChange.sink { [weak self] _ in
self?.mainScheduler.schedule { [weak self] in
- guard let self else {
- return
+ if let strongSelf = self {
+ strongSelf.viewController?.displayState(strongSelf.state)
}
-
- self.viewController?.displayState(self.state)
}
}
self.onViewAction = { [weak self] viewAction in
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt
index 048fbbcaea..8b4c5735b8 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticPart.kt
@@ -43,6 +43,7 @@ enum class HyperskillAnalyticPart(val partName: String) {
DAILY_STUDY_REMINDERS_HOUR_INTERVAL_PICKER_MODAL("daily_study_reminders_hour_interval_picker_modal"),
REQUEST_REVIEW_MODAL("request_review_modal"),
USERS_INTERVIEW_WIDGET("users_interview_widget"),
+ NOTIFICATION_DAILY_STUDY_REMINDER_WIDGET("notification_daily_study_reminder_widget"),
UNSUPPORTED_QUIZ_PLACEHOLDER("unsupported_quiz_placeholder"),
CODE_BLANKS("code_blanks"),
CODE_BLOCK("code_block"),
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt
index 3101e834b8..99b95cc49b 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/analytic/domain/model/hyperskill/HyperskillAnalyticRoute.kt
@@ -124,6 +124,11 @@ sealed class HyperskillAnalyticRoute {
override val path: String
get() = "${super.path}/users-interview-widget"
}
+
+ class NotificationDailyStudyReminderWidget : StudyPlan() {
+ override val path: String
+ get() = "${super.path}/notification-daily-study-reminder-widget"
+ }
}
class Leaderboard : HyperskillAnalyticRoute() {
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
index 8cd7672f2b..31316aa99f 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/AppGraph.kt
@@ -35,6 +35,7 @@ import org.hyperskill.app.notification.local.injection.NotificationComponent
import org.hyperskill.app.notification.local.injection.NotificationFlowDataComponent
import org.hyperskill.app.notification.remote.injection.PlatformPushNotificationsDataComponent
import org.hyperskill.app.notification.remote.injection.PushNotificationsComponent
+import org.hyperskill.app.notification_daily_study_reminder_widget.injection.NotificationDailyStudyReminderWidgetComponent
import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboardingComponent
import org.hyperskill.app.onboarding.injection.OnboardingDataComponent
import org.hyperskill.app.paywall.domain.model.PaywallTransitionSource
@@ -212,6 +213,7 @@ interface AppGraph {
fun buildPaywallComponent(paywallTransitionSource: PaywallTransitionSource): PaywallComponent
fun buildManageSubscriptionComponent(): ManageSubscriptionComponent
fun buildUsersInterviewWidgetComponent(): UsersInterviewWidgetComponent
+ fun buildNotificationDailyStudyReminderWidgetComponent(): NotificationDailyStudyReminderWidgetComponent
fun buildProblemsLimitInfoModalComponent(
params: ProblemsLimitInfoModalFeatureParams
): ProblemsLimitInfoModalComponent
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
index 05915f1fc9..754aea699c 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/core/injection/BaseAppGraph.kt
@@ -64,6 +64,8 @@ import org.hyperskill.app.notification.local.injection.NotificationFlowDataCompo
import org.hyperskill.app.notification.local.injection.NotificationFlowDataComponentImpl
import org.hyperskill.app.notification.remote.injection.PushNotificationsComponent
import org.hyperskill.app.notification.remote.injection.PushNotificationsComponentImpl
+import org.hyperskill.app.notification_daily_study_reminder_widget.injection.NotificationDailyStudyReminderWidgetComponent
+import org.hyperskill.app.notification_daily_study_reminder_widget.injection.NotificationDailyStudyReminderWidgetComponentImpl
import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboardingComponent
import org.hyperskill.app.notifications_onboarding.injection.NotificationsOnboardingComponentImpl
import org.hyperskill.app.onboarding.injection.OnboardingDataComponent
@@ -547,6 +549,9 @@ abstract class BaseAppGraph : AppGraph {
override fun buildUsersInterviewWidgetComponent(): UsersInterviewWidgetComponent =
UsersInterviewWidgetComponentImpl(this)
+ override fun buildNotificationDailyStudyReminderWidgetComponent(): NotificationDailyStudyReminderWidgetComponent =
+ NotificationDailyStudyReminderWidgetComponentImpl(this)
+
override fun buildProblemsLimitInfoModalComponent(
params: ProblemsLimitInfoModalFeatureParams
): ProblemsLimitInfoModalComponent =
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/cache/NotificationDailyStudyReminderWidgetCacheDataSourceImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/cache/NotificationDailyStudyReminderWidgetCacheDataSourceImpl.kt
new file mode 100644
index 0000000000..0c71341ee5
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/cache/NotificationDailyStudyReminderWidgetCacheDataSourceImpl.kt
@@ -0,0 +1,21 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.cache
+
+import com.russhwolf.settings.Settings
+import org.hyperskill.app.notification_daily_study_reminder_widget.data.source.NotificationDailyStudyReminderWidgetCacheDataSource
+
+internal class NotificationDailyStudyReminderWidgetCacheDataSourceImpl(
+ private val settings: Settings
+) : NotificationDailyStudyReminderWidgetCacheDataSource {
+ override fun getIsNotificationDailyStudyReminderWidgetHidden(): Boolean =
+ settings.getBoolean(
+ NotificationDailyStudyReminderWidgetCacheKeyValues.NOTIFICATION_DAILY_STUDY_REMINDER_WIDGET_HIDDEN,
+ false
+ )
+
+ override fun setIsNotificationDailyStudyReminderWidgetHidden(isHidden: Boolean) {
+ settings.putBoolean(
+ NotificationDailyStudyReminderWidgetCacheKeyValues.NOTIFICATION_DAILY_STUDY_REMINDER_WIDGET_HIDDEN,
+ isHidden
+ )
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/cache/NotificationDailyStudyReminderWidgetCacheKeyValues.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/cache/NotificationDailyStudyReminderWidgetCacheKeyValues.kt
new file mode 100644
index 0000000000..b076cb6d99
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/cache/NotificationDailyStudyReminderWidgetCacheKeyValues.kt
@@ -0,0 +1,5 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.cache
+
+internal object NotificationDailyStudyReminderWidgetCacheKeyValues {
+ const val NOTIFICATION_DAILY_STUDY_REMINDER_WIDGET_HIDDEN = "notification_daily_study_reminder_widget_hidden"
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/data/repository/NotificationDailyStudyReminderWidgetRepositoryImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/data/repository/NotificationDailyStudyReminderWidgetRepositoryImpl.kt
new file mode 100644
index 0000000000..2d24ce7476
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/data/repository/NotificationDailyStudyReminderWidgetRepositoryImpl.kt
@@ -0,0 +1,15 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.data.repository
+
+import org.hyperskill.app.notification_daily_study_reminder_widget.data.source.NotificationDailyStudyReminderWidgetCacheDataSource
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.repository.NotificationDailyStudyReminderWidgetRepository
+
+internal class NotificationDailyStudyReminderWidgetRepositoryImpl(
+ private val notificationDailyStudyReminderWidgetCacheDataSource: NotificationDailyStudyReminderWidgetCacheDataSource
+) : NotificationDailyStudyReminderWidgetRepository {
+ override fun getIsNotificationDailyStudyReminderWidgetHidden(): Boolean =
+ notificationDailyStudyReminderWidgetCacheDataSource.getIsNotificationDailyStudyReminderWidgetHidden()
+
+ override fun setIsNotificationDailyStudyReminderWidgetHidden(isHidden: Boolean) {
+ notificationDailyStudyReminderWidgetCacheDataSource.setIsNotificationDailyStudyReminderWidgetHidden(isHidden)
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/data/source/NotificationDailyStudyReminderWidgetCacheDataSource.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/data/source/NotificationDailyStudyReminderWidgetCacheDataSource.kt
new file mode 100644
index 0000000000..f7dd750767
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/data/source/NotificationDailyStudyReminderWidgetCacheDataSource.kt
@@ -0,0 +1,6 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.data.source
+
+interface NotificationDailyStudyReminderWidgetCacheDataSource {
+ fun getIsNotificationDailyStudyReminderWidgetHidden(): Boolean
+ fun setIsNotificationDailyStudyReminderWidgetHidden(isHidden: Boolean)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetClickedCloseHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetClickedCloseHyperskillAnalyticEvent.kt
new file mode 100644
index 0000000000..65152276ff
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetClickedCloseHyperskillAnalyticEvent.kt
@@ -0,0 +1,29 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic
+
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget
+
+/**
+ * Represents an analytic event for clicking on a close button in the notification daily study reminder widget.
+ *
+ * JSON payload:
+ * ```
+ * {
+ * "route": "/study-plan/notification-daily-study-reminder-widget",
+ * "action": "click",
+ * "part": "notification_daily_study_reminder_widget",
+ * "target": "close"
+ * }
+ * ```
+ *
+ * @see HyperskillAnalyticEvent
+ */
+object NotificationDailyStudyReminderWidgetClickedCloseHyperskillAnalyticEvent : HyperskillAnalyticEvent(
+ route = HyperskillAnalyticRoute.StudyPlan.NotificationDailyStudyReminderWidget(),
+ action = HyperskillAnalyticAction.CLICK,
+ part = HyperskillAnalyticPart.NOTIFICATION_DAILY_STUDY_REMINDER_WIDGET,
+ target = HyperskillAnalyticTarget.CLOSE
+)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetClickedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetClickedHyperskillAnalyticEvent.kt
new file mode 100644
index 0000000000..ef644eaf97
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetClickedHyperskillAnalyticEvent.kt
@@ -0,0 +1,26 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic
+
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute
+
+/**
+ * Represents a click analytic event of the notification daily study reminder widget.
+ *
+ * JSON payload:
+ * ```
+ * {
+ * "route": "/study-plan/notification-daily-study-reminder-widget",
+ * "action": "click",
+ * "part": "notification_daily_study_reminder_widget"
+ * }
+ * ```
+ *
+ * @see HyperskillAnalyticEvent
+ */
+object NotificationDailyStudyReminderWidgetClickedHyperskillAnalyticEvent : HyperskillAnalyticEvent(
+ route = HyperskillAnalyticRoute.StudyPlan.NotificationDailyStudyReminderWidget(),
+ action = HyperskillAnalyticAction.CLICK,
+ part = HyperskillAnalyticPart.NOTIFICATION_DAILY_STUDY_REMINDER_WIDGET
+)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetViewedHyperskillAnalyticEvent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetViewedHyperskillAnalyticEvent.kt
new file mode 100644
index 0000000000..3a29d3c48e
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/analytic/NotificationDailyStudyReminderWidgetViewedHyperskillAnalyticEvent.kt
@@ -0,0 +1,23 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic
+
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent
+import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute
+
+/**
+ * Represents a view analytic event of the notification daily study reminder widget.
+ *
+ * JSON payload:
+ * ```
+ * {
+ * "route": "/study-plan/notification-daily-study-reminder-widget",
+ * "action": "view"
+ * }
+ * ```
+ *
+ * @see HyperskillAnalyticEvent
+ */
+object NotificationDailyStudyReminderWidgetViewedHyperskillAnalyticEvent : HyperskillAnalyticEvent(
+ route = HyperskillAnalyticRoute.StudyPlan.NotificationDailyStudyReminderWidget(),
+ action = HyperskillAnalyticAction.VIEW
+)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/repository/NotificationDailyStudyReminderWidgetRepository.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/repository/NotificationDailyStudyReminderWidgetRepository.kt
new file mode 100644
index 0000000000..c6efa6af3f
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/domain/repository/NotificationDailyStudyReminderWidgetRepository.kt
@@ -0,0 +1,6 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.domain.repository
+
+interface NotificationDailyStudyReminderWidgetRepository {
+ fun getIsNotificationDailyStudyReminderWidgetHidden(): Boolean
+ fun setIsNotificationDailyStudyReminderWidgetHidden(isHidden: Boolean)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/injection/NotificationDailyStudyReminderWidgetComponent.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/injection/NotificationDailyStudyReminderWidgetComponent.kt
new file mode 100644
index 0000000000..159b3aec80
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/injection/NotificationDailyStudyReminderWidgetComponent.kt
@@ -0,0 +1,9 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.injection
+
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetActionDispatcher
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetReducer
+
+interface NotificationDailyStudyReminderWidgetComponent {
+ val notificationDailyStudyReminderWidgetReducer: NotificationDailyStudyReminderWidgetReducer
+ val notificationDailyStudyReminderWidgetActionDispatcher: NotificationDailyStudyReminderWidgetActionDispatcher
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/injection/NotificationDailyStudyReminderWidgetComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/injection/NotificationDailyStudyReminderWidgetComponentImpl.kt
new file mode 100644
index 0000000000..e392d2801b
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/injection/NotificationDailyStudyReminderWidgetComponentImpl.kt
@@ -0,0 +1,39 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.injection
+
+import org.hyperskill.app.core.injection.AppGraph
+import org.hyperskill.app.core.presentation.ActionDispatcherOptions
+import org.hyperskill.app.notification_daily_study_reminder_widget.cache.NotificationDailyStudyReminderWidgetCacheDataSourceImpl
+import org.hyperskill.app.notification_daily_study_reminder_widget.data.repository.NotificationDailyStudyReminderWidgetRepositoryImpl
+import org.hyperskill.app.notification_daily_study_reminder_widget.data.source.NotificationDailyStudyReminderWidgetCacheDataSource
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.repository.NotificationDailyStudyReminderWidgetRepository
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.MainNotificationDailyStudyReminderWidgetActionDispatcher
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetActionDispatcher
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetReducer
+
+internal class NotificationDailyStudyReminderWidgetComponentImpl(
+ private val appGraph: AppGraph
+) : NotificationDailyStudyReminderWidgetComponent {
+ /* ktlint-disable */
+ private val notificationDailyStudyReminderWidgetCacheDataSource: NotificationDailyStudyReminderWidgetCacheDataSource =
+ NotificationDailyStudyReminderWidgetCacheDataSourceImpl(appGraph.commonComponent.settings)
+
+ private val notificationDailyStudyReminderWidgetRepository: NotificationDailyStudyReminderWidgetRepository =
+ NotificationDailyStudyReminderWidgetRepositoryImpl(
+ notificationDailyStudyReminderWidgetCacheDataSource
+ )
+
+ override val notificationDailyStudyReminderWidgetReducer: NotificationDailyStudyReminderWidgetReducer
+ get() = NotificationDailyStudyReminderWidgetReducer()
+
+ /* ktlint-disable */
+ override val notificationDailyStudyReminderWidgetActionDispatcher: NotificationDailyStudyReminderWidgetActionDispatcher
+ get() = NotificationDailyStudyReminderWidgetActionDispatcher(
+ MainNotificationDailyStudyReminderWidgetActionDispatcher(
+ config = ActionDispatcherOptions(),
+ notificationInteractor = appGraph.buildNotificationComponent().notificationInteractor,
+ notificationDailyStudyReminderWidgetRepository = notificationDailyStudyReminderWidgetRepository,
+ currentProfileStateRepository = appGraph.profileDataComponent.currentProfileStateRepository
+ ),
+ analyticInteractor = appGraph.analyticComponent.analyticInteractor
+ )
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/MainNotificationDailyStudyReminderWidgetActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/MainNotificationDailyStudyReminderWidgetActionDispatcher.kt
new file mode 100644
index 0000000000..217cb88fe6
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/MainNotificationDailyStudyReminderWidgetActionDispatcher.kt
@@ -0,0 +1,69 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.presentation
+
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import org.hyperskill.app.core.presentation.ActionDispatcherOptions
+import org.hyperskill.app.notification.local.domain.interactor.NotificationInteractor
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.repository.NotificationDailyStudyReminderWidgetRepository
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.Action
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.InternalAction
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.InternalMessage
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.Message
+import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
+import ru.nobird.app.presentation.redux.dispatcher.CoroutineActionDispatcher
+
+internal class MainNotificationDailyStudyReminderWidgetActionDispatcher(
+ config: ActionDispatcherOptions,
+ private val notificationInteractor: NotificationInteractor,
+ private val notificationDailyStudyReminderWidgetRepository: NotificationDailyStudyReminderWidgetRepository,
+ private val currentProfileStateRepository: CurrentProfileStateRepository
+) : CoroutineActionDispatcher(config.createConfig()) {
+ init {
+ currentProfileStateRepository.changes
+ .map { it.gamification.passedTopicsCount }
+ .distinctUntilChanged()
+ .onEach { passedTopicsCount ->
+ onNewMessage(
+ InternalMessage.PassedTopicsCountChanged(
+ passedTopicsCount
+ )
+ )
+ }
+ .launchIn(actionScope)
+ }
+
+ override suspend fun doSuspendableAction(action: Action) {
+ when (action) {
+ InternalAction.FetchWidgetData ->
+ handleFetchWidgetData(::onNewMessage)
+ InternalAction.HideWidget ->
+ notificationDailyStudyReminderWidgetRepository.setIsNotificationDailyStudyReminderWidgetHidden(true)
+ is InternalAction.SaveDailyStudyRemindersIntervalStartHour -> {
+ notificationInteractor.setDailyStudyRemindersEnabled(enabled = true)
+ notificationInteractor.setDailyStudyReminderNotificationTime(notificationHour = action.startHour)
+ }
+ else -> {
+ // no op
+ }
+ }
+ }
+
+ private suspend fun handleFetchWidgetData(onNewMessage: (Message) -> Unit) {
+ val passedTopicsCount = currentProfileStateRepository
+ .getState(forceUpdate = false)
+ .map { it.gamification.passedTopicsCount }
+ .getOrDefault(defaultValue = 0)
+
+ val isWidgetHidden =
+ notificationDailyStudyReminderWidgetRepository.getIsNotificationDailyStudyReminderWidgetHidden()
+
+ onNewMessage(
+ InternalMessage.FetchWidgetDataResult(
+ passedTopicsCount = passedTopicsCount,
+ isWidgetHidden = isWidgetHidden
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetActionDispatcher.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetActionDispatcher.kt
new file mode 100644
index 0000000000..652f970a11
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetActionDispatcher.kt
@@ -0,0 +1,20 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.presentation
+
+import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
+import org.hyperskill.app.analytic.presentation.SingleAnalyticEventActionDispatcher
+import org.hyperskill.app.core.presentation.CompositeActionDispatcher
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.Action
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.InternalAction
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.Message
+
+class NotificationDailyStudyReminderWidgetActionDispatcher internal constructor(
+ mainNotificationDailyStudyReminderWidgetActionDispatcher: MainNotificationDailyStudyReminderWidgetActionDispatcher,
+ analyticInteractor: AnalyticInteractor
+) : CompositeActionDispatcher(
+ listOf(
+ mainNotificationDailyStudyReminderWidgetActionDispatcher,
+ SingleAnalyticEventActionDispatcher(analyticInteractor) {
+ (it as? InternalAction.LogAnalyticEvent)?.event
+ }
+ )
+)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetFeature.kt
new file mode 100644
index 0000000000..629e95ae0e
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetFeature.kt
@@ -0,0 +1,61 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.presentation
+
+import org.hyperskill.app.analytic.domain.model.AnalyticEvent
+
+object NotificationDailyStudyReminderWidgetFeature {
+ sealed interface State {
+ data object Idle : State
+ data object Loading : State
+ data object Hidden : State
+ data class Data(val passedTopicsCount: Int) : State
+ }
+
+ sealed interface ViewState {
+ data object Hidden : ViewState
+ data object Visible : ViewState
+ }
+
+ sealed interface Message {
+ data class Initialize(
+ val isNotificationPermissionGranted: Boolean
+ ) : Message
+
+ data object CloseClicked : Message
+ data object WidgetClicked : Message
+
+ data class NotificationPermissionRequestResult(
+ val isPermissionGranted: Boolean
+ ) : Message
+
+ data object ViewedEventMessage : Message
+ }
+
+ internal sealed interface InternalMessage : Message {
+ data class FetchWidgetDataResult(
+ val passedTopicsCount: Int,
+ val isWidgetHidden: Boolean
+ ) : InternalMessage
+
+ data class PassedTopicsCountChanged(
+ val passedTopicsCount: Int
+ ) : InternalMessage
+ }
+
+ sealed interface Action {
+ sealed interface ViewAction : Action {
+ data object RequestNotificationPermission : ViewAction
+ }
+ }
+
+ internal sealed interface InternalAction : Action {
+ data object FetchWidgetData : InternalAction
+
+ data object HideWidget : InternalAction
+
+ data class SaveDailyStudyRemindersIntervalStartHour(
+ val startHour: Int
+ ) : InternalAction
+
+ data class LogAnalyticEvent(val event: AnalyticEvent) : InternalAction
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetReducer.kt
new file mode 100644
index 0000000000..0d44c815be
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/presentation/NotificationDailyStudyReminderWidgetReducer.kt
@@ -0,0 +1,116 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.presentation
+
+import kotlinx.datetime.Clock
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toLocalDateTime
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic.NotificationDailyStudyReminderWidgetClickedCloseHyperskillAnalyticEvent
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic.NotificationDailyStudyReminderWidgetClickedHyperskillAnalyticEvent
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic.NotificationDailyStudyReminderWidgetViewedHyperskillAnalyticEvent
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.Action
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.InternalAction
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.InternalMessage
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.Message
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.State
+import ru.nobird.app.presentation.redux.reducer.StateReducer
+
+private typealias ReducerResult = Pair>
+
+class NotificationDailyStudyReminderWidgetReducer : StateReducer {
+ override fun reduce(state: State, message: Message): ReducerResult =
+ when (message) {
+ is Message.Initialize -> handleInitialize(state, message)
+ is InternalMessage.FetchWidgetDataResult -> handleFetchWidgetDataResult(state, message)
+ Message.CloseClicked -> handleCloseClicked(state)
+ Message.WidgetClicked -> handleWidgetClicked(state)
+ is Message.NotificationPermissionRequestResult -> handleNotificationPermissionRequestResult(state, message)
+ is InternalMessage.PassedTopicsCountChanged -> handlePassedTopicsCountChanged(state, message)
+ Message.ViewedEventMessage -> handleViewedEvent(state)
+ } ?: (state to emptySet())
+
+ private fun handleInitialize(
+ state: State,
+ message: Message.Initialize
+ ): ReducerResult? =
+ if (state is State.Idle && !message.isNotificationPermissionGranted) {
+ State.Loading to setOf(InternalAction.FetchWidgetData)
+ } else {
+ null
+ }
+
+ private fun handleFetchWidgetDataResult(
+ state: State,
+ message: InternalMessage.FetchWidgetDataResult
+ ): ReducerResult? {
+ if (state !is State.Loading) {
+ return null
+ }
+
+ return if (message.isWidgetHidden) {
+ State.Hidden to emptySet()
+ } else {
+ State.Data(passedTopicsCount = message.passedTopicsCount) to emptySet()
+ }
+ }
+
+ private fun handleCloseClicked(state: State): ReducerResult? =
+ if (state is State.Data) {
+ State.Hidden to setOf(
+ InternalAction.HideWidget,
+ InternalAction.LogAnalyticEvent(
+ NotificationDailyStudyReminderWidgetClickedCloseHyperskillAnalyticEvent
+ )
+ )
+ } else {
+ null
+ }
+
+ private fun handleWidgetClicked(state: State): ReducerResult? =
+ if (state is State.Data) {
+ state to setOf(
+ Action.ViewAction.RequestNotificationPermission,
+ InternalAction.LogAnalyticEvent(
+ NotificationDailyStudyReminderWidgetClickedHyperskillAnalyticEvent
+ )
+ )
+ } else {
+ null
+ }
+
+ private fun handleNotificationPermissionRequestResult(
+ state: State,
+ message: Message.NotificationPermissionRequestResult
+ ): ReducerResult? =
+ if (state is State.Data) {
+ State.Hidden to buildSet {
+ if (message.isPermissionGranted) {
+ val dailyStudyRemindersStartHour = Clock.System.now()
+ .toLocalDateTime(TimeZone.currentSystemDefault())
+ .hour
+ add(InternalAction.SaveDailyStudyRemindersIntervalStartHour(dailyStudyRemindersStartHour))
+ }
+ }
+ } else {
+ null
+ }
+
+ private fun handlePassedTopicsCountChanged(
+ state: State,
+ message: InternalMessage.PassedTopicsCountChanged
+ ): ReducerResult? =
+ if (state is State.Data) {
+ state.copy(passedTopicsCount = message.passedTopicsCount) to emptySet()
+ } else {
+ null
+ }
+
+ private fun handleViewedEvent(state: State): ReducerResult? =
+ if (state is State.Data) {
+ state to setOf(
+ InternalAction.LogAnalyticEvent(
+ NotificationDailyStudyReminderWidgetViewedHyperskillAnalyticEvent
+ )
+ )
+ } else {
+ null
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/view/mapper/NotificationDailyStudyReminderWidgetViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/view/mapper/NotificationDailyStudyReminderWidgetViewStateMapper.kt
new file mode 100644
index 0000000000..c3cb76178c
--- /dev/null
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/notification_daily_study_reminder_widget/view/mapper/NotificationDailyStudyReminderWidgetViewStateMapper.kt
@@ -0,0 +1,20 @@
+package org.hyperskill.app.notification_daily_study_reminder_widget.view.mapper
+
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.State
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.ViewState
+
+internal object NotificationDailyStudyReminderWidgetViewStateMapper {
+ fun map(state: State): ViewState =
+ when (state) {
+ State.Idle,
+ State.Loading,
+ State.Hidden ->
+ ViewState.Hidden
+ is State.Data ->
+ if (state.passedTopicsCount > 0) {
+ ViewState.Visible
+ } else {
+ ViewState.Hidden
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenComponentImpl.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenComponentImpl.kt
index 9b271148f9..e7af243b53 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenComponentImpl.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenComponentImpl.kt
@@ -3,6 +3,7 @@ package org.hyperskill.app.study_plan.screen.injection
import org.hyperskill.app.core.injection.AppGraph
import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarScreen
import org.hyperskill.app.gamification_toolbar.injection.GamificationToolbarComponent
+import org.hyperskill.app.notification_daily_study_reminder_widget.injection.NotificationDailyStudyReminderWidgetComponent
import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature
import org.hyperskill.app.study_plan.widget.injection.StudyPlanWidgetComponent
import org.hyperskill.app.users_interview_widget.injection.UsersInterviewWidgetComponent
@@ -16,6 +17,9 @@ internal class StudyPlanScreenComponentImpl(private val appGraph: AppGraph) : St
private val usersInterviewWidgetComponent: UsersInterviewWidgetComponent =
appGraph.buildUsersInterviewWidgetComponent()
+ private val notificationDailyStudyReminderWidgetComponent: NotificationDailyStudyReminderWidgetComponent =
+ appGraph.buildNotificationDailyStudyReminderWidgetComponent()
+
private val studyPlanWidgetComponent: StudyPlanWidgetComponent =
appGraph.buildStudyPlanWidgetComponent()
@@ -28,6 +32,10 @@ internal class StudyPlanScreenComponentImpl(private val appGraph: AppGraph) : St
usersInterviewWidgetReducer = usersInterviewWidgetComponent.usersInterviewWidgetReducer,
usersInterviewWidgetActionDispatcher = usersInterviewWidgetComponent
.usersInterviewWidgetActionDispatcher,
+ notificationDailyStudyReminderWidgetReducer = notificationDailyStudyReminderWidgetComponent
+ .notificationDailyStudyReminderWidgetReducer,
+ notificationDailyStudyReminderWidgetActionDispatcher = notificationDailyStudyReminderWidgetComponent
+ .notificationDailyStudyReminderWidgetActionDispatcher,
studyPlanWidgetReducer = studyPlanWidgetComponent.studyPlanWidgetReducer,
studyPlanWidgetDispatcher = studyPlanWidgetComponent.studyPlanWidgetDispatcher,
studyPlanWidgetViewStateMapper = studyPlanWidgetComponent.studyPlanWidgetViewStateMapper,
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenFeatureBuilder.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenFeatureBuilder.kt
index 6c9f68b287..e9ea56fe7f 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenFeatureBuilder.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/injection/StudyPlanScreenFeatureBuilder.kt
@@ -10,6 +10,9 @@ import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarA
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarReducer
import org.hyperskill.app.logging.presentation.wrapWithLogger
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetActionDispatcher
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetReducer
import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature
import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature.InternalAction
import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenReducer
@@ -36,6 +39,8 @@ internal object StudyPlanScreenFeatureBuilder {
toolbarActionDispatcher: GamificationToolbarActionDispatcher,
usersInterviewWidgetReducer: UsersInterviewWidgetReducer,
usersInterviewWidgetActionDispatcher: UsersInterviewWidgetActionDispatcher,
+ notificationDailyStudyReminderWidgetReducer: NotificationDailyStudyReminderWidgetReducer,
+ notificationDailyStudyReminderWidgetActionDispatcher: NotificationDailyStudyReminderWidgetActionDispatcher,
studyPlanWidgetReducer: StudyPlanWidgetReducer,
studyPlanWidgetDispatcher: StudyPlanWidgetActionDispatcher,
studyPlanWidgetViewStateMapper: StudyPlanWidgetViewStateMapper,
@@ -46,6 +51,7 @@ internal object StudyPlanScreenFeatureBuilder {
val studyPlanScreenReducer = StudyPlanScreenReducer(
toolbarReducer = toolbarReducer,
usersInterviewWidgetReducer = usersInterviewWidgetReducer,
+ notificationDailyStudyReminderWidgetReducer = notificationDailyStudyReminderWidgetReducer,
studyPlanWidgetReducer = studyPlanWidgetReducer
).wrapWithLogger(buildVariant, logger, LOG_TAG)
@@ -59,6 +65,7 @@ internal object StudyPlanScreenFeatureBuilder {
StudyPlanScreenFeature.State(
toolbarState = GamificationToolbarFeature.State.Idle,
usersInterviewWidgetState = UsersInterviewWidgetFeature.State.Idle,
+ notificationDailyStudyReminderWidgetState = NotificationDailyStudyReminderWidgetFeature.State.Idle,
studyPlanWidgetState = StudyPlanWidgetFeature.State()
),
reducer = studyPlanScreenReducer
@@ -80,6 +87,14 @@ internal object StudyPlanScreenFeatureBuilder {
transformMessage = StudyPlanScreenFeature.Message::UsersInterviewWidgetMessage
)
)
+ .wrapWithActionDispatcher(
+ notificationDailyStudyReminderWidgetActionDispatcher.transform(
+ transformAction = {
+ it.safeCast()?.action
+ },
+ transformMessage = StudyPlanScreenFeature.Message::NotificationDailyStudyReminderWidgetMessage
+ )
+ )
.wrapWithActionDispatcher(
studyPlanWidgetDispatcher.transform(
transformAction = {
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt
index 121b5a0a31..efe62104d4 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenFeature.kt
@@ -3,6 +3,7 @@ package org.hyperskill.app.study_plan.screen.presentation
import org.hyperskill.app.analytic.domain.model.AnalyticEvent
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature.isRefreshing
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature
import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature
import org.hyperskill.app.study_plan.widget.view.model.StudyPlanWidgetViewState
import org.hyperskill.app.users_interview_widget.presentation.UsersInterviewWidgetFeature
@@ -11,8 +12,11 @@ object StudyPlanScreenFeature {
internal data class State(
val toolbarState: GamificationToolbarFeature.State,
val usersInterviewWidgetState: UsersInterviewWidgetFeature.State,
+ val notificationDailyStudyReminderWidgetState: NotificationDailyStudyReminderWidgetFeature.State,
val studyPlanWidgetState: StudyPlanWidgetFeature.State
) {
+ companion object;
+
val isRefreshing: Boolean
get() = toolbarState.isRefreshing ||
studyPlanWidgetState.isRefreshing
@@ -22,6 +26,7 @@ object StudyPlanScreenFeature {
val trackTitle: String?,
val toolbarViewState: GamificationToolbarFeature.ViewState,
val usersInterviewWidgetState: UsersInterviewWidgetFeature.State,
+ val notificationDailyStudyReminderWidgetViewState: NotificationDailyStudyReminderWidgetFeature.ViewState,
val studyPlanWidgetViewState: StudyPlanWidgetViewState,
val isRefreshing: Boolean
)
@@ -47,6 +52,10 @@ object StudyPlanScreenFeature {
val message: UsersInterviewWidgetFeature.Message
) : Message
+ data class NotificationDailyStudyReminderWidgetMessage(
+ val message: NotificationDailyStudyReminderWidgetFeature.Message
+ ) : Message
+
data class StudyPlanWidgetMessage(
val message: StudyPlanWidgetFeature.Message
) : Message
@@ -66,6 +75,10 @@ object StudyPlanScreenFeature {
val viewAction: UsersInterviewWidgetFeature.Action.ViewAction
) : ViewAction
+ data class NotificationDailyStudyReminderWidgetViewAction(
+ val viewAction: NotificationDailyStudyReminderWidgetFeature.Action.ViewAction
+ ) : ViewAction
+
data class StudyPlanWidgetViewAction(
val viewAction: StudyPlanWidgetFeature.Action.ViewAction
) : ViewAction
@@ -83,6 +96,10 @@ object StudyPlanScreenFeature {
val action: UsersInterviewWidgetFeature.Action
) : InternalAction
+ data class NotificationDailyStudyReminderWidgetAction(
+ val action: NotificationDailyStudyReminderWidgetFeature.Action
+ ) : InternalAction
+
data class StudyPlanWidgetAction(
val action: StudyPlanWidgetFeature.Action
) : InternalAction
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt
index 560cf7b673..0624a4fbed 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/presentation/StudyPlanScreenReducer.kt
@@ -2,6 +2,8 @@ package org.hyperskill.app.study_plan.screen.presentation
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarReducer
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetReducer
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedChangeTrackHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedPullToRefreshHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedRetryContentLoadingHyperskillAnalyticEvent
@@ -17,6 +19,7 @@ internal typealias StudyPlanScreenReducerResult = Pair {
override fun reduce(
@@ -47,6 +50,16 @@ internal class StudyPlanScreenReducer(
reduceUsersInterviewWidgetMessage(state.usersInterviewWidgetState, message.message)
state.copy(usersInterviewWidgetState = usersInterviewWidgetState) to usersInterviewWidgetActions
}
+ is StudyPlanScreenFeature.Message.NotificationDailyStudyReminderWidgetMessage -> {
+ val (notificationDailyStudyReminderWidgetState, notificationDailyStudyReminderWidgetActions) =
+ reduceNotificationDailyStudyReminderWidgetMessage(
+ state.notificationDailyStudyReminderWidgetState,
+ message.message
+ )
+ state.copy(
+ notificationDailyStudyReminderWidgetState = notificationDailyStudyReminderWidgetState
+ ) to notificationDailyStudyReminderWidgetActions
+ }
is StudyPlanScreenFeature.Message.StudyPlanWidgetMessage -> {
val (widgetState, widgetActions) =
reduceStudyPlanWidgetMessage(state.studyPlanWidgetState, message.message)
@@ -173,6 +186,26 @@ internal class StudyPlanScreenReducer(
return usersInterviewWidgetState to actions
}
+ private fun reduceNotificationDailyStudyReminderWidgetMessage(
+ state: NotificationDailyStudyReminderWidgetFeature.State,
+ message: NotificationDailyStudyReminderWidgetFeature.Message
+ ): Pair> {
+ val (notificationDailyStudyReminderWidgetState, notificationDailyStudyReminderWidgetActions) =
+ notificationDailyStudyReminderWidgetReducer.reduce(state, message)
+
+ val actions = notificationDailyStudyReminderWidgetActions
+ .map {
+ if (it is NotificationDailyStudyReminderWidgetFeature.Action.ViewAction) {
+ StudyPlanScreenFeature.Action.ViewAction.NotificationDailyStudyReminderWidgetViewAction(it)
+ } else {
+ StudyPlanScreenFeature.InternalAction.NotificationDailyStudyReminderWidgetAction(it)
+ }
+ }
+ .toSet()
+
+ return notificationDailyStudyReminderWidgetState to actions
+ }
+
private fun reduceStudyPlanWidgetMessage(
state: StudyPlanWidgetFeature.State,
message: StudyPlanWidgetFeature.Message
diff --git a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/view/StudyPlanScreenViewStateMapper.kt b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/view/StudyPlanScreenViewStateMapper.kt
index 7224f4ba97..0bde7810e7 100644
--- a/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/view/StudyPlanScreenViewStateMapper.kt
+++ b/shared/src/commonMain/kotlin/org/hyperskill/app/study_plan/screen/view/StudyPlanScreenViewStateMapper.kt
@@ -3,8 +3,11 @@ package org.hyperskill.app.study_plan.screen.view
import org.hyperskill.app.SharedResources
import org.hyperskill.app.core.view.mapper.ResourceProvider
import org.hyperskill.app.gamification_toolbar.view.mapper.GamificationToolbarViewStateMapper
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature
+import org.hyperskill.app.notification_daily_study_reminder_widget.view.mapper.NotificationDailyStudyReminderWidgetViewStateMapper
import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature
import org.hyperskill.app.study_plan.widget.view.mapper.StudyPlanWidgetViewStateMapper
+import org.hyperskill.app.users_interview_widget.presentation.UsersInterviewWidgetFeature
internal class StudyPlanScreenViewStateMapper(
private val studyPlanWidgetViewStateMapper: StudyPlanWidgetViewStateMapper,
@@ -15,6 +18,7 @@ internal class StudyPlanScreenViewStateMapper(
trackTitle = getTrackTitle(state),
toolbarViewState = GamificationToolbarViewStateMapper.map(state.toolbarState),
usersInterviewWidgetState = state.usersInterviewWidgetState,
+ notificationDailyStudyReminderWidgetViewState = getNotificationDailyStudyReminderWidgetViewState(state),
studyPlanWidgetViewState = studyPlanWidgetViewStateMapper.map(state.studyPlanWidgetState),
isRefreshing = state.isRefreshing
)
@@ -28,4 +32,15 @@ internal class StudyPlanScreenViewStateMapper(
title
)
}
+
+ private fun getNotificationDailyStudyReminderWidgetViewState(
+ state: StudyPlanScreenFeature.State
+ ): NotificationDailyStudyReminderWidgetFeature.ViewState =
+ if (state.usersInterviewWidgetState is UsersInterviewWidgetFeature.State.Visible) {
+ NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden
+ } else {
+ NotificationDailyStudyReminderWidgetViewStateMapper.map(
+ state.notificationDailyStudyReminderWidgetState
+ )
+ }
}
\ No newline at end of file
diff --git a/shared/src/commonMain/moko-resources/base/strings.xml b/shared/src/commonMain/moko-resources/base/strings.xml
index 0d693244f1..e4da7c6570 100644
--- a/shared/src/commonMain/moko-resources/base/strings.xml
+++ b/shared/src/commonMain/moko-resources/base/strings.xml
@@ -639,6 +639,10 @@
We want to hear your story!
Share your Hyperskill app experience in an online feedback session, and receive an Amazon gift card
+
+ Build a habit
+ Stay on top of your learning with daily reminder notifications
+
Search
Find topic
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/notification_daily_study_reminder_widget/NotificationDailyStudyReminderWidgetTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/notification_daily_study_reminder_widget/NotificationDailyStudyReminderWidgetTest.kt
new file mode 100644
index 0000000000..88c0c9f980
--- /dev/null
+++ b/shared/src/commonTest/kotlin/org/hyperskill/notification_daily_study_reminder_widget/NotificationDailyStudyReminderWidgetTest.kt
@@ -0,0 +1,138 @@
+package org.hyperskill.notification_daily_study_reminder_widget
+
+import kotlin.test.Test
+import kotlin.test.assertContains
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic.NotificationDailyStudyReminderWidgetClickedCloseHyperskillAnalyticEvent
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic.NotificationDailyStudyReminderWidgetClickedHyperskillAnalyticEvent
+import org.hyperskill.app.notification_daily_study_reminder_widget.domain.analytic.NotificationDailyStudyReminderWidgetViewedHyperskillAnalyticEvent
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.Action
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.InternalAction
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.InternalMessage
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.Message
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature.State
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetReducer
+
+class NotificationDailyStudyReminderWidgetTest {
+ private val reducer = NotificationDailyStudyReminderWidgetReducer()
+
+ @Test
+ fun `Initialize message when permission not granted should trigger loading state and fetch widget data`() {
+ val (state, actions) = reducer.reduce(
+ State.Idle,
+ Message.Initialize(isNotificationPermissionGranted = false)
+ )
+
+ assertEquals(State.Loading, state)
+ assertContains(actions, InternalAction.FetchWidgetData)
+ }
+
+ @Test
+ fun `FetchWidgetDataResult with hidden widget should result in Hidden state`() {
+ val (state, actions) = reducer.reduce(
+ State.Loading,
+ InternalMessage.FetchWidgetDataResult(
+ passedTopicsCount = 5,
+ isWidgetHidden = true
+ )
+ )
+
+ assertEquals(State.Hidden, state)
+ assertTrue(actions.isEmpty())
+ }
+
+ @Test
+ fun `FetchWidgetDataResult with visible widget should result in Data state`() {
+ val (state, actions) = reducer.reduce(
+ State.Loading,
+ InternalMessage.FetchWidgetDataResult(
+ passedTopicsCount = 5,
+ isWidgetHidden = false
+ )
+ )
+
+ assertEquals(State.Data(passedTopicsCount = 5), state)
+ assertTrue(actions.isEmpty())
+ }
+
+ @Test
+ fun `CloseClicked message in Data state should hide widget and log analytic event`() {
+ val (state, actions) = reducer.reduce(
+ State.Data(passedTopicsCount = 5),
+ Message.CloseClicked
+ )
+
+ assertEquals(State.Hidden, state)
+ assertContains(actions, InternalAction.HideWidget)
+ assertTrue {
+ actions.any { action ->
+ action is InternalAction.LogAnalyticEvent &&
+ action.event is NotificationDailyStudyReminderWidgetClickedCloseHyperskillAnalyticEvent
+ }
+ }
+ }
+
+ @Test
+ fun `WidgetClicked message in Data state should request permission and log analytic event`() {
+ val (state, actions) = reducer.reduce(
+ State.Data(passedTopicsCount = 5),
+ Message.WidgetClicked
+ )
+
+ assertEquals(State.Data(passedTopicsCount = 5), state)
+ assertContains(actions, Action.ViewAction.RequestNotificationPermission)
+ assertTrue {
+ actions.any { action ->
+ action is InternalAction.LogAnalyticEvent &&
+ action.event is NotificationDailyStudyReminderWidgetClickedHyperskillAnalyticEvent
+ }
+ }
+ }
+
+ @Test
+ fun `ViewedEventMessage in Data state should log viewed analytic event`() {
+ val (state, actions) = reducer.reduce(
+ State.Data(passedTopicsCount = 5),
+ Message.ViewedEventMessage
+ )
+
+ assertEquals(State.Data(passedTopicsCount = 5), state)
+ assertTrue {
+ actions.any { action ->
+ action is InternalAction.LogAnalyticEvent &&
+ action.event is NotificationDailyStudyReminderWidgetViewedHyperskillAnalyticEvent
+ }
+ }
+ }
+
+ @Test
+ fun `NotificationPermissionRequestResult when permission granted should save daily reminder interval`() {
+ val (state, actions) = reducer.reduce(
+ State.Data(passedTopicsCount = 5),
+ Message.NotificationPermissionRequestResult(
+ isPermissionGranted = true
+ )
+ )
+
+ assertEquals(State.Hidden, state)
+ assertTrue {
+ actions.any { action ->
+ action is InternalAction.SaveDailyStudyRemindersIntervalStartHour
+ }
+ }
+ }
+
+ @Test
+ fun `PassedTopicsCountChanged should update passed topics count in Data state`() {
+ val (state, actions) = reducer.reduce(
+ State.Data(passedTopicsCount = 5),
+ InternalMessage.PassedTopicsCountChanged(
+ passedTopicsCount = 10
+ )
+ )
+
+ assertEquals(State.Data(passedTopicsCount = 10), state)
+ assertTrue(actions.isEmpty())
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/notification_daily_study_reminder_widget/NotificationDailyStudyReminderWidgetViewStateMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/notification_daily_study_reminder_widget/NotificationDailyStudyReminderWidgetViewStateMapperTest.kt
new file mode 100644
index 0000000000..d8d7cf4ff7
--- /dev/null
+++ b/shared/src/commonTest/kotlin/org/hyperskill/notification_daily_study_reminder_widget/NotificationDailyStudyReminderWidgetViewStateMapperTest.kt
@@ -0,0 +1,43 @@
+package org.hyperskill.notification_daily_study_reminder_widget
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature
+import org.hyperskill.app.notification_daily_study_reminder_widget.view.mapper.NotificationDailyStudyReminderWidgetViewStateMapper
+
+class NotificationDailyStudyReminderWidgetViewStateMapperTest {
+ @Test
+ fun `map Idle state to Hidden ViewState`() {
+ val state = NotificationDailyStudyReminderWidgetFeature.State.Idle
+ val viewState = NotificationDailyStudyReminderWidgetViewStateMapper.map(state)
+ assertEquals(NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden, viewState)
+ }
+
+ @Test
+ fun `map Loading state to Hidden ViewState`() {
+ val state = NotificationDailyStudyReminderWidgetFeature.State.Loading
+ val viewState = NotificationDailyStudyReminderWidgetViewStateMapper.map(state)
+ assertEquals(NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden, viewState)
+ }
+
+ @Test
+ fun `map Hidden state to Hidden ViewState`() {
+ val state = NotificationDailyStudyReminderWidgetFeature.State.Hidden
+ val viewState = NotificationDailyStudyReminderWidgetViewStateMapper.map(state)
+ assertEquals(NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden, viewState)
+ }
+
+ @Test
+ fun `map Data state with passedTopicsCount greater than 0 to Visible ViewState`() {
+ val state = NotificationDailyStudyReminderWidgetFeature.State.Data(passedTopicsCount = 5)
+ val viewState = NotificationDailyStudyReminderWidgetViewStateMapper.map(state)
+ assertEquals(NotificationDailyStudyReminderWidgetFeature.ViewState.Visible, viewState)
+ }
+
+ @Test
+ fun `map Data state with passedTopicsCount equals 0 to Hidden ViewState`() {
+ val state = NotificationDailyStudyReminderWidgetFeature.State.Data(passedTopicsCount = 0)
+ val viewState = NotificationDailyStudyReminderWidgetViewStateMapper.map(state)
+ assertEquals(NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden, viewState)
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenFeatureStubState.kt b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenFeatureStubState.kt
new file mode 100644
index 0000000000..dd184aafea
--- /dev/null
+++ b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenFeatureStubState.kt
@@ -0,0 +1,21 @@
+package org.hyperskill.study_plan.screen
+
+import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature
+import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature
+import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature
+import org.hyperskill.app.users_interview_widget.presentation.UsersInterviewWidgetFeature
+
+internal fun StudyPlanScreenFeature.State.Companion.stub(
+ toolbarState: GamificationToolbarFeature.State = GamificationToolbarFeature.State.Idle,
+ usersInterviewWidgetState: UsersInterviewWidgetFeature.State = UsersInterviewWidgetFeature.State.Idle,
+ notificationDailyStudyReminderWidgetState: NotificationDailyStudyReminderWidgetFeature.State =
+ NotificationDailyStudyReminderWidgetFeature.State.Idle,
+ studyPlanWidgetState: StudyPlanWidgetFeature.State = StudyPlanWidgetFeature.State()
+): StudyPlanScreenFeature.State =
+ StudyPlanScreenFeature.State(
+ toolbarState = toolbarState,
+ usersInterviewWidgetState = usersInterviewWidgetState,
+ notificationDailyStudyReminderWidgetState = notificationDailyStudyReminderWidgetState,
+ studyPlanWidgetState = studyPlanWidgetState
+ )
\ No newline at end of file
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenTest.kt
index f1bf734916..30457a59f8 100644
--- a/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenTest.kt
+++ b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenTest.kt
@@ -7,6 +7,7 @@ import kotlin.test.fail
import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarScreen
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarReducer
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetReducer
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedChangeTrackHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedPullToRefreshHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedRetryContentLoadingHyperskillAnalyticEvent
@@ -23,13 +24,17 @@ class StudyPlanScreenTest {
private val reducer = StudyPlanScreenReducer(
GamificationToolbarReducer(GamificationToolbarScreen.STUDY_PLAN),
UsersInterviewWidgetReducer(),
+ NotificationDailyStudyReminderWidgetReducer(),
StudyPlanWidgetReducer()
)
@Test
fun `Viewed event message should trigger logging view analytic event`() {
- val (state, actions) = reducer.reduce(stubState(), StudyPlanScreenFeature.Message.ViewedEventMessage)
- assertEquals(state, stubState())
+ val (state, actions) = reducer.reduce(
+ StudyPlanScreenFeature.State.stub(),
+ StudyPlanScreenFeature.Message.ViewedEventMessage
+ )
+ assertEquals(state, StudyPlanScreenFeature.State.stub())
assertEquals(actions.size, 1)
val targetAction = actions.first() as StudyPlanScreenFeature.InternalAction.LogAnalyticEvent
@@ -42,7 +47,10 @@ class StudyPlanScreenTest {
@Test
fun `Pull-to-refresh message should trigger logging pull-to-refresh analytic event`() {
- val (_, actions) = reducer.reduce(stubState(), StudyPlanScreenFeature.Message.PullToRefresh)
+ val (_, actions) = reducer.reduce(
+ StudyPlanScreenFeature.State.stub(),
+ StudyPlanScreenFeature.Message.PullToRefresh
+ )
assertTrue {
val targetAction = actions
.filterIsInstance()
@@ -54,13 +62,13 @@ class StudyPlanScreenTest {
@Test
fun `Retry content loading message should trigger logging analytic event`() {
val (actualState, actions) = reducer.reduce(
- stubState(),
+ StudyPlanScreenFeature.State.stub(),
StudyPlanScreenFeature.Message.RetryContentLoading
)
- val expectedState = stubState(
+ val expectedState = StudyPlanScreenFeature.State.stub(
toolbarState = GamificationToolbarFeature.State.Loading,
- questionnaireWidgetState = UsersInterviewWidgetFeature.State.Loading,
+ usersInterviewWidgetState = UsersInterviewWidgetFeature.State.Loading,
studyPlanWidgetState = StudyPlanWidgetFeature.State(
sectionsStatus = ContentStatus.LOADING
)
@@ -77,11 +85,11 @@ class StudyPlanScreenTest {
@Test
fun `ChangeTrackButtonClicked message should navigate to track selection screen`() {
val (state, actions) = reducer.reduce(
- stubState(),
+ StudyPlanScreenFeature.State.stub(),
StudyPlanScreenFeature.Message.ChangeTrackButtonClicked
)
- assertEquals(state, stubState())
+ assertEquals(state, StudyPlanScreenFeature.State.stub())
assertTrue {
actions.any {
it is StudyPlanScreenFeature.Action.ViewAction.NavigateTo.TrackSelectionScreen
@@ -94,15 +102,4 @@ class StudyPlanScreenTest {
}
}
}
-
- private fun stubState(
- toolbarState: GamificationToolbarFeature.State = GamificationToolbarFeature.State.Idle,
- questionnaireWidgetState: UsersInterviewWidgetFeature.State = UsersInterviewWidgetFeature.State.Idle,
- studyPlanWidgetState: StudyPlanWidgetFeature.State = StudyPlanWidgetFeature.State()
- ): StudyPlanScreenFeature.State =
- StudyPlanScreenFeature.State(
- toolbarState,
- questionnaireWidgetState,
- studyPlanWidgetState
- )
}
\ No newline at end of file
diff --git a/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenViewStateMapperTest.kt b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenViewStateMapperTest.kt
new file mode 100644
index 0000000000..81b8e72ce3
--- /dev/null
+++ b/shared/src/commonTest/kotlin/org/hyperskill/study_plan/screen/StudyPlanScreenViewStateMapperTest.kt
@@ -0,0 +1,107 @@
+package org.hyperskill.study_plan.screen
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.hyperskill.ResourceProviderStub
+import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter
+import org.hyperskill.app.notification_daily_study_reminder_widget.presentation.NotificationDailyStudyReminderWidgetFeature
+import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature
+import org.hyperskill.app.study_plan.screen.view.StudyPlanScreenViewStateMapper
+import org.hyperskill.app.study_plan.widget.view.mapper.StudyPlanWidgetViewStateMapper
+import org.hyperskill.app.users_interview_widget.presentation.UsersInterviewWidgetFeature
+
+class StudyPlanScreenViewStateMapperTest {
+ private val viewStateMapper = StudyPlanScreenViewStateMapper(
+ studyPlanWidgetViewStateMapper = StudyPlanWidgetViewStateMapper(
+ dateFormatter = SharedDateFormatter(
+ ResourceProviderStub()
+ )
+ ),
+ resourceProvider = ResourceProviderStub()
+ )
+
+ @Test
+ fun `NotificationDailyStudyReminderWidgetViewState should be Hidden when UsersInterviewWidget is Visible`() {
+ val state = StudyPlanScreenFeature.State.stub(
+ usersInterviewWidgetState = UsersInterviewWidgetFeature.State.Visible,
+ notificationDailyStudyReminderWidgetState = NotificationDailyStudyReminderWidgetFeature.State.Data(
+ passedTopicsCount = 5
+ )
+ )
+
+ val viewState = viewStateMapper.map(state)
+
+ assertEquals(
+ NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden,
+ viewState.notificationDailyStudyReminderWidgetViewState
+ )
+ }
+
+ /* ktlint-disable */
+ @Test
+ fun `NotificationDailyStudyReminderWidgetViewState should be Visible when Data state has passedTopicsCount greater than 0 and UsersInterviewWidget is not Visible`() {
+ val state = StudyPlanScreenFeature.State.stub(
+ usersInterviewWidgetState = UsersInterviewWidgetFeature.State.Idle,
+ notificationDailyStudyReminderWidgetState = NotificationDailyStudyReminderWidgetFeature.State.Data(
+ passedTopicsCount = 5
+ )
+ )
+
+ val viewState = viewStateMapper.map(state)
+
+ assertEquals(
+ NotificationDailyStudyReminderWidgetFeature.ViewState.Visible,
+ viewState.notificationDailyStudyReminderWidgetViewState
+ )
+ }
+
+ /* ktlint-disable */
+ @Test
+ fun `NotificationDailyStudyReminderWidgetViewState should be Hidden when Data state has passedTopicsCount equals 0 and UsersInterviewWidget is not Visible`() {
+ val state = StudyPlanScreenFeature.State.stub(
+ usersInterviewWidgetState = UsersInterviewWidgetFeature.State.Idle,
+ notificationDailyStudyReminderWidgetState = NotificationDailyStudyReminderWidgetFeature.State.Data(
+ passedTopicsCount = 0
+ )
+ )
+
+ val viewState = viewStateMapper.map(state)
+
+ assertEquals(
+ NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden,
+ viewState.notificationDailyStudyReminderWidgetViewState
+ )
+ }
+
+ /* ktlint-disable */
+ @Test
+ fun `NotificationDailyStudyReminderWidgetViewState should be Hidden when State is Idle and UsersInterviewWidget is not Visible`() {
+ val state = StudyPlanScreenFeature.State.stub(
+ usersInterviewWidgetState = UsersInterviewWidgetFeature.State.Idle,
+ notificationDailyStudyReminderWidgetState = NotificationDailyStudyReminderWidgetFeature.State.Idle
+ )
+
+ val viewState = viewStateMapper.map(state)
+
+ assertEquals(
+ NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden,
+ viewState.notificationDailyStudyReminderWidgetViewState
+ )
+ }
+
+ /* ktlint-disable */
+ @Test
+ fun `NotificationDailyStudyReminderWidgetViewState should be Hidden when State is Loading and UsersInterviewWidget is not Visible`() {
+ val state = StudyPlanScreenFeature.State.stub(
+ usersInterviewWidgetState = UsersInterviewWidgetFeature.State.Idle,
+ notificationDailyStudyReminderWidgetState = NotificationDailyStudyReminderWidgetFeature.State.Loading
+ )
+
+ val viewState = viewStateMapper.map(state)
+
+ assertEquals(
+ NotificationDailyStudyReminderWidgetFeature.ViewState.Hidden,
+ viewState.notificationDailyStudyReminderWidgetViewState
+ )
+ }
+}
\ No newline at end of file