diff --git a/airhub_app/lib/pages/music_creation_page.dart b/airhub_app/lib/pages/music_creation_page.dart index d1813d0..b03eb79 100644 --- a/airhub_app/lib/pages/music_creation_page.dart +++ b/airhub_app/lib/pages/music_creation_page.dart @@ -65,6 +65,7 @@ class _MusicCreationPageState extends State late AnimationController _flipController; late Animation _flipAnimation; late AnimationController _genRingController; + late AnimationController _mysteryShimmerController; // ── Playlist Data (matching HTML) ── final List<_Track> _playlist = [ @@ -108,7 +109,7 @@ class _MusicCreationPageState extends State {'icon': Icons.directions_run, 'color': 0xFFF5C6A5, 'title': 'Happy Funk', 'desc': '活力 · 奔跑 · 阳光'}, {'icon': Icons.nights_stay_outlined, 'color': 0xFFCBB8E0, 'title': 'Deep Sleep', 'desc': '白噪音 · 助眠 · 梦境'}, {'icon': Icons.psychology_outlined, 'color': 0xFFA8D8C8, 'title': 'Focus Flow', 'desc': '心流 · 专注 · 效率'}, - {'icon': Icons.redeem_outlined, 'color': 0xFFF5B0B0, 'title': '盲盒惊喜', 'desc': 'AI 随机生成神曲'}, + {'icon': Icons.redeem_outlined, 'color': 0xFFD4A0E8, 'title': '盲盒惊喜', 'desc': 'AI 随机生成神曲'}, {'icon': Icons.auto_awesome, 'color': 0xFFECCFA8, 'title': '自由创作', 'desc': '输入灵感 · 生成音乐'}, ]; @@ -153,6 +154,12 @@ class _MusicCreationPageState extends State vsync: this, ); + // Mystery box diagonal shimmer — 3s loop + _mysteryShimmerController = AnimationController( + duration: const Duration(milliseconds: 3000), + vsync: this, + )..repeat(); + // ── Audio Player Setup ── _audioPlayer = AudioPlayer(); @@ -222,6 +229,7 @@ class _MusicCreationPageState extends State _tonearmController.dispose(); _flipController.dispose(); _genRingController.dispose(); + _mysteryShimmerController.dispose(); super.dispose(); } @@ -336,6 +344,7 @@ class _MusicCreationPageState extends State setState(() { _isGenerating = false; + _selectedMoodIndex = null; // 生成完成,取消选中态 }); // If already playing, show confirm dialog; otherwise auto-play @@ -683,25 +692,27 @@ class _MusicCreationPageState extends State child: CustomPaint( painter: _VinylBackGroovesPainter(), child: Center( - // HTML: .lyrics-container { width: 150px; height: 150px; overflow-y: auto; } - child: SizedBox( + child: Container( width: 150, height: 150, + decoration: BoxDecoration( + // Dark overlay to cover groove lines behind text + color: const Color(0xFF18181B).withOpacity(0.75), + shape: BoxShape.circle, + ), child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(10), child: Text( track.lyrics.isNotEmpty ? track.lyrics : '生成音乐后\n点我看歌词', - // HTML: .lyrics-content { color: rgba(255,255,255,0.75); - // font-size: 11px; line-height: 1.5; text-align: center; } style: GoogleFonts.dmSans( - fontSize: 11, - height: 1.5, + fontSize: 12, + height: 1.6, color: track.lyrics.isNotEmpty - ? Colors.white.withOpacity(0.75) - : Colors.white.withOpacity(0.35), + ? Colors.white.withOpacity(0.92) + : Colors.white.withOpacity(0.4), fontStyle: track.lyrics.isEmpty ? FontStyle.italic : FontStyle.normal, @@ -917,132 +928,175 @@ class _MusicCreationPageState extends State Widget _buildMoodCard(int index) { final mood = _moods[index]; final isActive = _selectedMoodIndex == index; - final isMystery = index == 4; - final isCustom = index == 5; + final themeColor = Color(mood['color'] as int); + final isMystery = index == 4; // 盲盒惊喜 + final isCustom = index == 5; // 自由创作 - return GestureDetector( - onTap: () => _selectMood(index), - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: const Cubic(0.25, 0.46, 0.45, 0.94), // HTML transition curve - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - decoration: BoxDecoration( - // HTML: .mood-card { background: var(--glass-bg); backdrop-filter: blur(16px); - // border: 1px solid var(--glass-border); border-radius: 14px; } - color: isActive - ? Colors.white - : isMystery - ? null - : Colors.white.withOpacity(0.65), - // HTML: .mood-card.mystery { background: linear-gradient(135deg, rgba(255,255,255,0.7), rgba(236,207,168,0.3)) } - gradient: isMystery && !isActive - ? LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Colors.white.withOpacity(0.7), - const Color(0xFFECCFA8).withOpacity(0.3), - ], - ) - : null, - borderRadius: BorderRadius.circular(14), - border: Border.all( - // HTML: .mood-card.active { border-color: #ECCFA8; } - // HTML: .mood-card.custom { border: 1px dashed var(--primary-color); } - color: isActive - ? const Color(0xFFECCFA8) - : isCustom - ? const Color(0xFFECCFA8).withOpacity(0.5) - : Colors.white.withOpacity(0.4), - width: 1, - ), - // HTML: .mood-card.active { box-shadow: 0 10px 25px -5px rgba(236,207,168,0.4); } - boxShadow: isActive - ? [ - BoxShadow( - color: const Color(0xFFECCFA8).withOpacity(0.4), - offset: const Offset(0, 10), - blurRadius: 25, - spreadRadius: -5, - ), - ] - : [ - // HTML: .mood-card { box-shadow: 0 4px 6px -1px rgba(0,0,0,0.01); } - BoxShadow( - color: Colors.black.withOpacity(0.01), - offset: const Offset(0, 4), - blurRadius: 6, - spreadRadius: -1, - ), - ], + // ── Card background color logic ── + Color cardColor; + if (isCustom) { + // 自由创作: white glass morphism + cardColor = isActive + ? Colors.white + : Colors.white.withOpacity(0.65); + } else if (isMystery) { + // 盲盒惊喜: richer purple tint, more eye-catching + cardColor = isActive + ? Color.lerp(Colors.white, themeColor, 0.40)! + : Color.lerp(Colors.white.withOpacity(0.50), themeColor, 0.30)!; + } else { + // Normal mood cards: themed tint + cardColor = isActive + ? Color.lerp(Colors.white, themeColor, 0.30)! + : Color.lerp(Colors.white.withOpacity(0.55), themeColor, 0.20)!; + } + + // ── Border color logic ── + Color borderColor; + if (isCustom) { + borderColor = isActive + ? const Color(0xFFECCFA8) + : Colors.white.withOpacity(0.4); + } else { + borderColor = isActive + ? themeColor.withOpacity(0.55) + : themeColor.withOpacity(0.18); + } + + final cardBody = AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: const Cubic(0.25, 0.46, 0.45, 0.94), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + decoration: BoxDecoration( + color: cardColor, + borderRadius: BorderRadius.circular(14), + border: Border.all( + color: borderColor, + width: isActive ? 1.5 : 1, ), - child: Stack( - children: [ - Row( - children: [ - // Icon circle - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Color(mood['color'] as int).withOpacity(0.4), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - mood['icon'] as IconData, - size: 20, - color: Color(mood['color'] as int).withOpacity(1.0), - ), + boxShadow: isActive + ? [ + BoxShadow( + color: (isCustom ? const Color(0xFFECCFA8) : themeColor) + .withOpacity(0.30), + offset: const Offset(0, 6), + blurRadius: 18, + spreadRadius: -4, ), - const SizedBox(width: 10), - // Title + Desc - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - mood['title'] as String, - style: GoogleFonts.outfit( - fontSize: 13, - fontWeight: FontWeight.w600, - color: const Color(0xFF374151), - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Text( - mood['desc'] as String, - style: GoogleFonts.dmSans( - fontSize: 10, - color: const Color(0xFF9CA3AF), - height: 1.2, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], + ] + : [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + offset: const Offset(0, 2), + blurRadius: 8, + spreadRadius: -1, + ), + ], + ), + child: Row( + children: [ + Icon( + mood['icon'] as IconData, + size: 24, + color: isActive + ? (isCustom ? const Color(0xFFECCFA8) : themeColor) + : themeColor, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + mood['title'] as String, + style: GoogleFonts.outfit( + fontSize: 14, + fontWeight: isActive ? FontWeight.w700 : FontWeight.w600, + color: isActive + ? const Color(0xFF1F2937) + : const Color(0xFF374151), ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + mood['desc'] as String, + style: GoogleFonts.dmSans( + fontSize: 11, + color: isActive + ? const Color(0xFF6B7280) + : const Color(0xFF9CA3AF), + height: 1.3, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ], ), - // Active dot - if (isActive) - Positioned( - top: 0, - right: 0, - child: Container( - width: 8, - height: 8, - decoration: const BoxDecoration( - color: Color(0xFFC99672), - shape: BoxShape.circle, + ), + ], + ), + ); + + return GestureDetector( + onTap: () => _selectMood(index), + child: Stack( + children: [ + // Main card body + cardBody, + // ── Mystery box: soft diagonal gleam ── + if (isMystery) + Positioned.fill( + child: ClipRRect( + borderRadius: BorderRadius.circular(14), + child: IgnorePointer( + child: AnimatedBuilder( + animation: _mysteryShimmerController, + builder: (context, _) { + final t = _mysteryShimmerController.value; + // Wide, soft sweep — barely visible glow + final sweep = -2.0 + t * 5.0; + return Opacity( + opacity: 0.35, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment(sweep - 1.2, sweep - 1.2), + end: Alignment(sweep + 1.2, sweep + 1.2), + colors: [ + Colors.white.withOpacity(0.0), + Colors.white.withOpacity(0.08), + Colors.white.withOpacity(0.18), + Colors.white.withOpacity(0.08), + Colors.white.withOpacity(0.0), + ], + stops: const [0.0, 0.25, 0.5, 0.75, 1.0], + ), + ), + ), + ); + }, ), ), ), - ], - ), + ), + // Active indicator dot — top-right + if (isActive) + Positioned( + top: 8, + right: 8, + child: Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: isCustom ? const Color(0xFFECCFA8) : themeColor, + shape: BoxShape.circle, + ), + ), + ), + ], ), ); }