@@ -7,10 +7,17 @@ import androidx.compose.material.icons.filled.ArrowForward
77import androidx.compose.material.icons.filled.Settings
88import androidx.compose.material.icons.filled.Info
99
10+ import androidx.compose.ui.draw.scale
11+
12+
1013import androidx.compose.animation.AnimatedVisibility
1114import androidx.compose.animation.slideInVertically
1215import androidx.compose.animation.slideOutVertically
1316import androidx.compose.foundation.clickable
17+ import androidx.compose.animation.core.animateFloat
18+ import androidx.compose.animation.core.infiniteRepeatable
19+ import androidx.compose.animation.core.rememberInfiniteTransition
20+ import androidx.compose.animation.core.tween
1421import androidx.compose.foundation.background
1522import androidx.compose.foundation.gestures.detectTapGestures
1623import androidx.compose.foundation.gestures.detectVerticalDragGestures
@@ -319,6 +326,7 @@ fun ReaderScreen(
319326 onTopDoubleTap = {
320327 if (! showMenu.value) {
321328 showMenu.value = true
329+ viewModel.dismissMenuGuide()
322330 }
323331 }
324332 ) { pageIndexToRender ->
@@ -350,6 +358,20 @@ fun ReaderScreen(
350358 }
351359 }
352360
361+
362+
363+ // 用户引导层
364+ if (! state.value.hasShownMenuGuide &&
365+ state.value.currentChapterIndex == 0 &&
366+ state.value.pageIndex == 0 &&
367+ ! state.value.isLoading &&
368+ state.value.error == null
369+ ) {
370+ MenuGuideOverlay (
371+ onDismiss = { viewModel.dismissMenuGuide() }
372+ )
373+ }
374+
353375 // 下拉菜单(带遮罩层)
354376 if (showMenu.value) {
355377 // 遮罩层:点击或滑动菜单以外区域关闭菜单
@@ -375,7 +397,11 @@ fun ReaderScreen(
375397 onBgmToggle = { viewModel.playBgm(it) },
376398 onBgmNext = { viewModel.nextBgm() },
377399 onBgmVolumeChange = { viewModel.setBgmVolume(it) },
378- onShowToc = { showToc.value = true },
400+ onShowToc = {
401+ showToc.value = true
402+ // 如果在引导显示时打开了目录(也是通过菜单打开的),也应该关闭引导
403+ viewModel.dismissMenuGuide()
404+ },
379405 narrationEnabled = state.value.narrationEnabled,
380406 onNarrationToggle = { enabled ->
381407 viewModel.toggleNarration(enabled)
@@ -419,6 +445,140 @@ fun ReaderScreen(
419445}
420446}
421447
448+ /* *
449+ * 用户引导层
450+ */
451+ /* *
452+ * 用户引导层 (Ripple Animation)
453+ */
454+ @Composable
455+ private fun MenuGuideOverlay (
456+ onDismiss : () -> Unit
457+ ) {
458+ // 动画状态
459+ val infiniteTransition = androidx.compose.animation.core.rememberInfiniteTransition(label = " GuidePulse" )
460+
461+ // 两个波纹动画,错开播放,形成连贯感
462+ val scale1 by infiniteTransition.animateFloat(
463+ initialValue = 0.5f ,
464+ targetValue = 1.5f ,
465+ animationSpec = androidx.compose.animation.core.infiniteRepeatable(
466+ animation = androidx.compose.animation.core.tween(2000 ),
467+ repeatMode = androidx.compose.animation.core.RepeatMode .Restart
468+ ),
469+ label = " Scale1"
470+ )
471+ val alpha1 by infiniteTransition.animateFloat(
472+ initialValue = 0.6f ,
473+ targetValue = 0f ,
474+ animationSpec = androidx.compose.animation.core.infiniteRepeatable(
475+ animation = androidx.compose.animation.core.tween(2000 ),
476+ repeatMode = androidx.compose.animation.core.RepeatMode .Restart
477+ ),
478+ label = " Alpha1"
479+ )
480+
481+ val scale2 by infiniteTransition.animateFloat(
482+ initialValue = 0.5f ,
483+ targetValue = 1.5f ,
484+ animationSpec = androidx.compose.animation.core.infiniteRepeatable(
485+ animation = androidx.compose.animation.core.tween(2000 , delayMillis = 1000 ),
486+ repeatMode = androidx.compose.animation.core.RepeatMode .Restart
487+ ),
488+ label = " Scale2"
489+ )
490+ val alpha2 by infiniteTransition.animateFloat(
491+ initialValue = 0.6f ,
492+ targetValue = 0f ,
493+ animationSpec = androidx.compose.animation.core.infiniteRepeatable(
494+ animation = androidx.compose.animation.core.tween(2000 , delayMillis = 1000 ),
495+ repeatMode = androidx.compose.animation.core.RepeatMode .Restart
496+ ),
497+ label = " Alpha2"
498+ )
499+
500+ Box (
501+ modifier = Modifier
502+ .fillMaxSize()
503+ // 渐变背景,顶部深色,底部透明,不再全屏遮挡
504+ .background(
505+ androidx.compose.ui.graphics.Brush .verticalGradient(
506+ colors = listOf (
507+ Color .Black .copy(alpha = 0.7f ),
508+ Color .Transparent
509+ ),
510+ endY = 600f // 只遮挡顶部一部分
511+ )
512+ )
513+ .clickable(
514+ interactionSource = remember { androidx.compose.foundation.interaction.MutableInteractionSource () },
515+ indication = null
516+ ) { onDismiss() }
517+ ) {
518+ Column (
519+ modifier = Modifier
520+ .align(Alignment .TopCenter )
521+ .padding(top = 60 .dp), // 避开状态栏,定位到顶部交互区
522+ horizontalAlignment = Alignment .CenterHorizontally
523+ ) {
524+ // 波纹动画区域
525+ Box (
526+ contentAlignment = Alignment .Center ,
527+ modifier = Modifier .size(80 .dp)
528+ ) {
529+ // 波纹 1
530+ Box (
531+ modifier = Modifier
532+ .size(80 .dp)
533+ .scale(scale1)
534+ .background(MaterialTheme .colorScheme.primary.copy(alpha = alpha1), androidx.compose.foundation.shape.CircleShape )
535+ )
536+ // 波纹 2
537+ Box (
538+ modifier = Modifier
539+ .size(80 .dp)
540+ .scale(scale2)
541+ .background(MaterialTheme .colorScheme.primary.copy(alpha = alpha2), androidx.compose.foundation.shape.CircleShape )
542+ )
543+
544+ // 中心手势图标
545+ androidx.compose.material3.Icon (
546+ imageVector = androidx.compose.material.icons.Icons .Default .Info ,
547+ contentDescription = null ,
548+ tint = MaterialTheme .colorScheme.onPrimary,
549+ modifier = Modifier
550+ .size(32 .dp)
551+ .background(MaterialTheme .colorScheme.primary, androidx.compose.foundation.shape.CircleShape )
552+ .padding(6 .dp)
553+ )
554+ }
555+
556+ Spacer (modifier = Modifier .height(16 .dp))
557+
558+ // 提示卡片
559+ androidx.compose.material3.Surface (
560+ shape = RoundedCornerShape (percent = 50 ),
561+ color = MaterialTheme .colorScheme.surfaceContainerHigh,
562+ tonalElevation = 6 .dp,
563+ shadowElevation = 6 .dp,
564+ border = androidx.compose.foundation.BorderStroke (1 .dp, MaterialTheme .colorScheme.primary.copy(alpha = 0.2f ))
565+ ) {
566+ Row (
567+ modifier = Modifier .padding(horizontal = 20 .dp, vertical = 10 .dp),
568+ verticalAlignment = Alignment .CenterVertically
569+ ) {
570+ Text (
571+ text = androidx.compose.ui.res.stringResource(id = com.xuyutech.hongbaoshu.R .string.guide_double_tap_menu),
572+ style = MaterialTheme .typography.labelLarge,
573+ color = MaterialTheme .colorScheme.onSurface
574+ )
575+ }
576+ }
577+ }
578+ }
579+ }
580+
581+
422582/* *
423583 * 根据页码索引获取页面数据(支持跨章节预览)
424584 * 返回:(章节, 页面, 全书页码, 全书总页数)
0 commit comments