Redesigned Splash Screen and Login Screen

This commit is contained in:
Mohammad Salameh
2024-03-10 14:49:17 +03:00
parent ce34933738
commit a6018b282e
19 changed files with 468 additions and 218 deletions

View File

@ -1,19 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/auth/view/widgets/auth_view_body.dart';
class AuthView extends StatelessWidget {
const AuthView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
return const Scaffold(
body: AuthViewBody(),
);
},
);
}
}

View File

@ -1,38 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/shared_widgets/default_button.dart';
import 'package:syncrow_app/features/shared_widgets/syncrow_logo.dart';
import '../../../../navigation/routing_constants.dart';
class AuthViewBody extends StatelessWidget {
const AuthViewBody({
super.key,
});
@override
Widget build(BuildContext context) {
return 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()),
DefaultButton(
text: 'Login',
onPressed: () {
Navigator.pushNamed(context, Routes.authLogin);
},
),
const SizedBox(height: 15),
const DefaultButton(
text: 'Sign Up',
isSecondary: true,
),
const SizedBox(height: 20),
],
),
);
}
}

View File

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class DontHaveAnAccount extends StatelessWidget {
const DontHaveAnAccount({
super.key,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
BodyLarge(
text: "Don't have an account?",
style: context.displaySmall.copyWith(color: Colors.white),
),
TextButton(
onPressed: () {
Navigator.pushNamed(context, Routes.authSignUp);
},
child: BodyLarge(
text: "Sign Up",
style: context.displaySmall.copyWith(
color: Colors.black,
fontWeight: FontsManager.bold,
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/utils/context_extension.dart';
class ForgetPassword extends StatelessWidget {
const ForgetPassword({
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
const Spacer(),
TextButton(
onPressed: () {
//TODO Navigate to forgot password
},
child: BodyMedium(
text: "Forgot Password?",
style: context.bodyMedium.copyWith(
color: Colors.white,
),
),
),
],
);
}
}

View File

@ -17,22 +17,18 @@ class LoginButton extends StatelessWidget {
children: [
Expanded(
child: DefaultButton(
enabled: AuthCubit.get(context).agreeToTerms,
text: state is AuthLoading
? null
: state is AuthSuccess
? null
: "Login",
child: state is AuthSuccess
? const Icon(
Icons.check_circle_outline,
color: Colors.white,
)
: const SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(
color: Colors.white,
isDone: state is AuthSuccess,
isLoading: state is AuthLoading,
customButtonStyle: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Colors.black.withOpacity(.25),
),
foregroundColor: MaterialStateProperty.all(
Colors.white,
),
),
child: const Text(
'Login',
),
onPressed: () {
if (AuthCubit.get(context).formKey.currentState!.validate()) {

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
class LoginDivider extends StatelessWidget {
const LoginDivider({
super.key,
});
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Row(
children: [
Expanded(
child: Divider(
color: Colors.white,
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: BodyMedium(
text: "or Sign in with",
style: TextStyle(
color: Colors.white,
),
),
),
Expanded(
child: Divider(
color: Colors.white,
),
),
],
),
);
}
}

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/utils/resource_manager/styles_manager.dart';
class LoginForm extends StatelessWidget {
const LoginForm({
@ -14,8 +15,14 @@ class LoginForm extends StatelessWidget {
builder: (context, state) {
return Form(
key: AuthCubit.get(context).formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const BodyMedium(
text: "Email",
fontColor: Colors.white,
),
TextFormField(
controller: AuthCubit.get(context).emailController,
validator: (value) {
@ -35,24 +42,23 @@ class LoginForm extends StatelessWidget {
onTapOutside: (event) {
AuthCubit.get(context).formKey.currentState!.validate();
},
decoration: InputDecoration(
labelText: 'Email',
enabledBorder: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(10),
),
filled: true,
fillColor: ColorsManager.backgroundColor.withAlpha(100),
floatingLabelBehavior: FloatingLabelBehavior.never,
contentPadding: const EdgeInsets.all(10),
),
decoration: defaultInputDecoration(context,
hint: "Example@email.com"),
),
const SizedBox(height: 10),
const BodyMedium(
text: "Password",
fontColor: Colors.white,
),
TextFormField(
controller: AuthCubit.get(context).passwordController,
validator: (value) {
if (value != null) {
if (value.isEmpty) {
if (value.isNotEmpty) {
if (value.length < 6) {
return 'Password must be at least 8 characters';
}
} else {
return 'Please enter your password';
}
}
@ -62,30 +68,12 @@ class LoginForm extends StatelessWidget {
AuthCubit.get(context).formKey.currentState!.validate();
},
obscureText: !AuthCubit.get(context).isPasswordVisible,
decoration: InputDecoration(
labelText: 'Password',
enabledBorder: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(10),
),
suffixIcon: IconButton(
icon: Icon(
AuthCubit.get(context).isPasswordVisible
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
AuthCubit.get(context).changePasswordVisibility();
},
),
filled: true,
fillColor: ColorsManager.backgroundColor.withAlpha(100),
floatingLabelBehavior: FloatingLabelBehavior.never,
contentPadding: const EdgeInsets.all(10),
),
decoration: defaultInputDecoration(context,
hint: "At least 8 characters"),
),
],
),
),
);
},
);

View File

@ -1,12 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/dont_have_an_account.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/forget_password.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/login_button.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/login_divider.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/login_form.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/login_user_agreement.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/login_with_google_facebook.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class LoginView extends StatelessWidget {
const LoginView({super.key});
@ -29,29 +36,80 @@ class LoginView extends StatelessWidget {
},
builder: (context, state) {
return Scaffold(
appBar: AppBar(),
body: const Padding(
padding: EdgeInsets.symmetric(
body: Stack(
children: [
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.imagesBackground,
),
fit: BoxFit.cover,
),
),
),
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Assets.imagesVector),
fit: BoxFit.cover,
opacity: 0.9,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: Constants.defaultPadding,
),
child: SingleChildScrollView(
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: SvgPicture.asset(
Assets.imagesLogo,
width: 160,
),
),
const SizedBox(
height: 40,
),
TitleMedium(
text: 'Login',
style: context.titleMedium.copyWith(
fontWeight: FontsManager.extraBold,
color: Colors.white,
),
SizedBox(
height: 10,
),
LoginForm(),
SizedBox(height: 10),
LoginUserAgreement(),
SizedBox(height: 10),
LoginButton(),
const SizedBox(
height: 20,
),
const LoginForm(),
const SizedBox(height: 10),
// const LoginUserAgreement(),
const ForgetPassword(),
const SizedBox(height: 10),
const LoginButton(),
const LoginDivider(),
const LoginWithGoogleFacebook(),
const DontHaveAnAccount(),
],
),
),
),
),
)
],
),
);
},
);

View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
class LoginWithGoogleFacebook extends StatelessWidget {
const LoginWithGoogleFacebook({
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: DefaultContainer(
child: SizedBox.square(
dimension: 24,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(Assets.iconsGoogle),
SizedBox(width: 10),
BodyMedium(
text: "Google",
style: context.bodyMedium.copyWith(
color: Colors.black,
),
),
],
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultContainer(
child: SizedBox.square(
dimension: 24,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(Assets.iconsFacebook),
SizedBox(width: 10),
BodyMedium(
text: "Facebook",
style: context.bodyMedium.copyWith(color: Colors.black),
),
],
),
),
),
),
],
);
}
}

