mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 22:57:12 -04:00
config sign in screen
This commit is contained in:
parent
d30ee5db89
commit
084f939ed0
9 changed files with 236 additions and 173 deletions
6
app_flowy/.vscode/tasks.json
vendored
6
app_flowy/.vscode/tasks.json
vendored
|
@ -18,9 +18,9 @@
|
||||||
"options": {
|
"options": {
|
||||||
"cwd": "${workspaceFolder}/../"
|
"cwd": "${workspaceFolder}/../"
|
||||||
},
|
},
|
||||||
"problemMatcher": [
|
// "problemMatcher": [
|
||||||
"$rustc"
|
// "$rustc"
|
||||||
],
|
// ],
|
||||||
"label": "BuildRust"
|
"label": "BuildRust"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -15,7 +15,7 @@ analyzer:
|
||||||
- "**/*.g.dart"
|
- "**/*.g.dart"
|
||||||
- "**/*.freezed.dart"
|
- "**/*.freezed.dart"
|
||||||
- "packages/flowy_editor/**"
|
- "packages/flowy_editor/**"
|
||||||
- "packages/flowy_infra_ui/**"
|
# - "packages/flowy_infra_ui/**"
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
|
|
BIN
app_flowy/assets/images/app_flowy_logo.jpg
Normal file
BIN
app_flowy/assets/images/app_flowy_logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -1,8 +1,15 @@
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/user/application/sign_in/sign_in_bloc.dart';
|
import 'package:app_flowy/user/application/sign_in/sign_in_bloc.dart';
|
||||||
import 'package:app_flowy/user/presentation/sign_in/widgets/body.dart';
|
import 'package:app_flowy/user/presentation/sign_in/widgets/background.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
|
||||||
class SignInScreen extends StatelessWidget {
|
class SignInScreen extends StatelessWidget {
|
||||||
const SignInScreen({Key? key}) : super(key: key);
|
const SignInScreen({Key? key}) : super(key: key);
|
||||||
|
@ -11,9 +18,129 @@ class SignInScreen extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => getIt<SignInBloc>(),
|
create: (context) => getIt<SignInBloc>(),
|
||||||
child: const Scaffold(
|
child: Scaffold(
|
||||||
body: Body(),
|
body: BlocProvider(
|
||||||
|
create: (context) => getIt<SignInBloc>(),
|
||||||
|
child: BlocConsumer<SignInBloc, SignInState>(
|
||||||
|
listenWhen: (p, c) => p != c,
|
||||||
|
listener: (context, state) {
|
||||||
|
state.signInFailure.fold(
|
||||||
|
() {},
|
||||||
|
(result) => _handleStateErrors(result, context),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
builder: (context, state) => const SignInForm(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleStateErrors(
|
||||||
|
Either<UserDetail, UserError> some, BuildContext context) {
|
||||||
|
some.fold(
|
||||||
|
(userDetail) => _showHomeScreen(context, userDetail),
|
||||||
|
(result) => _showErrorMessage(context, result.msg),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showErrorMessage(BuildContext context, String msg) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(msg),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showHomeScreen(BuildContext context, UserDetail userDetail) {
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return HomeScreen(userDetail);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SignInForm extends StatelessWidget {
|
||||||
|
const SignInForm({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: SignInFormContainer(
|
||||||
|
children: [
|
||||||
|
const SignInTitle(
|
||||||
|
title: 'Login to Appflowy',
|
||||||
|
logoSize: Size(60, 60),
|
||||||
|
),
|
||||||
|
const VSpace(30),
|
||||||
|
RoundedInputField(
|
||||||
|
hintText: 'email',
|
||||||
|
onChanged: (value) =>
|
||||||
|
context.read<SignInBloc>().add(SignInEvent.emailChanged(value)),
|
||||||
|
),
|
||||||
|
RoundedInputField(
|
||||||
|
obscureText: true,
|
||||||
|
hintText: 'password',
|
||||||
|
onChanged: (value) => context
|
||||||
|
.read<SignInBloc>()
|
||||||
|
.add(SignInEvent.passwordChanged(value)),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
textStyle: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
onPressed: () => _showForgetPasswordScreen(context),
|
||||||
|
child: const Text(
|
||||||
|
'Forgot Password?',
|
||||||
|
style: TextStyle(color: Colors.lightBlue),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RoundedButton(
|
||||||
|
title: 'Login',
|
||||||
|
height: 60,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
color: Colors.lightBlue,
|
||||||
|
press: () {
|
||||||
|
context
|
||||||
|
.read<SignInBloc>()
|
||||||
|
.add(const SignInEvent.signedInWithUserEmailAndPassword());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const VSpace(10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text("Dont't have an account",
|
||||||
|
style: TextStyle(color: Colors.blueGrey, fontSize: 12)),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
textStyle: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
child: const Text(
|
||||||
|
'Sign Up',
|
||||||
|
style: TextStyle(color: Colors.lightBlue),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
),
|
||||||
|
if (context.read<SignInBloc>().state.isSubmitting) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const LinearProgressIndicator(value: null),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showForgetPasswordScreen(BuildContext context) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,29 +1,57 @@
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class SignInBackground extends StatelessWidget {
|
class SignInFormContainer extends StatelessWidget {
|
||||||
final Widget child;
|
final List<Widget> children;
|
||||||
const SignInBackground({
|
const SignInFormContainer({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.child,
|
required this.children,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var size = MediaQuery.of(context).size;
|
final size = MediaQuery.of(context).size;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: size.height,
|
width: size.width * 0.3,
|
||||||
width: double.infinity,
|
child: Column(
|
||||||
child: Stack(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
alignment: Alignment.center,
|
children: children,
|
||||||
children: [
|
),
|
||||||
Image(
|
);
|
||||||
fit: BoxFit.cover,
|
}
|
||||||
width: size.width,
|
}
|
||||||
height: size.height,
|
|
||||||
image: const AssetImage(
|
class SignInTitle extends StatelessWidget {
|
||||||
'assets/images/appflowy_launch_splash.jpg')),
|
final String title;
|
||||||
child,
|
final Size logoSize;
|
||||||
],
|
const SignInTitle({
|
||||||
));
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.logoSize,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image(
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
width: logoSize.width,
|
||||||
|
height: logoSize.height,
|
||||||
|
image: const AssetImage('assets/images/app_flowy_logo.jpg')),
|
||||||
|
const VSpace(30),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
|
||||||
import 'package:app_flowy/user/application/sign_in/sign_in_bloc.dart';
|
|
||||||
import 'package:app_flowy/user/presentation/sign_in/widgets/background.dart';
|
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
|
||||||
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
class Body extends StatelessWidget {
|
|
||||||
const Body({Key? key}) : super(key: key);
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocProvider(
|
|
||||||
create: (context) => getIt<SignInBloc>(),
|
|
||||||
child: const SignInBackground(
|
|
||||||
child: SignInForm(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignInForm extends StatelessWidget {
|
|
||||||
const SignInForm({
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocConsumer<SignInBloc, SignInState>(
|
|
||||||
listenWhen: (p, c) => p != c,
|
|
||||||
listener: (context, state) {
|
|
||||||
state.signInFailure.fold(
|
|
||||||
() {},
|
|
||||||
(result) => _handleStateErrors(result, context),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
builder: (context, state) {
|
|
||||||
return SignInFormBackground(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
RoundedInputField(
|
|
||||||
icon: Icons.person,
|
|
||||||
hintText: 'email',
|
|
||||||
onChanged: (value) => context
|
|
||||||
.read<SignInBloc>()
|
|
||||||
.add(SignInEvent.emailChanged(value)),
|
|
||||||
),
|
|
||||||
RoundedInputField(
|
|
||||||
icon: Icons.lock,
|
|
||||||
obscureText: true,
|
|
||||||
hintText: 'password',
|
|
||||||
onChanged: (value) => context
|
|
||||||
.read<SignInBloc>()
|
|
||||||
.add(SignInEvent.passwordChanged(value)),
|
|
||||||
),
|
|
||||||
RoundedButton(
|
|
||||||
title: 'LOGIN',
|
|
||||||
press: () {
|
|
||||||
context
|
|
||||||
.read<SignInBloc>()
|
|
||||||
.add(const SignInEvent.signedInWithUserEmailAndPassword());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (state.isSubmitting) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const LinearProgressIndicator(value: null),
|
|
||||||
]
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleStateErrors(
|
|
||||||
Either<UserDetail, UserError> some, BuildContext context) {
|
|
||||||
some.fold(
|
|
||||||
(userDetail) => showHomeScreen(context, userDetail),
|
|
||||||
(result) => _showErrorMessage(context, result.msg),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showErrorMessage(BuildContext context, String msg) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(msg),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showHomeScreen(BuildContext context, UserDetail userDetail) {
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return HomeScreen(userDetail);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignInFormBackground extends StatelessWidget {
|
|
||||||
final List<Widget> children;
|
|
||||||
const SignInFormBackground({
|
|
||||||
Key? key,
|
|
||||||
required this.children,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final size = MediaQuery.of(context).size;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: size.width * 0.4,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center, children: children),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,13 +3,23 @@ import 'package:flutter/material.dart';
|
||||||
class RoundedButton extends StatelessWidget {
|
class RoundedButton extends StatelessWidget {
|
||||||
final VoidCallback? press;
|
final VoidCallback? press;
|
||||||
final String? title;
|
final String? title;
|
||||||
final Size? size;
|
final double? width;
|
||||||
|
final double? height;
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
final Color borderColor;
|
||||||
|
final Color color;
|
||||||
|
final Color textColor;
|
||||||
|
|
||||||
const RoundedButton({
|
const RoundedButton({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.press,
|
this.press,
|
||||||
this.title,
|
this.title,
|
||||||
this.size,
|
this.width,
|
||||||
|
this.height,
|
||||||
|
this.borderRadius = BorderRadius.zero,
|
||||||
|
this.borderColor = Colors.transparent,
|
||||||
|
this.color = Colors.transparent,
|
||||||
|
this.textColor = Colors.white,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -17,15 +27,22 @@ class RoundedButton extends StatelessWidget {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
maxWidth: size?.width ?? double.infinity,
|
maxWidth: width ?? double.infinity,
|
||||||
minHeight: 50,
|
minHeight: 50,
|
||||||
maxHeight: size?.height ?? double.infinity,
|
maxHeight: height ?? 60,
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||||
child: TextButton(
|
decoration: BoxDecoration(
|
||||||
child: Text(title ?? ''),
|
border: Border.all(color: borderColor),
|
||||||
onPressed: press,
|
borderRadius: borderRadius,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
child: SizedBox.expand(
|
||||||
|
child: TextButton(
|
||||||
|
child: Text(title ?? '', style: TextStyle(color: textColor)),
|
||||||
|
onPressed: press,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,26 +10,33 @@ class RoundedInputField extends StatelessWidget {
|
||||||
const RoundedInputField({
|
const RoundedInputField({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.hintText,
|
this.hintText,
|
||||||
this.icon = Icons.person,
|
this.icon,
|
||||||
this.obscureText = false,
|
this.obscureText = false,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final Icon? newIcon = icon == null
|
||||||
|
? null
|
||||||
|
: Icon(
|
||||||
|
icon!,
|
||||||
|
color: const Color(0xFF6F35A5),
|
||||||
|
);
|
||||||
|
|
||||||
return TextFieldContainer(
|
return TextFieldContainer(
|
||||||
child: TextFormField(
|
borderRadius: BorderRadius.circular(10),
|
||||||
onChanged: onChanged,
|
borderColor: Colors.blueGrey,
|
||||||
cursorColor: const Color(0xFF6F35A5),
|
child: TextFormField(
|
||||||
obscureText: obscureText,
|
onChanged: onChanged,
|
||||||
decoration: InputDecoration(
|
cursorColor: const Color(0xFF6F35A5),
|
||||||
icon: Icon(
|
obscureText: obscureText,
|
||||||
icon,
|
decoration: InputDecoration(
|
||||||
color: const Color(0xFF6F35A5),
|
icon: newIcon,
|
||||||
|
hintText: hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
hintText: hintText,
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,21 +3,30 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class TextFieldContainer extends StatelessWidget {
|
class TextFieldContainer extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
final Color borderColor;
|
||||||
|
final Size? size;
|
||||||
const TextFieldContainer({
|
const TextFieldContainer({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
this.borderRadius = BorderRadius.zero,
|
||||||
|
this.borderColor = Colors.white,
|
||||||
|
this.size,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
double height = size == null ? 50 : size!.height;
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
height: height,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: borderColor),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(30),
|
borderRadius: borderRadius,
|
||||||
),
|
),
|
||||||
child: child,
|
child: Align(alignment: Alignment.center, child: child),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue