diff --git a/assets/images/home.svg b/assets/images/home.svg
new file mode 100644
index 0000000..fc25f0b
--- /dev/null
+++ b/assets/images/home.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/stats.svg b/assets/images/stats.svg
new file mode 100644
index 0000000..6191e14
--- /dev/null
+++ b/assets/images/stats.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/images/success.svg b/assets/images/success.svg
new file mode 100644
index 0000000..07c6cc4
--- /dev/null
+++ b/assets/images/success.svg
@@ -0,0 +1,11 @@
+
diff --git a/assets/sounds/point.mp3 b/assets/sounds/point.mp3
new file mode 100644
index 0000000..d130cce
Binary files /dev/null and b/assets/sounds/point.mp3 differ
diff --git a/assets/sounds/wind.mp3 b/assets/sounds/wind.mp3
new file mode 100644
index 0000000..e20ee10
Binary files /dev/null and b/assets/sounds/wind.mp3 differ
diff --git a/coverage/lcov.info b/coverage/lcov.info
new file mode 100644
index 0000000..9959c24
--- /dev/null
+++ b/coverage/lcov.info
@@ -0,0 +1,365 @@
+SF:lib/utils/animation/scale_on_press_widget.dart
+DA:7,3
+DA:14,3
+DA:22,4
+DA:23,4
+DA:29,0
+DA:31,2
+DA:33,1
+DA:34,2
+DA:35,1
+DA:39,4
+DA:41,4
+DA:43,2
+DA:44,0
+DA:45,2
+DA:46,0
+DA:47,4
+DA:48,4
+DA:49,4
+DA:50,8
+DA:51,4
+DA:52,8
+DA:53,4
+DA:54,8
+DA:55,6
+DA:57,4
+DA:58,8
+DA:60,8
+DA:61,4
+DA:62,4
+DA:63,4
+DA:65,4
+DA:66,4
+DA:67,4
+DA:70,4
+DA:72,8
+LF:35
+LH:32
+end_of_record
+SF:lib/utils/animation/animated_scale.dart
+DA:5,4
+DA:13,8
+DA:14,4
+DA:22,4
+DA:23,4
+DA:30,4
+DA:32,20
+DA:33,8
+DA:37,4
+DA:39,16
+DA:42,4
+DA:43,4
+DA:44,4
+DA:45,8
+DA:46,8
+LF:15
+LH:15
+end_of_record
+SF:lib/common/resources/r.dart
+DA:2,9
+DA:3,9
+DA:4,6
+DA:5,9
+DA:6,3
+DA:7,6
+LF:6
+LH:6
+end_of_record
+SF:lib/features/home/widgets/home_app_bar.dart
+DA:9,0
+DA:11,1
+DA:13,1
+DA:15,1
+DA:16,1
+DA:23,1
+DA:24,2
+DA:25,1
+DA:27,3
+DA:34,0
+DA:39,1
+DA:41,1
+DA:42,1
+DA:43,2
+DA:44,0
+DA:45,1
+DA:46,2
+DA:47,1
+DA:48,1
+DA:49,1
+LF:20
+LH:17
+end_of_record
+SF:lib/features/home/widgets/home_bottom_nav_bar.dart
+DA:7,0
+DA:9,1
+DA:10,1
+DA:17,0
+DA:18,0
+DA:19,0
+DA:23,0
+DA:24,0
+DA:25,0
+DA:29,0
+DA:31,0
+DA:33,0
+DA:35,0
+DA:37,0
+DA:39,1
+DA:41,1
+DA:49,1
+DA:54,1
+DA:62,1
+DA:64,1
+DA:65,1
+DA:66,1
+DA:67,1
+DA:68,1
+DA:69,1
+DA:70,1
+DA:72,2
+DA:73,1
+DA:76,1
+DA:77,1
+DA:78,1
+DA:79,1
+DA:80,1
+DA:81,1
+DA:83,2
+DA:84,1
+LF:36
+LH:24
+end_of_record
+SF:lib/features/home/widgets/home_fab.dart
+DA:8,0
+DA:10,1
+DA:12,1
+DA:15,0
+DA:16,0
+DA:18,2
+DA:20,1
+DA:22,1
+DA:23,2
+DA:25,1
+DA:28,1
+LF:11
+LH:8
+end_of_record
+SF:lib/features/home/widgets/home_screen.dart
+DA:10,0
+DA:12,1
+DA:14,1
+DA:16,1
+LF:4
+LH:3
+end_of_record
+SF:lib/features/home/widgets/animated_circle_progress.dart
+DA:9,0
+DA:14,0
+DA:15,0
+DA:21,1
+DA:23,1
+DA:32,1
+DA:34,1
+DA:35,5
+DA:36,2
+DA:38,2
+DA:39,3
+DA:40,2
+DA:41,4
+DA:42,2
+DA:43,2
+DA:45,2
+DA:48,1
+DA:50,1
+DA:52,5
+DA:53,5
+DA:57,1
+DA:59,2
+DA:60,2
+DA:61,1
+DA:66,1
+DA:67,9
+DA:77,1
+DA:79,2
+DA:80,1
+DA:81,1
+DA:82,1
+DA:83,2
+DA:85,1
+DA:86,1
+DA:87,2
+DA:88,1
+DA:89,1
+DA:90,5
+DA:91,1
+DA:92,1
+DA:94,1
+DA:95,1
+DA:96,5
+DA:97,1
+DA:98,1
+DA:99,1
+DA:100,1
+DA:102,1
+DA:105,0
+LF:49
+LH:45
+end_of_record
+SF:lib/utils/extensions/gradient_extensions.dart
+DA:4,3
+DA:5,6
+DA:8,1
+DA:9,2
+DA:12,4
+DA:13,4
+DA:15,4
+DA:16,4
+DA:22,2
+DA:25,2
+DA:26,2
+DA:27,2
+DA:35,12
+DA:36,6
+DA:38,4
+DA:39,4
+LF:16
+LH:16
+end_of_record
+SF:lib/features/home/widgets/animated_nav_button.dart
+DA:7,1
+DA:14,1
+DA:21,1
+DA:23,1
+DA:25,1
+DA:26,1
+DA:27,1
+DA:28,0
+DA:29,0
+DA:30,0
+DA:31,1
+DA:37,1
+DA:42,1
+DA:48,1
+DA:50,1
+DA:52,1
+DA:54,3
+DA:55,1
+DA:56,1
+DA:58,4
+DA:64,1
+DA:65,2
+DA:66,1
+DA:67,1
+DA:70,1
+DA:71,2
+DA:72,0
+DA:73,2
+DA:74,2
+DA:75,2
+DA:80,0
+DA:85,0
+DA:90,0
+DA:91,0
+DA:95,0
+DA:96,0
+DA:97,0
+LF:37
+LH:26
+end_of_record
+SF:lib/features/home/widgets/home_body.dart
+DA:5,0
+DA:7,1
+DA:9,1
+DA:10,3
+LF:4
+LH:3
+end_of_record
+SF:lib/features/splash/splash_screen.dart
+DA:12,2
+DA:14,2
+DA:15,2
+DA:21,2
+DA:23,2
+DA:24,10
+DA:31,2
+DA:38,2
+DA:40,2
+DA:41,4
+DA:46,2
+DA:48,2
+DA:50,2
+DA:53,2
+DA:55,6
+DA:57,2
+DA:58,4
+DA:59,2
+DA:60,2
+DA:70,1
+DA:72,1
+DA:74,2
+DA:76,2
+DA:78,0
+DA:79,0
+DA:84,2
+DA:86,2
+DA:87,6
+DA:89,2
+DA:90,4
+DA:91,2
+LF:31
+LH:29
+end_of_record
+SF:lib/utils/extensions/navigation_extension.dart
+DA:5,1
+DA:10,2
+DA:11,1
+DA:14,0
+DA:16,2
+DA:20,1
+DA:21,1
+DA:22,1
+DA:23,1
+DA:29,3
+DA:31,2
+LF:11
+LH:10
+end_of_record
+SF:lib/main.dart
+DA:5,2
+DA:8,2
+DA:11,1
+DA:13,1
+DA:14,1
+DA:17,1
+LF:6
+LH:6
+end_of_record
+SF:lib/playground/test_main.dart
+DA:7,1
+DA:9,2
+DA:14,1
+DA:16,1
+DA:17,1
+DA:18,1
+DA:19,1
+DA:20,1
+DA:22,1
+DA:23,2
+DA:27,1
+DA:29,1
+DA:30,2
+DA:42,1
+DA:44,1
+DA:45,1
+DA:46,1
+DA:47,1
+DA:48,1
+DA:50,1
+DA:51,2
+DA:57,1
+DA:59,1
+DA:60,2
+LF:24
+LH:24
+end_of_record
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index ca61192..b8994f1 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -263,13 +263,21 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
"${PODS_ROOT}/../Flutter/Flutter.framework",
+ "${BUILT_PRODUCTS_DIR}/assets_audio_player/assets_audio_player.framework",
+ "${BUILT_PRODUCTS_DIR}/assets_audio_player_web/assets_audio_player_web.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
+ "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/assets_audio_player.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/assets_audio_player_web.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
diff --git a/lib/common/resources/r.dart b/lib/common/resources/r.dart
new file mode 100644
index 0000000..c488906
--- /dev/null
+++ b/lib/common/resources/r.dart
@@ -0,0 +1,40 @@
+class R {
+ static Strings string = Strings();
+ static SVGImages svg = SVGImages();
+ static LottieFiles lottie = LottieFiles();
+ static AudioFiles audio = AudioFiles();
+ static Placeholders placeholders = Placeholders();
+ static BuildMode buildMode = BuildMode();
+}
+
+class Placeholders {
+ String johDoeImage =
+ "https://pbs.twimg.com/profile_images/1057989852942270464/bt45DHmR.jpg";
+}
+
+class Strings {
+ String appName = "Productive";
+ String loginWithGoogle = "Continue With Google";
+ String todo = "Todo";
+ String addTodo = "+ Add Todo";
+}
+
+class SVGImages {
+ String homeIcon = "assets/images/home.svg";
+ String statsIcon = "assets/images/stats.svg";
+ String googleIcon = "assets/images/google.svg";
+ String successIcon = "assets/images/success.svg";
+}
+
+class LottieFiles {
+ String paperPlane = "assets/lottie/paper_plane.json";
+}
+
+class AudioFiles {
+ String splashBreeze = "assets/sounds/wind.mp3";
+ String welcomeTone = "assets/sounds/point.mp3";
+}
+
+class BuildMode{
+ bool isTesting = false;
+}
diff --git a/lib/features/home/home_screen.dart b/lib/features/home/home_screen.dart
deleted file mode 100644
index d0d708b..0000000
--- a/lib/features/home/home_screen.dart
+++ /dev/null
@@ -1,20 +0,0 @@
-import 'package:flutter/material.dart';
-
-class HomeScreen extends StatelessWidget {
- const HomeScreen();
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: Center(
- child: Text(Name(abc: "Laxman").abc),
- ),
- );
- }
-}
-
-class Name {
- Name({this.abc = "Hello World"});
-
- String abc;
-}
diff --git a/lib/features/home/widgets/animated_circle_progress.dart b/lib/features/home/widgets/animated_circle_progress.dart
new file mode 100644
index 0000000..cf68d12
--- /dev/null
+++ b/lib/features/home/widgets/animated_circle_progress.dart
@@ -0,0 +1,107 @@
+import 'dart:math';
+
+import 'package:assets_audio_player/assets_audio_player.dart';
+import 'package:flutter/material.dart';
+import 'package:productive/common/resources/r.dart';
+import 'package:productive/utils/extensions/gradient_extensions.dart';
+
+class AnimatedMotivationMeter extends StatefulWidget {
+ const AnimatedMotivationMeter({
+ this.motivation,
+ this.height,
+ this.width,
+ Key key,
+ }) : assert(motivation <= 1.0 && motivation >= 0),
+ super(key: key);
+
+ final double motivation;
+ final double height;
+ final double width;
+
+ @override
+ _AnimatedMotivationMeterState createState() =>
+ _AnimatedMotivationMeterState();
+}
+
+class _AnimatedMotivationMeterState extends State
+ with SingleTickerProviderStateMixin {
+ Animation animation;
+ AnimationController controller;
+ final _audioPlayer = AssetsAudioPlayer();
+
+ @override
+ void initState() {
+ super.initState();
+ _audioPlayer.open(Audio(R.audio.welcomeTone), volume: 0.5);
+ controller = AnimationController(
+ vsync: this, duration: const Duration(milliseconds: 2000));
+ animation = Tween(
+ begin: widget.motivation * 0.5,
+ end: widget.motivation,
+ ).chain(CurveTween(curve: Curves.decelerate)).animate(controller)
+ ..addListener(() {
+ setState(() {});
+ });
+ controller.forward();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return CustomPaint(
+ willChange: true,
+ painter: CircleProgressPainter(widget.motivation, animation.value),
+ size: Size(widget.width, widget.height),
+ );
+ }
+
+ @override
+ void dispose() {
+ _audioPlayer.dispose();
+ controller.dispose();
+ super.dispose();
+ }
+}
+
+class CircleProgressPainter extends CustomPainter {
+ CircleProgressPainter(this.maxMotivation, this.animatingMotivation) {
+ endAngle = pi * 2 * min(animatingMotivation, 0.95);
+ }
+
+ final double startAngle = -pi / 2;
+ final double gradientRotationAngle = -pi / 2 - 0.14;
+ double endAngle;
+ final double maxMotivation;
+ final double animatingMotivation;
+ Paint _ink;
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ _ink ??= Paint()
+ ..style = PaintingStyle.stroke
+ ..strokeWidth = 6
+ ..strokeCap = StrokeCap.round
+ ..shader = SweepGradient(
+ colors: blueGradientColors,
+ startAngle: startAngle,
+ endAngle: endAngle,
+ transform: GradientRotation(gradientRotationAngle))
+ .createShader(
+ Rect.fromCenter(
+ center: Offset(size.height / 2, size.width / 2),
+ width: size.width,
+ height: size.height),
+ );
+ canvas.drawArc(
+ Rect.fromCenter(
+ center: Offset(size.height / 2, size.width / 2),
+ width: size.width,
+ height: size.height),
+ startAngle,
+ endAngle,
+ false,
+ _ink);
+ }
+
+ @override
+ bool shouldRepaint(CustomPainter oldDelegate) => true;
+}
diff --git a/lib/features/home/widgets/animated_nav_button.dart b/lib/features/home/widgets/animated_nav_button.dart
new file mode 100644
index 0000000..9d6e871
--- /dev/null
+++ b/lib/features/home/widgets/animated_nav_button.dart
@@ -0,0 +1,100 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:productive/utils/animation/animated_scale.dart';
+import 'package:productive/utils/extensions/gradient_extensions.dart';
+
+class AnimatedNavIcon extends StatelessWidget {
+ const AnimatedNavIcon({
+ Key key,
+ @required this.navState,
+ @required this.select,
+ @required this.touch,
+ @required this.unTouch,
+ @required this.child,
+ }) : super(key: key);
+ final NavButtonsState navState;
+ final Function() select;
+ final Function() touch;
+ final Function() unTouch;
+ final Widget child;
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: select,
+ onLongPress: touch,
+ onLongPressUp: unTouch,
+ onTapUp: (_) => unTouch,
+ onLongPressStart: (_) => touch,
+ onLongPressEnd: (_) => unTouch,
+ child: child,
+ );
+ }
+}
+
+class NavIcon extends StatelessWidget {
+ const NavIcon(
+ {Key key,
+ @required this.navState,
+ @required this.svgPath,
+ @required this.position})
+ : super(key: key);
+
+ final NavButtonsState navState;
+ final String svgPath;
+ final int position;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(32.0),
+ child: AnimatedScale(
+ duration: const Duration(milliseconds: 200),
+ scale: _getScale(position, navState),
+ child: SvgPicture.asset(
+ svgPath,
+ height: 32,
+ ).getShadedWidget(_getGradient(position, navState)),
+ ),
+ );
+ }
+}
+
+LinearGradient _getGradient(int buttonIndex, NavButtonsState navState) {
+ return buttonIndex == navState.selectedIndex
+ ? blueLinearGradient
+ : greyLinearGradient;
+}
+
+double _getScale(int buttonIndex, NavButtonsState navState) {
+ if (buttonIndex == navState.pressedIndex &&
+ buttonIndex == navState.selectedIndex) return 0.8;
+ if (buttonIndex == navState.pressedIndex) return 0.6;
+ if (buttonIndex != navState.pressedIndex &&
+ buttonIndex != navState.selectedIndex) return 0.8;
+ return 1.0;
+}
+
+class NavButtonsState {
+ const NavButtonsState({this.selectedIndex, this.pressedIndex});
+
+ final int selectedIndex;
+ final int pressedIndex;
+
+ NavButtonsState copyWith({
+ int selectedIndex,
+ int pressedIndex,
+ }) {
+ if ((selectedIndex == null ||
+ identical(selectedIndex, this.selectedIndex)) &&
+ (pressedIndex == null || identical(pressedIndex, this.pressedIndex))) {
+ return this;
+ }
+
+ return NavButtonsState(
+ selectedIndex: selectedIndex ?? this.selectedIndex,
+ pressedIndex: pressedIndex ?? this.pressedIndex,
+ );
+ }
+}
diff --git a/lib/features/home/widgets/home_app_bar.dart b/lib/features/home/widgets/home_app_bar.dart
new file mode 100644
index 0000000..0a79d4c
--- /dev/null
+++ b/lib/features/home/widgets/home_app_bar.dart
@@ -0,0 +1,53 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:productive/common/resources/r.dart';
+
+import 'animated_circle_progress.dart';
+
+class AppbarContent extends StatelessWidget {
+ const AppbarContent({Key key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Stack(
+ alignment: Alignment.center,
+ children: const [
+ AnimatedMotivationMeter(motivation: 0.6, height: 42, width: 42),
+ DisplayPic(height: 36, width: 36),
+ ],
+ ),
+ Text(
+ R.string.todo,
+ style: TextStyle(fontSize: 28, fontWeight: FontWeight.w600),
+ ),
+ SvgPicture.asset(R.svg.successIcon, height: 28, width: 28)
+ ],
+ );
+ }
+}
+
+class DisplayPic extends StatelessWidget {
+ const DisplayPic({this.height, this.width, Key key}) : super(key: key);
+
+ final double height;
+ final double width;
+
+ @override
+ Widget build(BuildContext context) {
+ return ClipRRect(
+ borderRadius: BorderRadius.circular(200),
+ child: R.buildMode.isTesting
+ ? Container()
+ : CachedNetworkImage(
+ imageUrl: R.placeholders.johDoeImage,
+ placeholder: (context, url) => const CircularProgressIndicator(),
+ height: height,
+ width: width,
+ ),
+ );
+ }
+}
diff --git a/lib/features/home/widgets/home_body.dart b/lib/features/home/widgets/home_body.dart
new file mode 100644
index 0000000..42a6eb3
--- /dev/null
+++ b/lib/features/home/widgets/home_body.dart
@@ -0,0 +1,13 @@
+import 'package:flutter/material.dart';
+import 'package:productive/common/resources/r.dart';
+
+class HomeBody extends StatelessWidget {
+ const HomeBody({Key key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Center(
+ child: Text(R.string.appName),
+ );
+ }
+}
diff --git a/lib/features/home/widgets/home_bottom_nav_bar.dart b/lib/features/home/widgets/home_bottom_nav_bar.dart
new file mode 100644
index 0000000..3d92289
--- /dev/null
+++ b/lib/features/home/widgets/home_bottom_nav_bar.dart
@@ -0,0 +1,91 @@
+import 'package:flutter/material.dart';
+import 'package:productive/common/resources/r.dart';
+
+import 'animated_nav_button.dart';
+
+class BottomNav extends StatefulWidget {
+ const BottomNav({Key key}) : super(key: key);
+
+ @override
+ _BottomNavState createState() => _BottomNavState();
+}
+
+class _BottomNavState extends State {
+ NavButtonsState navState =
+ const NavButtonsState(selectedIndex: 0, pressedIndex: -1);
+
+ void _selectPage(int index) {
+ setState(() {
+ navState = navState.copyWith(selectedIndex: index);
+ });
+ }
+
+ void _touchIcon(int index) {
+ setState(() {
+ navState = navState.copyWith(pressedIndex: index);
+ });
+ }
+
+ void _touchHomeIcon() => _touchIcon(0);
+
+ void _touchStatsIcon() => _touchIcon(1);
+
+ void _unTouch() => _touchIcon(-1);
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: const BoxDecoration(boxShadow: [
+ BoxShadow(
+ color: Color(0x33CCCCCC),
+ offset: Offset(-4, -4),
+ blurRadius: 8,
+ spreadRadius: 8)
+ ]),
+ child: ClipRRect(
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(32),
+ topRight: Radius.circular(32),
+ ),
+ child: BottomAppBar(
+ elevation: 20,
+ shape: const AutomaticNotchedShape(
+ RoundedRectangleBorder(),
+ StadiumBorder(
+ side: BorderSide(),
+ ),
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ AnimatedNavIcon(
+ key: const Key("first_icon"),
+ navState: navState,
+ select: () => _selectPage(0),
+ unTouch: _unTouch,
+ touch: _touchHomeIcon,
+ child: NavIcon(
+ position: 0,
+ svgPath: R.svg.homeIcon,
+ navState: navState,
+ ),
+ ),
+ AnimatedNavIcon(
+ key: const Key("second_icon"),
+ navState: navState,
+ select: () => _selectPage(1),
+ unTouch: _unTouch,
+ touch: _touchStatsIcon,
+ child: NavIcon(
+ position: 1,
+ svgPath: R.svg.statsIcon,
+ navState: navState,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/features/home/widgets/home_fab.dart b/lib/features/home/widgets/home_fab.dart
new file mode 100644
index 0000000..3f917fa
--- /dev/null
+++ b/lib/features/home/widgets/home_fab.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/material.dart';
+import 'package:productive/common/resources/r.dart';
+import 'package:productive/features/splash/splash_screen.dart';
+import 'package:productive/utils/extensions/gradient_extensions.dart';
+import 'package:productive/utils/extensions/navigation_extension.dart';
+
+class Fab extends StatelessWidget {
+ const Fab({Key key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return RaisedButton(
+ key: const Key("home_fab"),
+ color: Colors.transparent,
+ splashColor: Colors.transparent,
+ onPressed: () {
+ context.navigateTo(const SplashScreen());
+ },
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(80.0)),
+ padding: const EdgeInsets.all(0.0),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12),
+ child: Text(
+ R.string.addTodo,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 18, color: Colors.white, fontWeight: FontWeight.w600),
+ ),
+ ).withBlueGradientBg(),
+ );
+ }
+}
diff --git a/lib/features/home/widgets/home_screen.dart b/lib/features/home/widgets/home_screen.dart
new file mode 100644
index 0000000..fa94f94
--- /dev/null
+++ b/lib/features/home/widgets/home_screen.dart
@@ -0,0 +1,27 @@
+import 'package:flutter/material.dart';
+import 'package:productive/features/home/widgets/home_body.dart';
+import 'package:productive/utils/animation/scale_on_press_widget.dart';
+
+import 'home_app_bar.dart';
+import 'home_bottom_nav_bar.dart';
+import 'home_fab.dart';
+
+class HomeScreen extends StatelessWidget {
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: const HomeBody(),
+ appBar: AppBar(
+ title: const AppbarContent(), automaticallyImplyLeading: false),
+ floatingActionButton: const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 2, vertical: 2),
+ child: ScaleOnPressWidget(
+ child: Fab(),
+ ),
+ ),
+ floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
+ bottomNavigationBar: const BottomNav(),
+ );
+ }
+}
diff --git a/lib/features/splash/spalash_screen.dart b/lib/features/splash/spalash_screen.dart
deleted file mode 100644
index 09b64d8..0000000
--- a/lib/features/splash/spalash_screen.dart
+++ /dev/null
@@ -1,122 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_svg/flutter_svg.dart';
-import 'package:lottie/lottie.dart';
-import 'package:dartx/dartx.dart';
-import 'package:productive/features/home/home_screen.dart';
-import 'package:productive/utils/extensions/navigation_extension.dart';
-
-class SplashScreen extends StatefulWidget {
- const SplashScreen();
-
- @override
- _SplashScreenState createState() => _SplashScreenState();
-}
-
-class _SplashScreenState extends State {
- @override
- void initState() {
- super.initState();
- context.navigateTo(const HomeScreen(), delay: 2.seconds);
- }
-
- @override
- Widget build(BuildContext context) {
- return const Scaffold(
- body: SplashScreenBody(),
- );
- }
-}
-
-class SplashScreenBody extends StatelessWidget {
- const SplashScreenBody();
-
- @override
- Widget build(BuildContext context) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- const Spacer(
- flex: 3,
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Lottie.asset(
- 'assets/lottie/paper_plane.json',
- height: 300,
- width: 300,
- ),
- ],
- ),
- const Spacer(
- flex: 3,
- ),
- ShaderMask(
- blendMode: BlendMode.srcIn,
- shaderCallback: (Rect bound) {
- return const LinearGradient(
- colors: [
- Color(0xFF01A4FF),
- Color(0xFF0186FF),
- ],
- begin: Alignment(-1.0, -8.0),
- end: Alignment(1.0, 4.0),
- ).createShader(bound);
- },
- child: Text(
- "Productive",
- style: TextStyle(
- fontWeight: FontWeight.w700,
- fontSize: 64,
- color: Colors.blue,
- ),
- ),
- ),
- const Spacer(
- flex: 5,
- ),
- InkWell(
- onTap: () {},
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 48.0),
- child: Container(
- padding: const EdgeInsets.only(
- top: 12, bottom: 8, left: 12, right: 12),
- decoration: BoxDecoration(
- boxShadow: const [
- BoxShadow(
- color: Color(0x33CCCCCC),
- blurRadius: 4,
- offset: Offset(4, 4),
- spreadRadius: 4)
- ],
- color: Colors.white,
- borderRadius: BorderRadius.circular(200),
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SvgPicture.asset("assets/images/google.svg"),
- const SizedBox(
- width: 16,
- ),
- Text(
- "Continue with google",
- style: TextStyle(
- fontSize: 17,
- fontWeight: FontWeight.w700,
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- const Spacer(
- flex: 2,
- )
- ],
- );
- }
-}
diff --git a/lib/features/splash/splash_screen.dart b/lib/features/splash/splash_screen.dart
new file mode 100644
index 0000000..411f580
--- /dev/null
+++ b/lib/features/splash/splash_screen.dart
@@ -0,0 +1,92 @@
+import 'package:assets_audio_player/assets_audio_player.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:lottie/lottie.dart';
+import 'package:productive/common/resources/r.dart';
+import 'package:productive/features/home/widgets/home_screen.dart';
+import 'package:productive/utils/animation/scale_on_press_widget.dart';
+import 'package:productive/utils/extensions/gradient_extensions.dart';
+import 'package:productive/utils/extensions/navigation_extension.dart';
+
+class SplashScreen extends StatefulWidget {
+ const SplashScreen({Key key}) : super(key: key);
+
+ @override
+ _SplashScreenState createState() => _SplashScreenState();
+}
+
+class _SplashScreenState extends State {
+ final _audioPlayer = AssetsAudioPlayer();
+
+ @override
+ void initState() {
+ super.initState();
+ _audioPlayer.open(Audio(R.audio.splashBreeze),
+ loopMode: LoopMode.single,
+ playInBackground: PlayInBackground.disabledRestoreOnForeground,
+ seek: const Duration(seconds: 2),
+ volume: 0.5);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return const Scaffold(
+ body: SplashScreenBody(),
+ );
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ _audioPlayer.dispose();
+ }
+}
+
+class SplashScreenBody extends StatelessWidget {
+ const SplashScreenBody({Key key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ const Spacer(flex: 3),
+ Lottie.asset(R.lottie.paperPlane, height: 300, width: 300),
+ const Spacer(flex: 3),
+ Text(
+ R.string.appName,
+ style: TextStyle(fontWeight: FontWeight.w700, fontSize: 64),
+ ).withBlueGradientFg(),
+ const Spacer(flex: 5),
+ GoogleSignInButton(),
+ const Spacer(flex: 2)
+ ],
+ );
+ }
+}
+
+class GoogleSignInButton extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return ScaleOnPressWidget(
+ key: const Key("login"),
+ onTap: () {
+ context.navigateTo(HomeScreen(), replace: true);
+ },
+ outerPadding: const EdgeInsets.symmetric(horizontal: 48.0),
+ innerPadding:
+ const EdgeInsets.only(top: 12, bottom: 8, left: 12, right: 12),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset(R.svg.googleIcon),
+ const SizedBox(width: 16),
+ Text(
+ R.string.loginWithGoogle,
+ style: TextStyle(fontSize: 17, fontWeight: FontWeight.w700),
+ ),
+ ],
+ ));
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index 598676a..5d6c1c2 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,18 +1,20 @@
import 'package:flutter/material.dart';
-import 'package:productive/features/splash/spalash_screen.dart';
-void main() {
- runApp(ProductiveApp());
-}
+import 'package:productive/features/splash/splash_screen.dart';
+import 'package:productive/utils/design/colors.dart';
+
+void main() => runApp(const ProductiveApp());
class ProductiveApp extends StatelessWidget {
+ const ProductiveApp({Key key, this.testChild}) : super(key: key);
+ final Widget testChild;
+
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
- fontFamily: 'Product Sans',
- ),
+ fontFamily: 'Product Sans', primarySwatch: whiteMaterialColor),
debugShowCheckedModeBanner: false,
- home: const SplashScreen(),
+ home: testChild ?? const SplashScreen(),
);
}
}
diff --git a/lib/playground/test_main.dart b/lib/playground/test_main.dart
new file mode 100644
index 0000000..a28d0cd
--- /dev/null
+++ b/lib/playground/test_main.dart
@@ -0,0 +1,69 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+import '../utils/extensions/navigation_extension.dart';
+
+class DummyApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(home: Screen1(),);
+ }
+}
+
+class Screen1 extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: Column(
+ children: [
+ MaterialButton(
+ key: const Key("go_screen_2"),
+ onPressed: () {
+ context.navigateTo(Screen2());
+ },
+ child: const Text("Go to screen2"),
+ ),
+ GestureDetector(
+ key: const Key("gd1"),
+ onTap: () {
+ context.navigateTo(Screen2());
+ },
+ child: const Text("Jump to screen2"),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class Screen2 extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: Column(
+ children: [
+ MaterialButton(
+ key: const Key("go_screen_1"),
+ onPressed: () {
+ context.navigateTo(Screen1());
+// Navigator.of(context)
+// .push(MaterialPageRoute(builder: (context) => Screen1()));
+ },
+ child: const Text("Go to screen1"),
+ ),
+ GestureDetector(
+ key: const Key("gd2"),
+ onTap: () {
+ context.navigateTo(Screen1());
+ },
+ child: const Text("Jump to screen1"),
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/utils/animation/animated_scale.dart b/lib/utils/animation/animated_scale.dart
new file mode 100644
index 0000000..d30bc3e
--- /dev/null
+++ b/lib/utils/animation/animated_scale.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+
+class AnimatedScale extends ImplicitlyAnimatedWidget {
+ const AnimatedScale({
+ Key key,
+ this.child,
+ @required this.scale,
+ Curve curve = Curves.linear,
+ @required Duration duration,
+ VoidCallback onEnd,
+ this.alignment = Alignment.center,
+ }) : assert(scale != null && scale >= 0.0 && scale <= 1.0),
+ super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+ final Widget child;
+
+ final double scale;
+
+ final Alignment alignment;
+
+ @override
+ _AnimatedScaleState createState() => _AnimatedScaleState();
+}
+
+class _AnimatedScaleState extends ImplicitlyAnimatedWidgetState {
+ Tween _scale;
+ Animation _scaleAnimation;
+
+ @override
+ void forEachTween(TweenVisitor visitor) {
+ _scale = visitor(_scale, widget.scale,
+ (dynamic value) => Tween(begin: value as double))
+ as Tween;
+ }
+
+ @override
+ void didUpdateTweens() {
+ _scaleAnimation = animation.drive(_scale);
+ }
+
+ @override
+ Widget build(BuildContext context) => ScaleTransition(
+ scale: _scaleAnimation,
+ alignment: widget.alignment,
+ child: widget.child,
+ );
+}
\ No newline at end of file
diff --git a/lib/utils/animation/scale_on_press_widget.dart b/lib/utils/animation/scale_on_press_widget.dart
new file mode 100644
index 0000000..e2ace3f
--- /dev/null
+++ b/lib/utils/animation/scale_on_press_widget.dart
@@ -0,0 +1,70 @@
+import 'package:dartx/dartx.dart';
+import 'package:flutter/material.dart';
+
+import 'animated_scale.dart';
+
+class ScaleOnPressWidget extends StatefulWidget {
+ const ScaleOnPressWidget(
+ {this.child,
+ this.onTap,
+ this.outerPadding = EdgeInsets.zero,
+ this.innerPadding = EdgeInsets.zero,
+ this.scaleFactor = 0.95,
+ Key key})
+ : super(key: key);
+
+ final Widget child;
+ final GestureTapCallback onTap;
+ final EdgeInsets outerPadding;
+ final EdgeInsets innerPadding;
+ final double scaleFactor;
+
+ @override
+ _ScaleOnPressWidgetState createState() => _ScaleOnPressWidgetState();
+}
+
+class _ScaleOnPressWidgetState extends State {
+ bool isPressed = false;
+
+ void _updatePressed(bool pressed) {
+ setState(() {
+ isPressed = pressed;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onLongPress: () => _updatePressed(true),
+ onTapDown: (_) => _updatePressed(true),
+ onTapUp: (_) => _updatePressed(false),
+ onTap: widget.onTap,
+ child: Padding(
+ padding: widget.outerPadding,
+ child: AnimatedScale(
+ duration: (isPressed ? 50 : 200).milliseconds,
+ scale: isPressed ? widget.scaleFactor : 1.0,
+ curve: Curves.decelerate,
+ child: AnimatedContainer(
+ duration: (isPressed ? 50 : 200).milliseconds,
+ curve: Curves.decelerate,
+ padding: widget.innerPadding,
+ decoration: BoxDecoration(
+ boxShadow: [
+ BoxShadow(
+ color: const Color(0x33CCCCCC),
+ blurRadius: isPressed ? 0 : 4,
+ offset: isPressed ? const Offset(0, 0) : const Offset(4, 4),
+ spreadRadius: isPressed ? 0 : 4)
+ ],
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(200),
+ ),
+ child: widget.child,
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/utils/design/colors.dart b/lib/utils/design/colors.dart
new file mode 100644
index 0000000..7e3aae0
--- /dev/null
+++ b/lib/utils/design/colors.dart
@@ -0,0 +1,17 @@
+import 'package:flutter/material.dart';
+
+const MaterialColor whiteMaterialColor = MaterialColor(
+ 0xFFFFFFFF,
+ {
+ 50: Color(0xFFFFFFFF),
+ 100: Color(0xFFFFFFFF),
+ 200: Color(0xFFFFFFFF),
+ 300: Color(0xFFFFFFFF),
+ 400: Color(0xFFFFFFFF),
+ 500: Color(0xFFFFFFFF),
+ 600: Color(0xFFFFFFFF),
+ 700: Color(0xFFFFFFFF),
+ 800: Color(0xFFFFFFFF),
+ 900: Color(0xFFFFFFFF),
+ },
+);
diff --git a/lib/utils/extensions/gradient_extensions.dart b/lib/utils/extensions/gradient_extensions.dart
new file mode 100644
index 0000000..0df611f
--- /dev/null
+++ b/lib/utils/extensions/gradient_extensions.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/material.dart';
+
+extension WidgetUnderGradient on Widget {
+ Widget withBlueGradientFg() {
+ return getShadedWidget(blueLinearGradient);
+ }
+
+ Widget withGreyGradientFg() {
+ return getShadedWidget(greyLinearGradient);
+ }
+
+ Widget getShadedWidget(LinearGradient gradient) {
+ return ShaderMask(
+ blendMode: BlendMode.srcIn,
+ shaderCallback: (Rect bound) {
+ return gradient.createShader(bound);
+ },
+ child: this,
+ );
+ }
+
+ Widget withBlueGradientBg(
+ {BorderRadiusGeometry borderRadius =
+ const BorderRadius.all(Radius.circular(200.0))}) {
+ return Ink(
+ decoration: BoxDecoration(
+ gradient: blueLinearGradient,
+ borderRadius: borderRadius,
+ ),
+ child: this,
+ );
+ }
+}
+
+LinearGradient blueLinearGradient = getLinearGradient(blueGradientColors);
+LinearGradient greyLinearGradient = getLinearGradient(_greyGradientColors);
+
+LinearGradient getLinearGradient(List colors) {
+ return LinearGradient(
+ colors: colors,
+ begin: const Alignment(-1.0, -1.0),
+ end: const Alignment(0.7, 0.7),
+ transform: const GradientRotation(-3.14 / 3),
+ );
+}
+
+const List blueGradientColors = [Color(0xFF0186FF), Color(0xFF00C2FF)];
+const List _greyGradientColors = [Color(0xFFD0D1D1), Color(0xFF8E9496)];
diff --git a/lib/utils/extensions/navigation_extension.dart b/lib/utils/extensions/navigation_extension.dart
index 14a7ec6..0537fad 100644
--- a/lib/utils/extensions/navigation_extension.dart
+++ b/lib/utils/extensions/navigation_extension.dart
@@ -1,13 +1,35 @@
-import 'package:flutter/material.dart';
import 'package:dartx/dartx.dart';
+import 'package:flutter/material.dart';
extension NavigationExtension on BuildContext {
Future navigateTo(
Widget destination, {
Duration delay = const Duration(),
+ bool replace = false,
}) async {
await delay.delay;
- Navigator.of(this)
- .push(MaterialPageRoute(builder: (context) => destination));
+ final route = _createRoute(destination);
+
+ if (replace) {
+ Navigator.of(this).pushReplacement(route);
+ } else {
+ Navigator.of(this).push(route);
+ }
+ }
+
+ PageRouteBuilder _createRoute(Widget destination) {
+ return PageRouteBuilder(
+ pageBuilder: (context, animation, secondaryAnimation) => destination,
+ transitionsBuilder: (context, animation, secondaryAnimation, child) {
+ const begin = Offset(1.0, 0.0);
+ const end = Offset.zero;
+ const curve = Curves.decelerate;
+
+ final tween =
+ Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
+
+ return SlideTransition(position: animation.drive(tween), child: child);
+ },
+ );
}
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 6ae2cd3..1e99684 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -14,6 +14,8 @@ dependencies:
google_fonts: ^1.1.0
flutter_svg: ^0.17.4
lottie: ^0.4.0+1
+ preview: ^0.0.8
+ cached_network_image: ^2.2.0+1
#Architecture
rxdart: ^0.24.1
@@ -22,6 +24,7 @@ dependencies:
#Utils
dartx: ^0.4.2
+ assets_audio_player: ^2.0.6+4
dev_dependencies:
flutter_test:
@@ -33,6 +36,7 @@ flutter:
assets:
- assets/lottie/
- assets/images/
+ - assets/sounds/
fonts:
- family: Product Sans
@@ -43,10 +47,3 @@ flutter:
style: italic
- asset: assets/fonts/Product_Sans_Bold_Italic.ttf
style: italic
-
-
-
-# fonts Balsamiq+Sans, Poppins, Tenali, Rubik, Patrick Hand, Lobster two, Handlee, El Messiri, Alice,
-# Amaranth, Comic neue, Jost, Capriola, Combo, Reem Kufi, Cabin Sketch,Caladea,Cinzel, Cousard,Sen,
-# Delius, Metrophobic,Pompiere, Gabriela,Kurale,Vioada Libre,Amarante,Soloway,
-
diff --git a/test/features/home/home_screen_components_test.dart b/test/features/home/home_screen_components_test.dart
new file mode 100644
index 0000000..6a370e4
--- /dev/null
+++ b/test/features/home/home_screen_components_test.dart
@@ -0,0 +1,87 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:productive/common/resources/r.dart';
+import 'package:productive/features/home/widgets/animated_circle_progress.dart';
+import 'package:productive/features/home/widgets/home_app_bar.dart';
+import 'package:productive/features/home/widgets/home_body.dart';
+import 'package:productive/features/home/widgets/home_bottom_nav_bar.dart';
+import 'package:productive/features/home/widgets/home_screen.dart';
+
+import '../../test_utils/test_utils.dart';
+
+void main() {
+ group("Test Animated Motivation Meter", () {
+ setUpAll(() {
+ R.buildMode.isTesting = true;
+ });
+
+ tearDownAll(() {
+ R.buildMode.isTesting = false;
+ });
+
+ testWidgets("test animated progressbar", (WidgetTester tester) async {
+ await tester.pumpWidget(
+ // ignore: prefer_const_constructors
+ AnimatedMotivationMeter(height: 200, width: 200, motivation: 0.5));
+ expect(find.byType(CustomPaint), findsNWidgets(1));
+ });
+ });
+
+ group("Test Animated Nav Bar", () {
+ setUpAll(() {
+ R.buildMode.isTesting = true;
+ });
+
+ tearDownAll(() {
+ R.buildMode.isTesting = false;
+ });
+
+ testWidgets("gestures are working", (WidgetTester tester) async {
+ // ignore: prefer_const_constructors
+ await tester.pumpWidget(BottomNav().insideMaterialApp);
+ expect(find.byType(GestureDetector), findsNWidgets(2));
+
+ await tester.press(find.byKey(const Key("first_icon")));
+ await tester.pumpAndSettle();
+ await tester.tap(find.byKey(const Key("first_icon")));
+ await tester.pumpAndSettle();
+ await tester.longPress(find.byKey(const Key("second_icon")));
+ await tester.pumpAndSettle();
+ await tester.tap(find.byKey(const Key("second_icon")));
+ await tester.pumpAndSettle();
+ await tester.tap(find.byKey(const Key("first_icon")));
+ await tester.pumpAndSettle();
+ });
+ });
+
+ group("Test home Appbar", () {
+ testWidgets("HomeAppbar contains DisplayPic Widget",
+ (WidgetTester tester) async {
+ // ignore: prefer_const_constructors
+ await tester.pumpWidget(AppbarContent().asScaffold);
+ expect(find.byType(DisplayPic), findsOneWidget);
+
+ // ignore: prefer_const_constructors
+ await tester.pumpWidget(DisplayPic().asScaffold);
+ expect(find.byType(ClipRRect), findsOneWidget);
+ expect(find.byType(CachedNetworkImage), findsOneWidget);
+ });
+ });
+
+ group("Test Home Body", () {
+ testWidgets("Home Body contains App Name", (WidgetTester tester) async {
+ // ignore: prefer_const_constructors
+ await tester.pumpWidget(HomeBody().asScaffold);
+ expect(find.text(R.string.appName), findsOneWidget);
+ });
+ });
+
+ group("Test Home Fab", () {
+ testWidgets("Home Body contains App Name", (WidgetTester tester) async {
+ // ignore: prefer_const_constructors
+ await tester.pumpWidget(HomeScreen().asScaffold);
+ await tester.press(find.byKey(const Key("home_fab")));
+ });
+ });
+}
diff --git a/test/features/home/home_screen_test.dart b/test/features/home/home_screen_test.dart
new file mode 100644
index 0000000..7619986
--- /dev/null
+++ b/test/features/home/home_screen_test.dart
@@ -0,0 +1,29 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:productive/common/resources/r.dart';
+import 'package:productive/features/home/widgets/home_app_bar.dart';
+import 'package:productive/features/home/widgets/home_bottom_nav_bar.dart';
+import 'package:productive/features/home/widgets/home_fab.dart';
+import 'package:productive/features/home/widgets/home_screen.dart';
+
+import '../../test_utils/test_utils.dart';
+
+void main() {
+ group("All required widgets are on the Home screen", () {
+ testWidgets(
+ "Fab,BottomNav and AppbarContent exist",
+ (WidgetTester tester) async {
+ await tester.pumpWidget(HomeScreen().asScaffold);
+ expect(find.byType(Fab), findsOneWidget);
+ expect(find.byType(RaisedButton), findsOneWidget);
+ expect(find.byType(BottomNav), findsOneWidget);
+ expect(find.byType(AppbarContent), findsOneWidget);
+ expect(find.byType(CachedNetworkImage), findsOneWidget);
+
+ await tester.pumpWidget(const Fab().testWidget);
+ expect(find.text(R.string.addTodo), findsOneWidget);
+ },
+ );
+ });
+}
diff --git a/test/features/splash/splash_screen_test.dart b/test/features/splash/splash_screen_test.dart
new file mode 100644
index 0000000..63e2646
--- /dev/null
+++ b/test/features/splash/splash_screen_test.dart
@@ -0,0 +1,61 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:lottie/lottie.dart';
+import 'package:productive/common/resources/r.dart';
+import 'package:productive/features/home/widgets/home_body.dart';
+import 'package:productive/features/home/widgets/home_fab.dart';
+import 'package:productive/features/splash/splash_screen.dart';
+import 'package:productive/utils/animation/animated_scale.dart';
+
+import '../../test_utils/test_utils.dart';
+
+void main() {
+ group("All required widgets are on the Splash screen", () {
+ setUpAll(() {
+ R.buildMode.isTesting = true;
+ });
+
+ tearDownAll(() {
+ R.buildMode.isTesting = false;
+ });
+ testWidgets(
+ "There is Lottie Widget",
+ (WidgetTester tester) async {
+ // ignore: prefer_const_constructors
+ await tester.pumpWidget(SplashScreen().asScaffold);
+ expect(find.byType(LottieBuilder), findsOneWidget);
+ },
+ );
+
+ testWidgets(
+ "There is a Productive Text Logo",
+ (WidgetTester tester) async {
+ await tester.pumpWidget(const SplashScreen().asScaffold);
+ expect(find.byType(ShaderMask), findsOneWidget);
+ expect(find.text(R.string.appName), findsOneWidget);
+ },
+ );
+
+ testWidgets(
+ "There is a Google Sign in Button",
+ (WidgetTester tester) async {
+ // ignore: prefer_const_constructors
+ await tester.pumpWidget(SplashScreenBody().asScaffold);
+ expect(find.byType(AnimatedScale), findsOneWidget);
+ expect(find.text(R.string.loginWithGoogle), findsOneWidget);
+ },
+ );
+
+ testWidgets("Can navigate to HomeScreen", (WidgetTester tester) async {
+ // ignore: prefer_const_constructors
+ await tester.pumpWidget( SplashScreenBody().insideMaterialApp);
+ final loginButton = find.byKey(const Key("login"));
+ expect(loginButton, findsOneWidget);
+ await tester.tap(loginButton);
+ await tester.pumpAndSettle();
+ expect(loginButton, findsNothing);
+ expect(find.byType(HomeBody), findsOneWidget);
+ expect(find.byType(Fab), findsOneWidget);
+ });
+ });
+}
diff --git a/test/main_test.dart b/test/main_test.dart
new file mode 100644
index 0000000..159c68d
--- /dev/null
+++ b/test/main_test.dart
@@ -0,0 +1,19 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:productive/main.dart' as app;
+import 'package:productive/main.dart';
+
+void main() {
+ group("Basic App Setup test", () {
+ testWidgets('App is MaterialApp', (WidgetTester tester) async {
+ await tester.pumpWidget(ProductiveApp(
+ testChild: Container(),
+ ));
+ expect(find.byType(MaterialApp), findsOneWidget);
+ });
+
+ testWidgets("main method runs successfully", (WidgetTester tester) async {
+ app.main();
+ });
+ });
+}
diff --git a/test/playgorund/testmain_test.dart b/test/playgorund/testmain_test.dart
new file mode 100644
index 0000000..efb60fd
--- /dev/null
+++ b/test/playgorund/testmain_test.dart
@@ -0,0 +1,43 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:productive/playground/test_main.dart';
+
+void main() {
+ group("Dummy widget test", () {
+ testWidgets(
+ "Screen contains a button to go to page2",
+ (WidgetTester tester) async {
+ await tester.pumpWidget(DummyApp());
+ final buttonFinder = find.byType(MaterialButton);
+ expect(buttonFinder, findsOneWidget);
+ expect(find.text("Go to screen2"), findsOneWidget);
+ await tester.tap(buttonFinder);
+ await tester.pumpAndSettle();
+ expect(buttonFinder, findsOneWidget);
+ expect(find.text("Go to screen1"), findsOneWidget);
+ expect(find.text("Go to screen2"), findsNothing);
+ await tester.tap(buttonFinder);
+ await tester.pumpAndSettle();
+ expect(find.text("Go to screen2"), findsOneWidget);
+ },
+ );
+
+ testWidgets(
+ "Gesture Detector also works like button",
+ (WidgetTester tester) async {
+ await tester.pumpWidget(DummyApp());
+ expect(find.byKey(const Key("gd1")), findsOneWidget);
+ expect(find.text("Jump to screen2"), findsOneWidget);
+ await tester.tap(find.byKey(const Key("gd1")));
+ await tester.pumpAndSettle();
+ expect(find.byKey(const Key("gd2")), findsOneWidget);
+ expect(find.text("Jump to screen1"), findsOneWidget);
+ expect(find.text("Jump to screen2"), findsNothing);
+ await tester.tap(find.byKey(const Key("gd2")));
+ await tester.pumpAndSettle();
+ expect(find.byKey(const Key("gd1")), findsOneWidget);
+ expect(find.text("Jump to screen2"), findsOneWidget);
+ },
+ );
+ });
+}
diff --git a/test/test_utils/test_utils.dart b/test/test_utils/test_utils.dart
new file mode 100644
index 0000000..ef7394a
--- /dev/null
+++ b/test/test_utils/test_utils.dart
@@ -0,0 +1,10 @@
+import 'package:flutter/material.dart';
+
+extension TestWidget on Widget {
+ Widget get testWidget =>
+ Directionality(textDirection: TextDirection.ltr, child: this);
+
+ Widget get asScaffold => MaterialApp(home: this);
+
+ Widget get insideMaterialApp =>MaterialApp(home: Scaffold(body: this,));
+}
diff --git a/test/unit_test.dart b/test/unit_test.dart
deleted file mode 100644
index 13e9bf3..0000000
--- a/test/unit_test.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-
-void main() {
- group("blablabla", () {
- test("unit tests", () {
- expect(5, 4 + 1);
- });
- });
-}
diff --git a/test/utils/animation/scale_on_press_widget_test.dart b/test/utils/animation/scale_on_press_widget_test.dart
new file mode 100644
index 0000000..e133007
--- /dev/null
+++ b/test/utils/animation/scale_on_press_widget_test.dart
@@ -0,0 +1,20 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:productive/utils/animation/scale_on_press_widget.dart';
+
+import '../../test_utils/test_utils.dart';
+
+void main() {
+ group("Gradient Extensions Test", () {
+ testWidgets("Gradient on Foreground Test", (WidgetTester tester) async {
+ final Widget scaledIcon =
+ ScaleOnPressWidget(child: Icon(Icons.ac_unit)).testWidget;
+ await tester.pumpWidget(scaledIcon);
+ expect(find.byType(GestureDetector), findsOneWidget);
+ await tester.press(find.byType(GestureDetector));
+ await tester.pumpAndSettle();
+ await tester.longPress(find.byType(GestureDetector));
+ await tester.pumpAndSettle();
+ });
+ });
+}
diff --git a/test/utils/extensions/gradient_extensions_test.dart b/test/utils/extensions/gradient_extensions_test.dart
new file mode 100644
index 0000000..cddf01f
--- /dev/null
+++ b/test/utils/extensions/gradient_extensions_test.dart
@@ -0,0 +1,31 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:productive/utils/extensions/gradient_extensions.dart';
+
+import '../../test_utils/test_utils.dart';
+
+void main() {
+ group("Gradient Extensions Test", () {
+ testWidgets(
+ "Gradient on Foreground Test",
+ (WidgetTester tester) async {
+ final Widget blueFg = const Text("").withBlueGradientFg().testWidget;
+ await tester.pumpWidget(blueFg);
+ expect(find.byType(ShaderMask), findsOneWidget);
+ final Widget greyFg = const Text("").withGreyGradientFg().testWidget;
+ await tester.pumpWidget(greyFg);
+ expect(find.byType(ShaderMask), findsOneWidget);
+ },
+ );
+
+ testWidgets(
+ "Gradient on Background Test",
+ (WidgetTester tester) async {
+ final Widget blueBg =
+ const Text("").withBlueGradientBg().insideMaterialApp;
+ await tester.pumpWidget(blueBg);
+ expect(find.byType(Ink), findsOneWidget);
+ },
+ );
+ });
+}
diff --git a/test/widget_test.dart b/test/widget_test.dart
deleted file mode 100644
index c9ce1fd..0000000
--- a/test/widget_test.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:productive/main.dart';
-void main() {
- testWidgets('App Follows MaterialTheme', (WidgetTester tester) async {
- await tester.pumpWidget(ProductiveApp());
- expect(find.byType(MaterialApp), findsOneWidget);
- });
-}
\ No newline at end of file