diff --git a/assets/images/black-logo.png b/assets/images/black-logo.png new file mode 100644 index 0000000..0d42805 Binary files /dev/null and b/assets/images/black-logo.png differ diff --git a/assets/images/box-empty.jpg b/assets/images/box-empty.jpg new file mode 100644 index 0000000..2064a31 Binary files /dev/null and b/assets/images/box-empty.jpg differ diff --git a/assets/images/white-logo.png b/assets/images/white-logo.png new file mode 100644 index 0000000..2e73e35 Binary files /dev/null and b/assets/images/white-logo.png differ diff --git a/lib/features/auth/auth_provider.dart b/lib/features/auth/auth_provider.dart new file mode 100644 index 0000000..4a7cc47 --- /dev/null +++ b/lib/features/auth/auth_provider.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/auth/models/user_model.dart'; + +class AuthProvider extends ChangeNotifier { + // todo remove temp user + UserModel user = UserModel( + id: '1', + name: 'John Doe', + email: '', + photoUrl: '', + isAgreementAccepted: false, + isAnonymous: false, + isEmailVerified: false, + phoneNumber: '', + ); +} diff --git a/lib/features/auth/auth_view.dart b/lib/features/auth/auth_view.dart new file mode 100644 index 0000000..c2b789b --- /dev/null +++ b/lib/features/auth/auth_view.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:provider/provider.dart'; +import 'package:syncrow_app/features/home/widgets/syncrow_logo.dart'; +import 'package:syncrow_app/features/shared_widgets/default_text_button.dart'; +import 'package:syncrow_app/navigation/route_manager.dart'; + +import '../../navigation/routing_constants.dart'; +import 'auth_provider.dart'; + +class AuthPage extends StatelessWidget { + const AuthPage({super.key}); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (BuildContext context) => AuthProvider(), + builder: (context, child) => Scaffold( + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Expanded(child: SizedBox()), + const SyncrowLogo(), + const Expanded(flex: 2, child: SizedBox()), + DefaultTextButton( + text: 'Login', + onPressed: () { + RouteManager().routerManagerPopAndPushNamed( + routeName: Routes.homeRoute, + context: context, + ); + }, + isPrimary: true, + ), + const Gap(15), + const DefaultTextButton(text: 'Sign Up'), + const Gap(20), + Center( + child: InkWell( + onTap: () {}, + child: const Text( + 'Try as a Guest', + style: TextStyle( + color: Colors.grey, + ), + ), + ), + ), + const Gap(30), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/auth/models/user_model.dart b/lib/features/auth/models/user_model.dart new file mode 100644 index 0000000..87049a7 --- /dev/null +++ b/lib/features/auth/models/user_model.dart @@ -0,0 +1,51 @@ +class UserModel { + final String? id; + final String? email; + final String? name; + final String? photoUrl; + + final String? phoneNumber; + + final bool? isAnonymous; + + final bool? isEmailVerified; + + final bool? isAgreementAccepted; + + UserModel({ + required this.id, + required this.email, + required this.name, + required this.photoUrl, + required this.phoneNumber, + required this.isAnonymous, + required this.isEmailVerified, + required this.isAgreementAccepted, + }); + + factory UserModel.fromJson(Map json) { + return UserModel( + id: json['id'], + email: json['email'], + name: json['name'], + photoUrl: json['photoUrl'], + phoneNumber: json['phoneNumber'], + isAnonymous: json['isAnonymous'], + isEmailVerified: json['isEmailVerified'], + isAgreementAccepted: json['isAgreementAccepted'], + ); + } + + Map toJson() { + return { + 'id': id, + 'email': email, + 'name': name, + 'photoUrl': photoUrl, + 'phoneNumber': phoneNumber, + 'isAnonymous': isAnonymous, + 'isEmailVerified': isEmailVerified, + 'isAgreementAccepted': isAgreementAccepted, + }; + } +} diff --git a/lib/features/auth/provider/auth_provider.dart b/lib/features/auth/provider/auth_provider.dart deleted file mode 100644 index 1ebc853..0000000 --- a/lib/features/auth/provider/auth_provider.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:flutter/material.dart'; - -class AuthProvider extends ChangeNotifier {} diff --git a/lib/features/auth/view/auth_view.dart b/lib/features/auth/view/auth_view.dart deleted file mode 100644 index d31c5dd..0000000 --- a/lib/features/auth/view/auth_view.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import '../provider/auth_provider.dart'; - -class AuthView extends StatelessWidget { - const AuthView({super.key}); - - @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (BuildContext context) => AuthProvider(), - builder: (context, child) => _buildPage(context), - ); - } - - Widget _buildPage(BuildContext context) { - final provider = context.read(); - - return Container(); - } -} diff --git a/lib/features/home/home_provider.dart b/lib/features/home/home_provider.dart index ec7a1b2..d753662 100644 --- a/lib/features/home/home_provider.dart +++ b/lib/features/home/home_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/home/model/device_model.dart'; import 'package:syncrow_app/features/profile/profile_view.dart'; import 'package:syncrow_app/features/scene/scene_view.dart'; import 'package:syncrow_app/features/smart/smart_view.dart'; @@ -16,12 +17,14 @@ class HomeProvider extends ChangeNotifier { static int pageIndex = 0; - Future> getDevices() async { + var devices = []; + + Future> getDevices() async { state.setLoading(); await Future.delayed(const Duration(seconds: 2)); state.setSuccess(); notifyListeners(); - return []; + return devices = []; } Map> appBarActions = { @@ -66,7 +69,7 @@ class HomeProvider extends ChangeNotifier { const ProfilePage(), ]; - Widget currentPage() { + Widget getCurrentPage() { notifyListeners(); return pages[pageIndex]; diff --git a/lib/features/home/home_view.dart b/lib/features/home/home_view.dart index b94a330..0f08283 100644 --- a/lib/features/home/home_view.dart +++ b/lib/features/home/home_view.dart @@ -18,7 +18,7 @@ class HomePage extends StatelessWidget { appBar: const DefaultAppBar(), body: provider.state.loading ? const Center(child: CircularProgressIndicator()) - : provider.currentPage(), + : provider.getCurrentPage(), bottomNavigationBar: const DefaultNavBar(), ); }, diff --git a/lib/features/home/model/device_model.dart b/lib/features/home/model/device_model.dart new file mode 100644 index 0000000..75dba0d --- /dev/null +++ b/lib/features/home/model/device_model.dart @@ -0,0 +1 @@ +class DeviceModel {} diff --git a/lib/features/home/widgets/default_app_bar.dart b/lib/features/home/widgets/default_app_bar.dart index 1a223f4..4dfba41 100644 --- a/lib/features/home/widgets/default_app_bar.dart +++ b/lib/features/home/widgets/default_app_bar.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:syncrow_app/features/home/widgets/syncrow_logo.dart'; +import 'package:syncrow_app/resource_manager/color_manager.dart'; import '../home_provider.dart'; @@ -11,14 +13,15 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { return Consumer( builder: (context, provider, child) { return AppBar( - title: const Text('Syncrow'), + title: const SyncrowLogo(), actions: [ IconButton( icon: const Icon(Icons.mic), onPressed: () {}, ), IconButton( - icon: const Icon(Icons.add_circle), + icon: const Icon(Icons.add_circle, + color: ColorsManager.primaryColor), onPressed: () {}, ), ], diff --git a/lib/features/home/widgets/home_view_body.dart b/lib/features/home/widgets/home_view_body.dart index 87f9bdd..b912d43 100644 --- a/lib/features/home/widgets/home_view_body.dart +++ b/lib/features/home/widgets/home_view_body.dart @@ -1,4 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:provider/provider.dart'; +import 'package:syncrow_app/features/home/home_provider.dart'; +import 'package:syncrow_app/features/shared_widgets/default_text_button.dart'; +import 'package:syncrow_app/resource_manager/assets_manager.dart'; class HomeViewBody extends StatelessWidget { const HomeViewBody({ @@ -7,8 +12,40 @@ class HomeViewBody extends StatelessWidget { @override Widget build(BuildContext context) { - return const Center( - child: Text('Home'), + return ChangeNotifierProvider( + create: (BuildContext context) => HomeProvider(), + child: Consumer( + builder: (context, provider, child) { + return provider.devices.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + ImageManager.boxEmpty, + opacity: const AlwaysStoppedAnimation(0.5), + scale: 1, + width: 140, + ), + const Gap(15), + const Text( + 'No Devices', + style: TextStyle( + color: Colors.grey, + fontSize: 18, + ), + ), + const Gap(15), + const DefaultTextButton( + text: 'Add Device', + isPrimary: true, + ), + ], + ), + ) + : const SizedBox(); + }, + ), ); } } diff --git a/lib/features/home/widgets/syncrow_logo.dart b/lib/features/home/widgets/syncrow_logo.dart new file mode 100644 index 0000000..e091f23 --- /dev/null +++ b/lib/features/home/widgets/syncrow_logo.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/resource_manager/assets_manager.dart'; + +class SyncrowLogo extends StatelessWidget { + const SyncrowLogo({ + super.key, + this.isDark = true, + this.width = 150, + }); + + final bool isDark; + + final double width; + + @override + Widget build(BuildContext context) { + return Image.asset(isDark ? ImageManager.blackLogo : ImageManager.whiteLogo, + scale: 1, width: width); + } +} diff --git a/lib/features/shared_widgets/default_text_button.dart b/lib/features/shared_widgets/default_text_button.dart new file mode 100644 index 0000000..065af0c --- /dev/null +++ b/lib/features/shared_widgets/default_text_button.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/resource_manager/color_manager.dart'; + +class DefaultTextButton extends StatelessWidget { + const DefaultTextButton({ + super.key, + this.onPressed, + required this.text, + this.isPrimary = false, + }); + + final void Function()? onPressed; + final String text; + + final bool isPrimary; + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: onPressed, + style: isPrimary + ? ButtonStyle( + backgroundColor: + MaterialStateProperty.all(ColorsManager.primaryColor), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ) + : null, + child: Text( + text, + style: TextStyle(color: isPrimary ? Colors.white : Colors.black), + ), + ); + } +} diff --git a/lib/features/splash/splash_view.dart b/lib/features/splash/splash_view.dart index 00c9401..3342f2e 100644 --- a/lib/features/splash/splash_view.dart +++ b/lib/features/splash/splash_view.dart @@ -1,12 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/splash/user_agreement_dialog.dart'; import 'package:syncrow_app/navigation/route_manager.dart'; import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/resource_manager/assets_manager.dart'; class SplashView extends StatelessWidget { const SplashView({super.key}); @override Widget build(BuildContext context) { + //TODO Remove delay + // Future.delayed(const Duration(seconds: 5), () { + // RouteManager().routerManagerPushUntil( + // context, + // routeName: Routes.homeRoute, + // ); + // }); return Scaffold( appBar: AppBar( actions: [ @@ -14,15 +23,23 @@ class SplashView extends StatelessWidget { icon: const Icon(Icons.arrow_forward), onPressed: () { RouteManager().routerManagerPopAndPushNamed( - routeName: Routes.homeRoute, + routeName: Routes.authRoute, context: context, ); }, ), ], ), - body: const Center( - child: Text('Splash Screen'), + body: Center( + child: InkWell( + //TODO check if user agreement is accepted + onTap: () { + showDialog( + context: context, + builder: (context) => const UserAgreementDialog(), + ); + }, + child: Image.asset(ImageManager.blackLogo)), ), ); } diff --git a/lib/features/splash/user_agreement_dialog.dart b/lib/features/splash/user_agreement_dialog.dart new file mode 100644 index 0000000..f942af5 --- /dev/null +++ b/lib/features/splash/user_agreement_dialog.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/default_text_button.dart'; + +class UserAgreementDialog extends StatelessWidget { + const UserAgreementDialog({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + elevation: 40, + title: const Text('User Agreement'), + content: + const Text('By using this app you agree to the terms and conditions'), + actions: [ + DefaultTextButton( + text: 'I Agree', + onPressed: () { + Navigator.of(context).pop(); + }, + isPrimary: true, + ), + DefaultTextButton( + text: 'I Disagree', + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + } +} diff --git a/lib/my_app.dart b/lib/my_app.dart index c8b2bdb..ef84f8d 100644 --- a/lib/my_app.dart +++ b/lib/my_app.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; +import 'package:syncrow_app/resource_manager/color_manager.dart'; -import 'features/auth/provider/auth_provider.dart'; +import 'features/auth/auth_provider.dart'; import 'navigation/router.dart' as router; import 'navigation/routing_constants.dart'; import 'services/navigation_service.dart'; @@ -33,7 +34,7 @@ class _MyAppState extends State { debugShowCheckedModeBanner: false, navigatorKey: NavigationService.navigatorKey, scaffoldMessengerKey: NavigationService.snackbarKey, - color: Colors.white, + color: ColorsManager.primaryColor, title: 'Syncrow App', onGenerateRoute: router.Router.generateRoute, initialRoute: Routes.splash, diff --git a/lib/navigation/router.dart b/lib/navigation/router.dart index 165f05c..0d3f949 100644 --- a/lib/navigation/router.dart +++ b/lib/navigation/router.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/auth/auth_view.dart'; import 'package:syncrow_app/features/home/home_view.dart'; import 'package:syncrow_app/features/profile/profile_view.dart'; import 'package:syncrow_app/features/scene/scene_view.dart'; @@ -30,6 +31,10 @@ class Router { return MaterialPageRoute( builder: (_) => const SmartPage(), settings: settings); + case Routes.authRoute: + return MaterialPageRoute( + builder: (_) => const AuthPage(), settings: settings); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/navigation/routing_constants.dart b/lib/navigation/routing_constants.dart index 7641e16..4093b7e 100644 --- a/lib/navigation/routing_constants.dart +++ b/lib/navigation/routing_constants.dart @@ -4,4 +4,5 @@ class Routes { static const String sceneRoute = '/scene'; static const String smartRoute = '/smart'; static const String profileRoute = '/profile'; + static const String authRoute = '/auth'; } diff --git a/lib/resource_manager/assets_manager.dart b/lib/resource_manager/assets_manager.dart index 5290087..8e1f813 100644 --- a/lib/resource_manager/assets_manager.dart +++ b/lib/resource_manager/assets_manager.dart @@ -1,13 +1,15 @@ -class ImagesAssets { - // static const String base = 'assets/images/'; +class ImageManager { + static const String base = 'assets/images'; - // static const String bigLightLogo = '$base/image.png'; + static const String whiteLogo = '$base/white-logo.png'; + static const String blackLogo = '$base/black-logo.png'; + static const String boxEmpty = '$base/box-empty.jpg'; } -class IconsAssets { +class IconsManager { // static const String facebookIcon = 'assets/icons/icon.png'; } -class VideosAssets { +class VideosManager { // static const String registrationVideo = 'assets/videos/video.mp4'; } diff --git a/lib/resource_manager/color_manager.dart b/lib/resource_manager/color_manager.dart index cd6913a..39a7377 100644 --- a/lib/resource_manager/color_manager.dart +++ b/lib/resource_manager/color_manager.dart @@ -1,53 +1,9 @@ - +import 'package:flutter/material.dart'; class ColorsManager { -// static const Color primaryLightColor = Color(0xFF3a96db); -// static const Color onPrimaryLightColor = Color(0xFFFFFFFF); + static const Color primaryColor = Color(0xFF023dfe); + static const Color secondaryColor = Colors.white; + static const Color primaryTextColor = Colors.black; -// static const Color primaryDarkColor = Color(0xFF24588d); -// static const Color onPrimaryDarkColor = Color(0xFFFFFFFF); - -// static const Color secondaryLightColor = Color(0xFFFFFFFF); -// static const Color onSecondaryLightColor = Color(0xFF4D4E56); - -// static const Color secondaryDarkColor = Color(0xFF4D4E56); -// static const Color onSecondaryDarkColor = Color(0xFFFFFFFF); - -// static const Color thirdDarkColor = Color(0xFFE0E6ED); -// static const Color onThirdDarkColor = Color(0xFF4D4E56); - -// static const Color errorColor = Color(0xFFff4c4c); -// static const Color onErrorColor = Color(0xFFFFFFFF); - -// static const Color textColor = Colors.black; -// static const Color textColorGrey = Colors.grey; -// static const Color textColorLight = Colors.white; - -// static const lightColorScheme = ColorScheme( -// brightness: Brightness.light, -// primary: ColorsManager.primaryLightColor, -// onPrimary: ColorsManager.onPrimaryLightColor, -// secondary: ColorsManager.secondaryLightColor, -// onSecondary: ColorsManager.onSecondaryLightColor, -// error: ColorsManager.errorColor, -// onError: ColorsManager.onErrorColor, -// background: Colors.white, -// onBackground: Colors.black, -// surface: Colors.white, -// onSurface: Colors.black, -// ); - -// static const darkColorScheme = ColorScheme( -// brightness: Brightness.dark, -// primary: ColorsManager.primaryDarkColor, -// onPrimary: ColorsManager.onPrimaryDarkColor, -// secondary: ColorsManager.secondaryDarkColor, -// onSecondary: ColorsManager.onSecondaryDarkColor, -// error: ColorsManager.errorColor, -// onError: ColorsManager.onErrorColor, -// background: secondaryDarkColor, -// onBackground: onSecondaryDarkColor, -// surface: secondaryDarkColor, -// onSurface: onSecondaryDarkColor, -// ); + static const Color greyColor = Color(0xFFd9d9d9); } diff --git a/pubspec.lock b/pubspec.lock index faa09d5..bb1677a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -301,6 +301,14 @@ packages: description: flutter source: sdk version: "0.0.0" + gap: + dependency: "direct main" + description: + name: gap + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d + url: "https://pub.dev" + source: hosted + version: "3.0.1" get_it: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 7e7ed09..5d69049 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: cupertino_icons: ^1.0.6 shared_preferences: ^2.2.2 flutter_animated_dialog: ^2.0.1 + gap: ^3.0.1 # Utility Packages flutter_secure_storage: ^9.0.0