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-877: Shared support badges in profile #591

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,6 @@ enum class HyperskillAnalyticTarget(val targetName: String) {
GO_TO_STUDY_PLAN("go_to_study_plan"),
CHANGE_TRACK("change_track"),
CHANGE_PROJECT("change_project"),
BADGES_VISIBILITY_BUTTON("badges_visibility_button"),
BADGE_CARD("badges_card")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.hyperskill.app.badges.data.repository

import org.hyperskill.app.badges.domain.model.Badge
import org.hyperskill.app.badges.domain.repository.BadgesRepository
import org.hyperskill.app.badges.remote.BadgesRemoteDataSourceImpl

internal class BadgesRepositoryImpl(
private val remoteDataSource: BadgesRemoteDataSourceImpl
XanderZhu marked this conversation as resolved.
Show resolved Hide resolved
) : BadgesRepository {
override suspend fun getReceivedBadges(): Result<List<Badge>> =
remoteDataSource.getReceivedBadges()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.hyperskill.app.badges.data.source

import org.hyperskill.app.badges.domain.model.Badge

interface BadgesRemoteDataSource {
suspend fun getReceivedBadges(): Result<List<Badge>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.hyperskill.app.badges.domain.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class Badge(
@SerialName("id")
val id: Long,
@SerialName("kind")
val kind: BadgeKind = BadgeKind.UNKNOWN,
@SerialName("title")
val title: String,
@SerialName("level")
val level: Int,
@SerialName("value")
val value: Int,
@SerialName("current_level_value")
val currentLevelValue: Int,
@SerialName("next_level_value")
val nextLevelValue: Int? = null,
@SerialName("is_max_level")
val isMaxLevel: Boolean,
@SerialName("image_full")
val imageFull: String,
@SerialName("image_preview")
val imagePreview: String,
@SerialName("rank")
val rank: BadgeRank = BadgeRank.UNKNOWN
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.hyperskill.app.badges.domain.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

// The order is important, don't change it!
@Serializable
enum class BadgeKind {
@SerialName("project master")
ProjectMaster,
@SerialName("topic master")
TopicMaster,
@SerialName("committed learner")
CommittedLearner,
@SerialName("brilliant mind")
BrilliantMind,
@SerialName("helping hand")
HelpingHand,
@SerialName("sweetheart")
Sweetheart,
@SerialName("benefactor")
Benefactor,
@SerialName("bounty hunter")
BountyHunter,

UNKNOWN
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.hyperskill.app.badges.domain.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
enum class BadgeRank {
@SerialName("Apprentice")
APPRENTICE,
@SerialName("Expert")
EXPERT,
@SerialName("Master")
MASTER,
@SerialName("Legendary")
LEGENDARY,

UNKNOWN
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.hyperskill.app.badges.domain.repository

import org.hyperskill.app.badges.domain.model.Badge

interface BadgesRepository {
suspend fun getReceivedBadges(): Result<List<Badge>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.hyperskill.app.badges.injection

import org.hyperskill.app.badges.domain.repository.BadgesRepository

interface BadgesDataComponent {
val badgesRepository: BadgesRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.hyperskill.app.badges.injection

import org.hyperskill.app.badges.data.repository.BadgesRepositoryImpl
import org.hyperskill.app.badges.domain.repository.BadgesRepository
import org.hyperskill.app.badges.remote.BadgesRemoteDataSourceImpl
import org.hyperskill.app.core.injection.AppGraph

internal class BadgesDataComponentImpl(
appGraph: AppGraph
) : BadgesDataComponent {

private val badgesRemoteDataSource: BadgesRemoteDataSourceImpl =
XanderZhu marked this conversation as resolved.
Show resolved Hide resolved
BadgesRemoteDataSourceImpl(appGraph.networkComponent.authorizedHttpClient)

override val badgesRepository: BadgesRepository
get() = BadgesRepositoryImpl(badgesRemoteDataSource)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.hyperskill.app.badges.remote

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.http.ContentType
import io.ktor.http.contentType
import org.hyperskill.app.badges.data.source.BadgesRemoteDataSource
import org.hyperskill.app.badges.domain.model.Badge

class BadgesRemoteDataSourceImpl(
private val httpClient: HttpClient
) : BadgesRemoteDataSource {
override suspend fun getReceivedBadges(): Result<List<Badge>> =
runCatching {
httpClient.get("/api/badges") {
contentType(ContentType.Application.Json)
}.body<BadgesResponse>().badges
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.hyperskill.app.badges.remote

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.hyperskill.app.badges.domain.model.Badge
import org.hyperskill.app.core.remote.Meta
import org.hyperskill.app.core.remote.MetaResponse

@Serializable
class BadgesResponse(
@SerialName("meta")
override val meta: Meta,
@SerialName("badges")
val badges: List<Badge>
) : MetaResponse
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.hyperskill.app.analytic.injection.AnalyticComponent
import org.hyperskill.app.auth.injection.AuthComponent
import org.hyperskill.app.auth.injection.AuthCredentialsComponent
import org.hyperskill.app.auth.injection.AuthSocialComponent
import org.hyperskill.app.badges.injection.BadgesDataComponent
import org.hyperskill.app.comments.injection.CommentsDataComponent
import org.hyperskill.app.debug.injection.DebugComponent
import org.hyperskill.app.devices.injection.DevicesDataComponent
Expand Down Expand Up @@ -145,4 +146,6 @@ interface AppGraph {
fun buildPushNotificationsComponent(): PushNotificationsComponent
fun buildClickedNotificationComponent(): NotificationClickHandlingComponent
fun buildProgressScreenComponent(): ProgressScreenComponent

fun buildBadgesDataComponent(): BadgesDataComponent
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import org.hyperskill.app.auth.injection.AuthCredentialsComponent
import org.hyperskill.app.auth.injection.AuthCredentialsComponentImpl
import org.hyperskill.app.auth.injection.AuthSocialComponent
import org.hyperskill.app.auth.injection.AuthSocialComponentImpl
import org.hyperskill.app.badges.injection.BadgesDataComponent
import org.hyperskill.app.badges.injection.BadgesDataComponentImpl
import org.hyperskill.app.comments.injection.CommentsDataComponent
import org.hyperskill.app.comments.injection.CommentsDataComponentImpl
import org.hyperskill.app.debug.injection.DebugComponent
Expand Down Expand Up @@ -419,4 +421,7 @@ abstract class BaseAppGraph : AppGraph {

override fun buildProgressScreenComponent(): ProgressScreenComponent =
ProgressScreenComponentImpl(this)

override fun buildBadgesDataComponent(): BadgesDataComponent =
BadgesDataComponentImpl(this)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.hyperskill.app.profile.domain.analytic.badges

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
import org.hyperskill.app.badges.domain.model.BadgeKind

/**
* Represents click on the badge in profile analytics event.
*
* JSON payload:
* ```
* {
* "route": "/profile",
* "action": "click",
* "part": "main",
* "target": "badge_card",
* "context": {
* "badge_kind": "Project Mastery",
* "is_locked": true
* }
* }
* ```
* @see HyperskillAnalyticEvent
*/
class ProfileClickedBadgeCardHyperskillAnalyticsEvent(
private val badgeKind: BadgeKind,
private val isLocked: Boolean
) : HyperskillAnalyticEvent(
HyperskillAnalyticRoute.Profile(),
HyperskillAnalyticAction.CLICK,
HyperskillAnalyticPart.MAIN,
HyperskillAnalyticTarget.BADGE_CARD
) {
companion object {
private const val PARAM_BADGE_KIND = "badge_kind"
private const val PARAM_LOCKED = "is_locked"
}

override val params: Map<String, Any>
get() = super.params + mapOf(
PARAM_CONTEXT to mapOf(
PARAM_BADGE_KIND to badgeKind.name,
PARAM_LOCKED to isLocked
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.hyperskill.app.profile.domain.analytic.badges

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
import org.hyperskill.app.profile.presentation.ProfileFeature

/**
* Represents click on the showAll or showLess badges button in profile analytics event.
*
* JSON payload:
* ```
* {
* "route": "/profile",
* "action": "click",
* "part": "main",
* "target": "badges_visibility_button",
* "context": {
* button: "show_all"
* }
* }
* ```
* @see HyperskillAnalyticEvent
*/
class ProfileClickedBadgesVisibilityButtonHyperskillAnalyticsEvent(
private val visibilityButton: ProfileFeature.Message.BadgesVisibilityButton
) : HyperskillAnalyticEvent(
HyperskillAnalyticRoute.Profile(),
HyperskillAnalyticAction.CLICK,
HyperskillAnalyticPart.MAIN,
HyperskillAnalyticTarget.BADGES_VISIBILITY_BUTTON
) {

companion object {
private const val PARAM_BUTTON = "button"
private const val SHOW_ALL = "show_all"
private const val SHOW_LESS = "show_less"
}

override val params: Map<String, Any>
get() = super.params + mapOf(
PARAM_CONTEXT to mapOf(
PARAM_BUTTON to when (visibilityButton) {
ProfileFeature.Message.BadgesVisibilityButton.SHOW_ALL -> SHOW_ALL
ProfileFeature.Message.BadgesVisibilityButton.SHOW_LESS -> SHOW_LESS
}
)
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.hyperskill.app.profile.injection

import org.hyperskill.app.profile.presentation.ProfileFeature
import org.hyperskill.app.profile.view.BadgesViewStateMapper
import ru.nobird.app.presentation.redux.feature.Feature

interface ProfileComponent {
val profileFeature: Feature<ProfileFeature.State, ProfileFeature.Message, ProfileFeature.Action>
val badgesViewStateMapper: BadgesViewStateMapper
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.hyperskill.app.profile.injection

import org.hyperskill.app.core.injection.AppGraph
import org.hyperskill.app.profile.presentation.ProfileFeature
import org.hyperskill.app.profile.view.BadgesViewStateMapper
import ru.nobird.app.presentation.redux.feature.Feature

class ProfileComponentImpl(private val appGraph: AppGraph) : ProfileComponent {
Expand All @@ -16,6 +17,10 @@ class ProfileComponentImpl(private val appGraph: AppGraph) : ProfileComponent {
appGraph.buildNotificationComponent().notificationInteractor,
appGraph.buildMagicLinksDataComponent().urlPathProcessor,
appGraph.streakFlowDataComponent.streakFlow,
appGraph.notificationFlowDataComponent.dailyStudyRemindersEnabledFlow
appGraph.notificationFlowDataComponent.dailyStudyRemindersEnabledFlow,
appGraph.buildBadgesDataComponent().badgesRepository
)

override val badgesViewStateMapper: BadgesViewStateMapper
get() = BadgesViewStateMapper(appGraph.commonComponent.resourceProvider)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.hyperskill.app.profile.injection

import org.hyperskill.app.analytic.domain.interactor.AnalyticInteractor
import org.hyperskill.app.badges.domain.repository.BadgesRepository
import org.hyperskill.app.core.presentation.ActionDispatcherOptions
import org.hyperskill.app.magic_links.domain.interactor.UrlPathProcessor
import org.hyperskill.app.notification.local.domain.flow.DailyStudyRemindersEnabledFlow
Expand Down Expand Up @@ -31,7 +32,8 @@ object ProfileFeatureBuilder {
notificationInteractor: NotificationInteractor,
urlPathProcessor: UrlPathProcessor,
streakFlow: StreakFlow,
dailyStudyRemindersEnabledFlow: DailyStudyRemindersEnabledFlow
dailyStudyRemindersEnabledFlow: DailyStudyRemindersEnabledFlow,
badgesRepository: BadgesRepository
): Feature<State, Message, Action> {
val profileReducer = ProfileReducer()
val profileActionDispatcher = ProfileActionDispatcher(
Expand All @@ -45,7 +47,8 @@ object ProfileFeatureBuilder {
notificationInteractor,
urlPathProcessor,
streakFlow,
dailyStudyRemindersEnabledFlow
dailyStudyRemindersEnabledFlow,
badgesRepository
)

return ReduxFeature(State.Idle, profileReducer)
Expand Down
Loading