Skip to content

Commit

Permalink
Merge pull request #1072 from hyperskill/release/1.61
Browse files Browse the repository at this point in the history
Release 1.61
  • Loading branch information
ivan-magda authored Jun 24, 2024
2 parents 6ee2241 + 7fe5d33 commit b444291
Show file tree
Hide file tree
Showing 428 changed files with 9,466 additions and 3,965 deletions.
2 changes: 1 addition & 1 deletion androidHyperskillApp/Gemfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
source "https://rubygems.org"
ruby "3.3.0"

gem "fastlane", "2.220.0"
gem "fastlane", "2.221.1"

eval_gemfile("fastlane/Pluginfile")
26 changes: 13 additions & 13 deletions androidHyperskillApp/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.939.0)
aws-sdk-core (3.196.1)
aws-partitions (1.946.0)
aws-sdk-core (3.197.2)
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.82.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sdk-kms (1.85.0)
aws-sdk-core (~> 3, >= 3.197.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-s3 (1.152.3)
aws-sdk-core (~> 3, >= 3.197.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
Expand Down Expand Up @@ -68,7 +68,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.3.1)
fastlane (2.220.0)
fastlane (2.221.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
Expand Down Expand Up @@ -159,9 +159,9 @@ GEM
httpclient (2.8.3)
jmespath (1.6.2)
json (2.7.2)
jwt (2.8.1)
jwt (2.8.2)
base64
mini_magick (4.12.0)
mini_magick (4.13.1)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
Expand All @@ -171,15 +171,15 @@ GEM
optparse (0.5.0)
os (1.1.4)
plist (3.7.1)
public_suffix (5.0.5)
public_suffix (5.1.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.8)
strscan (>= 3.0.9)
rexml (3.2.9)
strscan
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
Expand Down Expand Up @@ -224,7 +224,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
fastlane (= 2.220.0)
fastlane (= 2.221.1)
fastlane-plugin-firebase_app_distribution

RUBY VERSION
Expand Down
2 changes: 1 addition & 1 deletion androidHyperskillApp/fastlane/Fastfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fastlane_version "2.220.0"
fastlane_version "2.221.0"

default_platform(:android)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.tooling.preview.Preview
import org.hyperskill.app.R
import org.hyperskill.app.android.core.view.ui.widget.compose.HtmlText
import org.hyperskill.app.android.core.view.ui.widget.compose.ClickableHtmlText
import org.hyperskill.app.android.core.view.ui.widget.compose.HyperskillTheme

@Composable
Expand All @@ -16,7 +16,7 @@ fun ChallengeDescription(
onLinkClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
HtmlText(
ClickableHtmlText(
text = description,
modifier = modifier,
baseSpanStyle = SpanStyle(color = colorResource(id = R.color.color_on_surface_alpha_60)),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.hyperskill.app.android.core.extensions

import android.view.HapticFeedbackConstants
import android.view.View
import androidx.core.view.HapticFeedbackConstantsCompat

fun View.performRejectHapticFeedback(flag: Int = 0): Boolean =
performHapticFeedback(HapticFeedbackConstantsCompat.REJECT, flag = flag)

fun View.performConfirmHapticFeedback(flag: Int = 0): Boolean =
performHapticFeedback(HapticFeedbackConstantsCompat.CONFIRM, flag = flag)

// If REJECT feedback was not performed, then use default to haptic feedback
@Suppress("DEPRECATION")
fun View.performHapticFeedback(
feedbackConstant: Int,
flag: Int = 0,
defaultFeedbackConstant: Int = HapticFeedbackConstantsCompat.LONG_PRESS
): Boolean =
performHapticFeedback(
/* feedbackConstant = */ feedbackConstant,
/* flags = */ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING or flag
) || performHapticFeedback(defaultFeedbackConstant)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.hyperskill.app.android.core.extensions

import android.view.View
import android.view.ViewGroup
import androidx.core.widget.NestedScrollView

private const val DEFAULT_SMOOTH_SCROLL_DURATION = 250

fun NestedScrollView.smoothScrollToBottom(durationMilliseconds: Int = DEFAULT_SMOOTH_SCROLL_DURATION) {
val scrollView = this
val childCount = scrollView.childCount
if (childCount > 0) {
val view: View = scrollView.getChildAt(childCount - 1)
val lp = view.layoutParams as ViewGroup.MarginLayoutParams
val y = view.bottom + lp.bottomMargin + scrollView.paddingBottom
scrollView.smoothScrollTo(0, y, durationMilliseconds)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@ private const val URL_TAG = "url"

@Composable
fun HtmlText(
text: String,
baseSpanStyle: SpanStyle? = null,
content: @Composable (AnnotatedString) -> Unit
) {
val annotatedString = remember(text) {
HtmlCompat
.fromHtml(
text.replace("\n", "<br>"),
HtmlCompat.FROM_HTML_MODE_COMPACT
)
.toAnnotatedString(
baseSpanStyle = baseSpanStyle,
underlineLinks = false
)
}
content(annotatedString)
}

@Composable
fun ClickableHtmlText(
text: String,
modifier: Modifier = Modifier,
baseSpanStyle: SpanStyle? = null,
Expand All @@ -35,15 +55,19 @@ fun HtmlText(
style: TextStyle = LocalTextStyle.current,
onUrlClick: ((url: String) -> Unit)? = null
) {
val spannedText = remember(text) {
HtmlCompat.fromHtml(text.replace("\n", "<br>"), HtmlCompat.FROM_HTML_MODE_COMPACT)
val annotatedString = remember(text) {
HtmlCompat
.fromHtml(
text.replace("\n", "<br>"),
HtmlCompat.FROM_HTML_MODE_COMPACT
)
.toAnnotatedString(
baseSpanStyle = baseSpanStyle,
linkColor = if (isHighlightLink) linkColor else Color.Unspecified,
underlineLinks = false
)
}
val uriHandler = LocalUriHandler.current
val annotatedString = spannedText.toAnnotatedString(
baseSpanStyle = baseSpanStyle,
linkColor = if (isHighlightLink) linkColor else Color.Unspecified,
underlineLinks = false
)
ClickableText(
modifier = modifier,
text = annotatedString,
Expand All @@ -62,7 +86,7 @@ fun HtmlText(
fun Spanned.toAnnotatedString(
baseSpanStyle: SpanStyle?,
underlineLinks: Boolean,
linkColor: Color
linkColor: Color = Color.Blue
): AnnotatedString =
buildAnnotatedString {
val spanned = this@toAnnotatedString
Expand Down Expand Up @@ -101,7 +125,7 @@ fun Spanned.toAnnotatedString(
@Preview
@Composable
private fun LinksHtmlTextPreview() {
HtmlText(
ClickableHtmlText(
/*ktlint-disable*/
text = "<b>Some text</b> \n<a href=\"https://developer.android.com/jetpack/androidx/releases/compose\" target=\"_blank\">" +
"link text</a>, the rest of the text"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ object HyperskillButtonDefaults {
): ButtonColors =
ButtonDefaults.buttonColors(
backgroundColor = backgroundColor,
disabledBackgroundColor = backgroundColor.copy(alpha = 0.38f),
contentColor = contentColor
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import org.hyperskill.app.android.R

@Composable
fun HyperskillCard(
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
cornerRadius: Dp = dimensionResource(id = R.dimen.corner_radius),
contentPadding: PaddingValues? = null,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
onClick: (() -> Unit)? = null,
content: @Composable BoxScope.() -> Unit
Expand All @@ -42,7 +42,11 @@ fun HyperskillCard(
it
}
}
.padding(contentPadding),
.apply {
if (contentPadding != null) {
padding(contentPadding)
}
},
content = content
)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.hyperskill.app.android.core.view.ui.widget.compose

import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.DurationBasedAnimationSpec
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
Expand All @@ -16,16 +20,30 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color

private const val INITIAL_VALUE = -2f
private const val TARGET_VALUE = 2f

private val DefaultColors: List<Color> = listOf(
Color.Transparent,
Color.White.copy(alpha = 0.7f),
Color.Transparent
)

object ShimmerDefaults {
const val InfiniteShimmerDurationMillis: Int = 2000
const val ShimmerShotDurationMillis: Int = 1200
}

/**
* Applies shimmer animation to the target Composable.
* Animation is playing one time.
* To start animation call [ShimmerState.runShimmerAnimation] on the [ShimmerState] instance.
* To start animation call [ShimmerShotState.runShimmerAnimation] on the [ShimmerShotState] instance.
*/
fun Modifier.shimmerShot(shimmerState: ShimmerState): Modifier =
fun Modifier.shimmerShot(shimmerState: ShimmerShotState): Modifier =
composed {
val startOffsetX by animateFloatAsState(
targetValue = shimmerState.targetValue,
animationSpec = shimmerState.startOffsetXAnimationSpec,
animationSpec = shimmerState.animationSpec,
label = "shimmer"
)
drawWithContent {
Expand All @@ -43,26 +61,73 @@ fun Modifier.shimmerShot(shimmerState: ShimmerState): Modifier =
}
}

@Stable
class ShimmerState(
val colors: List<Color> = listOf(
Color.Transparent,
Color.White.copy(alpha = 0.7f),
Color.Transparent
),
durationMillis: Int = 1200,
fun Modifier.infiniteShimmer(
play: Boolean,
colors: List<Color> = DefaultColors,
durationMillis: Int = ShimmerDefaults.InfiniteShimmerDurationMillis,
delayMillis: Int = 0,
easing: Easing = FastOutSlowInEasing
) {
): Modifier =
if (play) {
infiniteShimmer(
colors = colors,
durationMillis = durationMillis,
delayMillis = delayMillis,
easing = easing
)
} else {
this
}

private fun Modifier.infiniteShimmer(
colors: List<Color>,
durationMillis: Int,
delayMillis: Int,
easing: Easing
): Modifier =
composed {
val transition = rememberInfiniteTransition(label = "")

val translateAnimation = transition.animateFloat(
initialValue = INITIAL_VALUE,
targetValue = TARGET_VALUE,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = durationMillis,
easing = easing,
delayMillis = delayMillis.toInt()
),
repeatMode = RepeatMode.Restart,
),
label = "Shimmer loading animation",
)

drawWithContent {
val width = size.width
val height = size.height
val offset = translateAnimation.value * width

companion object {
private const val INITIAL_VALUE = -2f
private const val TARGET_VALUE = 2f
drawContent()
val brush = Brush.linearGradient(
colors = colors,
start = Offset(offset, 0f),
end = Offset(offset + width, height)
)
drawRect(brush)
}
}

@Stable
class ShimmerShotState(
val colors: List<Color> = DefaultColors,
durationMillis: Int = ShimmerDefaults.ShimmerShotDurationMillis,
easing: Easing = FastOutSlowInEasing
) {

var targetValue: Float by mutableStateOf(INITIAL_VALUE)
private set

val startOffsetXAnimationSpec: AnimationSpec<Float> = tween(
val animationSpec: DurationBasedAnimationSpec<Float> = tween(
durationMillis = durationMillis,
easing = easing
)
Expand Down
Loading

0 comments on commit b444291

Please sign in to comment.