-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop' into minor_fixes/new_gpt_generated_code_with_e…
…rrors_onboarding_cache_flag
- Loading branch information
Showing
246 changed files
with
4,045 additions
and
1,305 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
androidHyperskillApp/src/main/java/org/hyperskill/app/android/clarity/ClarityDelegate.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package org.hyperskill.app.android.clarity | ||
|
||
import android.content.Context | ||
import androidx.core.os.bundleOf | ||
import androidx.savedstate.SavedStateRegistry | ||
import com.microsoft.clarity.Clarity | ||
import com.microsoft.clarity.ClarityConfig | ||
import com.microsoft.clarity.models.LogLevel | ||
import com.russhwolf.settings.Settings | ||
import org.hyperskill.app.android.BuildConfig | ||
|
||
class ClarityDelegate( | ||
private val settings: Settings | ||
) { | ||
|
||
companion object { | ||
private const val SAVED_STATE_PROVIDER_KEY = "CLARITY_MANAGER" | ||
private const val IS_FIRST_SESSION_KEY = "IS_ANDROID_FIRST_SESSION" | ||
private const val IS_DEBUG_KEY = "IS_DEBUG" | ||
private val clarityConfig = ClarityConfig( | ||
projectId = BuildConfig.CLARITY_PROJECT_ID, | ||
logLevel = if (BuildConfig.DEBUG) LogLevel.Debug else LogLevel.None, | ||
allowMeteredNetworkUsage = false, | ||
allowedDomains = emptyList(), | ||
disableOnLowEndDevices = true | ||
) | ||
} | ||
|
||
private var isFirstSession: Boolean = false | ||
|
||
fun init(context: Context, savedStateRegistry: SavedStateRegistry) { | ||
registerSavedStateProvider(savedStateRegistry) | ||
isFirstSession = isFirstSession(savedStateRegistry, settings) | ||
setNotFirstSession(settings) | ||
if (isFirstSession) { | ||
Clarity.initialize(context, clarityConfig) | ||
Clarity.setCustomTag(IS_DEBUG_KEY, BuildConfig.DEBUG.toString()) | ||
} | ||
} | ||
|
||
fun setUserId(userId: Long) { | ||
if (isFirstSession) { | ||
Clarity.setCustomUserId(userId.toString()) | ||
} | ||
} | ||
|
||
private fun isFirstSession(savedStateRegistry: SavedStateRegistry, settings: Settings): Boolean = | ||
getIsFirstSession(savedStateRegistry) ?: getIsFirstSession(settings) | ||
|
||
private fun getIsFirstSession(savedStateRegistry: SavedStateRegistry): Boolean? = | ||
savedStateRegistry | ||
.consumeRestoredStateForKey(SAVED_STATE_PROVIDER_KEY) | ||
?.getBoolean(IS_FIRST_SESSION_KEY) | ||
|
||
private fun getIsFirstSession(settings: Settings): Boolean = | ||
settings.getBoolean(IS_FIRST_SESSION_KEY, defaultValue = true) | ||
|
||
private fun setNotFirstSession(settings: Settings) { | ||
settings.putBoolean(IS_FIRST_SESSION_KEY, false) | ||
} | ||
|
||
private fun registerSavedStateProvider(savedStateRegistry: SavedStateRegistry) { | ||
savedStateRegistry.registerSavedStateProvider(SAVED_STATE_PROVIDER_KEY) { | ||
bundleOf(IS_FIRST_SESSION_KEY to isFirstSession) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
androidHyperskillApp/src/main/java/org/hyperskill/app/android/core/extensions/Screenshoot.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package org.hyperskill.app.android.core.extensions | ||
|
||
import android.app.Activity | ||
import android.os.Build | ||
import androidx.fragment.app.Fragment | ||
import androidx.lifecycle.Lifecycle | ||
import androidx.lifecycle.LifecycleEventObserver | ||
|
||
/** | ||
* Sets a callback to be invoked when a screenshot is captured on the screen. | ||
* Works only on Android 14 and higher. | ||
* | ||
* @param block The code block to be executed when a screenshot is captured. | ||
*/ | ||
inline fun Fragment.doOnScreenShootCaptured( | ||
crossinline block: () -> Unit | ||
) { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { | ||
val callBack = Activity.ScreenCaptureCallback { | ||
block() | ||
} | ||
lifecycle.addObserver( | ||
LifecycleEventObserver { _, event -> | ||
when (event) { | ||
Lifecycle.Event.ON_START -> { | ||
requireActivity().registerScreenCaptureCallback( | ||
/* executor = */ requireActivity().mainExecutor, | ||
/* callback = */ callBack | ||
) | ||
} | ||
Lifecycle.Event.ON_STOP -> { | ||
requireActivity().unregisterScreenCaptureCallback(callBack) | ||
} | ||
else -> { | ||
// no op | ||
} | ||
} | ||
} | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
73 changes: 73 additions & 0 deletions
73
...p/src/main/java/org/hyperskill/app/android/core/view/ui/widget/compose/ShimmerModifier.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package org.hyperskill.app.android.core.view.ui.widget.compose | ||
|
||
import androidx.compose.animation.core.AnimationSpec | ||
import androidx.compose.animation.core.Easing | ||
import androidx.compose.animation.core.FastOutSlowInEasing | ||
import androidx.compose.animation.core.animateFloatAsState | ||
import androidx.compose.animation.core.tween | ||
import androidx.compose.runtime.Stable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.composed | ||
import androidx.compose.ui.draw.drawWithContent | ||
import androidx.compose.ui.geometry.Offset | ||
import androidx.compose.ui.graphics.Brush | ||
import androidx.compose.ui.graphics.Color | ||
|
||
/** | ||
* Applies shimmer animation to the target Composable. | ||
* Animation is playing one time. | ||
* To start animation call [ShimmerState.runShimmerAnimation] on the [ShimmerState] instance. | ||
*/ | ||
fun Modifier.shimmerShot(shimmerState: ShimmerState): Modifier = | ||
composed { | ||
val startOffsetX by animateFloatAsState( | ||
targetValue = shimmerState.targetValue, | ||
animationSpec = shimmerState.startOffsetXAnimationSpec, | ||
label = "shimmer" | ||
) | ||
drawWithContent { | ||
val width = size.width | ||
val height = size.height | ||
val offset = startOffsetX * width | ||
|
||
drawContent() | ||
val brush = Brush.linearGradient( | ||
colors = shimmerState.colors, | ||
start = Offset(offset, 0f), | ||
end = Offset(offset + width, height) | ||
) | ||
drawRect(brush) | ||
} | ||
} | ||
|
||
@Stable | ||
class ShimmerState( | ||
val colors: List<Color> = listOf( | ||
Color.Transparent, | ||
Color.White.copy(alpha = 0.7f), | ||
Color.Transparent | ||
), | ||
durationMillis: Int = 1200, | ||
easing: Easing = FastOutSlowInEasing | ||
) { | ||
|
||
companion object { | ||
private const val INITIAL_VALUE = -2f | ||
private const val TARGET_VALUE = 2f | ||
} | ||
|
||
var targetValue: Float by mutableStateOf(INITIAL_VALUE) | ||
private set | ||
|
||
val startOffsetXAnimationSpec: AnimationSpec<Float> = tween( | ||
durationMillis = durationMillis, | ||
easing = easing | ||
) | ||
|
||
fun runShimmerAnimation() { | ||
targetValue = TARGET_VALUE | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
.../main/java/org/hyperskill/app/android/core/view/ui/widget/compose/TypewriterTextEffect.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package org.hyperskill.app.android.core.view.ui.widget.compose | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.rememberUpdatedState | ||
import androidx.compose.runtime.setValue | ||
import kotlin.random.Random | ||
import kotlin.time.Duration.Companion.milliseconds | ||
import kotlinx.coroutines.delay | ||
|
||
/** | ||
* A composable function that displays a text with a typewriter-like effect, revealing characters in chunks. | ||
* | ||
* @param text The input text to be displayed with the typewriter effect. | ||
* @param minDelayInMillis The minimum delay in milliseconds between revealing character chunks, defaults to 30ms. | ||
* @param maxDelayInMillis The maximum delay in milliseconds between revealing character chunks, defaults to 80ms. | ||
* @param minCharacterChunk The minimum number of characters to reveal at once, defaults to 1. | ||
* @param maxCharacterChunk The maximum number of characters to reveal at once, defaults to 3. | ||
* @param onEffectCompleted A callback function invoked when the entire text has been revealed. | ||
* @param displayTextComposable A composable function that receives the text to display with the typewriter effect. | ||
* | ||
* @throws IllegalArgumentException if [minDelayInMillis] is greater than [maxDelayInMillis]. | ||
* @throws IllegalArgumentException if [minCharacterChunk] is greater than [maxCharacterChunk]. | ||
*/ | ||
@Suppress("MaxLineLength") | ||
@Composable | ||
fun TypewriterTextEffect( | ||
text: String, | ||
startTypingDelayInMillis: Int? = null, | ||
minDelayInMillis: Long = 30, | ||
maxDelayInMillis: Long = 80, | ||
minCharacterChunk: Int = 1, | ||
maxCharacterChunk: Int = 3, | ||
onEffectCompleted: () -> Unit = {}, | ||
displayTextComposable: @Composable (displayedText: String) -> Unit | ||
) { | ||
// Ensure minDelayInMillis is less than or equal to maxDelayInMillis | ||
require(minDelayInMillis <= maxDelayInMillis) { | ||
"TypewriterTextEffect: Invalid delay range. minDelayInMillis ($minDelayInMillis) must be less than or equal to maxDelayInMillis ($maxDelayInMillis)." //ktlint-disable | ||
} | ||
|
||
// Ensure minCharacterChunk is less than or equal to maxCharacterChunk | ||
require(minCharacterChunk <= maxCharacterChunk) { | ||
"TypewriterTextEffect: Invalid character chunk range. minCharacterChunk ($minCharacterChunk) must be less than or equal to maxCharacterChunk ($maxCharacterChunk)." //ktlint-disable | ||
} | ||
|
||
val currentOnEffectCompleted by rememberUpdatedState(newValue = onEffectCompleted) | ||
|
||
// Initialize and remember the displayedText | ||
var displayedText by remember { mutableStateOf("") } | ||
|
||
// Call the displayTextComposable with the current displayedText value | ||
displayTextComposable(displayedText) | ||
|
||
// Launch the effect to update the displayedText value over time | ||
LaunchedEffect(text) { | ||
if (startTypingDelayInMillis != null) { | ||
delay(startTypingDelayInMillis.milliseconds) | ||
} | ||
|
||
val textLength = text.length | ||
var endIndex = 0 | ||
|
||
while (endIndex < textLength) { | ||
endIndex = minOf( | ||
endIndex + Random.nextInt(minCharacterChunk, maxCharacterChunk + 1), | ||
textLength | ||
) | ||
displayedText = text.substring(startIndex = 0, endIndex = endIndex) | ||
delay(Random.nextLong(minDelayInMillis, maxDelayInMillis)) | ||
} | ||
currentOnEffectCompleted() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
.../src/main/java/org/hyperskill/app/android/latex/view/model/block/DataMobileHiddenBlock.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
package org.hyperskill.app.android.latex.view.model.block | ||
|
||
object DataMobileHiddenBlock : ContentBlock { | ||
|
||
private const val DATA_MOBILE_HIDDEN_TAG = "data-mobile-hidden" | ||
|
||
override val header: String = """ | ||
<script type="text/javascript" src="file:///android_asset/scripts/remove_data_mobile_hidden_elements.js"></script> | ||
""".trimIndent() | ||
|
||
override fun isEnabled(content: String): Boolean = | ||
DATA_MOBILE_HIDDEN_TAG in content | ||
} |
6 changes: 6 additions & 0 deletions
6
...n/java/org/hyperskill/app/android/latex/view/model/block/RemoveIFrameElementsInjection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
package org.hyperskill.app.android.latex.view.model.block | ||
|
||
object RemoveIFrameElementsInjection : ContentBlock { | ||
|
||
private const val I_FRAME_TAG = "iframe" | ||
|
||
override val header: String = """ | ||
<script type="text/javascript" src="file:///android_asset/scripts/remove_iframes.js"></script> | ||
""".trimIndent() | ||
|
||
override fun isEnabled(content: String): Boolean = | ||
I_FRAME_TAG in content | ||
} |
Oops, something went wrong.