View File

@ -29,7 +29,9 @@ class NoDevicesView extends StatelessWidget {
),
const SizedBox(height: 15),
const DefaultButton(
text: 'Add Device',
child: Text(
'Add Device',
),
),
],
),

View File

@ -29,7 +29,9 @@ class SceneViewNoScenes extends StatelessWidget {
),
const SizedBox(height: 20),
const DefaultButton(
text: 'Create Scene',
child: Text(
'Add a scene',
),
)
],
),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class DefaultButton extends StatelessWidget {
@ -6,46 +7,84 @@ class DefaultButton extends StatelessWidget {
super.key,
this.enabled = true,
this.onPressed,
this.child,
required this.child,
this.isSecondary = false,
this.text,
this.isLoading = false,
this.isDone = false,
this.customTextStyle,
this.customButtonStyle,
});
final void Function()? onPressed;
final Widget? child;
final Widget child;
final String? text;
final bool isSecondary;
final bool enabled;
final bool isDone;
final bool isLoading;
final TextStyle? customTextStyle;
final ButtonStyle? customButtonStyle;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: enabled ? onPressed : null,
style: isSecondary
? null
: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
enabled ? ColorsManager.primaryColor : Colors.grey),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
: customButtonStyle ??
ButtonStyle(
textStyle: MaterialStateProperty.all(
customTextStyle ??
context.bodyMedium.copyWith(
fontSize: 16,
),
),
),
child: text != null
? Text(
text!,
style: TextStyle(
color: isSecondary
foregroundColor: MaterialStateProperty.all(
isSecondary
? Colors.black
: enabled
? Colors.white
: Colors.black,
),
backgroundColor: MaterialStateProperty.all(
enabled ? ColorsManager.primaryColor : Colors.grey),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
fixedSize: MaterialStateProperty.all(
const Size.fromHeight(50),
),
padding: MaterialStateProperty.all(
const EdgeInsets.all(10),
),
minimumSize: MaterialStateProperty.all(
const Size.fromHeight(50),
),
),
child: SizedBox(
height: 50,
child: Center(
child: isLoading
? const SizedBox.square(
dimension: 24,
child: CircularProgressIndicator(
color: Colors.white,
),
)
: isDone
? const Icon(
Icons.check_circle_outline,
color: Colors.white,
)
: child,
),
),
);
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/splash/view/widgets/user_agreement_dialog.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
@ -10,25 +10,46 @@ class SplashView extends StatelessWidget {
Widget build(BuildContext context) {
//TODO remove this delay
Future.delayed(
const Duration(seconds: 3),
const Duration(seconds: 5),
() {
Navigator.popAndPushNamed(
context,
Routes.authRoute,
Routes.authLogin,
);
},
);
return Scaffold(
body: Center(
child: InkWell(
//TODO check if user agreement is accepted
onTap: () {
showDialog(
context: context,
builder: (context) => const UserAgreementDialog(),
);
},
child: Image.asset(Assets.imagesBlackLogo)),
body: Stack(
alignment: Alignment.center,
children: [
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.imagesBackground,
),
fit: BoxFit.cover,
),
),
),
Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Assets.imagesVector),
fit: BoxFit.cover,
opacity: 0.9,
),
),
),
SvgPicture.asset(
Assets.imagesLogo,
width: 240,
)
],
),
);
}

