animated_background.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. import 'package:flutter/material.dart';
  2. import 'dart:math' as math;
  3. import '../../utils/colors.dart';
  4. import '../../utils/theme_manager.dart';
  5. import '../../utils/theme_notifier.dart';
  6. class AnimatedBackground extends StatefulWidget {
  7. final Widget child;
  8. final bool isZenMode;
  9. final Function()? onShake;
  10. const AnimatedBackground({
  11. super.key,
  12. required this.child,
  13. this.isZenMode = false,
  14. this.onShake,
  15. });
  16. @override
  17. State<AnimatedBackground> createState() => _AnimatedBackgroundState();
  18. }
  19. class _AnimatedBackgroundState extends State<AnimatedBackground>
  20. with TickerProviderStateMixin {
  21. late AnimationController _controller1;
  22. late AnimationController _controller2;
  23. late AnimationController _controller3;
  24. late AnimationController _shakeController;
  25. late AnimationController _seasonalController;
  26. bool _isShaking = false;
  27. // Static variables to preserve animation state across navigation
  28. static double _preservedLayer1Value = 0.0;
  29. static double _preservedLayer2Value = 0.0;
  30. static double _preservedLayer3Value = 0.0;
  31. static double _preservedSeasonalValue = 0.0;
  32. static DateTime _lastPreservedTime = DateTime.now();
  33. @override
  34. void initState() {
  35. super.initState();
  36. // Calculate time elapsed since last preservation
  37. final now = DateTime.now();
  38. final elapsedSeconds =
  39. now.difference(_lastPreservedTime).inMilliseconds / 1000.0;
  40. // Update preserved values based on elapsed time
  41. _preservedLayer1Value =
  42. (_preservedLayer1Value + elapsedSeconds / 60.0) % 1.0;
  43. _preservedLayer2Value =
  44. (_preservedLayer2Value + elapsedSeconds / 80.0) % 1.0;
  45. _preservedLayer3Value =
  46. (_preservedLayer3Value + elapsedSeconds / 100.0) % 1.0;
  47. _preservedSeasonalValue =
  48. (_preservedSeasonalValue + elapsedSeconds / 60.0) % 1.0;
  49. // Create multiple animation controllers for layered effects
  50. _controller1 = AnimationController(
  51. duration: const Duration(seconds: 60),
  52. vsync: this,
  53. );
  54. _controller1.value = _preservedLayer1Value;
  55. _controller1.repeat();
  56. _controller2 = AnimationController(
  57. duration: const Duration(seconds: 80),
  58. vsync: this,
  59. );
  60. _controller2.value = _preservedLayer2Value;
  61. _controller2.repeat();
  62. _controller3 = AnimationController(
  63. duration: const Duration(seconds: 100),
  64. vsync: this,
  65. );
  66. _controller3.value = _preservedLayer3Value;
  67. _controller3.repeat();
  68. // Faster controller for seasonal patterns
  69. _seasonalController = AnimationController(
  70. duration: const Duration(seconds: 60), // Faster for seasonal effects
  71. vsync: this,
  72. );
  73. _seasonalController.value = _preservedSeasonalValue;
  74. _seasonalController.repeat();
  75. // Shake effect controller
  76. _shakeController = AnimationController(
  77. duration: const Duration(milliseconds: 800),
  78. vsync: this,
  79. );
  80. }
  81. @override
  82. void dispose() {
  83. // Preserve current animation values for next instance
  84. _preservedLayer1Value = _controller1.value;
  85. _preservedLayer2Value = _controller2.value;
  86. _preservedLayer3Value = _controller3.value;
  87. _preservedSeasonalValue = _seasonalController.value;
  88. _lastPreservedTime = DateTime.now();
  89. _controller1.dispose();
  90. _controller2.dispose();
  91. _controller3.dispose();
  92. _shakeController.dispose();
  93. _seasonalController.dispose();
  94. super.dispose();
  95. }
  96. void triggerShake() {
  97. if (!_isShaking) {
  98. _isShaking = true;
  99. _shakeController.forward().then((_) {
  100. _shakeController.reset();
  101. _isShaking = false;
  102. });
  103. widget.onShake?.call();
  104. }
  105. }
  106. @override
  107. Widget build(BuildContext context) {
  108. return ThemeAwareBuilder(
  109. builder: (context, theme) {
  110. final seasonalPattern = ThemeManager.effectiveTheme;
  111. return Stack(
  112. children: [
  113. // Base background
  114. Container(
  115. decoration: BoxDecoration(
  116. gradient: LinearGradient(
  117. begin: Alignment.topLeft,
  118. end: Alignment.bottomRight,
  119. colors: [
  120. ZenColors.currentAppBackground,
  121. ZenColors.currentSecondaryBackground,
  122. ],
  123. ),
  124. ),
  125. ),
  126. // Seasonal pattern overlay
  127. if (seasonalPattern != SeasonalTheme.default_)
  128. _buildSeasonalPattern(seasonalPattern),
  129. // Animated gradient layers
  130. ...List.generate(2, (index) => _buildAnimatedLayer(index)),
  131. // Content overlay
  132. widget.child,
  133. ],
  134. );
  135. },
  136. );
  137. }
  138. Widget _buildSeasonalPattern(SeasonalTheme theme) {
  139. switch (theme) {
  140. case SeasonalTheme.winter:
  141. return _buildSnowfall();
  142. case SeasonalTheme.summer:
  143. return _buildSunshine();
  144. case SeasonalTheme.autumn:
  145. return _buildFallingLeaves();
  146. case SeasonalTheme.spring:
  147. return _buildFlowersBlooming();
  148. default:
  149. return const SizedBox.shrink();
  150. }
  151. }
  152. Widget _buildSnowfall() {
  153. return AnimatedBuilder(
  154. animation: _seasonalController,
  155. builder: (context, child) {
  156. return Container(
  157. decoration: BoxDecoration(
  158. gradient: RadialGradient(
  159. center: Alignment.topCenter,
  160. radius: 1.5,
  161. colors: [Colors.white.withValues(alpha: 0.1), Colors.transparent],
  162. ),
  163. ),
  164. child: CustomPaint(
  165. painter: SnowfallPainter(_seasonalController.value),
  166. size: Size.infinite,
  167. ),
  168. );
  169. },
  170. );
  171. }
  172. Widget _buildSunshine() {
  173. return AnimatedBuilder(
  174. animation: _seasonalController,
  175. builder: (context, child) {
  176. return Container(
  177. decoration: BoxDecoration(
  178. gradient: RadialGradient(
  179. center: Alignment.topCenter,
  180. radius: 1.5,
  181. colors: [
  182. Colors.yellow.withValues(alpha: 0.15),
  183. Colors.transparent,
  184. ],
  185. ),
  186. ),
  187. child: CustomPaint(
  188. painter: SunshinePainter(_seasonalController.value),
  189. size: Size.infinite,
  190. ),
  191. );
  192. },
  193. );
  194. }
  195. Widget _buildFallingLeaves() {
  196. return AnimatedBuilder(
  197. animation: _seasonalController,
  198. builder: (context, child) {
  199. return Container(
  200. decoration: BoxDecoration(
  201. gradient: RadialGradient(
  202. center: Alignment.topCenter,
  203. radius: 1.5,
  204. colors: [
  205. Colors.orange.withValues(alpha: 0.1),
  206. Colors.transparent,
  207. ],
  208. ),
  209. ),
  210. child: CustomPaint(
  211. painter: FallingLeavesPainter(_seasonalController.value),
  212. size: Size.infinite,
  213. ),
  214. );
  215. },
  216. );
  217. }
  218. Widget _buildFlowersBlooming() {
  219. return AnimatedBuilder(
  220. animation: _seasonalController,
  221. builder: (context, child) {
  222. return Container(
  223. decoration: BoxDecoration(
  224. gradient: RadialGradient(
  225. center: Alignment.topCenter,
  226. radius: 1.5,
  227. colors: [Colors.pink.withValues(alpha: 0.1), Colors.transparent],
  228. ),
  229. ),
  230. child: CustomPaint(
  231. painter: FlowersPainter(_seasonalController.value),
  232. size: Size.infinite,
  233. ),
  234. );
  235. },
  236. );
  237. }
  238. Widget _buildAnimatedLayer(int layerIndex) {
  239. AnimationController controller;
  240. List<Color> colors;
  241. switch (layerIndex) {
  242. case 0:
  243. controller = _controller1;
  244. colors =
  245. widget.isZenMode
  246. ? ZenColors.zenModeColors
  247. : ZenColors.animationLayer1;
  248. break;
  249. case 1:
  250. controller = _controller2;
  251. colors = ZenColors.animationLayer2;
  252. break;
  253. default:
  254. controller = _controller3;
  255. colors = ZenColors.animationLayer3;
  256. }
  257. return AnimatedBuilder(
  258. animation: Listenable.merge([controller, _shakeController]),
  259. builder: (context, child) {
  260. // Add shake effect
  261. double shakeIntensity = 0.0;
  262. if (_isShaking) {
  263. shakeIntensity =
  264. math.sin(_shakeController.value * math.pi * 8) *
  265. (1 - _shakeController.value) *
  266. 10;
  267. }
  268. return Transform.translate(
  269. offset: Offset(
  270. shakeIntensity * (math.Random().nextDouble() - 0.5) * 2,
  271. shakeIntensity * (math.Random().nextDouble() - 0.5) * 2,
  272. ),
  273. child: Transform.rotate(
  274. angle: controller.value * 2 * math.pi,
  275. child: Container(
  276. decoration: BoxDecoration(
  277. gradient: RadialGradient(
  278. center: Alignment(
  279. math.sin(controller.value * 2 * math.pi) * 0.5,
  280. math.cos(controller.value * 2 * math.pi) * 0.5,
  281. ),
  282. radius:
  283. 1.5 +
  284. math.sin(controller.value * math.pi) * 0.5 +
  285. (shakeIntensity * 0.1),
  286. colors: colors,
  287. ),
  288. ),
  289. ),
  290. ),
  291. );
  292. },
  293. );
  294. }
  295. }
  296. class SnowfallPainter extends CustomPainter {
  297. final double animationValue;
  298. SnowfallPainter(this.animationValue);
  299. @override
  300. void paint(Canvas canvas, Size size) {
  301. final paint =
  302. Paint()
  303. ..color = Colors.white.withValues(alpha: 0.3)
  304. ..style = PaintingStyle.fill;
  305. // Fixed seed for consistent positions (removed unused variable)
  306. // Draw 80 snowflakes with falling animation covering entire screen
  307. for (int i = 0; i < 80; i++) {
  308. // Better distribution using sine and cosine functions with different frequencies
  309. final angle1 =
  310. i * 2.39996; // Use irrational number for better distribution
  311. final baseX = (math.sin(angle1) * 0.5 + 0.5) * size.width;
  312. // Consistent falling animation starting from top
  313. const fallSpeed = 0.2; // Same speed for all snowflakes
  314. final fallOffset = animationValue * fallSpeed * size.height * 1.2;
  315. final animatedY =
  316. (fallOffset + (i * 20) % size.height) % (size.height + 100) - 100;
  317. final snowflakeSize = (2 + math.sin(i * 0.3) * 2) * 3; // 3x larger
  318. _drawDetailedSnowflake(
  319. canvas,
  320. Offset(baseX, animatedY),
  321. snowflakeSize,
  322. paint,
  323. );
  324. }
  325. }
  326. void _drawDetailedSnowflake(
  327. Canvas canvas,
  328. Offset center,
  329. double size,
  330. Paint paint,
  331. ) {
  332. // Draw a 6-pointed snowflake
  333. final strokePaint =
  334. Paint()
  335. ..color = paint.color
  336. ..strokeWidth = size * 0.1
  337. ..style = PaintingStyle.stroke;
  338. // Draw 6 main spokes
  339. for (int i = 0; i < 6; i++) {
  340. final angle = i * math.pi / 3;
  341. final startX = center.dx + math.cos(angle) * size * 0.2;
  342. final startY = center.dy + math.sin(angle) * size * 0.2;
  343. final endX = center.dx + math.cos(angle) * size;
  344. final endY = center.dy + math.sin(angle) * size;
  345. canvas.drawLine(Offset(startX, startY), Offset(endX, endY), strokePaint);
  346. // Draw small branches on each spoke
  347. final branchSize = size * 0.3;
  348. final branchX = center.dx + math.cos(angle) * size * 0.6;
  349. final branchY = center.dy + math.sin(angle) * size * 0.6;
  350. // Left branch
  351. final leftBranchAngle = angle - math.pi / 4;
  352. canvas.drawLine(
  353. Offset(branchX, branchY),
  354. Offset(
  355. branchX + math.cos(leftBranchAngle) * branchSize,
  356. branchY + math.sin(leftBranchAngle) * branchSize,
  357. ),
  358. strokePaint,
  359. );
  360. // Right branch
  361. final rightBranchAngle = angle + math.pi / 4;
  362. canvas.drawLine(
  363. Offset(branchX, branchY),
  364. Offset(
  365. branchX + math.cos(rightBranchAngle) * branchSize,
  366. branchY + math.sin(rightBranchAngle) * branchSize,
  367. ),
  368. strokePaint,
  369. );
  370. }
  371. // Draw center circle
  372. canvas.drawCircle(center, size * 0.15, paint);
  373. }
  374. @override
  375. bool shouldRepaint(CustomPainter oldDelegate) => true;
  376. }
  377. class SunshinePainter extends CustomPainter {
  378. final double animationValue;
  379. SunshinePainter(this.animationValue);
  380. @override
  381. void paint(Canvas canvas, Size size) {
  382. final center = Offset(size.width * 0.8, size.height * 0.2);
  383. // Draw sun rays
  384. final rayPaint =
  385. Paint()
  386. ..color = Colors.yellow.withValues(alpha: 0.2)
  387. ..strokeWidth = 2
  388. ..style = PaintingStyle.stroke;
  389. for (int i = 0; i < 12; i++) {
  390. final angle = (i * math.pi * 2 / 12) + (animationValue * math.pi * 2);
  391. final startRadius = 30;
  392. final endRadius = 60 + math.sin(animationValue * math.pi * 4) * 10;
  393. final start = Offset(
  394. center.dx + math.cos(angle) * startRadius,
  395. center.dy + math.sin(angle) * startRadius,
  396. );
  397. final end = Offset(
  398. center.dx + math.cos(angle) * endRadius,
  399. center.dy + math.sin(angle) * endRadius,
  400. );
  401. canvas.drawLine(start, end, rayPaint);
  402. }
  403. // Draw sun
  404. final sunPaint =
  405. Paint()
  406. ..color = Colors.yellow.withValues(alpha: 0.3)
  407. ..style = PaintingStyle.fill;
  408. canvas.drawCircle(center, 25, sunPaint);
  409. }
  410. @override
  411. bool shouldRepaint(CustomPainter oldDelegate) => true;
  412. }
  413. class FallingLeavesPainter extends CustomPainter {
  414. final double animationValue;
  415. FallingLeavesPainter(this.animationValue);
  416. @override
  417. void paint(Canvas canvas, Size size) {
  418. final paint =
  419. Paint()
  420. ..color = Colors.orange.withValues(alpha: 0.4)
  421. ..style = PaintingStyle.fill;
  422. // Fixed seed for consistent positions (removed unused variable)
  423. // Draw 40 leaves with slow falling animation
  424. for (int i = 0; i < 40; i++) {
  425. // Better distribution using trigonometric functions
  426. final angle1 = i * 1.618; // Golden ratio for better distribution
  427. final baseX = (math.sin(angle1) * 0.5 + 0.5) * size.width;
  428. // Consistent falling animation starting from top with slight swaying
  429. const fallSpeed = 0.2; // Same speed as snowflakes
  430. final swayAmount = math.sin(animationValue * math.pi * 2 + i) * 25;
  431. final fallOffset = animationValue * fallSpeed * size.height * 1.2;
  432. final animatedY =
  433. (fallOffset + (i * 30) % size.height) % (size.height + 100) - 100;
  434. final animatedX = baseX + swayAmount;
  435. final leafSize = (6 + math.sin(i * 0.4) * 4) * 3; // 3x larger
  436. _drawDetailedLeaf(
  437. canvas,
  438. Offset(animatedX, animatedY),
  439. leafSize,
  440. paint,
  441. i,
  442. );
  443. }
  444. }
  445. void _drawDetailedLeaf(
  446. Canvas canvas,
  447. Offset center,
  448. double size,
  449. Paint paint,
  450. int index,
  451. ) {
  452. // Create a more detailed leaf shape
  453. final leafPaint =
  454. Paint()
  455. ..color = paint.color
  456. ..style = PaintingStyle.fill;
  457. final strokePaint =
  458. Paint()
  459. ..color = paint.color.withValues(alpha: 0.8)
  460. ..strokeWidth = size * 0.05
  461. ..style = PaintingStyle.stroke;
  462. // Different leaf shapes based on index
  463. final leafType = index % 3;
  464. if (leafType == 0) {
  465. // Oak leaf shape
  466. _drawOakLeaf(canvas, center, size, leafPaint, strokePaint);
  467. } else if (leafType == 1) {
  468. // Maple leaf shape
  469. _drawMapleLeaf(canvas, center, size, leafPaint, strokePaint);
  470. } else {
  471. // Simple oval leaf with stem
  472. _drawSimpleLeaf(canvas, center, size, leafPaint, strokePaint);
  473. }
  474. }
  475. void _drawOakLeaf(
  476. Canvas canvas,
  477. Offset center,
  478. double size,
  479. Paint fillPaint,
  480. Paint strokePaint,
  481. ) {
  482. final path = Path();
  483. path.moveTo(center.dx, center.dy - size * 0.4);
  484. // Create wavy edges
  485. for (int i = 0; i < 6; i++) {
  486. final angle = i * math.pi / 3;
  487. final radius = size * (0.3 + math.sin(i * 2) * 0.1);
  488. path.lineTo(
  489. center.dx + math.cos(angle) * radius,
  490. center.dy + math.sin(angle) * radius,
  491. );
  492. }
  493. path.close();
  494. canvas.drawPath(path, fillPaint);
  495. canvas.drawPath(path, strokePaint);
  496. // Draw stem
  497. canvas.drawLine(
  498. center,
  499. Offset(center.dx, center.dy + size * 0.3),
  500. strokePaint,
  501. );
  502. }
  503. void _drawMapleLeaf(
  504. Canvas canvas,
  505. Offset center,
  506. double size,
  507. Paint fillPaint,
  508. Paint strokePaint,
  509. ) {
  510. final path = Path();
  511. // Create 5-pointed maple leaf shape
  512. for (int i = 0; i < 5; i++) {
  513. final angle = i * math.pi * 2 / 5 - math.pi / 2;
  514. final radius = size * (i % 2 == 0 ? 0.4 : 0.2);
  515. if (i == 0) {
  516. path.moveTo(
  517. center.dx + math.cos(angle) * radius,
  518. center.dy + math.sin(angle) * radius,
  519. );
  520. } else {
  521. path.lineTo(
  522. center.dx + math.cos(angle) * radius,
  523. center.dy + math.sin(angle) * radius,
  524. );
  525. }
  526. }
  527. path.close();
  528. canvas.drawPath(path, fillPaint);
  529. canvas.drawPath(path, strokePaint);
  530. // Draw stem
  531. canvas.drawLine(
  532. center,
  533. Offset(center.dx, center.dy + size * 0.3),
  534. strokePaint,
  535. );
  536. }
  537. void _drawSimpleLeaf(
  538. Canvas canvas,
  539. Offset center,
  540. double size,
  541. Paint fillPaint,
  542. Paint strokePaint,
  543. ) {
  544. // Draw oval leaf
  545. canvas.drawOval(
  546. Rect.fromCenter(center: center, width: size, height: size * 0.6),
  547. fillPaint,
  548. );
  549. // Draw center vein
  550. canvas.drawLine(
  551. Offset(center.dx, center.dy - size * 0.3),
  552. Offset(center.dx, center.dy + size * 0.3),
  553. strokePaint,
  554. );
  555. // Draw side veins
  556. for (int i = 0; i < 3; i++) {
  557. final y = center.dy + (i - 1) * size * 0.15;
  558. canvas.drawLine(
  559. Offset(center.dx, y),
  560. Offset(center.dx + size * 0.3, y),
  561. strokePaint,
  562. );
  563. canvas.drawLine(
  564. Offset(center.dx, y),
  565. Offset(center.dx - size * 0.3, y),
  566. strokePaint,
  567. );
  568. }
  569. // Draw stem
  570. canvas.drawLine(
  571. center,
  572. Offset(center.dx, center.dy + size * 0.4),
  573. strokePaint,
  574. );
  575. }
  576. @override
  577. bool shouldRepaint(CustomPainter oldDelegate) => true;
  578. }
  579. class FlowersPainter extends CustomPainter {
  580. final double animationValue;
  581. FlowersPainter(this.animationValue);
  582. @override
  583. void paint(Canvas canvas, Size size) {
  584. final petalPaint =
  585. Paint()
  586. ..color = Colors.pink.withValues(alpha: 0.3)
  587. ..style = PaintingStyle.fill;
  588. final centerPaint =
  589. Paint()
  590. ..color = Colors.yellow.withValues(alpha: 0.4)
  591. ..style = PaintingStyle.fill;
  592. // Fixed seed for consistent positions (removed unused variable)
  593. // Draw 25 flowers with gentle falling animation
  594. for (int i = 0; i < 25; i++) {
  595. // Better distribution using sine for X position
  596. final angle = i * 2.399963; // Golden angle in radians
  597. final baseX = (math.sin(angle) * 0.5 + 0.5) * size.width;
  598. // Consistent falling animation starting from top
  599. const fallSpeed = 0.2; // Same speed as other elements
  600. final fallOffset = animationValue * fallSpeed * size.height * 1.2;
  601. final animatedY =
  602. (fallOffset + (i * 25) % size.height) % (size.height + 100) - 100;
  603. final center = Offset(baseX, animatedY);
  604. // Blooming animation - flowers grow and shrink gently
  605. final bloomPhase = (animationValue + i * 0.1) % 1.0;
  606. final scale =
  607. (0.5 + math.sin(bloomPhase * math.pi * 2) * 0.3) * 2; // 2x larger
  608. // Draw petals
  609. for (int petal = 0; petal < 5; petal++) {
  610. final petalAngle = petal * math.pi * 2 / 5;
  611. final petalCenter = Offset(
  612. center.dx + math.cos(petalAngle) * 8 * scale,
  613. center.dy + math.sin(petalAngle) * 8 * scale,
  614. );
  615. canvas.drawCircle(petalCenter, 6 * scale, petalPaint);
  616. }
  617. // Draw flower center
  618. canvas.drawCircle(center, 4 * scale, centerPaint);
  619. }
  620. }
  621. @override
  622. bool shouldRepaint(CustomPainter oldDelegate) => true;
  623. }