From af3b941711a3cdca785b87e18989777fe8d8b504 Mon Sep 17 00:00:00 2001 From: Yuri Petrov <48598325+petrovyuri@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:53:38 +0300 Subject: [PATCH] =?UTF-8?q?feat(app):=20=D0=92=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B7=D0=B0=20splash=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: PetrovY --- .friflex_config/stateless_widget.json | 19 +++++ assets/lottie/splash.json | 1 + lib/app/app.dart | 114 +++++++++++++++---------- lib/app/app_providers.dart | 29 +++++++ lib/app/depends_providers.dart | 26 ++++++ lib/di/di_container.dart | 84 ++++++------------ lib/di/di_repositories.dart | 30 ++++--- lib/di/di_services.dart | 50 +++++++++++ lib/features/debug/debug_screen.dart | 27 +++++- lib/features/error/error_screen.dart | 38 +++++++-- lib/features/splash/splash_screen.dart | 18 ++++ lib/gen/assets.gen.dart | 80 +++++++++++++++++ lib/router/app_router.dart | 7 +- lib/runner/app_runner.dart | 97 +++++++++++++++------ lib/runner/errors_handlers.dart | 8 +- lib/runner/timer_runner.dart | 1 + pubspec.lock | 26 +++++- pubspec.yaml | 3 + 18 files changed, 503 insertions(+), 155 deletions(-) create mode 100644 .friflex_config/stateless_widget.json create mode 100644 assets/lottie/splash.json create mode 100644 lib/app/app_providers.dart create mode 100644 lib/app/depends_providers.dart create mode 100644 lib/di/di_services.dart create mode 100644 lib/features/splash/splash_screen.dart diff --git a/.friflex_config/stateless_widget.json b/.friflex_config/stateless_widget.json new file mode 100644 index 0000000..ea3d96d --- /dev/null +++ b/.friflex_config/stateless_widget.json @@ -0,0 +1,19 @@ +{ + "imports": [ + "import 'package:flutter/widgets.dart';" + ], + "classStructure": [ + "/// {@template {className}}", + "/// ", + "/// {@endtemplate}", + "class {className} extends StatelessWidget {", + " /// {@macro {className}}", + " const {className}({super.key});", + " ", + " @override", + " Widget build(BuildContext context) {", + " return const Placeholder();", + " }", + "}" + ] +} \ No newline at end of file diff --git a/assets/lottie/splash.json b/assets/lottie/splash.json new file mode 100644 index 0000000..737fc67 --- /dev/null +++ b/assets/lottie/splash.json @@ -0,0 +1 @@ +{"nm":"Flow 45","ddd":0,"h":500,"w":500,"meta":{"g":"LottieFiles Figma v62"},"layers":[{"ty":0,"nm":"Component 1","sr":1,"st":0,"op":121,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[250,250],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[250,250],"t":48},{"s":[250,250],"t":120}]},"s":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":48},{"s":[100,100],"t":120}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[145,250],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[368,250],"t":48},{"s":[145,250],"t":120}]},"r":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":48},{"s":[0],"t":120}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":48},{"s":[100],"t":120}]}},"masksProperties":[{"nm":"","inv":false,"mode":"a","x":{"a":0,"k":0},"o":{"a":0,"k":100},"pt":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[500,0],[500,500],[0,500],[0,0]]}],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[500,0],[500,500],[0,500],[0,0]]}],"t":48},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[500,0],[500,500],[0,500],[0,0]]}],"t":120}]}}],"w":500,"h":500,"refId":"1","ind":1}],"v":"5.7.0","fr":60,"op":120,"ip":0,"assets":[{"nm":"[Asset] Component 1","id":"1","layers":[{"ty":4,"nm":"Vector 2622","sr":1,"st":0,"op":121,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[45.81,78.46],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[25.81,83.46],"t":72},{"s":[45.81,78.46],"t":120}]},"s":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":72},{"s":[100,100],"t":120}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[275.44,190.46],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[275.44,195.46],"t":72},{"s":[275.44,190.46],"t":120}]},"r":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[180],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[180],"t":72},{"s":[180],"t":120}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[91.63,21.09],[0,0],[71.63,156.91],[91.63,21.09]]}],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[31.63,31.09],[0,0],[51.63,166.91],[31.63,31.09]]}],"t":72},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[91.63,21.09],[0,0],[71.63,156.91],[91.63,21.09]]}],"t":120}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.2863,0.6314,0.651],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.2863,0.6314,0.651],"t":72},{"s":[0.2863,0.6314,0.651],"t":120}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}}],"ind":1},{"ty":4,"nm":"Vector 2620","sr":1,"st":0,"op":121,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[45.81,57.91],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[25.81,57.91],"t":72},{"s":[45.81,57.91],"t":120}]},"s":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,-100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,-100],"t":72},{"s":[100,-100],"t":120}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[275.44,329.59],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[275.44,329.59],"t":72},{"s":[275.44,329.59],"t":120}]},"r":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[180],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[180],"t":72},{"s":[180],"t":120}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[91.63,0],[0,19.62],[71.63,115.83],[91.63,0]]}],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[31.63,0],[0,29.62],[51.63,115.83],[31.63,0]]}],"t":72},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[91.63,0],[0,19.62],[71.63,115.83],[91.63,0]]}],"t":120}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.2863,0.6314,0.651],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.2863,0.6314,0.651],"t":72},{"s":[0.2863,0.6314,0.651],"t":120}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}}],"ind":2},{"ty":4,"nm":"Vector 2621","sr":1,"st":0,"op":121,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[25.81,83.46],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[45.81,78.46],"t":72},{"s":[25.81,83.46],"t":120}]},"s":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,-100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,-100],"t":72},{"s":[100,-100],"t":120}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[223.81,195.46],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[223.81,190.46],"t":72},{"s":[223.81,195.46],"t":120}]},"r":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":72},{"s":[0],"t":120}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[31.63,31.09],[0,0],[51.63,166.91],[31.63,31.09]]}],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[91.63,21.09],[0,0],[71.63,156.91],[91.63,21.09]]}],"t":72},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[31.63,31.09],[0,0],[51.63,166.91],[31.63,31.09]]}],"t":120}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.549,0.7333,0.4118],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.549,0.7333,0.4118],"t":72},{"s":[0.549,0.7333,0.4118],"t":120}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}}],"ind":3},{"ty":4,"nm":"Vector 2619","sr":1,"st":0,"op":121,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[25.81,57.91],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[45.81,57.91],"t":72},{"s":[25.81,57.91],"t":120}]},"s":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":72},{"s":[100,100],"t":120}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[223.81,329.59],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[223.81,329.59],"t":72},{"s":[223.81,329.59],"t":120}]},"r":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":72},{"s":[0],"t":120}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[31.63,0],[0,29.62],[51.63,115.83],[31.63,0]]}],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[91.63,0],[0,19.62],[71.63,115.83],[91.63,0]]}],"t":72},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[31.63,0],[0,29.62],[51.63,115.83],[31.63,0]]}],"t":120}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.549,0.7333,0.4118],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.549,0.7333,0.4118],"t":72},{"s":[0.549,0.7333,0.4118],"t":120}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}}],"ind":4},{"ty":4,"nm":"Vector 2623","sr":1,"st":0,"op":121,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[61.45,36.96],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[61.45,36.96],"t":72},{"s":[61.45,36.96],"t":120}]},"s":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":72},{"s":[100,100],"t":120}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[259.45,264.24],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[239.45,264.24],"t":72},{"s":[259.45,264.24],"t":120}]},"r":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":72},{"s":[0],"t":120}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[70.9,73.91],[0,51.63],[30.9,0],[122.89,41.63],[70.9,73.91]]}],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[50.9,73.91],[0,41.63],[90.9,0],[122.89,51.63],[50.9,73.91]]}],"t":72},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[70.9,73.91],[0,51.63],[30.9,0],[122.89,41.63],[70.9,73.91]]}],"t":120}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.1294,0.3176,0.4784],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.1294,0.3176,0.4784],"t":72},{"s":[0.1294,0.3176,0.4784],"t":120}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}}],"ind":5},{"ty":4,"nm":"Component 1 Bg","sr":1,"st":0,"op":121,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[250,250],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[250,250],"t":72},{"s":[250,250],"t":120}]},"s":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100,100],"t":72},{"s":[100,100],"t":120}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[250,250],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[250,250],"t":72},{"s":[250,250],"t":120}]},"r":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":72},{"s":[0],"t":120}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[100],"t":72},{"s":[100],"t":120}]}},"ef":[],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[500,0],[500,500],[0,500],[0,0]]}],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[500,0],[500,500],[0,500],[0,0]]}],"t":72},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[500,0],[500,500],[0,500],[0,0]]}],"t":120}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.9608,0.9608,0.9608],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0.9608,0.9608,0.9608],"t":72},{"s":[0.9608,0.9608,0.9608],"t":120}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":0},{"o":{"x":0.56,"y":0},"i":{"x":0.38,"y":1},"s":[0],"t":72},{"s":[0],"t":120}]}}],"ind":6}]}]} \ No newline at end of file diff --git a/lib/app/app.dart b/lib/app/app.dart index 36737b2..1effd46 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -1,46 +1,96 @@ import 'package:flutter/material.dart'; import 'package:friflex_starter/app/app_context_ext.dart'; +import 'package:friflex_starter/app/app_providers.dart'; +import 'package:friflex_starter/app/depends_providers.dart'; import 'package:friflex_starter/app/theme/app_theme.dart'; import 'package:friflex_starter/app/theme/theme_notifier.dart'; import 'package:friflex_starter/di/di_container.dart'; +import 'package:friflex_starter/features/error/error_screen.dart'; +import 'package:friflex_starter/features/splash/splash_screen.dart'; import 'package:friflex_starter/l10n/gen/app_localizations.dart'; import 'package:friflex_starter/l10n/localization_notifier.dart'; import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; -/// Класс для реализации объекта приложения -class App extends StatelessWidget { - /// Создает экземпляр приложения - /// - /// Принимает: - /// - [diContainer] - набор зависимостей приложения - /// - [router] - экземпляр роутера приложения +/// Класс приложения +class App extends StatefulWidget { const App({ super.key, - required this.diContainer, required this.router, + required this.initDependencies, }); - - /// Набор зависимостей приложения - final DiContainer diContainer; - - /// Экземпляр роутера приложения + /// Роутер приложения final GoRouter router; + /// Функция для инициализации зависимостей + final Future Function() initDependencies; + + @override + State createState() => _AppState(); +} + +class _AppState extends State { + /// Мутабельная Future для инициализации зависимостей + late Future _initFuture; + + @override + void initState() { + super.initState(); + _initFuture = widget.initDependencies(); + } @override Widget build(BuildContext context) { return AppProviders( - diContainer: diContainer, + // Consumer для локализации добавляем выше чем DependsProviders + // чтобы при изменении локализации перестраивался весь виджет + // Но, это не обязательно, можно добавить в DependsProviders child: LocalizationConsumer( - builder: () => ThemeConsumer( - builder: () => _App(router: router), + builder: () => FutureBuilder( + future: _initFuture, + builder: (_, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + case ConnectionState.waiting: + case ConnectionState.active: + // Пока инициализация показываем Splash + return const SplashScreen(); + case ConnectionState.done: + if (snapshot.hasError) { + return ErrorScreen( + error: snapshot.error, + stackTrace: snapshot.stackTrace, + onRetry: _retryInit, + ); + } + + final diContainer = snapshot.data; + if (diContainer == null) { + return ErrorScreen( + error: + 'Ошибка инициализации зависимостей, diContainer = null', + stackTrace: null, + onRetry: _retryInit, + ); + } + return DependsProviders( + diContainer: diContainer, + child: ThemeConsumer( + builder: () => _App(router: widget.router), + ), + ); + } + }, ), ), ); } + + void _retryInit() { + setState(() { + _initFuture = widget.initDependencies(); + }); + } } -/// Класс для реализации объекта приложения class _App extends StatelessWidget { const _App({required this.router}); @@ -59,31 +109,3 @@ class _App extends StatelessWidget { ); } } - -/// Класс для реализации провайдеров приложения -final class AppProviders extends StatelessWidget { - const AppProviders({ - super.key, - required this.child, - required this.diContainer, - }); - - final Widget child; - final DiContainer diContainer; - - @override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - Provider.value(value: diContainer), // Передаем контейнер зависимостей - ChangeNotifierProvider( - create: (_) => ThemeNotifier(), - ), // Провайдер для темы - ChangeNotifierProvider( - create: (_) => LocalizationNotifier(), - ), // Провайдер для локализации - ], - child: child, - ); - } -} diff --git a/lib/app/app_providers.dart b/lib/app/app_providers.dart new file mode 100644 index 0000000..41496ff --- /dev/null +++ b/lib/app/app_providers.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:friflex_starter/app/theme/theme_notifier.dart'; +import 'package:friflex_starter/l10n/localization_notifier.dart'; +import 'package:provider/provider.dart'; + +/// Класс для добавления провайдеров темы и локализации +final class AppProviders extends StatelessWidget { + const AppProviders({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => ThemeNotifier(), + ), // Провайдер для темы + ChangeNotifierProvider( + create: (_) => LocalizationNotifier(), + ), // Провайдер для локализации + ], + child: child, + ); + } +} diff --git a/lib/app/depends_providers.dart b/lib/app/depends_providers.dart new file mode 100644 index 0000000..a05e512 --- /dev/null +++ b/lib/app/depends_providers.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:friflex_starter/di/di_container.dart'; +import 'package:provider/provider.dart'; + +/// Класс для внедрения глобальных зависимостей +final class DependsProviders extends StatelessWidget { + const DependsProviders({ + super.key, + required this.child, + required this.diContainer, + }); + + final Widget child; + final DiContainer diContainer; + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + // Сюда добавляем глобальные блоки, inherited и т.д. + Provider.value(value: diContainer), // Передаем контейнер зависимостей + ], + child: child, + ); + } +} diff --git a/lib/di/di_container.dart b/lib/di/di_container.dart index 550b4fa..96d3eda 100644 --- a/lib/di/di_container.dart +++ b/lib/di/di_container.dart @@ -1,13 +1,12 @@ -import 'package:app_services/app_services.dart'; import 'package:friflex_starter/app/app_config/app_config.dart'; import 'package:friflex_starter/app/app_config/i_app_config.dart'; import 'package:friflex_starter/app/app_env.dart'; import 'package:friflex_starter/app/http/app_http_client.dart'; import 'package:friflex_starter/app/http/i_http_client.dart'; import 'package:friflex_starter/di/di_repositories.dart'; +import 'package:friflex_starter/di/di_services.dart'; import 'package:friflex_starter/di/di_typedefs.dart'; import 'package:friflex_starter/features/debug/i_debug_service.dart'; -import 'package:i_app_services/i_app_services.dart'; /// {@template dependencies_container} /// Контейнер для зависимостей @@ -22,82 +21,53 @@ final class DiContainer { /// Сервис для отладки, получаем из конструктора late final IDebugService debugService; - /// Сервис для работы с путями - late final IPathProvider pathProvider; - /// Конфигурация приложения late final IAppConfig appConfig; - /// Сервис для работы с локальным хранилищем - late final ISecureStorage secureStorage; - /// Сервис для работы с HTTP запросами - late final IHttpClient httpClient; + late final IHttpClient Function(IDebugService, IAppConfig) httpClientFactory; + /// Репозитории приложения late final DiRepositories repositories; + /// Сервисы приложения + late final DiServices services; + /// Метод для инициализации зависимостей Future init({ required OnProgress onProgress, required OnComplete onComplete, required OnError onError, }) async { - // Инициализация сервисов - await _initServices( - onComplete: onComplete, - onError: onError, - onProgress: onProgress, - ); - - // Инициализация репозиториев - repositories = DiRepositories(); - repositories.init( - onProgress: onProgress, - onComplete: onComplete, - onError: onError, - diContainer: this, - ); - - onComplete('Инициализация зависимостей завершена!'); - } - - /// Метод для инициализации сервисов - Future _initServices({ - required OnComplete onComplete, - required OnError onError, - required OnProgress onProgress, - }) async { + // Инициализация конфигурации приложения appConfig = switch (env) { AppEnv.dev => AppConfigDev(), AppEnv.prod => AppConfigProd(), AppEnv.stage => AppConfigStage() }; - httpClient = AppHttpClient( - debugService: debugService, - appConfig: appConfig, - ); + // Инициализация HTTP клиента + httpClientFactory = (debugService, appConfig) => AppHttpClient( + debugService: debugService, + appConfig: appConfig, + ); - try { - pathProvider = AppPathProvider(); - onProgress(AppPathProvider.name); - } on Object catch (error, stackTrace) { - onError( - 'Ошибка инициализации ${IPathProvider.name}', - error, - stackTrace, + // Инициализация сервисов + services = DiServices() + ..init( + onProgress: onProgress, + onError: onError, + diContainer: this, + ); + // throw Exception('Тестовая - ошибка инициализации зависимостей'); + // Инициализация репозиториев + repositories = DiRepositories() + ..init( + onProgress: onProgress, + onError: onError, + diContainer: this, ); - } - try { - secureStorage = AppSecureStorage(secretKey: appConfig.secretKey); - onProgress(AppSecureStorage.name); - } on Object catch (error, stackTrace) { - onError( - 'Ошибка инициализации ${ISecureStorage.name}', - error, - stackTrace, - ); - } + onComplete('Инициализация зависимостей завершена!'); } } diff --git a/lib/di/di_repositories.dart b/lib/di/di_repositories.dart index 151c066..f76422e 100644 --- a/lib/di/di_repositories.dart +++ b/lib/di/di_repositories.dart @@ -38,20 +38,21 @@ final class DiRepositories { /// /// Принимает: /// - [onProgress] - обратный вызов при прогрессе - /// - [onComplete] - обратный вызов при успешной инициализации /// - [diContainer] - контейнер зависимостей void init({ required OnProgress onProgress, - required OnComplete onComplete, required OnError onError, required DiContainer diContainer, }) { try { //Инициализация репозитория авторизации - authRepository = lazyInitRepo( + authRepository = _lazyInitRepo( mockFactory: AuthMockRepository.new, mainFactory: () => AuthRepository( - httpClient: diContainer.httpClient, + httpClient: diContainer.httpClientFactory( + diContainer.debugService, + diContainer.appConfig, + ), ), onProgress: onProgress, environment: diContainer.env, @@ -67,10 +68,13 @@ final class DiRepositories { try { // Инициализация репозитория сервиса управления токеном доступа - mainRepository = lazyInitRepo( + mainRepository = _lazyInitRepo( mockFactory: MainMockRepository.new, mainFactory: () => MainRepository( - httpClient: diContainer.httpClient, + httpClient: diContainer.httpClientFactory( + diContainer.debugService, + diContainer.appConfig, + ), ), onProgress: onProgress, environment: diContainer.env, @@ -84,7 +88,7 @@ final class DiRepositories { ); } - onComplete( + onProgress( 'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})', ); } @@ -96,18 +100,22 @@ final class DiRepositories { /// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами /// - [mainFactory] - функция - фабрика для инициализации основного репозиторий /// - [onProgress] - обратный вызов при прогрессе - T lazyInitRepo({ + T _lazyInitRepo({ required AppEnv environment, required T Function() mainFactory, required T Function() mockFactory, required OnProgress onProgress, }) { + final mockRepo = mockFactory(); + final mainRepo = mainFactory(); + final repo = switch (environment) { - AppEnv.dev => mockFactory(), - AppEnv.prod => mainFactory(), + AppEnv.dev => mockRepo, + AppEnv.prod => mainRepo, AppEnv.stage => - _mockReposToSwitch.contains(mockFactory().name) ? mockFactory() : mainFactory(), + _mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo, }; + onProgress(repo.name); return repo; } diff --git a/lib/di/di_services.dart b/lib/di/di_services.dart new file mode 100644 index 0000000..4cd358a --- /dev/null +++ b/lib/di/di_services.dart @@ -0,0 +1,50 @@ +import 'package:app_services/app_services.dart'; +import 'package:friflex_starter/di/di_container.dart'; +import 'package:friflex_starter/di/di_typedefs.dart'; +import 'package:i_app_services/i_app_services.dart'; + +/// Класс для инициализации сервисов +final class DiServices { + /// Сервис для работы с путями + late final IPathProvider pathProvider; + + /// Сервис для работы с локальным хранилищем + late final ISecureStorage secureStorage; + + /// Метод для инициализации репозиториев в приложении + /// + /// Принимает: + /// - [onProgress] - обратный вызов при прогрессе + /// - [diContainer] - контейнер зависимостей + /// - [onError] - обратный вызов при ошибке + void init({ + required OnProgress onProgress, + required OnError onError, + required DiContainer diContainer, + }) { + try { + pathProvider = AppPathProvider(); + onProgress(AppPathProvider.name); + } on Object catch (error, stackTrace) { + onError( + 'Ошибка инициализации ${IPathProvider.name}', + error, + stackTrace, + ); + } + try { + secureStorage = AppSecureStorage( + secretKey: diContainer.appConfig.secretKey, + ); + onProgress(AppSecureStorage.name); + } on Object catch (error, stackTrace) { + onError( + 'Ошибка инициализации ${ISecureStorage.name}', + error, + stackTrace, + ); + } + + onProgress('Инициализация сервисов завершена!'); + } +} diff --git a/lib/features/debug/debug_screen.dart b/lib/features/debug/debug_screen.dart index 9eda500..db04981 100644 --- a/lib/features/debug/debug_screen.dart +++ b/lib/features/debug/debug_screen.dart @@ -16,7 +16,7 @@ class DebugScreen extends StatelessWidget { padding: const EdgeInsets.all(16), children: [ Text( - 'Реализация SecureStorage: ${context.di.secureStorage.nameImpl}', + 'Реализация SecureStorage: ${context.di.services.secureStorage.nameImpl}', ), const SizedBox(height: 16), Text( @@ -82,14 +82,37 @@ class DebugScreen extends StatelessWidget { ), const SizedBox(height: 16), const Text('Тестовая иконка из assets'), + const SizedBox(height: 16), Assets.icons.home.svg(), + ElevatedButton( + onPressed: () { + throw Exception( + 'Тестовая ошибка Exception для отладки FlutterError', + ); + }, + child: const Text('Вызывать ошибку FlutterError'), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () async { + await _callError(); + }, + child: const Text('Вызывать ошибку PlatformDispatcher'), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () async { + await context.di.debugService.openDebugScreen(context); + }, + child: const Text('Вызывать Экран отладки'), + ), ], ), ), ); } - Future callError() async { + Future _callError() async { throw Exception('Тестовая ошибка Exception для отладки PlatformDispatcher'); } } diff --git a/lib/features/error/error_screen.dart b/lib/features/error/error_screen.dart index 04419bf..db157ba 100644 --- a/lib/features/error/error_screen.dart +++ b/lib/features/error/error_screen.dart @@ -5,16 +5,42 @@ import 'package:flutter/material.dart'; /// {@endtemplate} class ErrorScreen extends StatelessWidget { /// {@macro ErrorScreen} - const ErrorScreen({super.key}); + const ErrorScreen({ + super.key, + required this.error, + required this.stackTrace, + this.onRetry, + }); + + final Object? error; + final StackTrace? stackTrace; + final VoidCallback? onRetry; @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( home: Scaffold( - body: Center( - child: Text( - 'Что-то пошло не так, попробуйте перезагрузить приложение', - textAlign: TextAlign.center, + body: SafeArea( + child: Center( + child: ListView( + padding: const EdgeInsets.all(16), + children: [ + const SizedBox(height: 16), + ElevatedButton( + onPressed: onRetry, + child: const Text('Перезагрузить приложение'), + ), + const SizedBox(height: 16), + Text( + ''' + Что-то пошло не так, попробуйте перезагрузить приложение + error: $error + stackTrace: $stackTrace + ''', + textAlign: TextAlign.center, + ), + ], + ), ), ), ), diff --git a/lib/features/splash/splash_screen.dart b/lib/features/splash/splash_screen.dart new file mode 100644 index 0000000..2bec9bb --- /dev/null +++ b/lib/features/splash/splash_screen.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:friflex_starter/gen/assets.gen.dart'; + +/// {@template SplashScreen} +/// Экран загрузки приложения. +/// {@endtemplate} +class SplashScreen extends StatelessWidget { + /// {@macro SplashScreen} + const SplashScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Assets.lottie.splash.lottie(), + ); + } +} diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index 2beb3b6..cd0b6e2 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart' as _svg; +import 'package:lottie/lottie.dart' as _lottie; import 'package:vector_graphics/vector_graphics.dart' as _vg; class $AssetsFontsGen { @@ -50,11 +51,23 @@ class $AssetsIconsGen { List get values => [home]; } +class $AssetsLottieGen { + const $AssetsLottieGen(); + + /// File path: assets/lottie/splash.json + LottieGenImage get splash => + const LottieGenImage('assets/lottie/splash.json'); + + /// List of all assets + List get values => [splash]; +} + class Assets { Assets._(); static const $AssetsFontsGen fonts = $AssetsFontsGen(); static const $AssetsIconsGen icons = $AssetsIconsGen(); + static const $AssetsLottieGen lottie = $AssetsLottieGen(); } class SvgGenImage { @@ -133,3 +146,70 @@ class SvgGenImage { String get keyName => _assetName; } + +class LottieGenImage { + const LottieGenImage( + this._assetName, { + this.flavors = const {}, + }); + + final String _assetName; + final Set flavors; + + _lottie.LottieBuilder lottie({ + Animation? controller, + bool? animate, + _lottie.FrameRate? frameRate, + bool? repeat, + bool? reverse, + _lottie.LottieDelegates? delegates, + _lottie.LottieOptions? options, + void Function(_lottie.LottieComposition)? onLoaded, + _lottie.LottieImageProviderFactory? imageProviderFactory, + Key? key, + AssetBundle? bundle, + Widget Function( + BuildContext, + Widget, + _lottie.LottieComposition?, + )? frameBuilder, + ImageErrorWidgetBuilder? errorBuilder, + double? width, + double? height, + BoxFit? fit, + AlignmentGeometry? alignment, + String? package, + bool? addRepaintBoundary, + FilterQuality? filterQuality, + void Function(String)? onWarning, + }) { + return _lottie.Lottie.asset( + _assetName, + controller: controller, + animate: animate, + frameRate: frameRate, + repeat: repeat, + reverse: reverse, + delegates: delegates, + options: options, + onLoaded: onLoaded, + imageProviderFactory: imageProviderFactory, + key: key, + bundle: bundle, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder, + width: width, + height: height, + fit: fit, + alignment: alignment, + package: package, + addRepaintBoundary: addRepaintBoundary, + filterQuality: filterQuality, + onWarning: onWarning, + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} diff --git a/lib/router/app_router.dart b/lib/router/app_router.dart index bd4dcdb..f840ca1 100644 --- a/lib/router/app_router.dart +++ b/lib/router/app_router.dart @@ -3,6 +3,7 @@ import 'package:friflex_starter/features/debug/debug_routes.dart'; import 'package:friflex_starter/features/debug/i_debug_service.dart'; import 'package:friflex_starter/features/main/presentation/main_routes.dart'; import 'package:friflex_starter/features/root/root_screen.dart'; +import 'package:friflex_starter/features/splash/splash_screen.dart'; import 'package:go_router/go_router.dart'; /// Класс, реализующий роутер приложения и все поля классов @@ -14,7 +15,7 @@ class AppRouter { static final rootNavigatorKey = GlobalKey(); /// Начальный роут приложения - static String get initialLocation => '/debug'; + static String get initialLocation => '/debug'; /// Метод для создания экземпляра GoRouter static GoRouter createRouter(IDebugService debugService) { @@ -32,6 +33,10 @@ class AppRouter { DebugRoutes.buildShellBranch(), ], ), + GoRoute( + path: '/splash', + builder: (context, state) => const SplashScreen(), + ), ], ); } diff --git a/lib/runner/app_runner.dart b/lib/runner/app_runner.dart index 7e44d32..0425979 100644 --- a/lib/runner/app_runner.dart +++ b/lib/runner/app_runner.dart @@ -15,6 +15,11 @@ import 'package:go_router/go_router.dart'; part 'errors_handlers.dart'; +/// Время ожидания инициализации зависимостей +/// Если время превышено, то будет показан экран ошибки +/// В дальнейшем нужно убрать в env +const _initTimeout = Duration(seconds: 7); + /// Класс, реализующий раннер для конфигурирования приложения при запуске /// /// Порядок инициализации: @@ -33,38 +38,63 @@ class AppRunner { final AppEnv env; /// Контейнер зависимостей приложения - late final IDebugService _debugService; + late IDebugService _debugService; /// Роутер приложения - late final GoRouter router; + late GoRouter router; /// Таймер для отслеживания времени инициализации приложения - late final TimerRunner _timerRunner; + late TimerRunner _timerRunner; /// Метод для запуска приложения Future run() async { - WidgetsFlutterBinding.ensureInitialized(); - // Инициализация сервиса отладки - _debugService = DebugService(); + try { + WidgetsFlutterBinding.ensureInitialized(); + // Инициализация сервиса отладки + _debugService = DebugService(); - _timerRunner = TimerRunner(_debugService); + _timerRunner = TimerRunner(_debugService); - // Инициализация приложения - await _initApp(); + // Инициализация приложения + await _initApp(); - // Инициализация метода обработки ошибок - _initErrorHandlers(_debugService); + // Инициализация метода обработки ошибок + _initErrorHandlers(_debugService); - // Инициализация репозиториев и сервисов - final diContainer = await _initDependencies(_debugService); + // Инициализация роутера + router = AppRouter.createRouter(_debugService); - // Инициализация роутера - router = AppRouter.createRouter(_debugService); + // throw Exception('Test error'); - runApp( - App(diContainer: diContainer, router: router), - ); - await _onAppLoaded(); + runApp( + App( + router: router, + initDependencies: () { + return _initDependencies( + debugService: _debugService, + env: env, + timerRunner: _timerRunner, + ).timeout( + _initTimeout, + onTimeout: () { + return Future.error( + TimeoutException( + 'Превышено время ожидания инициализации зависимостей', + ), + ); + }, + ); + }, + ), + ); + await _onAppLoaded(); + } on Object catch (e, stackTrace) { + await _onAppLoaded(); + + /// Если произошла ошибка при инициализации приложения, + /// то запускаем экран ошибки + runApp(ErrorScreen(error: e, stackTrace: stackTrace, onRetry: run)); + } } /// Метод инициализации приложения, @@ -85,23 +115,36 @@ class AppRunner { WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.allowFirstFrame(); }); - - _timerRunner.stop(); } - /// Метод для инициализации зависимостей приложения - Future _initDependencies(IDebugService debugService) async { + // Метод для инициализации зависимостей приложения + Future _initDependencies({ + required IDebugService debugService, + required AppEnv env, + required TimerRunner timerRunner, + }) async { + // Имитация задержки инициализации + // TODO(yura): Удалить после проверки + await Future.delayed(const Duration(seconds: 3)); debugService.log(() => 'Тип сборки: ${env.name}'); final diContainer = DiContainer( env: env, dService: debugService, ); await diContainer.init( - onProgress: _timerRunner.logOnProgress, - onComplete: _timerRunner.logOnComplete, - onError: _timerRunner.logOnError, + onProgress: (name) => timerRunner.logOnProgress(name), + onComplete: (name) { + timerRunner + ..logOnComplete(name) + ..stop(); + }, + onError: (message, error, [stackTrace]) => debugService.logError( + message, + error: error, + stackTrace: stackTrace, + ), ); - + //throw Exception('Test error'); return diContainer; } } diff --git a/lib/runner/errors_handlers.dart b/lib/runner/errors_handlers.dart index 8ccaab5..de69e46 100644 --- a/lib/runner/errors_handlers.dart +++ b/lib/runner/errors_handlers.dart @@ -4,7 +4,7 @@ part of 'app_runner.dart'; void _initErrorHandlers(IDebugService debugService) { // Обработка ошибок в приложении FlutterError.onError = (details) { - _showErrorScreen(); + _showErrorScreen(details.exception, details.stack); debugService.logError( () => 'FlutterError.onError: ${details.exceptionAsString()}', error: details.exception, @@ -13,7 +13,7 @@ void _initErrorHandlers(IDebugService debugService) { }; // Обработка асинхронных ошибок в приложении PlatformDispatcher.instance.onError = (error, stack) { - _showErrorScreen(); + _showErrorScreen(error, stack); debugService.logError( () => 'PlatformDispatcher.instance.onError', error: error, @@ -24,11 +24,11 @@ void _initErrorHandlers(IDebugService debugService) { } /// Метод для показа экрана ошибки -void _showErrorScreen() { +void _showErrorScreen(Object error, StackTrace? stackTrace) { WidgetsBinding.instance.addPostFrameCallback((_) { AppRouter.rootNavigatorKey.currentState?.push( MaterialPageRoute( - builder: (_) => const ErrorScreen(), + builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace), ), ); }); diff --git a/lib/runner/timer_runner.dart b/lib/runner/timer_runner.dart index c5e6b6a..ba99236 100644 --- a/lib/runner/timer_runner.dart +++ b/lib/runner/timer_runner.dart @@ -36,6 +36,7 @@ class TimerRunner { _debugService.log( '$message, прогресс: ${_stopwatch.elapsedMilliseconds} мс', ); + } /// Метод для обработки прогресса инициализации зависимостей diff --git a/pubspec.lock b/pubspec.lock index dcc9139..8623c53 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,6 +29,14 @@ packages: relative: true source: path version: "0.0.1" + archive: + dependency: transitive + description: + name: archive + sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" + url: "https://pub.dev" + source: hosted + version: "4.0.2" args: dependency: transitive description: @@ -585,6 +593,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 + url: "https://pub.dev" + source: hosted + version: "3.3.1" macros: dependency: transitive description: @@ -754,6 +770,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" provider: dependency: "direct main" description: @@ -993,4 +1017,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.6.0 <4.0.0" - flutter: ">=3.24.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 771371b..3e98cbb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: flutter_svg: 2.0.17 flutter_localizations: sdk: flutter + lottie: 3.3.1 ### основной сервис с интерфейсами i_app_services: @@ -51,6 +52,7 @@ flutter: - assets/icons/ - assets/fonts/ - assets/images/ + - assets/lottie/ fonts: - family: Montserrat @@ -72,4 +74,5 @@ flutter_gen: line_length: 100 integrations: flutter_svg: true + lottie: true