diff --git a/.github/workflows/automerge_into_release.yml b/.github/workflows/automerge_into_release.yml index 19ce528619..d968572309 100644 --- a/.github/workflows/automerge_into_release.yml +++ b/.github/workflows/automerge_into_release.yml @@ -48,7 +48,10 @@ jobs: set -e echo "Getting latest tag without v* prefix..." - current_version_number=$(git tag --sort=committerdate | tail -1 | cut -c 2-) + current_version_number=$(git tag --sort=committerdate | tail -1) + if [[ $current_version_number == v* ]]; then + current_version_number=${current_version_number:1} + fi echo "Current version number is $current_version_number" major=$(echo $current_version_number | cut -d. -f1) diff --git a/androidHyperskillApp/Gemfile.lock b/androidHyperskillApp/Gemfile.lock index eee2e28ce9..45c6098cb1 100644 --- a/androidHyperskillApp/Gemfile.lock +++ b/androidHyperskillApp/Gemfile.lock @@ -5,25 +5,25 @@ GEM base64 nkf rexml - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.946.0) - aws-sdk-core (3.197.2) + aws-partitions (1.957.0) + aws-sdk-core (3.201.3) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.85.0) - aws-sdk-core (~> 3, >= 3.197.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.152.3) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.9.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) @@ -38,7 +38,7 @@ GEM domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.110.0) + excon (0.111.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -161,7 +161,7 @@ GEM json (2.7.2) jwt (2.8.2) base64 - mini_magick (4.13.1) + mini_magick (4.13.2) mini_mime (1.1.5) multi_json (1.15.0) multipart-post (2.4.1) @@ -171,14 +171,14 @@ GEM optparse (0.5.0) os (1.1.4) plist (3.7.1) - public_suffix (5.1.1) + public_suffix (6.0.1) rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.9) + rexml (3.3.2) strscan rouge (2.0.7) ruby2_keywords (0.0.5) @@ -204,13 +204,12 @@ GEM uber (0.1.0) unicode-display_width (2.5.0) word_wrap (1.0.0) - xcodeproj (1.24.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (~> 3.2.4) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/gamification_toolbar/view/ui/delegate/GamificationToolbarDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/gamification_toolbar/view/ui/delegate/GamificationToolbarDelegate.kt index 163758f661..cdb2ee7331 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/gamification_toolbar/view/ui/delegate/GamificationToolbarDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/gamification_toolbar/view/ui/delegate/GamificationToolbarDelegate.kt @@ -1,11 +1,13 @@ package org.hyperskill.app.android.gamification_toolbar.view.ui.delegate import android.content.Context +import android.util.TypedValue import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.doOnNextLayout import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePaddingRelative +import androidx.core.view.updatePadding import androidx.fragment.app.FragmentManager import androidx.lifecycle.LifecycleOwner import com.github.terrakok.cicerone.Router @@ -31,29 +33,15 @@ class GamificationToolbarDelegate( lifecycleOwner: LifecycleOwner, private val context: Context, private val viewBinding: LayoutGamificationToolbarBinding, - onNewMessage: (Message) -> Unit + onChangeTrackClicked: () -> Unit = {}, + onNewMessage: (Message) -> Unit, ) { private var subtitle: String? = null init { with(viewBinding) { - root.doOnApplyWindowInsets { _, insets, _ -> - val insetTop = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top - - val toolbar = viewBinding.gamificationToolbar - toolbar.updateLayoutParams { - height += insetTop - } - toolbar.updatePaddingRelative(top = insetTop) - - applyInsetsToCollapsingToolbarLayout( - context = context, - collapsingToolbarLayout = viewBinding.gamificationCollapsingToolbarLayout, - insetTop = insetTop, - subtitle = subtitle - ) - } + applyWindowInsets() gamificationAppBar.setElevationOnCollapsed(lifecycleOwner.lifecycle) gamificationAppBar.setExpanded(true) gamificationStreakDurationTextView.setOnClickListener { @@ -68,6 +56,41 @@ class GamificationToolbarDelegate( gamificationProblemsLimitTextView.setOnClickListener { onNewMessage(Message.ProblemsLimitClicked) } + subtitleTextView.setOnClickListener { + onChangeTrackClicked() + } + } + } + + private fun applyWindowInsets() { + var initialTopPadding = 0 + viewBinding.gamificationToolbar.doOnNextLayout { + initialTopPadding = it.paddingTop + } + + viewBinding.root.doOnApplyWindowInsets { _, insets, _ -> + val insetTop = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top + + val toolbar = viewBinding.gamificationToolbar + toolbar.updateLayoutParams { + val typedValue = TypedValue() + if (context.theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) { + val toolbarHeight = TypedValue.complexToDimensionPixelSize( + /* data = */ typedValue.data, + /* metrics = */ context.resources.displayMetrics + ) + height = toolbarHeight + insetTop + } + } + + toolbar.updatePadding(top = insetTop + initialTopPadding) + + applyInsetsToCollapsingToolbarLayout( + context = context, + collapsingToolbarLayout = viewBinding.gamificationCollapsingToolbarLayout, + insetTop = insetTop, + subtitle = subtitle + ) } } @@ -149,6 +172,7 @@ class GamificationToolbarDelegate( ) } + @Suppress("MagicNumber") private fun applyInsetsToCollapsingToolbarLayout( context: Context, collapsingToolbarLayout: CollapsingToolbarLayout, @@ -159,23 +183,32 @@ class GamificationToolbarDelegate( ?.top ?: 0 ) { + val subtitlePaddingVertical = + if (subtitle != null) { + context.resources.getDimensionPixelOffset(R.dimen.gamification_toolbar_subtitle_padding_vertical) * 2 + } else { + 0 + } + collapsingToolbarLayout.expandedTitleMarginBottom = - context.resources.getDimensionPixelOffset( - if (subtitle != null) { + if (subtitle != null) { + context.resources.getDimensionPixelOffset( R.dimen.gamification_toolbar_with_subtitle_expanded_title_margin_bottom - } else { + ) + subtitlePaddingVertical + } else { + context.resources.getDimensionPixelOffset( R.dimen.gamification_toolbar_default_expanded_title_margin_bottom - } - ) + ) + } + collapsingToolbarLayout.updateLayoutParams { - height = context.resources.getDimensionPixelOffset( - if (subtitle != null) { - R.dimen.gamification_toolbar_with_subtitle_height - } else { - R.dimen.gamification_toolbar_default_height - } - ) + insetTop + height = if (subtitle != null) { + context.resources.getDimensionPixelOffset(R.dimen.gamification_toolbar_with_subtitle_height) + + subtitlePaddingVertical + } else { + R.dimen.gamification_toolbar_default_height + } + insetTop } - collapsingToolbarLayout.expandedTitleMarginTop = insetTop + collapsingToolbarLayout.expandedTitleMarginTop = insetTop + subtitlePaddingVertical } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallContent.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallContent.kt index f8c1257d35..a72527b760 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallContent.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallContent.kt @@ -1,6 +1,5 @@ package org.hyperskill.app.android.paywall.ui -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -31,17 +30,14 @@ import androidx.compose.ui.unit.sp import org.hyperskill.app.R import org.hyperskill.app.android.core.extensions.compose.plus import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillButton -import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillButtonDefaults import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme @Composable fun PaywallContent( buyButtonText: String, priceText: String?, - isContinueWithLimitsButtonVisible: Boolean, onTermsOfServiceClick: () -> Unit, onBuySubscriptionClick: () -> Unit, - onContinueWithLimitsClick: () -> Unit, modifier: Modifier = Modifier, padding: PaddingValues = PaddingValues() ) { @@ -58,8 +54,7 @@ fun PaywallContent( Image( painter = painterResource(id = org.hyperskill.app.android.R.drawable.img_paywall), contentDescription = null, - modifier = Modifier - .align(Alignment.CenterHorizontally) + modifier = Modifier.align(Alignment.CenterHorizontally) ) Spacer(modifier = Modifier.height(24.dp)) Text( @@ -81,10 +76,6 @@ fun PaywallContent( ) { Text(text = buyButtonText) } - Spacer(modifier = Modifier.height(8.dp)) - if (isContinueWithLimitsButtonVisible) { - ContinueButton(onClick = onContinueWithLimitsClick) - } Spacer(modifier = Modifier.height(20.dp)) TermsOfService( modifier = Modifier @@ -116,24 +107,6 @@ private fun SubscriptionPrice( } } -@Composable -private fun ContinueButton( - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - HyperskillButton( - onClick = onClick, - colors = HyperskillButtonDefaults.buttonColors(colorResource(id = R.color.layer_1)), - border = BorderStroke(1.dp, colorResource(id = R.color.button_tertiary)), - modifier = modifier.fillMaxWidth() - ) { - Text( - text = stringResource(id = R.string.paywall_mobile_only_continue_btn), - color = colorResource(id = R.color.button_tertiary) - ) - } -} - @Composable private fun TermsOfService( modifier: Modifier = Modifier @@ -153,11 +126,9 @@ fun PaywallContentPreview() { HyperskillTheme { PaywallContent( buyButtonText = PaywallPreviewDefaults.BUY_BUTTON_TEXT, - isContinueWithLimitsButtonVisible = true, priceText = PaywallPreviewDefaults.PRICE_TEXT, onTermsOfServiceClick = {}, - onBuySubscriptionClick = {}, - onContinueWithLimitsClick = {} + onBuySubscriptionClick = {} ) } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallScreen.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallScreen.kt index 3c8bc5f7f1..85b63344d2 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallScreen.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/PaywallScreen.kt @@ -1,14 +1,22 @@ package org.hyperskill.app.android.paywall.ui +import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -16,11 +24,14 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.hyperskill.app.R import org.hyperskill.app.android.core.extensions.findActivity @@ -52,77 +63,128 @@ fun PaywallScreen( viewModel.onBuySubscriptionClick(activity) } }, - onContinueWithLimitsClick = viewModel::onContinueWithLimitsClick, + onCloseClick = viewModel::onCloseClick, onRetryLoadingClick = viewModel::onRetryLoadingClicked, onTermsOfServiceClick = viewModel::onTermsOfServiceClick ) } -@OptIn(ExperimentalLayoutApi::class) @Composable fun PaywallScreen( viewState: ViewState, onBackClick: () -> Unit, onBuySubscriptionClick: () -> Unit, - onContinueWithLimitsClick: () -> Unit, + onCloseClick: () -> Unit, onRetryLoadingClick: () -> Unit, onTermsOfServiceClick: () -> Unit +) { + PaywallScaffold( + isToolbarVisible = viewState.isToolbarVisible, + onBackClick = onBackClick, + onCloseClick = onCloseClick + ) { padding -> + val insets = WindowInsets.safeDrawing + when (val contentState = viewState.contentState) { + ViewStateContent.Idle -> { + // no op + } + ViewStateContent.Loading -> { + HyperskillProgressBar( + modifier = Modifier + .align(Alignment.Center) + ) + } + ViewStateContent.Error -> + ScreenDataLoadingError( + errorMessage = stringResource(id = R.string.paywall_placeholder_error_description), + modifier = Modifier + .background(PaywallDefaults.BackgroundColor) + .windowInsetsPadding(insets) + ) { + onRetryLoadingClick() + } + is ViewStateContent.Content -> + PaywallContent( + buyButtonText = contentState.buyButtonText, + priceText = contentState.priceText, + onBuySubscriptionClick = onBuySubscriptionClick, + onTermsOfServiceClick = onTermsOfServiceClick, + padding = padding + ) + ViewStateContent.SubscriptionSyncLoading -> + SubscriptionSyncLoading( + modifier = Modifier.windowInsetsPadding(insets) + ) + } + } +} + +@Composable +private fun PaywallScaffold( + isToolbarVisible: Boolean, + onBackClick: () -> Unit, + onCloseClick: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable BoxScope.(PaddingValues) -> Unit ) { Scaffold( topBar = { - if (viewState.isToolbarVisible) { + if (isToolbarVisible) { HyperskillTopAppBar( title = stringResource(id = R.string.paywall_screen_title), onNavigationIconClick = onBackClick, backgroundColor = PaywallDefaults.BackgroundColor, ) } - } + }, + modifier = modifier ) { padding -> - Box( + Column( modifier = Modifier .fillMaxSize() .background(PaywallDefaults.BackgroundColor) - .consumeStatusBarInsets(viewState.isToolbarVisible) ) { - val insets = WindowInsets.safeDrawing - when (val contentState = viewState.contentState) { - ViewStateContent.Idle -> { - // no op - } - ViewStateContent.Loading -> { - HyperskillProgressBar( - modifier = Modifier.align(Alignment.Center) - ) - } - ViewStateContent.Error -> - ScreenDataLoadingError( - errorMessage = stringResource(id = R.string.paywall_placeholder_error_description), - modifier = Modifier - .background(PaywallDefaults.BackgroundColor) - .windowInsetsPadding(insets) - ) { - onRetryLoadingClick() - } - is ViewStateContent.Content -> - PaywallContent( - buyButtonText = contentState.buyButtonText, - isContinueWithLimitsButtonVisible = contentState.isContinueWithLimitsButtonVisible, - priceText = contentState.priceText, - onBuySubscriptionClick = onBuySubscriptionClick, - onContinueWithLimitsClick = onContinueWithLimitsClick, - onTermsOfServiceClick = onTermsOfServiceClick, - padding = padding - ) - ViewStateContent.SubscriptionSyncLoading -> - SubscriptionSyncLoading( - modifier = Modifier.windowInsetsPadding(insets) - ) + if (!isToolbarVisible) { + CloseButton( + onClick = onCloseClick, + modifier = Modifier + .align(Alignment.End) + .padding(12.dp) + .windowInsetsPadding(WindowInsets.statusBars) + ) } + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .consumeStatusBarInsets(isToolbarVisible), + content = { + content(padding) + } + ) } } } +@Composable +private fun CloseButton( + modifier: Modifier = Modifier, + onClick: () -> Unit +) { + Box( + modifier = modifier + .requiredSize(32.dp) + .clip(CircleShape) + .clickable(onClick = onClick) + ) { + Image( + painter = painterResource(id = org.hyperskill.app.android.R.drawable.ic_close_topic_completed), + contentDescription = null, + modifier = Modifier.align(Alignment.Center) + ) + } +} + private class PaywallPreviewProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( @@ -130,7 +192,6 @@ private class PaywallPreviewProvider : PreviewParameterProvider { isToolbarVisible = true, contentState = ViewStateContent.Content( buyButtonText = PaywallPreviewDefaults.BUY_BUTTON_TEXT, - isContinueWithLimitsButtonVisible = false, priceText = "$11.99 / month" ) ), @@ -138,7 +199,6 @@ private class PaywallPreviewProvider : PreviewParameterProvider { isToolbarVisible = false, contentState = ViewStateContent.Content( buyButtonText = PaywallPreviewDefaults.BUY_BUTTON_TEXT, - isContinueWithLimitsButtonVisible = true, priceText = PaywallPreviewDefaults.PRICE_TEXT ) ), @@ -176,7 +236,7 @@ fun PaywallScreenPreview( viewState = viewState, onBackClick = {}, onBuySubscriptionClick = {}, - onContinueWithLimitsClick = {}, + onCloseClick = {}, onRetryLoadingClick = {}, onTermsOfServiceClick = {} ) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionDetails.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionDetails.kt index c89d388c9e..ff534f8a8c 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionDetails.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/paywall/ui/SubscriptionDetails.kt @@ -30,6 +30,7 @@ fun SubscriptionDetails( SubscriptionOption(text = stringResource(id = SharedR.string.mobile_only_subscription_feature_1)) SubscriptionOption(text = stringResource(id = SharedR.string.mobile_only_subscription_feature_2)) SubscriptionOption(text = stringResource(id = SharedR.string.mobile_only_subscription_feature_3)) + SubscriptionOption(text = stringResource(id = SharedR.string.mobile_only_subscription_feature_4)) } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/stage_implementation/fragment/StageStepWrapperFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/stage_implementation/fragment/StageStepWrapperFragment.kt index 44573ea973..1606a542f2 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/stage_implementation/fragment/StageStepWrapperFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/stage_implementation/fragment/StageStepWrapperFragment.kt @@ -32,6 +32,7 @@ import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step.presentation.StepFeature import org.hyperskill.app.step.presentation.StepViewModel import org.hyperskill.app.step_completion.presentation.StepCompletionFeature +import org.hyperskill.app.topic_completed_modal.presentation.TopicCompletedModalFeature import ru.nobird.android.view.base.ui.delegate.ViewStateDelegate import ru.nobird.android.view.base.ui.extension.argument import ru.nobird.android.view.redux.ui.extension.reduxViewModel @@ -207,12 +208,19 @@ class StageStepWrapperFragment : stepViewModel.onRefuseStreakSharingClick(streak) } - override fun navigateToStudyPlan() { - onNewMessage(StepCompletionFeature.Message.TopicCompletedModalGoToStudyPlanClicked) - } - - override fun navigateToNextTopic() { - onNewMessage(StepCompletionFeature.Message.TopicCompletedModalContinueNextTopicClicked) + override fun navigateTo(destination: TopicCompletedModalFeature.Action.ViewAction.NavigateTo) { + when (destination) { + TopicCompletedModalFeature.Action.ViewAction.NavigateTo.NextTopic -> + onNewMessage(StepCompletionFeature.Message.TopicCompletedModalContinueNextTopicClicked) + TopicCompletedModalFeature.Action.ViewAction.NavigateTo.StudyPlan -> + onNewMessage(StepCompletionFeature.Message.TopicCompletedModalGoToStudyPlanClicked) + is TopicCompletedModalFeature.Action.ViewAction.NavigateTo.Paywall -> + onNewMessage( + StepCompletionFeature.Message.TopicCompletedModalPaywallClicked( + destination.paywallTransitionSource + ) + ) + } } override fun fullScrollDown() { diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt index a61ed61a42..763f08a61d 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/delegate/StepDelegate.kt @@ -22,6 +22,7 @@ import org.hyperskill.app.android.main.view.ui.navigation.MainScreen import org.hyperskill.app.android.main.view.ui.navigation.MainScreenRouter import org.hyperskill.app.android.main.view.ui.navigation.Tabs import org.hyperskill.app.android.main.view.ui.navigation.switch +import org.hyperskill.app.android.paywall.navigation.PaywallScreen import org.hyperskill.app.android.request_review.dialog.RequestReviewDialogFragment import org.hyperskill.app.android.share_streak.fragment.ShareStreakDialogFragment import org.hyperskill.app.android.step.view.model.StepHost @@ -121,6 +122,9 @@ object StepDelegate { mainScreenRouter.switch(Tabs.STUDY_PLAN) } + is StepCompletionFeature.Action.ViewAction.NavigateTo.Paywall -> + fragment.requireRouter().navigateTo(PaywallScreen(stepCompletionAction.paywallTransitionSource)) + is StepCompletionFeature.Action.ViewAction.ReloadStep -> reloadStep(fragment, stepCompletionAction.stepRoute) diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/fragment/StepWrapperFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/fragment/StepWrapperFragment.kt index 42940c49ac..e3f6807a45 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/fragment/StepWrapperFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step/view/fragment/StepWrapperFragment.kt @@ -35,6 +35,7 @@ import org.hyperskill.app.step.domain.model.StepRoute import org.hyperskill.app.step.presentation.StepFeature import org.hyperskill.app.step.presentation.StepViewModel import org.hyperskill.app.step_completion.presentation.StepCompletionFeature +import org.hyperskill.app.topic_completed_modal.presentation.TopicCompletedModalFeature import ru.nobird.android.view.base.ui.delegate.ViewStateDelegate import ru.nobird.android.view.base.ui.extension.showIfNotExists import ru.nobird.android.view.redux.ui.extension.reduxViewModel @@ -201,12 +202,19 @@ class StepWrapperFragment : ?.onLimitsClick() } - override fun navigateToStudyPlan() { - onNewMessage(StepCompletionFeature.Message.TopicCompletedModalGoToStudyPlanClicked) - } - - override fun navigateToNextTopic() { - onNewMessage(StepCompletionFeature.Message.TopicCompletedModalContinueNextTopicClicked) + override fun navigateTo(destination: TopicCompletedModalFeature.Action.ViewAction.NavigateTo) { + when (destination) { + TopicCompletedModalFeature.Action.ViewAction.NavigateTo.NextTopic -> + onNewMessage(StepCompletionFeature.Message.TopicCompletedModalContinueNextTopicClicked) + TopicCompletedModalFeature.Action.ViewAction.NavigateTo.StudyPlan -> + onNewMessage(StepCompletionFeature.Message.TopicCompletedModalGoToStudyPlanClicked) + is TopicCompletedModalFeature.Action.ViewAction.NavigateTo.Paywall -> + onNewMessage( + StepCompletionFeature.Message.TopicCompletedModalPaywallClicked( + destination.paywallTransitionSource + ) + ) + } } override fun onPrimaryActionClicked(action: StepMenuPrimaryAction) { diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt index 52d3067f57..8a08d476df 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/step_quiz/view/fragment/DefaultStepQuizFragment.kt @@ -320,6 +320,9 @@ abstract class DefaultStepQuizFragment : StepQuizFeature.Action.ViewAction.ScrollToCallToActionButton -> { handleScrollToCallToActionButton() } + is StepQuizFeature.Action.ViewAction.StepQuizCodeBlanksViewAction -> { + // no op + } } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/StudyPlanActivityAdapterDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/StudyPlanActivityAdapterDelegate.kt index cc1ab2cb3d..368fc0be55 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/StudyPlanActivityAdapterDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/adapter/StudyPlanActivityAdapterDelegate.kt @@ -12,7 +12,7 @@ import ru.nobird.android.ui.adapterdelegates.AdapterDelegate import ru.nobird.android.ui.adapterdelegates.DelegateViewHolder class StudyPlanActivityAdapterDelegate( - private val onActivityClicked: (Long) -> Unit + private val onActivityClicked: (Long, Long) -> Unit ) : AdapterDelegate>() { override fun isForViewType(position: Int, data: StudyPlanRecyclerItem): Boolean = @@ -27,9 +27,9 @@ class StudyPlanActivityAdapterDelegate( init { itemView.setOnClickListener { - val activityId = (itemData as? StudyPlanRecyclerItem.Activity)?.id - if (activityId != null) { - onActivityClicked(activityId) + val activity = itemData as? StudyPlanRecyclerItem.Activity + if (activity != null) { + onActivityClicked(activity.id, activity.sectionId) } } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/StudyPlanWidgetDelegate.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/StudyPlanWidgetDelegate.kt index 5a6fb7d05e..c117573265 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/StudyPlanWidgetDelegate.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/delegate/StudyPlanWidgetDelegate.kt @@ -9,6 +9,7 @@ import org.hyperskill.app.android.R import org.hyperskill.app.android.core.view.ui.adapter.DataLoadingErrorAdapterDelegate import org.hyperskill.app.android.core.view.ui.adapter.decoration.itemDecoration import org.hyperskill.app.android.databinding.ErrorNoConnectionWithButtonBinding +import org.hyperskill.app.android.databinding.ItemStudyPlanPaywallBinding import org.hyperskill.app.android.study_plan.adapter.ActivityLoadingAdapterDelegate import org.hyperskill.app.android.study_plan.adapter.StudyPlanActivityAdapterDelegate import org.hyperskill.app.android.study_plan.adapter.StudyPlanItemAnimator @@ -34,11 +35,18 @@ class StudyPlanWidgetDelegate( private val studyPlanAdapter = DefaultDelegateAdapter().apply { addDelegate(StudyPlanSectionAdapterDelegate(onNewMessage)) addDelegate( - StudyPlanActivityAdapterDelegate { activityId -> - onNewMessage(StudyPlanWidgetFeature.Message.ActivityClicked(activityId)) + StudyPlanActivityAdapterDelegate { activityId, sectionId -> + onNewMessage( + StudyPlanWidgetFeature.Message.ActivityClicked( + activityId = activityId, + sectionId = sectionId + ) + ) } ) addDelegate(sectionsLoadingAdapterDelegate()) + addDelegate(loadAllTopicsButtonDelegate()) + addDelegate(paywallAdapterDelegate()) addDelegate(ActivityLoadingAdapterDelegate()) addDelegate( DataLoadingErrorAdapterDelegate { item -> @@ -54,10 +62,10 @@ class StudyPlanWidgetDelegate( ContextCompat.getColor(context, org.hyperskill.app.R.color.color_on_surface) @ColorInt private val activeActivityTextColor: Int = - ContextCompat.getColor(context, StudyPlanRecyclerItem.Activity.activeTextColorRes) + ContextCompat.getColor(context, org.hyperskill.app.R.color.color_on_surface_alpha_87) @ColorInt private val inactiveActivityTextColor: Int = - ContextCompat.getColor(context, StudyPlanRecyclerItem.Activity.inactiveTextColorRes) + ContextCompat.getColor(context, org.hyperskill.app.R.color.color_on_surface_alpha_60) private val sectionTopMargin = context.resources.getDimensionPixelOffset(R.dimen.study_plan_section_top_margin) @@ -65,17 +73,19 @@ class StudyPlanWidgetDelegate( context.resources.getDimensionPixelOffset(R.dimen.study_plan_activity_top_margin) private val activeIcon = - ContextCompat.getDrawable(context, StudyPlanRecyclerItem.Activity.nextActivityIconRes) + ContextCompat.getDrawable(context, R.drawable.ic_home_screen_arrow_button) private val skippedIcon = - ContextCompat.getDrawable(context, StudyPlanRecyclerItem.Activity.skippedActivityIconRes) + ContextCompat.getDrawable(context, R.drawable.ic_topic_skipped) private val completedIcon = - ContextCompat.getDrawable(context, StudyPlanRecyclerItem.Activity.completedActivityIconRes) + ContextCompat.getDrawable(context, R.drawable.ic_topic_completed) + private val lockedIcon = + ContextCompat.getDrawable(context, R.drawable.ic_activity_locked) private var studyPlanViewStateDelegate: ViewStateDelegate? = null private val sectionsLoadingItems: List = - List(SECTIONS_LOADING_ITEMS_COUNT) { - StudyPlanRecyclerItem.SectionLoading + List(SECTIONS_LOADING_ITEMS_COUNT) { index -> + StudyPlanRecyclerItem.SectionLoading(index) } fun setup(recyclerView: RecyclerView, errorViewBinding: ErrorNoConnectionWithButtonBinding) { @@ -122,11 +132,13 @@ class StudyPlanWidgetDelegate( private fun getTopMarginFor(item: StudyPlanRecyclerItem): Int = when (item) { - StudyPlanRecyclerItem.SectionLoading, - is StudyPlanRecyclerItem.Section -> sectionTopMargin + is StudyPlanRecyclerItem.SectionLoading, + is StudyPlanRecyclerItem.Section, + is StudyPlanRecyclerItem.PaywallBanner -> sectionTopMargin is StudyPlanRecyclerItem.ActivityLoading, is StudyPlanRecyclerItem.Activity, - is StudyPlanRecyclerItem.ActivitiesError -> activityTopMargin + is StudyPlanRecyclerItem.ActivitiesError, + is StudyPlanRecyclerItem.LoadAllTopicsButton -> activityTopMargin else -> 0 } @@ -154,25 +166,52 @@ class StudyPlanWidgetDelegate( R.layout.item_study_plan_section_loading ) + private fun paywallAdapterDelegate() = + adapterDelegate(R.layout.item_study_plan_paywall) { + val binding = ItemStudyPlanPaywallBinding.bind(itemView) + binding.studyPlanPaywallSubscribeButton.setOnClickListener { + onNewMessage(StudyPlanWidgetFeature.Message.SubscribeClicked) + } + } + + private fun loadAllTopicsButtonDelegate() = + adapterDelegate( + R.layout.item_study_plan_load_more_button + ) { + itemView.setOnClickListener { + val sectionId = item?.sectionId + if (sectionId != null) { + onNewMessage( + StudyPlanWidgetFeature.Message.LoadMoreActivitiesClicked(sectionId) + ) + } + } + } + private fun mapContentToRecyclerItems( studyPlanContent: StudyPlanWidgetViewState.Content ): List = - studyPlanContent.sections.flatMapIndexed { index, section -> - buildList { - add(mapSectionToRecyclerItem(index, section)) + buildList { + if (studyPlanContent.isPaywallBannerShown) { + add(StudyPlanRecyclerItem.PaywallBanner) + } + studyPlanContent.sections.forEachIndexed { sectionIndex, section -> + add(mapSectionToRecyclerItem(sectionIndex, section)) when (val sectionContent = section.content) { StudyPlanWidgetViewState.SectionContent.Collapsed -> { // no op } StudyPlanWidgetViewState.SectionContent.Loading -> { - addAll( - List(ACTIVITIES_LOADING_ITEMS_COUNT) { index -> - StudyPlanRecyclerItem.ActivityLoading(section.id, index) - } - ) + addAll(getActivitiesLoadingItems(section.id)) } is StudyPlanWidgetViewState.SectionContent.Content -> { - addAll(mapSectionContentToActivityItems(sectionContent)) + addAll(mapSectionItemsToActivityItems(section.id, sectionContent.sectionItems)) + if (sectionContent.isLoadAllTopicsButtonShown) { + add(StudyPlanRecyclerItem.LoadAllTopicsButton(section.id)) + } + if (sectionContent.isNextPageLoadingShowed) { + addAll(getActivitiesLoadingItems(section.id)) + } } StudyPlanWidgetViewState.SectionContent.Error -> { add(StudyPlanRecyclerItem.ActivitiesError(section.id)) @@ -200,12 +239,14 @@ class StudyPlanWidgetDelegate( isCurrentBadgeShown = section.isCurrentBadgeShown ) - private fun mapSectionContentToActivityItems( - content: StudyPlanWidgetViewState.SectionContent.Content + private fun mapSectionItemsToActivityItems( + sectionId: Long, + sectionItems: List ): List = - content.sectionItems.map { item -> + sectionItems.map { item -> StudyPlanRecyclerItem.Activity( id = item.id, + sectionId = sectionId, title = item.title, subtitle = item.subtitle, titleTextColor = if (item.state == StudyPlanWidgetViewState.SectionItemState.NEXT) { @@ -220,8 +261,14 @@ class StudyPlanWidgetDelegate( StudyPlanWidgetViewState.SectionItemState.NEXT -> activeIcon StudyPlanWidgetViewState.SectionItemState.SKIPPED -> skippedIcon StudyPlanWidgetViewState.SectionItemState.COMPLETED -> completedIcon + StudyPlanWidgetViewState.SectionItemState.LOCKED -> lockedIcon }, isIdeRequired = item.isIdeRequired ) } + + private fun getActivitiesLoadingItems(sectionId: Long) = + List(ACTIVITIES_LOADING_ITEMS_COUNT) { index -> + StudyPlanRecyclerItem.ActivityLoading(sectionId, index) + } } \ No newline at end of file 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 08f3dbcd48..5f1cbef518 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 @@ -17,15 +17,18 @@ import org.hyperskill.app.android.gamification_toolbar.view.ui.delegate.Gamifica import org.hyperskill.app.android.main.view.ui.navigation.MainScreenRouter import org.hyperskill.app.android.main.view.ui.navigation.Tabs import org.hyperskill.app.android.main.view.ui.navigation.switch +import org.hyperskill.app.android.paywall.navigation.PaywallScreen import org.hyperskill.app.android.stage_implementation.dialog.UnsupportedStageBottomSheet import org.hyperskill.app.android.study_plan.delegate.LearningActivityTargetViewActionHandler import org.hyperskill.app.android.study_plan.delegate.StudyPlanWidgetDelegate +import org.hyperskill.app.android.track_selection.list.navigation.TrackSelectionListScreen import org.hyperskill.app.android.users_interview_widget.delegate.UsersInterviewCardDelegate import org.hyperskill.app.core.injection.ReduxViewModelFactory import org.hyperskill.app.study_plan.presentation.StudyPlanScreenViewModel import org.hyperskill.app.study_plan.screen.presentation.StudyPlanScreenFeature import org.hyperskill.app.study_plan.widget.presentation.StudyPlanWidgetFeature import org.hyperskill.app.study_plan.widget.view.model.StudyPlanWidgetViewState +import org.hyperskill.app.track_selection.list.injection.TrackSelectionListParams import ru.nobird.android.view.redux.ui.extension.reduxViewModel import ru.nobird.app.presentation.redux.container.ReduxView @@ -99,10 +102,14 @@ class StudyPlanFragment : gamificationToolbarDelegate = GamificationToolbarDelegate( viewLifecycleOwner, requireContext(), - viewBinding.studyPlanAppBar - ) { message -> - studyPlanViewModel.onNewMessage(StudyPlanScreenFeature.Message.GamificationToolbarMessage(message)) - } + viewBinding.studyPlanAppBar, + onChangeTrackClicked = { + studyPlanViewModel.onNewMessage(StudyPlanScreenFeature.Message.ChangeTrackButtonClicked) + }, + onNewMessage = { message -> + studyPlanViewModel.onNewMessage(StudyPlanScreenFeature.Message.GamificationToolbarMessage(message)) + } + ) } private fun initUserQuestionnaireCardDelegate() { @@ -174,6 +181,11 @@ class StudyPlanFragment : viewAction = studyPlanWidgetViewAction.viewAction ) } + is StudyPlanWidgetFeature.Action.ViewAction.NavigateTo.Paywall -> { + requireRouter().navigateTo( + PaywallScreen(studyPlanWidgetViewAction.paywallTransitionSource) + ) + } } } is StudyPlanScreenFeature.Action.ViewAction.UsersInterviewWidgetViewAction -> { @@ -183,6 +195,13 @@ class StudyPlanFragment : logger = logger ) } + StudyPlanScreenFeature.Action.ViewAction.NavigateTo.TrackSelectionScreen -> { + requireRouter().navigateTo( + TrackSelectionListScreen( + TrackSelectionListParams() + ) + ) + } } } diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/model/StudyPlanRecyclerItem.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/model/StudyPlanRecyclerItem.kt index ae8efba452..82b9864b42 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/model/StudyPlanRecyclerItem.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/study_plan/model/StudyPlanRecyclerItem.kt @@ -2,7 +2,6 @@ package org.hyperskill.app.android.study_plan.model import android.graphics.drawable.Drawable import androidx.annotation.ColorInt -import org.hyperskill.app.android.R import ru.nobird.app.core.model.Identifiable interface StudyPlanRecyclerItem { @@ -17,38 +16,42 @@ interface StudyPlanRecyclerItem { val isCurrentBadgeShown: Boolean ) : StudyPlanRecyclerItem, Identifiable - object SectionLoading : StudyPlanRecyclerItem + data class SectionLoading( + val index: Int + ) : StudyPlanRecyclerItem, Identifiable { + override val id: String = "sections-list-loading-item-$index" + } + + data class LoadAllTopicsButton( + val sectionId: Long + ) : StudyPlanRecyclerItem, Identifiable { + override val id: String = "study-plan-$sectionId-load-all-topics" + } + + object PaywallBanner : StudyPlanRecyclerItem, Identifiable { + override val id: String = "study-plan-paywall-banner" + } data class ActivityLoading( val sectionId: Long, val index: Int ) : StudyPlanRecyclerItem, Identifiable { - override val id: String - get() = "$sectionId-$index" + override val id: String = "activity-loading-item-$sectionId-$index" } data class ActivitiesError(val sectionId: Long) : StudyPlanRecyclerItem, Identifiable { - override val id: String - get() = "section-content-error-$sectionId" + override val id: String = "section-content-error-$sectionId" } data class Activity( override val id: Long, + val sectionId: Long, val title: String, val subtitle: String?, @ColorInt val titleTextColor: Int, val progress: Int, val formattedProgress: String?, val endIcon: Drawable?, - val isIdeRequired: Boolean - ) : StudyPlanRecyclerItem, Identifiable { - companion object { - val activeTextColorRes: Int = org.hyperskill.app.R.color.color_on_surface_alpha_87 - val inactiveTextColorRes: Int = org.hyperskill.app.R.color.color_on_surface_alpha_60 - - const val nextActivityIconRes: Int = R.drawable.ic_home_screen_arrow_button - const val skippedActivityIconRes: Int = R.drawable.ic_topic_skipped - const val completedActivityIconRes: Int = R.drawable.ic_topic_completed - } - } + val isIdeRequired: Boolean, + ) : StudyPlanRecyclerItem, Identifiable } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/topic_completion/fragment/TopicCompletedDialogFragment.kt b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/topic_completion/fragment/TopicCompletedDialogFragment.kt index 26d7720d08..fb14fd95ad 100644 --- a/androidHyperskillApp/src/main/java/org/hyperskill/app/android/topic_completion/fragment/TopicCompletedDialogFragment.kt +++ b/androidHyperskillApp/src/main/java/org/hyperskill/app/android/topic_completion/fragment/TopicCompletedDialogFragment.kt @@ -138,12 +138,8 @@ class TopicCompletedDialogFragment : DialogFragment(R.layout.fragment_topic_comp TopicCompletedModalFeature.Action.ViewAction.Dismiss -> { dialog?.dismiss() } - TopicCompletedModalFeature.Action.ViewAction.NavigateTo.NextTopic -> { - (parentFragment as? Callback)?.navigateToNextTopic() - dismiss() - } - TopicCompletedModalFeature.Action.ViewAction.NavigateTo.StudyPlan -> { - (parentFragment as? Callback)?.navigateToStudyPlan() + is TopicCompletedModalFeature.Action.ViewAction.NavigateTo -> { + (parentFragment as? Callback)?.navigateTo(action) dismiss() } } @@ -153,7 +149,6 @@ class TopicCompletedDialogFragment : DialogFragment(R.layout.fragment_topic_comp requireNotNull(mediaPlayerDelegate) interface Callback { - fun navigateToNextTopic() - fun navigateToStudyPlan() + fun navigateTo(destination: TopicCompletedModalFeature.Action.ViewAction.NavigateTo) } } \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/drawable-hdpi/img_study_plan_paywall.webp b/androidHyperskillApp/src/main/res/drawable-hdpi/img_study_plan_paywall.webp new file mode 100644 index 0000000000..2dac11c89e Binary files /dev/null and b/androidHyperskillApp/src/main/res/drawable-hdpi/img_study_plan_paywall.webp differ diff --git a/androidHyperskillApp/src/main/res/drawable-mdpi/img_study_plan_paywall.webp b/androidHyperskillApp/src/main/res/drawable-mdpi/img_study_plan_paywall.webp new file mode 100644 index 0000000000..7695569c85 Binary files /dev/null and b/androidHyperskillApp/src/main/res/drawable-mdpi/img_study_plan_paywall.webp differ diff --git a/androidHyperskillApp/src/main/res/drawable-xhdpi/img_study_plan_paywall.webp b/androidHyperskillApp/src/main/res/drawable-xhdpi/img_study_plan_paywall.webp new file mode 100644 index 0000000000..ba0f7bd344 Binary files /dev/null and b/androidHyperskillApp/src/main/res/drawable-xhdpi/img_study_plan_paywall.webp differ diff --git a/androidHyperskillApp/src/main/res/drawable-xxhdpi/img_study_plan_paywall.webp b/androidHyperskillApp/src/main/res/drawable-xxhdpi/img_study_plan_paywall.webp new file mode 100644 index 0000000000..1e872cd2fb Binary files /dev/null and b/androidHyperskillApp/src/main/res/drawable-xxhdpi/img_study_plan_paywall.webp differ diff --git a/androidHyperskillApp/src/main/res/drawable-xxxhdpi/img_study_plan_paywall.webp b/androidHyperskillApp/src/main/res/drawable-xxxhdpi/img_study_plan_paywall.webp new file mode 100644 index 0000000000..9778640657 Binary files /dev/null and b/androidHyperskillApp/src/main/res/drawable-xxxhdpi/img_study_plan_paywall.webp differ diff --git a/androidHyperskillApp/src/main/res/drawable/ic_activity_locked.xml b/androidHyperskillApp/src/main/res/drawable/ic_activity_locked.xml new file mode 100644 index 0000000000..4f4e802971 --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/ic_activity_locked.xml @@ -0,0 +1,9 @@ + + + diff --git a/androidHyperskillApp/src/main/res/drawable/ic_change_track.xml b/androidHyperskillApp/src/main/res/drawable/ic_change_track.xml new file mode 100644 index 0000000000..40b1c8190a --- /dev/null +++ b/androidHyperskillApp/src/main/res/drawable/ic_change_track.xml @@ -0,0 +1,39 @@ + + + + + + + diff --git a/androidHyperskillApp/src/main/res/layout/item_study_plan_activity.xml b/androidHyperskillApp/src/main/res/layout/item_study_plan_activity.xml index 69857aca06..19eea6ebaf 100644 --- a/androidHyperskillApp/src/main/res/layout/item_study_plan_activity.xml +++ b/androidHyperskillApp/src/main/res/layout/item_study_plan_activity.xml @@ -85,7 +85,6 @@ app:layout_constraintEnd_toEndOf="@+id/endGuideline" app:layout_constraintTop_toTopOf="@+id/topGuideline" app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintVertical_bias="0" tools:ignore="ContentDescription" /> + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/item_study_plan_paywall.xml b/androidHyperskillApp/src/main/res/layout/item_study_plan_paywall.xml new file mode 100644 index 0000000000..d32d5ec7e6 --- /dev/null +++ b/androidHyperskillApp/src/main/res/layout/item_study_plan_paywall.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/androidHyperskillApp/src/main/res/layout/layout_gamification_toolbar.xml b/androidHyperskillApp/src/main/res/layout/layout_gamification_toolbar.xml index e8ba4ba401..04e9d1cee4 100644 --- a/androidHyperskillApp/src/main/res/layout/layout_gamification_toolbar.xml +++ b/androidHyperskillApp/src/main/res/layout/layout_gamification_toolbar.xml @@ -122,18 +122,24 @@ - diff --git a/androidHyperskillApp/src/main/res/values/dimens.xml b/androidHyperskillApp/src/main/res/values/dimens.xml index 3fba417112..6f28f801db 100644 --- a/androidHyperskillApp/src/main/res/values/dimens.xml +++ b/androidHyperskillApp/src/main/res/values/dimens.xml @@ -127,6 +127,7 @@ 100dp 8dp 30dp + 4dp 32dp diff --git a/androidHyperskillApp/src/main/res/values/styles.xml b/androidHyperskillApp/src/main/res/values/styles.xml index 07282a4006..94d59ffe8a 100644 --- a/androidHyperskillApp/src/main/res/values/styles.xml +++ b/androidHyperskillApp/src/main/res/values/styles.xml @@ -35,6 +35,12 @@ 8dp + +