View File

@ -15,15 +15,19 @@ class UserAgreementDialog extends StatelessWidget {
const Text('By using this app you agree to the terms and conditions'),
actions: [
DefaultButton(
text: 'I Agree',
child: const Text(
'Login',
),
onPressed: () {
Navigator.of(context).pop();
},
),
DefaultButton(
text: 'I Disagree',
onPressed: () => Navigator.of(context).pop(),
isSecondary: true,
child: const Text(
'Cancel',
),
),
],
);

View File

@ -15,12 +15,14 @@ class Assets {
static const String iconsDevices = 'assets/icons/Devices.svg';
static const String iconsDevicesFill = 'assets/icons/Devices-fill.svg';
static const String iconsDoorLock = 'assets/icons/doorLock.svg';
static const String iconsFacebook = 'assets/icons/Facebook.svg';
static const String iconsFan0 = 'assets/icons/fan-0.svg';
static const String iconsFan1 = 'assets/icons/fan-1.svg';
static const String iconsFan2 = 'assets/icons/fan-2.svg';
static const String iconsFan3 = 'assets/icons/fan-3.svg';
static const String iconsFrequency = 'assets/icons/frequency.svg';
static const String iconsGateway = 'assets/icons/Gateway.svg';
static const String iconsGoogle = 'assets/icons/Google.svg';
static const String iconsHome = 'assets/icons/home.svg';
static const String iconsHot1 = 'assets/icons/hot1.jpg';
static const String iconsKalvin = 'assets/icons/kalvin.svg';
@ -51,7 +53,9 @@ class Assets {
static const String imagesBackground = 'assets/images/Background.png';
static const String imagesBlackLogo = 'assets/images/black-logo.png';
static const String imagesBoxEmpty = 'assets/images/box-empty.jpg';
static const String imagesLogo = 'assets/images/Logo.svg';
static const String imagesTestDash = 'assets/images/test_dash.png';
static const String imagesTestDash2 = 'assets/images/test_dash2.png';
static const String imagesVector = 'assets/images/Vector.png';
static const String imagesWhiteLogo = 'assets/images/white-logo.png';
}

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/app_layout/view/app_layout.dart';
import 'package:syncrow_app/features/auth/view/auth_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/didnt_get_code/didnt_get_code_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/login/login_view.dart';
import 'package:syncrow_app/features/auth/view/widgets/one_time_password/one_time_password_view.dart';
@ -39,10 +38,6 @@ class Router {
return MaterialPageRoute(
builder: (_) => const LayoutPage(), settings: settings);
case Routes.authRoute:
return MaterialPageRoute(
builder: (_) => const AuthView(), settings: settings);
case Routes.authLogin:
return MaterialPageRoute(
builder: (_) => const LoginView(), settings: settings);

View File

@ -10,6 +10,9 @@ extension ContextExtension on BuildContext {
double get height => MediaQuery.sizeOf(this).height;
InputDecorationTheme get inputDecoration =>
Theme.of(this).inputDecorationTheme;
TextStyle get displayLarge => Theme.of(this).textTheme.displayLarge!;
TextStyle get displayMedium => Theme.of(this).textTheme.displayMedium!;

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'font_manager.dart';
@ -85,3 +86,18 @@ TextStyle getTextStyleExtraBold({
color: color,
fontWeight: fontWeight,
);
///inputDecoration
InputDecoration defaultInputDecoration(BuildContext context, {String? hint}) =>
InputDecoration(
enabledBorder: context.inputDecoration.enabledBorder,
focusedBorder: context.inputDecoration.enabledBorder,
errorBorder: context.inputDecoration.errorBorder,
focusedErrorBorder: context.inputDecoration.enabledBorder,
hintText: hint,
hintStyle: context.inputDecoration.hintStyle,
filled: context.inputDecoration.filled,
fillColor: context.inputDecoration.fillColor,
contentPadding: context.inputDecoration.contentPadding,
);

View File

@ -141,19 +141,32 @@ abstract class ThemeManager {
// ),
///input decoration theme
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
inputDecorationTheme: InputDecorationTheme(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
borderRadius: BorderRadius.all(Radius.circular(8)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(20),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: ColorsManager.primaryColor),
borderRadius: BorderRadius.all(Radius.circular(8)),
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(20),
),
labelStyle: TextStyle(
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(20),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: const BorderSide(color: Colors.red)),
hintStyle: const TextStyle(
fontFamily: FontsManager.fontFamily,
fontSize: FontSize.s14,
fontWeight: FontsManager.regular,
color: ColorsManager.textPrimaryColor,
),
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.all(10),
labelStyle: const TextStyle(
fontFamily: FontsManager.fontFamily,
color: Colors.grey,
fontSize: FontSize.s16,