added 3 screen mod to login
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum DisplaySize {
|
||||
extraSmall, //mobile
|
||||
small, //tablet
|
||||
medium, //laptop
|
||||
large, //desktop
|
||||
}
|
||||
|
||||
extension DisplaySizeDetector on Size {
|
||||
DisplaySize get displaySize {
|
||||
if (width < 600) {
|
||||
return DisplaySize.extraSmall;
|
||||
} else if (width < 1024) {
|
||||
return DisplaySize.small;
|
||||
} else if (width < 1440) {
|
||||
return DisplaySize.medium;
|
||||
} else {
|
||||
return DisplaySize.large;
|
||||
}
|
||||
}
|
||||
|
||||
String get displaySizeName {
|
||||
switch (displaySize) {
|
||||
case DisplaySize.extraSmall:
|
||||
return 'Mobile';
|
||||
case DisplaySize.small:
|
||||
return 'Tablet';
|
||||
case DisplaySize.medium:
|
||||
return 'Laptop';
|
||||
case DisplaySize.large:
|
||||
return 'Desktop';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// lib/view_models/login_view_model.dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoginViewModel with ChangeNotifier {
|
||||
final TextEditingController usernameController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
|
||||
final FocusNode usernameFocus = FocusNode();
|
||||
final FocusNode passwordFocus = FocusNode();
|
||||
|
||||
bool _obscurePassword = true;
|
||||
|
||||
LoginViewModel() {
|
||||
usernameController.addListener(_onTextChanged);
|
||||
passwordController.addListener(_onTextChanged);
|
||||
usernameFocus.addListener(_onTextChanged);
|
||||
passwordFocus.addListener(_onTextChanged);
|
||||
}
|
||||
|
||||
bool get obscurePassword => _obscurePassword;
|
||||
bool get isLoginEnabled =>
|
||||
usernameController.text.isNotEmpty &&
|
||||
passwordController.text.isNotEmpty;
|
||||
|
||||
void togglePasswordVisibility() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _onTextChanged() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
usernameController.dispose();
|
||||
passwordController.dispose();
|
||||
usernameFocus.dispose();
|
||||
passwordFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// lib/views/login_desktop_screen.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/routes/app_routes.dart';
|
||||
|
||||
import '../../core/constants/assets.dart';
|
||||
import '../../core/constants/strings.dart';
|
||||
import '../../core/utils/size_config.dart';
|
||||
import 'widgets/widgets.dart';
|
||||
import '../../view_models/login_view_model.dart';
|
||||
|
||||
class LoginDesktopScreen extends StatefulWidget {
|
||||
const LoginDesktopScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginDesktopScreen> createState() => _LoginDesktopScreenState();
|
||||
}
|
||||
|
||||
class _LoginDesktopScreenState extends State<LoginDesktopScreen> {
|
||||
late final LoginViewModel _viewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_viewModel = LoginViewModel();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_viewModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeConfig.init(context);
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
|
||||
child: SizedBox(
|
||||
width: 120.0.w(),
|
||||
child: ListenableBuilder(
|
||||
listenable: _viewModel,
|
||||
builder: (context, _) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
LoginLogo(width: 190.0.w(), height: 88.0.h()),
|
||||
SizedBox(height: 32.0.h()),
|
||||
const LoginTitle(),
|
||||
SizedBox(height: 32.0.h()),
|
||||
|
||||
LoginTextField(
|
||||
controller: _viewModel.usernameController,
|
||||
focusNode: _viewModel.usernameFocus,
|
||||
label: AppStrings.usernameLabel,
|
||||
iconPath: AssetsManager.userIcon,
|
||||
fieldHeight: 60.0.h(),
|
||||
borderRedus: 4.0.w(),
|
||||
prefixIconPadding: 4.0.w(),
|
||||
),
|
||||
SizedBox(height: 16.0.h()),
|
||||
|
||||
LoginTextField(
|
||||
controller: _viewModel.passwordController,
|
||||
focusNode: _viewModel.passwordFocus,
|
||||
label: AppStrings.passwordLabel,
|
||||
iconPath: AssetsManager.passwordIcon,
|
||||
prefixIconPadding: 4.0.w(),
|
||||
isPassword: true,
|
||||
obscureText: _viewModel.obscurePassword,
|
||||
fieldHeight: 60.0.h(),
|
||||
onToggleVisibility: _viewModel.togglePasswordVisibility,
|
||||
borderRedus: 4.0.w(),
|
||||
),
|
||||
SizedBox(height: 32.0.h()),
|
||||
|
||||
BaseButton(
|
||||
isEnabled: _viewModel.isLoginEnabled,
|
||||
onPressed: _viewModel.isLoginEnabled
|
||||
? () => HomeRouteData().go(context)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// lib/views/login_screen.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/routes/app_routes.dart';
|
||||
|
||||
import '../../core/constants/assets.dart';
|
||||
import '../../core/constants/strings.dart';
|
||||
import '../../core/utils/size_config.dart';
|
||||
import 'widgets/widgets.dart';
|
||||
import '../../view_models/login_view_model.dart';
|
||||
|
||||
class LoginMobileScreen extends StatefulWidget {
|
||||
const LoginMobileScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginMobileScreen> createState() => _LoginMobileScreenState();
|
||||
}
|
||||
|
||||
class _LoginMobileScreenState extends State<LoginMobileScreen> {
|
||||
late final LoginViewModel _viewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_viewModel = LoginViewModel();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_viewModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeConfig.init(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
|
||||
child: ListenableBuilder(
|
||||
listenable: _viewModel,
|
||||
builder: (context, _) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
LoginLogo(width: 190.0.w(), height: 88.0.h()),
|
||||
SizedBox(height: 32.0.h()),
|
||||
const LoginTitle(),
|
||||
SizedBox(height: 32.0.h()),
|
||||
|
||||
LoginTextField(
|
||||
controller: _viewModel.usernameController,
|
||||
focusNode: _viewModel.usernameFocus,
|
||||
label: AppStrings.usernameLabel,
|
||||
iconPath: AssetsManager.userIcon,
|
||||
),
|
||||
SizedBox(height: 16.0.h()),
|
||||
|
||||
LoginTextField(
|
||||
controller: _viewModel.passwordController,
|
||||
focusNode: _viewModel.passwordFocus,
|
||||
label: AppStrings.passwordLabel,
|
||||
iconPath: AssetsManager.passwordIcon,
|
||||
isPassword: true,
|
||||
obscureText: _viewModel.obscurePassword,
|
||||
onToggleVisibility: _viewModel.togglePasswordVisibility,
|
||||
),
|
||||
SizedBox(height: 32.0.h()),
|
||||
|
||||
BaseButton(
|
||||
isEnabled: _viewModel.isLoginEnabled,
|
||||
onPressed: _viewModel.isLoginEnabled
|
||||
? () {
|
||||
HomeRouteData().go(context);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,24 @@
|
||||
// lib/views/login_screen.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/routes/app_routes.dart';
|
||||
import 'package:tm_app/core/utils/display_size_detector.dart';
|
||||
import 'package:tm_app/views/login/login_desktop_screen.dart';
|
||||
import 'package:tm_app/views/login/login_mobile_screen.dart';
|
||||
import 'package:tm_app/views/login/login_tablet_screen.dart';
|
||||
|
||||
import '../../core/constants/assets.dart';
|
||||
import '../../core/constants/strings.dart';
|
||||
import '../../core/utils/size_config.dart';
|
||||
import 'widgets/widgets.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
class LoginScreen extends StatelessWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final _usernameController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
|
||||
final _usernameFocus = FocusNode();
|
||||
final _passwordFocus = FocusNode();
|
||||
|
||||
bool _obscurePassword = true;
|
||||
|
||||
bool get _isLoginEnabled =>
|
||||
_usernameController.text.isNotEmpty &&
|
||||
_passwordController.text.isNotEmpty;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_usernameController.addListener(_updateState);
|
||||
_passwordController.addListener(_updateState);
|
||||
_usernameFocus.addListener(_updateState);
|
||||
_passwordFocus.addListener(_updateState);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_usernameController.dispose();
|
||||
_passwordController.dispose();
|
||||
_usernameFocus.dispose();
|
||||
_passwordFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _updateState() => setState(() {});
|
||||
|
||||
void _togglePasswordVisibility() =>
|
||||
setState(() => _obscurePassword = !_obscurePassword);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeConfig.init(context);
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const LoginLogo(),
|
||||
SizedBox(height: 32.0.h()),
|
||||
const LoginTitle(),
|
||||
SizedBox(height: 32.0.h()),
|
||||
LoginTextField(
|
||||
controller: _usernameController,
|
||||
focusNode: _usernameFocus,
|
||||
label: AppStrings.usernameLabel,
|
||||
iconPath: AssetsManager.userIcon,
|
||||
),
|
||||
final displaySize = MediaQuery.of(context).size.displaySize;
|
||||
|
||||
SizedBox(height: 16.0.h()),
|
||||
|
||||
LoginTextField(
|
||||
controller: _passwordController,
|
||||
focusNode: _passwordFocus,
|
||||
label: AppStrings.passwordLabel,
|
||||
iconPath: AssetsManager.passwordIcon,
|
||||
isPassword: true,
|
||||
obscureText: _obscurePassword,
|
||||
onToggleVisibility: _togglePasswordVisibility,
|
||||
),
|
||||
|
||||
SizedBox(height: 32.0.h()),
|
||||
BaseButton(
|
||||
isEnabled: _isLoginEnabled,
|
||||
onPressed:
|
||||
_isLoginEnabled
|
||||
? () {
|
||||
HomeRouteData().go(context);
|
||||
switch (displaySize) {
|
||||
case DisplaySize.large:
|
||||
case DisplaySize.medium:
|
||||
return const LoginDesktopScreen();
|
||||
case DisplaySize.small:
|
||||
return const LoginTabletScreen();
|
||||
case DisplaySize.extraSmall:
|
||||
return const LoginMobileScreen();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// lib/views/login_tablet_screen.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/routes/app_routes.dart';
|
||||
|
||||
import '../../core/constants/assets.dart';
|
||||
import '../../core/constants/strings.dart';
|
||||
import '../../core/utils/size_config.dart';
|
||||
import 'widgets/widgets.dart';
|
||||
import '../../view_models/login_view_model.dart';
|
||||
|
||||
class LoginTabletScreen extends StatefulWidget {
|
||||
const LoginTabletScreen({super.key});
|
||||
|
||||
@override
|
||||
State<LoginTabletScreen> createState() => _LoginTabletScreenState();
|
||||
}
|
||||
|
||||
class _LoginTabletScreenState extends State<LoginTabletScreen> {
|
||||
late final LoginViewModel _viewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_viewModel = LoginViewModel();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_viewModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeConfig.init(context);
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
|
||||
child: SizedBox(
|
||||
width: 120.0.w(),
|
||||
child: ListenableBuilder(
|
||||
listenable: _viewModel,
|
||||
builder: (context, _) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
LoginLogo(width: 190.0.w(), height: 88.0.h()),
|
||||
SizedBox(height: 32.0.h()),
|
||||
const LoginTitle(),
|
||||
SizedBox(height: 32.0.h()),
|
||||
|
||||
LoginTextField(
|
||||
controller: _viewModel.usernameController,
|
||||
focusNode: _viewModel.usernameFocus,
|
||||
label: AppStrings.usernameLabel,
|
||||
iconPath: AssetsManager.userIcon,
|
||||
fieldHeight: 60.0.h(),
|
||||
borderRedus: 4.0.w(),
|
||||
prefixIconPadding: 4.0.w(),
|
||||
),
|
||||
SizedBox(height: 16.0.h()),
|
||||
|
||||
LoginTextField(
|
||||
controller: _viewModel.passwordController,
|
||||
focusNode: _viewModel.passwordFocus,
|
||||
label: AppStrings.passwordLabel,
|
||||
iconPath: AssetsManager.passwordIcon,
|
||||
prefixIconPadding: 4.0.w(),
|
||||
isPassword: true,
|
||||
obscureText: _viewModel.obscurePassword,
|
||||
fieldHeight: 60.0.h(),
|
||||
onToggleVisibility: _viewModel.togglePasswordVisibility,
|
||||
borderRedus: 4.0.w(),
|
||||
),
|
||||
SizedBox(height: 32.0.h()),
|
||||
|
||||
BaseButton(
|
||||
isEnabled: _viewModel.isLoginEnabled,
|
||||
onPressed: _viewModel.isLoginEnabled
|
||||
? () => HomeRouteData().go(context)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/constants/assets.dart';
|
||||
import '../../../core/utils/size_config.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class LoginLogo extends StatelessWidget {
|
||||
const LoginLogo({super.key});
|
||||
LoginLogo({required this.width,required this.height,super.key});
|
||||
|
||||
double width;
|
||||
double height;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Image.asset(AssetsManager.logo, width: 190.0.w(), height: 88.0.h());
|
||||
return Image.asset(AssetsManager.logo, );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ class LoginTextField extends StatelessWidget {
|
||||
final String iconPath;
|
||||
final bool isPassword;
|
||||
final bool obscureText;
|
||||
final double? borderRedus;
|
||||
final double? fieldHeight;
|
||||
final double? prefixIconPadding;
|
||||
final VoidCallback? onToggleVisibility;
|
||||
|
||||
const LoginTextField({
|
||||
@@ -22,19 +25,28 @@ class LoginTextField extends StatelessWidget {
|
||||
super.key,
|
||||
this.isPassword = false,
|
||||
this.obscureText = false,
|
||||
this.fieldHeight,
|
||||
this.onToggleVisibility,
|
||||
this.borderRedus,
|
||||
this.prefixIconPadding,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
return SizedBox(
|
||||
height: fieldHeight ?? 55.0.h(),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
obscureText: isPassword ? obscureText : false,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: (fieldHeight != null ? (fieldHeight! / 2.8) : 16.0).h(),
|
||||
horizontal: 12.0.w(),
|
||||
),
|
||||
labelText: label,
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.all(12.0.w()),
|
||||
padding: EdgeInsets.all(prefixIconPadding ?? 12.0.w()),
|
||||
child: SvgPicture.asset(
|
||||
iconPath,
|
||||
width: 24.0.w(),
|
||||
@@ -63,11 +75,12 @@ class LoginTextField extends StatelessWidget {
|
||||
)
|
||||
: null,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0.w()),
|
||||
borderRadius: BorderRadius.circular(borderRedus ?? 12.0.w()),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: AppColors.borderBlue),
|
||||
borderRadius: BorderRadius.circular(12.0.w()),
|
||||
borderRadius: BorderRadius.circular(borderRedus ?? 12.0.w()),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user