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

ALTAPPS-1026: Shared share streak #737

Merged
merged 7 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class StepDelegate<TFragment>(
.newInstance(earnedGemsText = stepCompletionAction.earnedGemsText)
.showIfNotExists(fragment.childFragmentManager, CompletedStepOfTheDayDialogFragment.TAG)
}
is StepCompletionFeature.Action.ViewAction.ShowShareStreakModal -> {
// TODO: ALTAPPS-1028 Show share streak modal
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ struct StepView: View {
presentSendDailyStudyRemindersPermissionAlert()
case .showProblemOfDaySolvedModal(let showProblemOfDaySolvedModalViewAction):
presentDailyStepCompletedModal(earnedGemsText: showProblemOfDaySolvedModalViewAction.earnedGemsText)
case .showShareStreakModal:
#warning("TODO: ALTAPPS-1027 Show share streak modal")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ enum class HyperskillAnalyticPart(val partName: String) {
PROJECT_COMPLETED_MODAL("project_completed_modal"),
NEXT_LEARNING_ACTIVITY_WIDGET("next_learning_activity_widget"),
FULL_SCREEN_CODE_EDITOR("full_screen_code_editor"),
CODE_EDITOR("code_editor")
CODE_EDITOR("code_editor"),
SHARE_STREAK_MODAL("share_streak_modal")
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,8 @@ enum class HyperskillAnalyticTarget(val targetName: String) {
START_LEARNING("start_learning"),
REMIND_ME_LATER("remind_me_later"),
FULL_SCREEN_CODE_EDITOR("full_screen_code_editor"),
CODE_INPUT_ACCESSORY_BUTTON("code_input_accessory_button")
CODE_INPUT_ACCESSORY_BUTTON("code_input_accessory_button"),
SHARE_YOUR_STREAK("share_your_streak"),
SHARE_STREAK_MODAL("share_streak_modal"),
SHARE("share")
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import org.hyperskill.app.projects.injection.ProjectsDataComponent
import org.hyperskill.app.providers.injection.ProvidersDataComponent
import org.hyperskill.app.reactions.injection.ReactionsDataComponent
import org.hyperskill.app.sentry.injection.SentryComponent
import org.hyperskill.app.share_streak.injection.ShareStreakDataComponent
import org.hyperskill.app.stage_implement.injection.StageImplementComponent
import org.hyperskill.app.stages.injection.StagesDataComponent
import org.hyperskill.app.step.domain.model.StepRoute
Expand Down Expand Up @@ -151,4 +152,5 @@ interface AppGraph {
fun buildBadgesDataComponent(): BadgesDataComponent
fun buildNotificationsOnboardingComponent(): NotificationsOnboardingComponent
fun buildFirstProblemOnboardingComponent(): FirstProblemOnboardingComponent
fun buildShareStreakDataComponent(): ShareStreakDataComponent
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ import org.hyperskill.app.providers.injection.ProvidersDataComponent
import org.hyperskill.app.providers.injection.ProvidersDataComponentImpl
import org.hyperskill.app.reactions.injection.ReactionsDataComponent
import org.hyperskill.app.reactions.injection.ReactionsDataComponentImpl
import org.hyperskill.app.share_streak.injection.ShareStreakDataComponent
import org.hyperskill.app.share_streak.injection.ShareStreakDataComponentImpl
import org.hyperskill.app.stage_implement.injection.StageImplementComponent
import org.hyperskill.app.stage_implement.injection.StageImplementComponentImpl
import org.hyperskill.app.stages.injection.StagesDataComponent
Expand Down Expand Up @@ -411,4 +413,7 @@ abstract class BaseAppGraph : AppGraph {

override fun buildFirstProblemOnboardingComponent(): FirstProblemOnboardingComponent =
FirstProblemOnboardingComponentImpl(this)

override fun buildShareStreakDataComponent(): ShareStreakDataComponent =
ShareStreakDataComponentImpl(this)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.hyperskill.app.core.injection

import org.hyperskill.app.gamification_toolbar.domain.repository.CurrentGamificationToolbarDataStateRepository
import org.hyperskill.app.learning_activities.domain.repository.NextLearningActivityStateRepository
import org.hyperskill.app.study_plan.domain.repository.CurrentStudyPlanStateRepository
import org.hyperskill.app.subscriptions.domain.repository.CurrentSubscriptionStateRepository
Expand All @@ -12,9 +13,12 @@ interface StateRepositoriesComponent {

val nextLearningActivityStateRepository: NextLearningActivityStateRepository

val currentGamificationToolbarDataStateRepository: CurrentGamificationToolbarDataStateRepository

suspend fun resetRepositories() {
currentSubscriptionStateRepository.resetState()
currentStudyPlanStateRepository.resetState()
nextLearningActivityStateRepository.resetState()
currentGamificationToolbarDataStateRepository.resetState()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.hyperskill.app.core.injection

import org.hyperskill.app.gamification_toolbar.data.repository.CurrentGamificationToolbarDataStateRepositoryImpl
import org.hyperskill.app.gamification_toolbar.domain.repository.CurrentGamificationToolbarDataStateRepository
import org.hyperskill.app.gamification_toolbar.remote.GamificationToolbarRemoteDataSourceImpl
import org.hyperskill.app.learning_activities.data.repository.NextLearningActivityStateRepositoryImpl
import org.hyperskill.app.learning_activities.domain.repository.NextLearningActivityStateRepository
import org.hyperskill.app.learning_activities.remote.LearningActivitiesRemoteDataSourceImpl
Expand Down Expand Up @@ -37,4 +40,13 @@ class StateRepositoriesComponentImpl(appGraph: AppGraph) : StateRepositoriesComp
override val nextLearningActivityStateRepository: NextLearningActivityStateRepository by lazy {
NextLearningActivityStateRepositoryImpl(LearningActivitiesRemoteDataSourceImpl(authorizedHttpClient))
}

/**
* Gamification toolbar
*/
override val currentGamificationToolbarDataStateRepository: CurrentGamificationToolbarDataStateRepository by lazy {
CurrentGamificationToolbarDataStateRepositoryImpl(
GamificationToolbarRemoteDataSourceImpl(authorizedHttpClient)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package org.hyperskill.app.gamification_toolbar.data.repository

import org.hyperskill.app.core.data.repository.BaseStateRepository
import org.hyperskill.app.gamification_toolbar.data.source.GamificationToolbarRemoteDataSource
import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarData
import org.hyperskill.app.gamification_toolbar.domain.repository.GamificationToolbarRepository
import org.hyperskill.app.gamification_toolbar.domain.repository.CurrentGamificationToolbarDataStateRepository

class GamificationToolbarRepositoryImpl(
class CurrentGamificationToolbarDataStateRepositoryImpl(
private val gamificationToolbarRemoteDataSource: GamificationToolbarRemoteDataSource
) : GamificationToolbarRepository {
override suspend fun getGamificationToolbarData(): Result<GamificationToolbarData> =
) : CurrentGamificationToolbarDataStateRepository, BaseStateRepository<GamificationToolbarData>() {
override suspend fun loadState(): Result<GamificationToolbarData> =
gamificationToolbarRemoteDataSource.getGamificationToolbarData()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.hyperskill.app.gamification_toolbar.domain.repository

import org.hyperskill.app.core.domain.repository.StateRepository
import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarData

interface CurrentGamificationToolbarDataStateRepository : StateRepository<GamificationToolbarData>

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ package org.hyperskill.app.gamification_toolbar.injection

import org.hyperskill.app.core.injection.AppGraph
import org.hyperskill.app.core.presentation.ActionDispatcherOptions
import org.hyperskill.app.gamification_toolbar.data.repository.GamificationToolbarRepositoryImpl
import org.hyperskill.app.gamification_toolbar.data.source.GamificationToolbarRemoteDataSource
import org.hyperskill.app.gamification_toolbar.domain.model.GamificationToolbarScreen
import org.hyperskill.app.gamification_toolbar.domain.repository.GamificationToolbarRepository
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarActionDispatcher
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarReducer
import org.hyperskill.app.gamification_toolbar.remote.GamificationToolbarRemoteDataSourceImpl

class GamificationToolbarComponentImpl(
private val appGraph: AppGraph,
Expand All @@ -17,12 +13,6 @@ class GamificationToolbarComponentImpl(
override val gamificationToolbarReducer: GamificationToolbarReducer
get() = GamificationToolbarReducer(screen)

private val gamificationToolbarRemoteDataSource: GamificationToolbarRemoteDataSource =
GamificationToolbarRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient)

private val gamificationToolbarRepository: GamificationToolbarRepository
get() = GamificationToolbarRepositoryImpl(gamificationToolbarRemoteDataSource)

override val gamificationToolbarActionDispatcher: GamificationToolbarActionDispatcher
get() {
val profileComponent = appGraph.profileDataComponent
Expand All @@ -33,7 +23,7 @@ class GamificationToolbarComponentImpl(
profileComponent.currentProfileStateRepository,
appGraph.stateRepositoriesComponent.currentStudyPlanStateRepository,
appGraph.stepCompletionFlowDataComponent.topicCompletedFlow,
gamificationToolbarRepository,
appGraph.stateRepositoriesComponent.currentGamificationToolbarDataStateRepository,
appGraph.analyticComponent.analyticInteractor,
appGraph.sentryComponent.sentryInteractor
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
import org.hyperskill.app.core.presentation.ActionDispatcherOptions
import org.hyperskill.app.gamification_toolbar.domain.repository.GamificationToolbarRepository
import org.hyperskill.app.gamification_toolbar.domain.repository.CurrentGamificationToolbarDataStateRepository
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature.Action
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature.InternalAction
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature.InternalMessage
import org.hyperskill.app.gamification_toolbar.presentation.GamificationToolbarFeature.Message
import org.hyperskill.app.profile.domain.repository.CurrentProfileStateRepository
import org.hyperskill.app.profile.domain.repository.observeHypercoinsBalance
import org.hyperskill.app.sentry.domain.interactor.SentryInteractor
import org.hyperskill.app.sentry.domain.withTransaction
import org.hyperskill.app.step_completion.domain.flow.TopicCompletedFlow
import org.hyperskill.app.step_quiz.domain.repository.SubmissionRepository
import org.hyperskill.app.streaks.domain.flow.StreakFlow
Expand All @@ -24,68 +27,73 @@ class GamificationToolbarActionDispatcher(
currentProfileStateRepository: CurrentProfileStateRepository,
currentStudyPlanStateRepository: CurrentStudyPlanStateRepository,
topicCompletedFlow: TopicCompletedFlow,
private val gamificationToolbarRepository: GamificationToolbarRepository,
private val currentGamificationToolbarDataStateRepository: CurrentGamificationToolbarDataStateRepository,
private val analyticInteractor: AnalyticInteractor,
private val sentryInteractor: SentryInteractor
) : CoroutineActionDispatcher<Action, Message>(config.createConfig()) {

init {
submissionRepository.solvedStepsSharedFlow
.onEach { onNewMessage(Message.StepSolved) }
.onEach { onNewMessage(InternalMessage.StepSolved) }
.launchIn(actionScope)

currentProfileStateRepository.observeHypercoinsBalance()
.onEach { hypercoinsBalance ->
onNewMessage(Message.HypercoinsBalanceChanged(hypercoinsBalance))
onNewMessage(InternalMessage.HypercoinsBalanceChanged(hypercoinsBalance))
}
.launchIn(actionScope)

streakFlow.observe()
.distinctUntilChanged()
.onEach { streak ->
onNewMessage(Message.StreakChanged(streak))
onNewMessage(InternalMessage.StreakChanged(streak))
}
.launchIn(actionScope)

currentStudyPlanStateRepository.changes
.distinctUntilChanged()
.onEach { studyPlan ->
onNewMessage(Message.StudyPlanChanged(studyPlan))
onNewMessage(InternalMessage.StudyPlanChanged(studyPlan))
}
.launchIn(actionScope)

topicCompletedFlow.observe()
.distinctUntilChanged()
.onEach {
onNewMessage(Message.TopicCompleted)
onNewMessage(InternalMessage.TopicCompleted)
}
.launchIn(actionScope)

currentGamificationToolbarDataStateRepository.changes
.distinctUntilChanged()
.onEach { onNewMessage(InternalMessage.GamificationToolbarDataChanged(it)) }
.launchIn(actionScope)
}

override suspend fun doSuspendableAction(action: Action) {
when (action) {
is Action.FetchGamificationToolbarData -> {
val sentryTransaction = action.screen.fetchContentSentryTransaction
sentryInteractor.startTransaction(sentryTransaction)

val gamificationToolbarData = gamificationToolbarRepository
.getGamificationToolbarData()
.getOrElse {
sentryInteractor.finishTransaction(sentryTransaction, throwable = it)
return onNewMessage(Message.FetchGamificationToolbarDataError)
}

sentryInteractor.finishTransaction(sentryTransaction)

onNewMessage(
Message.FetchGamificationToolbarDataSuccess(gamificationToolbarData)
)
}
is Action.LogAnalyticEvent ->
is InternalAction.FetchGamificationToolbarData ->
handleFetchGamificationToolbarDataAction(action, ::onNewMessage)
is InternalAction.LogAnalyticEvent ->
analyticInteractor.logEvent(action.analyticEvent)
else -> {
// no op
}
}
}

private suspend fun handleFetchGamificationToolbarDataAction(
action: InternalAction.FetchGamificationToolbarData,
onNewMessage: (Message) -> Unit
) {
sentryInteractor.withTransaction(
action.screen.fetchContentSentryTransaction,
onError = { InternalMessage.FetchGamificationToolbarDataError }
) {
currentGamificationToolbarDataStateRepository
.getState(forceUpdate = action.forceUpdate)
.getOrThrow()
.let(InternalMessage::FetchGamificationToolbarDataSuccess)
}.let(onNewMessage)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,46 +26,49 @@ object GamificationToolbarFeature {
get() = this is State.Content && isRefreshing

sealed interface Message {
object ClickedGems : Message
object ClickedStreak : Message
object ClickedProgress : Message
}

internal sealed interface InternalMessage : Message {
/**
* Initialization
*/
data class Initialize(val forceUpdate: Boolean = false) : Message

object FetchGamificationToolbarDataError : Message
data class Initialize(val forceUpdate: Boolean = false) : InternalMessage
object FetchGamificationToolbarDataError : InternalMessage
data class FetchGamificationToolbarDataSuccess(
val gamificationToolbarData: GamificationToolbarData
) : Message
) : InternalMessage

object PullToRefresh : Message
object PullToRefresh : InternalMessage

/**
* Flow Messages
*/
object StepSolved : Message
data class HypercoinsBalanceChanged(val hypercoinsBalance: Int) : Message
data class StreakChanged(val streak: Streak?) : Message
data class StudyPlanChanged(val studyPlan: StudyPlan) : Message
object TopicCompleted : Message

/**
* Clicks
*/
object ClickedGems : Message
object ClickedStreak : Message
object ClickedProgress : Message
object StepSolved : InternalMessage
data class HypercoinsBalanceChanged(val hypercoinsBalance: Int) : InternalMessage
data class StreakChanged(val streak: Streak?) : InternalMessage
data class StudyPlanChanged(val studyPlan: StudyPlan) : InternalMessage
object TopicCompleted : InternalMessage
data class GamificationToolbarDataChanged(
val gamificationToolbarData: GamificationToolbarData
) : InternalMessage
}

sealed interface Action {
data class FetchGamificationToolbarData(
val screen: GamificationToolbarScreen,
val forceUpdate: Boolean
) : Action

data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : Action

sealed interface ViewAction : Action {
object ShowProfileTab : ViewAction
object ShowProgressScreen : ViewAction
}
}

internal sealed interface InternalAction : Action {
data class FetchGamificationToolbarData(
val screen: GamificationToolbarScreen,
val forceUpdate: Boolean
) : InternalAction

data class LogAnalyticEvent(val analyticEvent: AnalyticEvent) : InternalAction
}
}
Loading