diff --git a/mobile/android/android-components/components/feature/summarize/src/main/java/mozilla/components/feature/summarize/SummarizationScreen.kt b/mobile/android/android-components/components/feature/summarize/src/main/java/mozilla/components/feature/summarize/SummarizationScreen.kt index efbe753acefb3..8c66d1b6a8e9b 100644 --- a/mobile/android/android-components/components/feature/summarize/src/main/java/mozilla/components/feature/summarize/SummarizationScreen.kt +++ b/mobile/android/android-components/components/feature/summarize/src/main/java/mozilla/components/feature/summarize/SummarizationScreen.kt @@ -31,11 +31,11 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.rememberNestedScrollInteropConnection -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 mozilla.components.compose.base.annotation.FlexibleWindowLightDarkPreview import mozilla.components.compose.base.modifier.thenConditional import mozilla.components.compose.base.theme.AcornTheme import mozilla.components.feature.summarize.ui.DownloadError @@ -111,7 +111,9 @@ private fun SummarizationScreen( }, ) } - is SummarizationState.Summarizing -> SummarizingContent() + is SummarizationState.Summarizing -> SummarizingContent( + modifier = Modifier.height(252.dp), + ) is SummarizationState.Summarized -> SummarizedContent( text = state.text, modifier = Modifier.verticalScroll(rememberScrollState()) @@ -194,16 +196,18 @@ private class SummarizationStatePreviewProvider : PreviewParameterProvider 0f) { - drawCircle( - brush = Brush.radialGradient( - colors = listOf( - color.copy(alpha = srcOverAlpha), - color.copy(alpha = 0f), - ), - center = circle.centerPx, - radius = circle.radiusPx, - ), - center = circle.centerPx, - radius = circle.radiusPx, - blendMode = BlendMode.SrcOver, - ) - } - - val screenAlpha = circle.alpha * circle.screenBlendWeight - if (screenAlpha > 0f) { - drawCircle( - brush = Brush.radialGradient( - colors = listOf( - color.copy(alpha = screenAlpha), - color.copy(alpha = 0f), - ), - center = circle.centerPx, - radius = circle.radiusPx, - ), - center = circle.centerPx, - radius = circle.radiusPx, - blendMode = BlendMode.Screen, - ) - } -} - private fun DrawScope.drawBackgroundWash() { val (start, end) = gradientLineEndpoints( widthPx = size.width, @@ -278,7 +210,6 @@ private fun DrawScope.drawBlob( paint: AndroidPaint, maskFilter: BlurMaskFilter, widthDp: Float = spec.widthDp, - useColorDodge: Boolean = false, ) { val xPx = positionDp.x * density val yPx = positionDp.y * density @@ -287,10 +218,6 @@ private fun DrawScope.drawBlob( val rotationPivot = Offset(spec.originalWidthDp / 2f, spec.originalWidthDp / 2f) paint.color = color.toArgb() - // COLOR_DODGE requires API 29+; on older devices blobs render with normal blending. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - paint.blendMode = if (useColorDodge) AndroidBlendMode.COLOR_DODGE else null - } paint.maskFilter = maskFilter translate(left = xPx, top = yPx) { @@ -302,90 +229,6 @@ private fun DrawScope.drawBlob( } } -private fun computeCircleGradientPx( - tMs: Float, - canvasWidthPx: Float, - canvasHeightPx: Float, - density: Float, -): CirclePxState { - val segment = when { - tMs < CircleAnimation.PHASE_1_TO_2_DURATION_MS -> CircleSegment( - start = CircleAnimation.PHASE_1, - end = CircleAnimation.PHASE_2, - progress = tMs / CircleAnimation.PHASE_1_TO_2_DURATION_MS, - ) - - tMs < CircleAnimation.PHASE_1_TO_2_DURATION_MS + - CircleAnimation.PHASE_2_TO_3_DURATION_MS -> CircleSegment( - start = CircleAnimation.PHASE_2, - end = CircleAnimation.PHASE_3, - progress = (tMs - CircleAnimation.PHASE_1_TO_2_DURATION_MS) / - CircleAnimation.PHASE_2_TO_3_DURATION_MS, - ) - - tMs < CircleAnimation.PHASE_1_TO_2_DURATION_MS + - CircleAnimation.PHASE_2_TO_3_DURATION_MS + - CircleAnimation.PHASE_3_TO_4_DURATION_MS -> CircleSegment( - start = CircleAnimation.PHASE_3, - end = CircleAnimation.PHASE_4, - progress = ( - tMs - CircleAnimation.PHASE_1_TO_2_DURATION_MS - - CircleAnimation.PHASE_2_TO_3_DURATION_MS - ) / CircleAnimation.PHASE_3_TO_4_DURATION_MS, - ) - - else -> CircleSegment( - start = CircleAnimation.PHASE_4, - end = CircleAnimation.PHASE_1, - progress = ( - tMs - CircleAnimation.PHASE_1_TO_2_DURATION_MS - - CircleAnimation.PHASE_2_TO_3_DURATION_MS - - CircleAnimation.PHASE_3_TO_4_DURATION_MS - ) / CircleAnimation.PHASE_4_TO_1_DURATION_MS, - ) - } - - val easedProgress = LinearOutSlowInEasing.transform(segment.progress.coerceIn(0f, 1f)) - val startCenter = phaseCenterPx(segment.start, canvasWidthPx, canvasHeightPx, density) - val endCenter = phaseCenterPx(segment.end, canvasWidthPx, canvasHeightPx, density) - val startRadius = (segment.start.sizeDp * density) / 2f - val endRadius = (segment.end.sizeDp * density) / 2f - val startScreenWeight = if (segment.start.blendMode == BlendMode.Screen) 1f else 0f - val endScreenWeight = if (segment.end.blendMode == BlendMode.Screen) 1f else 0f - val screenBlendWeight = lerp(startScreenWeight, endScreenWeight, easedProgress).coerceIn(0f, 1f) - - return CirclePxState( - centerPx = lerp(startCenter, endCenter, easedProgress), - radiusPx = lerp(startRadius, endRadius, easedProgress), - alpha = lerp(segment.start.alpha, segment.end.alpha, easedProgress), - srcOverBlendWeight = 1f - screenBlendWeight, - screenBlendWeight = screenBlendWeight, - ) -} - -private fun phaseCenterPx( - phase: CirclePhase, - canvasWidthPx: Float, - canvasHeightPx: Float, - density: Float, -): Offset { - val sizePx = phase.sizeDp * density - val leftPx = when { - phase.leftDp != null -> phase.leftDp * density - phase.rightDp != null -> canvasWidthPx - (phase.rightDp * density) - sizePx - else -> 0f - } - val topPx = when { - phase.topDp != null -> phase.topDp * density - phase.bottomDp != null -> canvasHeightPx - (phase.bottomDp * density) - sizePx - else -> 0f - } - return Offset( - x = leftPx + sizePx / 2f, - y = topPx + sizePx / 2f, - ) -} - private fun lerp(start: Offset, end: Offset, fraction: Float): Offset { return Offset( x = lerp(start.x, end.x, fraction), @@ -444,24 +287,6 @@ private fun rememberBlobTimeMs(): Float { return tMs } -@Composable -private fun rememberCircleGradientTimeMs(): Float { - val transition = rememberInfiniteTransition(label = "CircleGradientTime") - val tMs by transition.animateFloat( - initialValue = 0f, - targetValue = CircleAnimation.TOTAL_DURATION_MS, - animationSpec = infiniteRepeatable( - animation = tween( - durationMillis = CircleAnimation.TOTAL_DURATION_MS.toInt(), - easing = LinearEasing, - ), - repeatMode = RepeatMode.Restart, - ), - label = "CircleGradientMs", - ) - return tMs -} - private fun gradientLineEndpoints( widthPx: Float, heightPx: Float, diff --git a/mobile/android/android-components/components/feature/summarize/src/main/java/mozilla/components/feature/summarize/ui/gradient/PhaseData.kt b/mobile/android/android-components/components/feature/summarize/src/main/java/mozilla/components/feature/summarize/ui/gradient/PhaseData.kt index 5205be80dfb4d..2f0f54b126a0f 100644 --- a/mobile/android/android-components/components/feature/summarize/src/main/java/mozilla/components/feature/summarize/ui/gradient/PhaseData.kt +++ b/mobile/android/android-components/components/feature/summarize/src/main/java/mozilla/components/feature/summarize/ui/gradient/PhaseData.kt @@ -6,7 +6,6 @@ package mozilla.components.feature.summarize.ui.gradient import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.BlendMode internal object BlobAnimation { const val PHASE_1_TO_2_DURATION_MS = 4000f @@ -56,87 +55,47 @@ internal val ORANGE_BLOB = BlobSpec( rotationDeg = 240f, ) +// G1 (Group 3) is static — same position at all keyframes. +private val DARK_PURPLE_G1_POSE = BlobPose(offsetDp = Offset(135f, 159f), widthDp = 343f) + internal val DARK_PURPLE_G1_PHASES = BlobPhases( - phase1 = BlobPose(offsetDp = Offset(135f, 159f), widthDp = 343f), - phase2 = BlobPose(offsetDp = Offset(19f, 16f), widthDp = 343f), - phase3 = BlobPose(offsetDp = Offset(-21f, 188f), widthDp = 271f), + phase1 = DARK_PURPLE_G1_POSE, + phase2 = DARK_PURPLE_G1_POSE, + phase3 = DARK_PURPLE_G1_POSE, ) +private val LIGHT_PURPLE_G1_POSE = BlobPose(offsetDp = Offset(-15f, 4f), widthDp = 439f) + internal val LIGHT_PURPLE_G1_PHASES = BlobPhases( - phase1 = BlobPose(offsetDp = Offset(421.225f, 4f), widthDp = 439f), - phase2 = BlobPose(offsetDp = Offset(220f, 143f), widthDp = 439f), - phase3 = BlobPose(offsetDp = Offset(167f, 188f), widthDp = 405f), + phase1 = LIGHT_PURPLE_G1_POSE, + phase2 = LIGHT_PURPLE_G1_POSE, + phase3 = LIGHT_PURPLE_G1_POSE, ) +private val ORANGE_G1_POSE = BlobPose(offsetDp = Offset(373f, -27f), widthDp = 269.225f) + internal val ORANGE_G1_PHASES = BlobPhases( - phase1 = BlobPose(offsetDp = Offset(373f, -27.322f), widthDp = 269.225f), - phase2 = BlobPose(offsetDp = Offset(-10.225f, -13.322f), widthDp = 269.225f), - phase3 = BlobPose(offsetDp = Offset(491f, -1f), widthDp = 277f), + phase1 = ORANGE_G1_POSE, + phase2 = ORANGE_G1_POSE, + phase3 = ORANGE_G1_POSE, ) +// G2 (Group 2) animates. Phase 1 overlaps G1; phases 2/3 from Figma variants 6/4. +// Positions mapped by COLOR (where each color appears in the Figma keyframe). internal val DARK_PURPLE_G2_PHASES = BlobPhases( - phase1 = DARK_PURPLE_G1_PHASES.phase1, - phase2 = BlobPose(offsetDp = Offset(288f, 214f), widthDp = 259f), - phase3 = BlobPose(offsetDp = Offset(-21f, -1f), widthDp = 259f), + phase1 = DARK_PURPLE_G1_POSE, + phase2 = BlobPose(offsetDp = Offset(220f, 143f), widthDp = 439f), + phase3 = BlobPose(offsetDp = Offset(-63f, 156f), widthDp = 271f), ) internal val LIGHT_PURPLE_G2_PHASES = BlobPhases( - phase1 = LIGHT_PURPLE_G1_PHASES.phase1, - phase2 = BlobPose(offsetDp = Offset(242f, 155f), widthDp = 439f), - phase3 = BlobPose(offsetDp = Offset(9f, 35f), widthDp = 671f), + phase1 = LIGHT_PURPLE_G1_POSE, + phase2 = BlobPose(offsetDp = Offset(19f, 16f), widthDp = 343f), + phase3 = BlobPose(offsetDp = Offset(125f, 156f), widthDp = 405f), ) internal val ORANGE_G2_PHASES = BlobPhases( - phase1 = ORANGE_G1_PHASES.phase1, - phase2 = BlobPose(offsetDp = Offset(-61.225f, -13.322f), widthDp = 269.225f), - phase3 = BlobPose(offsetDp = Offset(208f, 4f), widthDp = 195f), -) - -internal object CircleAnimation { - const val PHASE_1_TO_2_DURATION_MS = 1000f - const val PHASE_2_TO_3_DURATION_MS = 800f - const val PHASE_3_TO_4_DURATION_MS = 1200f - const val PHASE_4_TO_1_DURATION_MS = 900f - const val TOTAL_DURATION_MS = - PHASE_1_TO_2_DURATION_MS + PHASE_2_TO_3_DURATION_MS + - PHASE_3_TO_4_DURATION_MS + PHASE_4_TO_1_DURATION_MS - - val PHASE_1 = CirclePhase( - sizeDp = 548f, - alpha = 0.45f, - blendMode = BlendMode.SrcOver, - leftDp = -5f, - topDp = 15f, - ) - val PHASE_2 = CirclePhase( - sizeDp = 464.793f, - alpha = 0.45f, - blendMode = BlendMode.Screen, - leftDp = -205f, - topDp = -23f, - ) - val PHASE_3 = CirclePhase( - sizeDp = 464.793f, - alpha = 0.45f, - blendMode = BlendMode.SrcOver, - leftDp = -133f, - topDp = -276f, - ) - val PHASE_4 = CirclePhase( - sizeDp = 464.793f, - alpha = 0.50f, - blendMode = BlendMode.Screen, - rightDp = -170.793f, - topDp = -251f, - ) -} - -internal data class CirclePhase( - val sizeDp: Float, - val alpha: Float, - val blendMode: BlendMode, - val leftDp: Float? = null, - val rightDp: Float? = null, - val topDp: Float? = null, - val bottomDp: Float? = null, + phase1 = ORANGE_G1_POSE, + phase2 = BlobPose(offsetDp = Offset(-10f, -13f), widthDp = 372f), + phase3 = BlobPose(offsetDp = Offset(385f, -33f), widthDp = 341f), ) diff --git a/mobile/android/android-components/components/feature/summarize/src/main/res/values/static_strings.xml b/mobile/android/android-components/components/feature/summarize/src/main/res/values/static_strings.xml index 30f7d00c4aafc..8d82f178a55f3 100644 --- a/mobile/android/android-components/components/feature/summarize/src/main/res/values/static_strings.xml +++ b/mobile/android/android-components/components/feature/summarize/src/main/res/values/static_strings.xml @@ -82,5 +82,5 @@ Unknown error - Creating summary… + Summarizing…