diff --git a/packages/smooth_app/assets/onboarding/birthday-cake.svg b/packages/smooth_app/assets/onboarding/birthday-cake.svg
deleted file mode 100644
index a26a2c391ec..00000000000
--- a/packages/smooth_app/assets/onboarding/birthday-cake.svg
+++ /dev/null
@@ -1,32 +0,0 @@
-
diff --git a/packages/smooth_app/assets/onboarding/cloud.svg b/packages/smooth_app/assets/onboarding/cloud.svg
new file mode 100644
index 00000000000..8127531dc4e
--- /dev/null
+++ b/packages/smooth_app/assets/onboarding/cloud.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/packages/smooth_app/assets/onboarding/reinvention.svg b/packages/smooth_app/assets/onboarding/reinvention.svg
deleted file mode 100644
index 641f75117ca..00000000000
--- a/packages/smooth_app/assets/onboarding/reinvention.svg
+++ /dev/null
@@ -1,36 +0,0 @@
-
diff --git a/packages/smooth_app/lib/data_models/onboarding_loader.dart b/packages/smooth_app/lib/data_models/onboarding_loader.dart
index 9ba991956e8..2b8602e0850 100644
--- a/packages/smooth_app/lib/data_models/onboarding_loader.dart
+++ b/packages/smooth_app/lib/data_models/onboarding_loader.dart
@@ -44,7 +44,7 @@ class OnboardingLoader {
}
return;
case OnboardingPage.NOT_STARTED:
- case OnboardingPage.REINVENTION:
+ case OnboardingPage.HOME_PAGE:
case OnboardingPage.SCAN_EXAMPLE:
case OnboardingPage.HEALTH_CARD_EXAMPLE:
case OnboardingPage.ECO_CARD_EXAMPLE:
diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb
index 7c55331791d..7cc4f69934a 100644
--- a/packages/smooth_app/lib/l10n/app_en.arb
+++ b/packages/smooth_app/lib/l10n/app_en.arb
@@ -1092,14 +1092,12 @@
}
}
},
- "onboarding_reinventing_text1": "We invented\nthe collaborative\nscanning app in 2012",
- "@onboarding_reinventing_text1": {
- "description": "Onboarding / Reinventing page: text 1/2. If possible, balanced on 3 lines."
- },
- "onboarding_reinventing_text2": "As we turn 10,\nwe're reinventing it\nfrom the ground up!",
- "@onboarding_reinventing_text2": {
- "description": "Onboarding / Reinventing page: text 2/2. If possible, balanced on 3 lines."
+ "onboarding_home_welcome_text1": "Welcome !",
+ "onboarding_home_welcome_text2": "The app that helps you choose food that is good for **you** and the **planet**!",
+ "@onboarding_home_welcome_text2": {
+ "description": "Onboarding home screen welcome text, text surrounded by * will be bold"
},
+ "onboarding_continue_button": "Continue",
"onboarding_welcome_loading_dialog_title": "Loading your first example product",
"@onboarding_welcome_loading_dialog_title": {
"description": "Title for the onboarding loading dialog"
diff --git a/packages/smooth_app/lib/pages/onboarding/onboarding_flow_navigator.dart b/packages/smooth_app/lib/pages/onboarding/onboarding_flow_navigator.dart
index 9cd670c5ee9..5cccb33335e 100644
--- a/packages/smooth_app/lib/pages/onboarding/onboarding_flow_navigator.dart
+++ b/packages/smooth_app/lib/pages/onboarding/onboarding_flow_navigator.dart
@@ -18,7 +18,7 @@ import 'package:smooth_app/widgets/will_pop_scope.dart';
enum OnboardingPage {
NOT_STARTED,
- REINVENTION,
+ HOME_PAGE,
WELCOME,
SCAN_EXAMPLE,
HEALTH_CARD_EXAMPLE,
@@ -52,7 +52,7 @@ enum OnboardingPage {
Color getBackgroundColor() {
switch (this) {
case OnboardingPage.NOT_STARTED:
- case OnboardingPage.REINVENTION:
+ case OnboardingPage.HOME_PAGE:
return const Color(0xFFDFF4FF);
case OnboardingPage.WELCOME:
return const Color(0xFFFCFCFC);
@@ -79,8 +79,8 @@ enum OnboardingPage {
final Color backgroundColor = getBackgroundColor();
switch (this) {
case OnboardingPage.NOT_STARTED:
- case OnboardingPage.REINVENTION:
- return ReinventionPage(backgroundColor);
+ case OnboardingPage.HOME_PAGE:
+ return const OnboardingHomePage();
case OnboardingPage.WELCOME:
return WelcomePage(backgroundColor);
case OnboardingPage.SCAN_EXAMPLE:
diff --git a/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart b/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart
index a60ab501412..690c8833d61 100644
--- a/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart
+++ b/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart
@@ -1,171 +1,277 @@
-import 'dart:io';
-
-import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_svg/flutter_svg.dart';
+import 'package:provider/provider.dart';
import 'package:rive/rive.dart';
-import 'package:smooth_app/generic_lib/design_constants.dart';
-import 'package:smooth_app/generic_lib/duration_constants.dart';
-import 'package:smooth_app/helpers/app_helper.dart';
-import 'package:smooth_app/pages/onboarding/next_button.dart';
+import 'package:smooth_app/data_models/onboarding_loader.dart';
+import 'package:smooth_app/data_models/preferences/user_preferences.dart';
+import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/pages/onboarding/onboarding_flow_navigator.dart';
+import 'package:smooth_app/pages/onboarding/v2/onboarding_bottom_hills.dart';
+import 'package:smooth_app/themes/smooth_theme_colors.dart';
+import 'package:smooth_app/widgets/smooth_text.dart';
/// Onboarding page: "reinvention"
-class ReinventionPage extends StatelessWidget {
- const ReinventionPage(this.backgroundColor);
-
- final Color backgroundColor;
+class OnboardingHomePage extends StatelessWidget {
+ const OnboardingHomePage({super.key});
@override
Widget build(BuildContext context) {
- const double muchTooBigFontSize = 150;
- final AppLocalizations appLocalizations = AppLocalizations.of(context);
- final TextStyle headlineStyle = Theme.of(context)
- .textTheme
- .displayMedium!
- .copyWith(fontSize: muchTooBigFontSize);
- final Size screenSize = MediaQuery.sizeOf(context);
- final double animHeight = 352.0 * screenSize.width / 375.0;
-
- return ColoredBox(
- color: backgroundColor,
- child: SafeArea(
- bottom: false,
+ return Scaffold(
+ backgroundColor: const Color(0xFFE3F3FE),
+ body: Provider.value(
+ value: OnboardingConfig._(MediaQuery.of(context)),
child: Stack(
children: [
- Positioned(
- left: 0.0,
- right: 0.0,
- bottom: 0.0,
- top: screenSize.height * 0.75,
- child: _Background(
- screenWidth: screenSize.width,
- ),
- ),
- Positioned(
- left: 0.0,
- right: 0.0,
- bottom: 0.0,
- child: RepaintBoundary(
- child: SizedBox(
- width: screenSize.width,
- height: animHeight,
- child: const RiveAnimation.asset(
- 'assets/onboarding/onboarding.riv',
- artboard: 'Reinvention',
- animations: ['Loop'],
- alignment: Alignment.bottomCenter,
- ),
- ),
- ),
+ const _OnboardingWelcomePageContent(),
+ OnboardingBottomHills(
+ onTap: () async {
+ final UserPreferences userPreferences =
+ context.read();
+ final LocalDatabase localDatabase =
+ context.read();
+
+ await OnboardingLoader(localDatabase)
+ .runAtNextTime(OnboardingPage.HOME_PAGE, context);
+ if (context.mounted) {
+ await OnboardingFlowNavigator(userPreferences).navigateToPage(
+ context,
+ OnboardingPage.HOME_PAGE.getNextPage(),
+ );
+ }
+ },
),
- Positioned(
- top: 0.0,
- left: 0.0,
- right: 0.0,
- bottom: animHeight - 20.0,
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Flexible(
- flex: 30,
- child: Padding(
- padding: const EdgeInsets.all(SMALL_SPACE),
- child: Center(
- child: AutoSizeText(
- appLocalizations.onboarding_reinventing_text1,
- style: headlineStyle,
- maxLines: 3,
- textAlign: TextAlign.center,
- ),
- ),
- ),
- ),
- Flexible(
- flex: 15,
- child: SvgPicture.asset(
- 'assets/onboarding/birthday-cake.svg',
- package: AppHelper.APP_PACKAGE,
- ),
- ),
- Flexible(
- flex: 30,
- child: Padding(
- padding: const EdgeInsets.all(SMALL_SPACE),
- child: Center(
- child: AutoSizeText(
- appLocalizations.onboarding_reinventing_text2,
- style: headlineStyle,
- maxLines: 3,
- textAlign: TextAlign.center,
- ),
- ),
- ),
- ),
- Flexible(
- flex: 25,
- child: SvgPicture.asset(
- 'assets/onboarding/title.svg',
- package: AppHelper.APP_PACKAGE,
- ),
- ),
- ],
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class _OnboardingWelcomePageContent extends StatelessWidget {
+ const _OnboardingWelcomePageContent();
+
+ @override
+ Widget build(BuildContext context) {
+ final AppLocalizations appLocalizations = AppLocalizations.of(context);
+ final double fontMultiplier = OnboardingConfig.of(context).fontMultiplier;
+ final double hillsHeight = OnboardingBottomHills.height(context);
+
+ return Padding(
+ padding: EdgeInsetsDirectional.only(
+ top: hillsHeight * 0.5 + MediaQuery.viewPaddingOf(context).top,
+ bottom: hillsHeight,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Expanded(
+ flex: 15,
+ child: Text(
+ appLocalizations.onboarding_home_welcome_text1,
+ style: TextStyle(
+ fontSize: 45 * fontMultiplier,
+ fontWeight: FontWeight.bold,
),
+ textAlign: TextAlign.center,
),
- Positioned(
- bottom: 0,
- child: SafeArea(
- bottom: !Platform.isIOS,
- child: const NextButton(
- OnboardingPage.REINVENTION,
- backgroundColor: null,
- nextKey: Key('nextAfterReinvention'),
+ ),
+ const Expanded(
+ flex: 37,
+ child: _SunAndCloud(),
+ ),
+ Expanded(
+ flex: 45,
+ child: FractionallySizedBox(
+ widthFactor: 0.65,
+ child: Align(
+ alignment: const Alignment(0, -0.2),
+ child: OnboardingText(
+ text: appLocalizations.onboarding_home_welcome_text2,
),
),
),
- ],
- ),
+ ),
+ ],
),
);
}
}
-class _Background extends StatelessWidget {
- const _Background({required this.screenWidth});
+class _SunAndCloud extends StatefulWidget {
+ const _SunAndCloud();
+
+ @override
+ State<_SunAndCloud> createState() => _SunAndCloudState();
+}
+
+class _SunAndCloudState extends State<_SunAndCloud>
+ with SingleTickerProviderStateMixin {
+ late AnimationController _controller;
+ late Animation _animation;
- final double screenWidth;
+ @override
+ void initState() {
+ super.initState();
+ _controller =
+ AnimationController(vsync: this, duration: const Duration(seconds: 2))
+ ..addListener(() => setState(() {}));
+ _animation = Tween(
+ begin: -1.0,
+ end: 1.0,
+ ).animate(_controller);
+ _controller.repeat(reverse: true);
+ }
@override
Widget build(BuildContext context) {
- return SizedBox.expand(
- child: RepaintBoundary(
- child: Stack(
+ final TextDirection textDirection = Directionality.of(context);
+
+ return RepaintBoundary(
+ child: LayoutBuilder(builder: (
+ BuildContext context,
+ BoxConstraints constraints,
+ ) {
+ return Stack(
children: [
- AnimatedPositioned(
- bottom: 0.0,
- right: 0.0,
- width: screenWidth * 0.808,
- duration: SmoothAnimationsDuration.short,
- child: SvgPicture.asset(
- 'assets/onboarding/hill_end.svg',
- fit: BoxFit.fill,
- ),
+ Positioned.directional(
+ top: constraints.maxHeight * 0.3,
+ bottom: constraints.maxHeight * 0.2,
+ start: (_animation.value * 161.0) * 0.3,
+ textDirection: textDirection,
+ child: SvgPicture.asset('assets/onboarding/cloud.svg'),
),
- AnimatedPositioned(
- bottom: 0.0,
- left: 0.0,
- width: screenWidth * 0.855,
- duration: SmoothAnimationsDuration.short,
- child: SvgPicture.asset(
- 'assets/onboarding/hill_start.svg',
- fit: BoxFit.fill,
+ const Align(
+ alignment: Alignment.center,
+ child: RiveAnimation.asset(
+ 'assets/animations/off.riv',
+ artboard: 'Success',
+ animations: ['Timeline 1'],
),
- )
+ ),
+ Positioned.directional(
+ top: constraints.maxHeight * 0.22,
+ bottom: constraints.maxHeight * 0.35,
+ end: (_animation.value * 40.0) - 31,
+ textDirection: textDirection,
+ child: SvgPicture.asset('assets/onboarding/cloud.svg'),
+ ),
],
- ),
+ );
+ }),
+ );
+ }
+
+ @override
+ void dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+}
+
+class OnboardingText extends StatelessWidget {
+ const OnboardingText({
+ required this.text,
+ this.margin,
+ super.key,
+ });
+
+ final String text;
+ final EdgeInsetsGeometry? margin;
+
+ @override
+ Widget build(BuildContext context) {
+ double fontMultiplier;
+ try {
+ fontMultiplier = OnboardingConfig.of(context).fontMultiplier;
+ } catch (_) {
+ fontMultiplier =
+ OnboardingConfig.computeFontMultiplier(MediaQuery.of(context));
+ }
+
+ final Color backgroundColor =
+ Theme.of(context).extension()!.orange;
+
+ return RichText(
+ text: TextSpan(
+ children: _extractChunks().map(((String text, bool highlighted) el) {
+ if (el.$2) {
+ return _createSpan(
+ el.$1,
+ 30 * fontMultiplier,
+ backgroundColor,
+ );
+ } else {
+ return TextSpan(text: el.$1);
+ }
+ }).toList(growable: false),
+ style: DefaultTextStyle.of(context).style.copyWith(
+ fontSize: 30 * fontMultiplier,
+ height: 1.53,
+ fontWeight: FontWeight.w600,
+ ),
),
+ textAlign: TextAlign.center,
);
}
+
+ Iterable<(String, bool)> _extractChunks() {
+ final Iterable matches =
+ RegExp(r'\*\*(.*?)\*\*').allMatches(text);
+
+ if (matches.length <= 1) {
+ return <(String, bool)>[(text, false)];
+ }
+
+ final List<(String, bool)> chunks = <(String, bool)>[];
+
+ int lastMatch = 0;
+
+ for (final RegExpMatch match in matches) {
+ if (matches.first.start > 0) {
+ chunks.add((text.substring(lastMatch, match.start), false));
+ }
+
+ chunks.add((text.substring(match.start + 2, match.end - 2), true));
+ lastMatch = match.end;
+ }
+
+ if (lastMatch < text.length) {
+ chunks.add((text.substring(lastMatch), false));
+ }
+
+ return chunks;
+ }
+
+ WidgetSpan _createSpan(String text, double fontSize, Color backgroundColor) =>
+ HighlightedTextSpan(
+ text: text,
+ textStyle: TextStyle(
+ color: Colors.white,
+ fontSize: fontSize,
+ fontWeight: FontWeight.w700,
+ ),
+ padding: const EdgeInsetsDirectional.only(
+ top: 1.0,
+ bottom: 5.0,
+ start: 15.0,
+ end: 15.0,
+ ),
+ margin: margin ?? const EdgeInsetsDirectional.symmetric(vertical: 2.5),
+ backgroundColor: backgroundColor,
+ radius: 30.0,
+ );
+}
+
+// TODO(g123k): Move elsewhere when the onboarding will be redesigned
+class OnboardingConfig {
+ OnboardingConfig._(MediaQueryData mediaQuery)
+ : fontMultiplier = computeFontMultiplier(mediaQuery);
+ final double fontMultiplier;
+
+ static double computeFontMultiplier(MediaQueryData mediaQuery) =>
+ ((mediaQuery.size.width * 45) / 428) / 45;
+
+ static OnboardingConfig of(BuildContext context) =>
+ context.watch();
}
diff --git a/packages/smooth_app/lib/pages/onboarding/v2/onboarding_bottom_hills.dart b/packages/smooth_app/lib/pages/onboarding/v2/onboarding_bottom_hills.dart
new file mode 100644
index 00000000000..49b3b784299
--- /dev/null
+++ b/packages/smooth_app/lib/pages/onboarding/v2/onboarding_bottom_hills.dart
@@ -0,0 +1,114 @@
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:smooth_app/generic_lib/design_constants.dart';
+import 'package:smooth_app/resources/app_icons.dart' as icons;
+import 'package:smooth_app/themes/smooth_theme_colors.dart';
+
+class OnboardingBottomHills extends StatelessWidget {
+ const OnboardingBottomHills({
+ required this.onTap,
+ super.key,
+ });
+
+ final VoidCallback onTap;
+
+ static double height(BuildContext context) {
+ final double screenHeight = MediaQuery.sizeOf(context).height;
+ final double bottomPadding = MediaQuery.viewPaddingOf(context).bottom;
+ return screenHeight * (0.12 + (bottomPadding / screenHeight));
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final TextDirection textDirection = Directionality.of(context);
+ final double bottomPadding = MediaQuery.viewPaddingOf(context).bottom;
+ final double maxHeight = OnboardingBottomHills.height(context);
+ final SmoothColorsThemeExtension colors =
+ Theme.of(context).extension()!;
+
+ return Positioned(
+ top: null,
+ bottom: 0.0,
+ left: 0.0,
+ right: 0.0,
+ height: maxHeight,
+ child: SizedBox(
+ child: Stack(
+ children: [
+ Positioned.directional(
+ start: 0.0,
+ bottom: 0.0,
+ textDirection: textDirection,
+ child: SvgPicture.asset(
+ 'assets/onboarding/hill_start.svg',
+ height: maxHeight,
+ ),
+ ),
+ Positioned.directional(
+ end: 0.0,
+ bottom: 0.0,
+ textDirection: textDirection,
+ child: SvgPicture.asset(
+ 'assets/onboarding/hill_end.svg',
+ height: maxHeight * 0.965,
+ ),
+ ),
+ Positioned.directional(
+ textDirection: textDirection,
+ bottom: bottomPadding + (Platform.isIOS ? 0.0 : 15.0),
+ end: 15.0,
+ child: TextButton(
+ style: ButtonStyle(
+ backgroundColor: MaterialStateProperty.all(
+ Colors.white,
+ ),
+ padding: MaterialStateProperty.all(
+ const EdgeInsetsDirectional.only(
+ start: LARGE_SPACE + 1.0,
+ end: LARGE_SPACE,
+ top: SMALL_SPACE,
+ bottom: SMALL_SPACE,
+ ),
+ ),
+ elevation: MaterialStateProperty.all(4.0),
+ iconColor: MaterialStateProperty.all(
+ colors.orange,
+ ),
+ foregroundColor: MaterialStateProperty.all(
+ colors.orange,
+ ),
+ iconSize: MaterialStateProperty.all(21.0),
+ shape: MaterialStateProperty.all(
+ RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(20.0),
+ ),
+ ),
+ shadowColor: MaterialStateProperty.all(
+ Colors.black.withOpacity(0.50),
+ ),
+ ),
+ onPressed: onTap,
+ child: Row(
+ children: [
+ Text(
+ AppLocalizations.of(context).onboarding_continue_button,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 22.0,
+ ),
+ ),
+ const SizedBox(width: LARGE_SPACE),
+ const icons.Arrow.right(),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/packages/smooth_app/lib/widgets/smooth_text.dart b/packages/smooth_app/lib/widgets/smooth_text.dart
index c8a3b9c5ef6..bfa2f17066c 100644
--- a/packages/smooth_app/lib/widgets/smooth_text.dart
+++ b/packages/smooth_app/lib/widgets/smooth_text.dart
@@ -190,3 +190,31 @@ class TextHighlighter extends StatelessWidget {
return endPosition + diff;
}
}
+
+class HighlightedTextSpan extends WidgetSpan {
+ HighlightedTextSpan({
+ required String text,
+ required TextStyle textStyle,
+ required EdgeInsetsGeometry padding,
+ required Color backgroundColor,
+ required double radius,
+ EdgeInsetsGeometry? margin,
+ }) : assert(radius > 0.0),
+ super(
+ alignment: PlaceholderAlignment.middle,
+ child: Container(
+ decoration: BoxDecoration(
+ color: backgroundColor,
+ borderRadius: BorderRadius.all(
+ Radius.circular(radius),
+ ),
+ ),
+ margin: margin,
+ padding: padding,
+ child: Text(
+ text,
+ style: textStyle,
+ ),
+ ),
+ );
+}