Skip to content

Commit

Permalink
Add review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-magda committed Nov 19, 2023
1 parent d3189ea commit 1bfa0b6
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,13 @@ class ChallengeWidgetActionDispatcher(
.createMagicLink(nextUrl = action.nextUrl)
.fold(
onSuccess = { magicLink ->
onNewMessage(InternalMessage.CreateMagicLinkSuccess(url = magicLink.url))
InternalMessage.CreateMagicLinkSuccess(url = magicLink.url)
},
onFailure = {
onNewMessage(InternalMessage.CreateMagicLinkError)
InternalMessage.CreateMagicLinkError
}
)
.let(onNewMessage)
}

private fun handleLaunchTimerAction(onNewMessage: (Message) -> Unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package org.hyperskill.app.challenges.widget.presentation

import org.hyperskill.app.analytic.domain.model.AnalyticEvent
import org.hyperskill.app.challenges.domain.model.Challenge
import org.hyperskill.app.challenges.domain.model.ChallengeStatus
import org.hyperskill.app.challenges.widget.view.mapper.ChallengeWidgetViewStateMapper
import org.hyperskill.app.challenges.widget.view.model.ChallengeWidgetViewState

object ChallengeWidgetFeature {
Expand All @@ -10,7 +12,7 @@ object ChallengeWidgetFeature {
data class Loading(val isLoadingSilently: Boolean) : State
object Error : State
data class Content(
val challenges: List<Challenge>,
val challenge: Challenge?,
val isLoadingMagicLink: Boolean = false,
internal val isRefreshing: Boolean = false
) : State
Expand Down Expand Up @@ -68,6 +70,22 @@ object ChallengeWidgetFeature {
object CreateMagicLinkError : InternalMessage
data class CreateMagicLinkSuccess(val url: String) : InternalMessage

/**
* In general this message is sent when the timer is started and when challenge status is
* [ChallengeStatus.NOT_STARTED] or [ChallengeStatus.STARTED].
*
* [ChallengeWidgetViewState.Content.Announcement] and [ChallengeWidgetViewState.Content.HappeningNow]
* view states has a formatted time remaining text ("Starts in" and "Complete in")
* and those texts should be updated every minute.
*
* When challenge status is [ChallengeStatus.NOT_STARTED] or [ChallengeStatus.STARTED] reducer handler function
* [ChallengeWidgetReducer.handleTimerTickMessage] don't changes state and don't produces any actions because
* formatting is done in the view state mapper [ChallengeWidgetViewStateMapper].
*
* @see InternalAction.LaunchTimer
* @see InternalAction.StopTimer
* @see ChallengeWidgetReducer.handleTimerTickMessage
*/
object TimerTick : InternalMessage

// Observe target types changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class ChallengeWidgetReducer : StateReducer<State, Message, Action> {
is State.Content ->
if (message.forceUpdate) {
State.Loading(
isLoadingSilently = state.challenges.isEmpty()
isLoadingSilently = state.challenge == null
) to setOf(InternalAction.FetchChallenges)
} else {
null
Expand All @@ -75,9 +75,11 @@ class ChallengeWidgetReducer : StateReducer<State, Message, Action> {
private fun handleFetchChallengesSuccessMessage(
message: InternalMessage.FetchChallengesSuccess
): ChallengeWidgetReducerResult {
val newState = State.Content(challenges = message.challenges)
val newState = State.Content(
challenge = message.challenges.firstOrNull()
)

val actions = when (newState.getCurrentChallenge()?.status) {
val actions = when (newState.challenge?.status) {
ChallengeStatus.NOT_STARTED,
ChallengeStatus.STARTED ->
setOf(InternalAction.LaunchTimer)
Expand Down Expand Up @@ -121,7 +123,7 @@ class ChallengeWidgetReducer : StateReducer<State, Message, Action> {
Action.ViewAction.OpenUrl(url = message.url, shouldOpenInApp = true),
InternalAction.LogAnalyticEvent(
ChallengeWidgetClickedLinkInTheDescriptionHyperskillAnalyticEvent(
challengeId = state.getCurrentChallenge()?.id,
challengeId = state.challenge?.id,
url = message.url
)
)
Expand All @@ -140,27 +142,29 @@ class ChallengeWidgetReducer : StateReducer<State, Message, Action> {

newState to setOf(
InternalAction.LogAnalyticEvent(
ChallengeWidgetClickedDeadlineReloadHyperskillAnalyticEvent(
challengeId = state.getCurrentChallenge()?.id
)
ChallengeWidgetClickedDeadlineReloadHyperskillAnalyticEvent(challengeId = state.challenge?.id)
)
)
} else {
null
}

private fun handleCollectRewardClickedMessage(state: State): ChallengeWidgetReducerResult =
state to buildSet {
state.getCurrentChallenge()?.rewardLink?.let {
add(InternalAction.CreateMagicLink(nextUrl = it))
}
add(
InternalAction.LogAnalyticEvent(
ChallengeWidgetClickedCollectRewardHyperskillAnalyticEvent(
challengeId = state.getCurrentChallenge()?.id
private fun handleCollectRewardClickedMessage(state: State): ChallengeWidgetReducerResult? =
if (state is State.Content) {
state.copy(isLoadingMagicLink = true) to buildSet {
state.challenge?.rewardLink?.let {
add(InternalAction.CreateMagicLink(nextUrl = it))
}
add(
InternalAction.LogAnalyticEvent(
ChallengeWidgetClickedCollectRewardHyperskillAnalyticEvent(
challengeId = state.challenge?.id
)
)
)
)
}
} else {
null
}

private fun handleCreateMagicLinkFailureMessage(state: State): ChallengeWidgetReducerResult? =
Expand All @@ -184,7 +188,7 @@ class ChallengeWidgetReducer : StateReducer<State, Message, Action> {

private fun handleStepSolvedMessage(state: State): ChallengeWidgetReducerResult? {
if (state is State.Content) {
val currentChallenge = state.getCurrentChallenge() ?: return null
val currentChallenge = state.challenge ?: return null
val currentChallengeTargetType = currentChallenge.targetType

if (currentChallengeTargetType != null) {
Expand All @@ -197,7 +201,7 @@ class ChallengeWidgetReducer : StateReducer<State, Message, Action> {
state to setOf(InternalAction.FetchChallenges)
ChallengeTargetType.STEP ->
state.copy(
challenges = state.setCurrentChallengeIntervalProgressAsCompleted() ?: state.challenges
challenge = state.setCurrentChallengeIntervalProgressAsCompleted() ?: state.challenge
) to emptySet()
}
} else {
Expand All @@ -210,21 +214,21 @@ class ChallengeWidgetReducer : StateReducer<State, Message, Action> {

private fun handleDailyStepCompletedMessage(state: State): ChallengeWidgetReducerResult? =
if (state is State.Content &&
state.getCurrentChallenge()?.targetType == ChallengeTargetType.DAILY_STEP
state.challenge?.targetType == ChallengeTargetType.DAILY_STEP
) {
state.copy(
challenges = state.setCurrentChallengeIntervalProgressAsCompleted() ?: state.challenges
challenge = state.setCurrentChallengeIntervalProgressAsCompleted() ?: state.challenge
) to emptySet()
} else {
null
}

private fun handleTopicCompletedMessage(state: State): ChallengeWidgetReducerResult? =
if (state is State.Content &&
state.getCurrentChallenge()?.targetType == ChallengeTargetType.TOPIC
state.challenge?.targetType == ChallengeTargetType.TOPIC
) {
state.copy(
challenges = state.setCurrentChallengeIntervalProgressAsCompleted() ?: state.challenges
challenge = state.setCurrentChallengeIntervalProgressAsCompleted() ?: state.challenge
) to emptySet()
} else {
null
Expand All @@ -237,7 +241,7 @@ class ChallengeWidgetReducer : StateReducer<State, Message, Action> {
is State.Loading ->
state to setOf(InternalAction.StopTimer)
is State.Content -> {
when (state.getCurrentChallenge()?.status) {
when (state.challenge?.status) {
null,
ChallengeStatus.COMPLETED,
ChallengeStatus.PARTIAL_COMPLETED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,20 @@ package org.hyperskill.app.challenges.widget.presentation
import org.hyperskill.app.challenges.domain.model.Challenge
import ru.nobird.app.core.model.mutate

internal fun ChallengeWidgetFeature.State.getCurrentChallenge(): Challenge? =
if (this is ChallengeWidgetFeature.State.Content) {
challenges.firstOrNull()
} else {
null
}

internal fun ChallengeWidgetFeature.State.setCurrentChallengeIntervalProgressAsCompleted(): List<Challenge>? {
if (this !is ChallengeWidgetFeature.State.Content) {
return null
}
internal fun ChallengeWidgetFeature.State.Content.setCurrentChallengeIntervalProgressAsCompleted(): Challenge? {
val currentChallenge = challenge ?: return null

val currentChallenge = getCurrentChallenge() ?: return null
val currentChallengeIndex = challenges
.indexOfFirst { it.id == currentChallenge.id }
.takeIf { it != -1 } ?: return null
val currentIntervalIndex = currentChallenge.currentInterval
?.minus(1)
?.takeIf { it > 0 && currentChallenge.progress.size > it }
?: return null

val currentIntervalIndex = currentChallenge.currentInterval?.minus(1) ?: return null
val newProgress = currentChallenge.progress.mapIndexed { index, progress ->
if (index == currentIntervalIndex) {
true
} else {
progress
}
}

return challenges.mutate {
val newProgress = currentChallenge.progress.mutate {
set(
index = currentChallengeIndex,
element = currentChallenge.copy(progress = newProgress)
index = currentIntervalIndex,
element = true
)
}

return currentChallenge.copy(progress = newProgress)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import org.hyperskill.app.SharedResources
import org.hyperskill.app.challenges.domain.model.Challenge
import org.hyperskill.app.challenges.domain.model.ChallengeStatus
import org.hyperskill.app.challenges.widget.presentation.ChallengeWidgetFeature
import org.hyperskill.app.challenges.widget.presentation.getCurrentChallenge
import org.hyperskill.app.challenges.widget.view.model.ChallengeWidgetViewState
import org.hyperskill.app.core.view.mapper.ResourceProvider
import org.hyperskill.app.core.view.mapper.date.SharedDateFormatter
Expand All @@ -36,7 +35,7 @@ class ChallengeWidgetViewStateMapper(
}

private fun getLoadedWidgetContent(state: ChallengeWidgetFeature.State.Content): ChallengeWidgetViewState {
val challenge = state.getCurrentChallenge() ?: return ChallengeWidgetViewState.Empty
val challenge = state.challenge ?: return ChallengeWidgetViewState.Empty
val challengeStatus = challenge.status ?: return ChallengeWidgetViewState.Empty

val headerData = getHeaderData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class SharedDateFormatter(private val resourceProvider: ResourceProvider) {
private const val DAYS_IN_YEAR = 365
private const val MONTHS_IN_YEAR = 12
private val DURATION_ONE_YEAR = DAYS_IN_YEAR.toDuration(DurationUnit.DAYS)

private const val HOURS_IN_DAY = 24
private const val MINUTES_IN_HOUR = 60
}

fun formatTimeDistance(millis: Long): String {
Expand Down Expand Up @@ -85,27 +88,26 @@ class SharedDateFormatter(private val resourceProvider: ResourceProvider) {
*/
fun formatDaysWithHoursAndMinutesCount(seconds: Long): String {
val duration = seconds.toDuration(DurationUnit.SECONDS)

val days = duration.inWholeDays.toInt()
val hours = duration.inWholeHours.toInt() % 24
val minutes = duration.inWholeMinutes.toInt() % 60
val hours = duration.inWholeHours.toInt() % HOURS_IN_DAY
val minutes = duration.inWholeMinutes.toInt() % MINUTES_IN_HOUR

if (days == 0 && hours == 0 && minutes == 0) {
return resourceProvider.getQuantityString(SharedResources.plurals.minutes, 1, 1)
}

var result = ""

if (days > 0) {
result += "${resourceProvider.getQuantityString(SharedResources.plurals.days, days, days)} "
}
if (hours > 0) {
result += "${resourceProvider.getQuantityString(SharedResources.plurals.hours, hours, hours)} "
}
if (minutes > 0) {
result += resourceProvider.getQuantityString(SharedResources.plurals.minutes, minutes, minutes)
return buildString {
if (days > 0) {
append("${resourceProvider.getQuantityString(SharedResources.plurals.days, days, days)} ")
}
if (hours > 0) {
append("${resourceProvider.getQuantityString(SharedResources.plurals.hours, hours, hours)} ")
}
if (minutes > 0) {
append(resourceProvider.getQuantityString(SharedResources.plurals.minutes, minutes, minutes))
}
}

return result
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ChallengeWidgetViewStateMapperTest {
),
startsInState = ChallengeWidgetViewState.Content.Announcement.StartsInState.Deadline
)
val actual = viewStateMapper.map(ChallengeWidgetFeature.State.Content(challenges = listOf(given)))
val actual = viewStateMapper.map(ChallengeWidgetFeature.State.Content(challenge = given))

assertEquals(expected, actual)
}
Expand Down

0 comments on commit 1bfa0b6

Please sign in to comment.