diff --git a/lib/domain/controllers/preferences_controller.dart b/lib/domain/controllers/preferences_controller.dart index e8cf1fc..988f660 100644 --- a/lib/domain/controllers/preferences_controller.dart +++ b/lib/domain/controllers/preferences_controller.dart @@ -43,4 +43,5 @@ enum PreferenceKeys { freeText, tipType, sleepPermissionGranted, + finishedIntroduction, } diff --git a/lib/main.dart b/lib/main.dart index 8fed67c..3f7072e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,22 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:infinite_horizons/domain/controllers/controllers.dart'; import 'package:infinite_horizons/presentation/core/color_schemes.dart'; +import 'package:infinite_horizons/presentation/core/theme_data.dart'; import 'package:infinite_horizons/presentation/pages/pages.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await PreferencesController.instance.init(); - PlayerController.instance.init(); - await VibrationController.instance.init(); await EasyLocalization.ensureInitialized(); - DndController.instance.init(); - NotificationsController.instance.init(); - HealthController.instance.init(); - final int loginCounter = - PreferencesController.instance.getInt(PreferenceKeys.loginCounter) ?? 0; - PreferencesController.instance - .setInt(PreferenceKeys.loginCounter, loginCounter + 1); runApp( EasyLocalization( @@ -43,11 +33,11 @@ class MyApp extends StatelessWidget { return MaterialApp( theme: theme.light(), darkTheme: theme.dark(), - title: 'Infinite Horizons', + title: AppThemeData.appName, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, - home: HomePage(), + home: SplashPage(), ); } } diff --git a/lib/presentation/atoms/animated_text_atom.dart b/lib/presentation/atoms/animated_text_atom.dart index 2b41a78..c6dd312 100644 --- a/lib/presentation/atoms/animated_text_atom.dart +++ b/lib/presentation/atoms/animated_text_atom.dart @@ -1,4 +1,5 @@ -import 'package:flutter/widgets.dart'; +import 'package:animated_text_kit/animated_text_kit.dart'; +import 'package:flutter/material.dart'; import 'package:infinite_horizons/presentation/atoms/atoms.dart'; import 'package:vibration/vibration.dart'; @@ -6,10 +7,14 @@ class AnimatedTextAtom extends StatefulWidget { const AnimatedTextAtom({ required this.text, required this.onDone, + required this.variant, + this.textColorWhite = false, }); final String text; final VoidCallback onDone; + final bool textColorWhite; + final AnimatedTextVariant variant; @override _AnimatedTextAtomState createState() => _AnimatedTextAtomState(); @@ -20,6 +25,7 @@ class _AnimatedTextAtomState extends State late AnimationController _controller; late Animation _charCount; bool isOnDone = false; + bool isFlicking = true; @override void initState() { @@ -51,13 +57,11 @@ class _AnimatedTextAtomState extends State widget.onDone(); } - @override - Widget build(BuildContext context) { + Widget typewriter() { String currentText = widget.text.substring(0, _charCount.value); - // Trigger vibration for each letter if (_charCount.value > 0 && _charCount.value <= widget.text.length) { - Vibration.vibrate(duration: 1); // Vibrate for 50ms + Vibration.vibrate(duration: 1); } if (_charCount.value == widget.text.length) { @@ -69,6 +73,54 @@ class _AnimatedTextAtomState extends State return TextAtom( currentText, variant: TextVariant.title, + style: const TextStyle(color: Colors.white), + ); + } + + Widget flicker() { + return SizedBox( + width: double.infinity, + height: 50, + child: DefaultTextStyle( + style: const TextStyle( + fontSize: 35, + color: Colors.white, + shadows: [ + Shadow( + blurRadius: 8.0, + color: Colors.white, + ), + ], + ), + child: AnimatedTextKit( + animatedTexts: [ + FlickerAnimatedText( + widget.text, + ), + ], + isRepeatingAnimation: false, + onNext: (i, b) { + setState(() { + isFlicking = false; + }); + }, + ), + ), ); } + + @override + Widget build(BuildContext context) { + switch (widget.variant) { + case AnimatedTextVariant.typewriter: + return typewriter(); + case AnimatedTextVariant.flicker: + return flicker(); + } + } +} + +enum AnimatedTextVariant { + typewriter, + flicker, } diff --git a/lib/presentation/atoms/atoms.dart b/lib/presentation/atoms/atoms.dart index 194cceb..a5b130b 100644 --- a/lib/presentation/atoms/atoms.dart +++ b/lib/presentation/atoms/atoms.dart @@ -4,6 +4,7 @@ export 'package:infinite_horizons/presentation/atoms/button_atom.dart'; export 'package:infinite_horizons/presentation/atoms/card_atom.dart'; export 'package:infinite_horizons/presentation/atoms/check_box_atom.dart'; export 'package:infinite_horizons/presentation/atoms/confetti_atom.dart'; +export 'package:infinite_horizons/presentation/atoms/image_atom.dart'; export 'package:infinite_horizons/presentation/atoms/list_tile_atom.dart'; export 'package:infinite_horizons/presentation/atoms/margined_expanded_atom.dart'; export 'package:infinite_horizons/presentation/atoms/progress_indicator_atom.dart'; diff --git a/lib/presentation/atoms/image_atom.dart b/lib/presentation/atoms/image_atom.dart new file mode 100644 index 0000000..85ee6a6 --- /dev/null +++ b/lib/presentation/atoms/image_atom.dart @@ -0,0 +1,37 @@ +import 'package:flutter/cupertino.dart'; + +class ImageAtom extends StatelessWidget { + const ImageAtom( + this.name, { + this.fit, + this.width, + this.height, + this.hero, + }); + + final String name; + final BoxFit? fit; + final double? width; + final double? height; + final String? hero; + + Widget image() { + return Image.asset( + name, + fit: fit, + width: width, + height: height, + ); + } + + @override + Widget build(BuildContext context) { + if (hero != null) { + return Hero( + tag: hero!, + child: image(), + ); + } + return image(); + } +} diff --git a/lib/presentation/atoms/text_atom.dart b/lib/presentation/atoms/text_atom.dart index 3bc962e..96798f7 100644 --- a/lib/presentation/atoms/text_atom.dart +++ b/lib/presentation/atoms/text_atom.dart @@ -42,6 +42,9 @@ class TextAtom extends StatelessWidget { case TextVariant.medium: tempStyle = textTheme.bodyMedium; } + if (style != null) { + tempStyle = tempStyle?.copyWith(color: style!.color); + } return Text( translate && text.isNotEmpty ? text.tr(args: translationArgs) : text, diff --git a/lib/presentation/core/theme_data.dart b/lib/presentation/core/theme_data.dart index a68ddab..73d275e 100644 --- a/lib/presentation/core/theme_data.dart +++ b/lib/presentation/core/theme_data.dart @@ -7,4 +7,6 @@ class AppThemeData { const EdgeInsets.symmetric(horizontal: generalSpacing); static Color logoBackgroundColor = const Color(0xff061E4A); + + static String appName = 'Infinite Horizons'; } diff --git a/lib/presentation/pages/convincing_page.dart b/lib/presentation/pages/convincing_page.dart new file mode 100644 index 0000000..21032ec --- /dev/null +++ b/lib/presentation/pages/convincing_page.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:infinite_horizons/domain/controllers/controllers.dart'; +import 'package:infinite_horizons/presentation/atoms/atoms.dart'; +import 'package:infinite_horizons/presentation/core/theme_data.dart'; +import 'package:infinite_horizons/presentation/molecules/molecules.dart'; +import 'package:infinite_horizons/presentation/pages/home_page.dart'; + +class ConvincingPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: ColoredBox( + color: AppThemeData.logoBackgroundColor, + child: Column( + children: [ + const TopBarMolecule(topBarType: TopBarType.none, margin: false), + MarginedExpandedAtom( + child: Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(width: double.infinity), + AnimatedTextAtom( + text: 'The app will', + variant: AnimatedTextVariant.flicker, + onDone: () {}, + textColorWhite: true, + ), + const SeparatorAtom(), + const SeparatorAtom(), + TextAtom( + '* Organize your breaks', + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(color: Colors.white), + ), + const SeparatorAtom(), + TextAtom( + '* Enrich you with efficiency tips', + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(color: Colors.white), + ), + const SeparatorAtom(), + TextAtom( + '* Encourage healthy habits', + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(color: Colors.white), + ), + ], + ), + ), + ), + const Expanded(child: Text('')), + SafeArea( + child: AnimatedOpacity( + opacity: 1.0, + duration: const Duration(seconds: 2), + child: ButtonAtom( + variant: ButtonVariant.highEmphasisFilled, + onPressed: () { + PreferencesController.instance + .setBool(PreferenceKeys.finishedIntroduction, true); + + Navigator.of(context).pop(); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => HomePage(), + ), + ); + }, + text: 'Home Page', + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/home_page.dart b/lib/presentation/pages/home_page.dart index b96a6b5..a856729 100644 --- a/lib/presentation/pages/home_page.dart +++ b/lib/presentation/pages/home_page.dart @@ -185,7 +185,9 @@ class HomePageState extends State { ], ), ) - : const CircularProgressIndicator(), + : const Center( + child: CircularProgressIndicator(), + ), ); } } diff --git a/lib/presentation/pages/pages.dart b/lib/presentation/pages/pages.dart index 3e69fd5..134428f 100644 --- a/lib/presentation/pages/pages.dart +++ b/lib/presentation/pages/pages.dart @@ -1,9 +1,11 @@ export 'package:infinite_horizons/presentation/pages/activity_page.dart'; +export 'package:infinite_horizons/presentation/pages/convincing_page.dart'; export 'package:infinite_horizons/presentation/pages/energy_tips_page.dart'; export 'package:infinite_horizons/presentation/pages/home_page.dart'; export 'package:infinite_horizons/presentation/pages/intro_page.dart'; export 'package:infinite_horizons/presentation/pages/ready_for_session_page.dart'; export 'package:infinite_horizons/presentation/pages/settings_page.dart'; +export 'package:infinite_horizons/presentation/pages/splash_screen_page.dart'; export 'package:infinite_horizons/presentation/pages/tip_information_page.dart'; export 'package:infinite_horizons/presentation/pages/tip_resources_page.dart'; export 'package:infinite_horizons/presentation/pages/youtube_player_page.dart'; diff --git a/lib/presentation/pages/splash_screen_page.dart b/lib/presentation/pages/splash_screen_page.dart new file mode 100644 index 0000000..2a32246 --- /dev/null +++ b/lib/presentation/pages/splash_screen_page.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:infinite_horizons/domain/controllers/controllers.dart'; +import 'package:infinite_horizons/presentation/atoms/atoms.dart'; +import 'package:infinite_horizons/presentation/core/theme_data.dart'; +import 'package:infinite_horizons/presentation/pages/pages.dart'; +import 'package:infinite_horizons/presentation/pages/welcome_page.dart'; + +class SplashPage extends StatefulWidget { + @override + State createState() => _SplashPageState(); +} + +class _SplashPageState extends State { + @override + void initState() { + super.initState(); + initializeApp(); + } + + Future initializeApp() async { + await PreferencesController.instance.init(); + PlayerController.instance.init(); + await VibrationController.instance.init(); + DndController.instance.init(); + NotificationsController.instance.init(); + HealthController.instance.init(); + final int loginCounter = + PreferencesController.instance.getInt(PreferenceKeys.loginCounter) ?? 0; + PreferencesController.instance + .setInt(PreferenceKeys.loginCounter, loginCounter + 1); + + _navigate(); + } + + Future _navigate() async { + final bool finishedIntroduction = PreferencesController.instance + .getBool(PreferenceKeys.finishedIntroduction) ?? + false; + + Navigator.of(context).pop(); + + if (finishedIntroduction) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => HomePage(), + ), + ); + return; + } + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => WelcomePage(), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ColoredBox( + color: AppThemeData.logoBackgroundColor, + child: const Center( + child: ImageAtom( + 'assets/logo.png', + hero: 'full_logo', + ), + ), + ), + ); + } +} diff --git a/lib/presentation/pages/welcome_page.dart b/lib/presentation/pages/welcome_page.dart new file mode 100644 index 0000000..aab3e31 --- /dev/null +++ b/lib/presentation/pages/welcome_page.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:infinite_horizons/presentation/atoms/atoms.dart'; +import 'package:infinite_horizons/presentation/core/theme_data.dart'; +import 'package:infinite_horizons/presentation/molecules/molecules.dart'; +import 'package:infinite_horizons/presentation/pages/convincing_page.dart'; + +class WelcomePage extends StatefulWidget { + @override + State createState() => _WelcomePageState(); +} + +class _WelcomePageState extends State { + bool showSubTitle = false; + double buttonOpacity = 0; + + @override + void initState() { + super.initState(); + initialize(); + } + + Future initialize() async { + await Future.delayed(const Duration(seconds: 1)); + setState(() { + showSubTitle = true; + }); + } + + Future animatedTextFinish() async { + await Future.delayed(const Duration(seconds: 2)); + setState(() { + buttonOpacity = 1; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ColoredBox( + color: AppThemeData.logoBackgroundColor, + child: Column( + children: [ + const TopBarMolecule(topBarType: TopBarType.none, margin: false), + TextAtom( + AppThemeData.appName, + style: Theme.of(context) + .textTheme + .displayMedium! + .copyWith(color: Colors.white), + ), + const SeparatorAtom(variant: SeparatorVariant.relatedElements), + if (showSubTitle) + AnimatedTextAtom( + text: 'Improves productivity', + variant: AnimatedTextVariant.typewriter, + onDone: animatedTextFinish, + textColorWhite: true, + ) + else + const TextAtom('', variant: TextVariant.title), + const ImageAtom( + 'assets/logo.png', + hero: 'full_logo', + ), + const SeparatorAtom(), + const Expanded(child: Text('')), + SafeArea( + child: AnimatedOpacity( + opacity: buttonOpacity, + duration: const Duration(seconds: 2), + child: ButtonAtom( + variant: ButtonVariant.highEmphasisFilled, + onPressed: () { + Navigator.of(context).pop(); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ConvincingPage(), + ), + ); + }, + text: 'Next', + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index f8293bf..fbda8bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,10 @@ environment: dependencies: animated_line_through: ^1.0.4 + animated_text_kit: + git: + url: https://github.com/guyluz11/Animated-Text-Kit.git + ref: master animated_toggle_switch: ^0.8.2 audioplayers: ^6.0.0 awesome_notifications: ^0.9.3+1