added 3 screen mod to login

This commit is contained in:
llsajjad
2025-08-09 01:32:19 +03:30
parent 05a05f867e
commit 9e5e25f784
8 changed files with 425 additions and 136 deletions
+35
View File
@@ -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';
}
}
}
+42
View File
@@ -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();
}
}
+94
View File
@@ -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,
),
],
);
},
),
),
),
),
);
}
}
+88
View File
@@ -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,
),
],
);
},
),
),
),
);
}
}
+15 -94
View File
@@ -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);
}
: null,
),
],
),
),
),
);
switch (displaySize) {
case DisplaySize.large:
case DisplaySize.medium:
return const LoginDesktopScreen();
case DisplaySize.small:
return const LoginTabletScreen();
case DisplaySize.extraSmall:
return const LoginMobileScreen();
}
}
}
+94
View File
@@ -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,
),
],
);
},
),
),
),
),
);
}
}
+6 -4
View File
@@ -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, );
}
}
+51 -38
View File
@@ -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,52 +25,62 @@ 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(
controller: controller,
focusNode: focusNode,
obscureText: isPassword ? obscureText : false,
decoration: InputDecoration(
labelText: label,
prefixIcon: Padding(
padding: EdgeInsets.all(12.0.w()),
child: SvgPicture.asset(
iconPath,
width: 24.0.w(),
height: 24.0.h(),
colorFilter: ColorFilter.mode(
focusNode.hasFocus ? Colors.black87 : Colors.grey,
BlendMode.srcIn,
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(prefixIconPadding ?? 12.0.w()),
child: SvgPicture.asset(
iconPath,
width: 24.0.w(),
height: 24.0.h(),
colorFilter: ColorFilter.mode(
focusNode.hasFocus ? Colors.black87 : Colors.grey,
BlendMode.srcIn,
),
),
),
),
suffixIcon:
isPassword
? IconButton(
onPressed: onToggleVisibility,
icon: SvgPicture.asset(
obscureText
? AssetsManager.passwordVisibility
: AssetsManager.passwordVisibilityOff,
width: 24.0.w(),
height: 24.0.h(),
colorFilter: ColorFilter.mode(
focusNode.hasFocus ? Colors.black87 : Colors.grey,
BlendMode.srcIn,
suffixIcon:
isPassword
? IconButton(
onPressed: onToggleVisibility,
icon: SvgPicture.asset(
obscureText
? AssetsManager.passwordVisibility
: AssetsManager.passwordVisibilityOff,
width: 24.0.w(),
height: 24.0.h(),
colorFilter: ColorFilter.mode(
focusNode.hasFocus ? Colors.black87 : Colors.grey,
BlendMode.srcIn,
),
),
),
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0.w()),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: AppColors.borderBlue),
borderRadius: BorderRadius.circular(12.0.w()),
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(borderRedus ?? 12.0.w()),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: AppColors.borderBlue),
borderRadius: BorderRadius.circular(borderRedus ?? 12.0.w()),
),
),
),
);