D1APP-43 authentication (dynamic)

This commit is contained in:
MohammadTaha Basiri 2022-01-04 19:43:27 +03:30
parent 0e312636a7
commit a472c97c4e
19 changed files with 317 additions and 114 deletions

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class LightThemeConfig { class LightThemeConfig {
static const Color _primary = Color(0XFF007EA7); static const Color _primary = Color(0xFF007EA7);
static const Color _white = Color(0XFFFFFFFF); static const Color _white = Color(0xFFFFFFFF);
static const Color _black = Color(0XFF292929); static const Color _black = Color(0xFF292929);
static const Color _background = Color(0XFFF8F8FA); static const Color _background = Color(0xFFF8F8FA);
static final ThemeData themeData = ThemeData( static final ThemeData themeData = ThemeData(
backgroundColor: _background, backgroundColor: _background,
@ -28,11 +28,11 @@ class LightThemeConfig {
static const ColorScheme _colorScheme = ColorScheme( static const ColorScheme _colorScheme = ColorScheme(
primary: _primary, primary: _primary,
primaryVariant: _white, primaryVariant: _white,
secondary: Color(0XFFD61515), secondary: Color(0xFFD61515),
secondaryVariant: _white, secondaryVariant: _white,
surface: _white, surface: _white,
background: _background, background: _background,
error: Color(0XFFF00505), error: Color(0xFFF00505),
onPrimary: _white, onPrimary: _white,
onSecondary: _white, onSecondary: _white,
onSurface: _black, onSurface: _black,
@ -72,9 +72,9 @@ class LightThemeConfig {
} }
class DarkThemeConfig { class DarkThemeConfig {
static const Color _primary = Color(0XFF007EA7); static const Color _primary = Color(0xFF007EA7);
static const Color _white = Color(0XFFFFFFFF); static const Color _white = Color(0xFFFFFFFF);
static const Color _background = Color(0XFF202224); static const Color _background = Color(0xFF202224);
static final ThemeData themeData = ThemeData( static final ThemeData themeData = ThemeData(
backgroundColor: _background, backgroundColor: _background,
@ -101,11 +101,11 @@ class DarkThemeConfig {
static const ColorScheme _colorScheme = ColorScheme( static const ColorScheme _colorScheme = ColorScheme(
primary: _primary, primary: _primary,
primaryVariant: _white, primaryVariant: _white,
secondary: Color(0XFFE53939), secondary: Color(0xFFE53939),
secondaryVariant: _white, secondaryVariant: _white,
surface: Color(0XFF181B1F), surface: Color(0xFF181B1F),
background: _background, background: _background,
error: Color(0XFFF53B3B), error: Color(0xFFF53B3B),
onPrimary: _white, onPrimary: _white,
onSecondary: _white, onSecondary: _white,
onSurface: text, onSurface: text,
@ -144,78 +144,81 @@ class DarkThemeConfig {
); );
// Grey colors // Grey colors
static const Color white = Color(0XFFFFFFFF); static const Color white = Color(0xFFFFFFFF);
static const Color title = Color(0XFFF5F5F5); static const Color title = Color(0xFFF5F5F5);
static const Color text = Color(0XFFD6D6D6); static const Color text = Color(0xFFD6D6D6);
static const Color hint = Color(0XFFBBBBBB); static const Color hint = Color(0xFFBBBBBB);
static const Color border = Color(0XFF666666); static const Color border = Color(0xFF666666);
// Error and success // Error and success
static const Color errorLight = Color(0XFFF0C9CD); static const Color errorLight = Color(0xFFF0C9CD);
static const Color error = Color(0XFFF53B3B); static const Color error = Color(0xFFF53B3B);
static const Color successLight = Color(0XFFBBD6B4); static const Color successLight = Color(0xFFBBD6B4);
static const Color success = Color(0XFF32A64C); static const Color success = Color(0xFF32A64C);
} }
extension DidvanColorScheme on ColorScheme { extension DidvanColorScheme on ColorScheme {
// Secondary colors // Secondary colors
Color get secondaryDisabled => brightness == Brightness.dark Color get secondaryDisabled => brightness == Brightness.dark
? const Color(0XFF703838) ? const Color(0xFF703838)
: const Color(0XFFFFC8C8); : const Color(0xFFFFC8C8);
Color get white => const Color(0XFFFFFFFF); Color get white => const Color(0xFFFFFFFF);
Color get focused => brightness == Brightness.dark Color get focused => brightness == Brightness.dark
? const Color(0XFF323C47) ? const Color(0xFF323C47)
: const Color(0XFFE6F3FA); : const Color(0xFFE6F3FA);
Color get navigation => brightness == Brightness.dark Color get navigation => brightness == Brightness.dark
? const Color(0XFF181B1F) ? const Color(0xFF181B1F)
: const Color(0XFF012348); : const Color(0xFF012348);
Color get focusedBorder => brightness == Brightness.dark Color get focusedBorder => brightness == Brightness.dark
? const Color(0XFFC8E0F4) ? const Color(0xFFC8E0F4)
: const Color(0XFF195D80); : const Color(0xFF195D80);
Color get title => brightness == Brightness.dark Color get title => brightness == Brightness.dark
? const Color(0XFFD6D6D6) ? const Color(0xFFD6D6D6)
: const Color(0XFF1B3C59); : const Color(0xFF1B3C59);
Color get text => brightness == Brightness.dark Color get text => brightness == Brightness.dark
? const Color(0XFFD6D6D6) ? const Color(0xFFD6D6D6)
: const Color(0XFF292929); : const Color(0xFF292929);
Color get inputText => brightness == Brightness.dark Color get inputText => brightness == Brightness.dark
? const Color(0XFFA3A3A3) ? const Color(0xFFA3A3A3)
: const Color(0XFF3D3D3D); : const Color(0xFF3D3D3D);
Color get caption => brightness == Brightness.dark Color get caption => brightness == Brightness.dark
? const Color(0XFFBBBBBB) ? const Color(0xFFBBBBBB)
: const Color(0XFF666666); : const Color(0xFF666666);
Color get hint => const Color(0XFFBBBBBB); Color get hint => const Color(0xFFBBBBBB);
Color get disabledText => brightness == Brightness.dark Color get disabledText => brightness == Brightness.dark
? const Color(0XFF666666) ? const Color(0xFF666666)
: const Color(0XFFE0E0E0); : const Color(0xFFE0E0E0);
Color get border => brightness == Brightness.dark Color get border => brightness == Brightness.dark
? const Color(0XFF666666) ? const Color(0xFF666666)
: const Color(0XFFE0E0E0); : const Color(0xFFE0E0E0);
Color get cardBorder => brightness == Brightness.dark Color get cardBorder => brightness == Brightness.dark
? const Color(0XFF666666) ? const Color(0xFF666666)
: const Color(0XFFEBEBEB); : const Color(0xFFEBEBEB);
Color get disabledBackground => brightness == Brightness.dark Color get disabledBackground => brightness == Brightness.dark
? const Color(0XFF1F1F1F) ? const Color(0xFF1F1F1F)
: const Color(0XFFE0E0E0); : const Color(0xFFE0E0E0);
Color get secondCTA => brightness == Brightness.dark Color get secondCTA => brightness == Brightness.dark
? const Color(0XFF474747) ? const Color(0xFF474747)
: const Color(0XFFF5F5F5); : const Color(0xFFF5F5F5);
Color get splash => brightness == Brightness.dark Color get splash => brightness == Brightness.dark
? const Color(0XFF333333) ? const Color(0xFF333333)
: const Color(0XFFC8E0F4); : const Color(0xFFC8E0F4);
Color get black => brightness == Brightness.dark Color get black => brightness == Brightness.dark
? const Color(0XFF1F1F1F) ? const Color(0xFF1F1F1F)
: const Color(0XFF292929); : const Color(0xFF292929);
Color get overlay => brightness == Brightness.dark
? const Color(0xFF0F1011)
: const Color(0xFF292929);
// Error and success colors // Error and success colors
Color get errorLight => brightness == Brightness.dark Color get errorLight => brightness == Brightness.dark
? const Color(0XFFF0C9CD) ? const Color(0xFFF0C9CD)
: const Color(0XFFFFF8F8); : const Color(0xFFFFF8F8);
Color get successLight => brightness == Brightness.dark Color get successLight => brightness == Brightness.dark
? const Color(0XFFBBD6B4) ? const Color(0xFFBBD6B4)
: const Color(0XFFF5FFFC); : const Color(0xFFF5FFFC);
Color get success => brightness == Brightness.dark Color get success => brightness == Brightness.dark
? const Color(0XFF32A64C) ? const Color(0xFF32A64C)
: const Color(0XFF2BB24A); : const Color(0xFF2BB24A);
} }

View File

@ -1,6 +1,7 @@
enum AppState { enum AppState {
idle, idle,
busy, busy,
isolatedBusy,
failed, failed,
} }

View File

@ -0,0 +1,5 @@
class AlertData {
final String message;
AlertData({required this.message});
}

View File

@ -1,7 +1,7 @@
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/pages/authentication/authentication_state.dart'; import 'package:didvan/pages/authentication/authentication_state.dart';
import 'package:didvan/pages/authentication/screens/password.dart'; import 'package:didvan/pages/authentication/screens/password.dart';
import 'package:didvan/pages/authentication/screens/phone_number.dart'; import 'package:didvan/pages/authentication/screens/username.dart';
import 'package:didvan/pages/authentication/screens/reset_password.dart'; import 'package:didvan/pages/authentication/screens/reset_password.dart';
import 'package:didvan/pages/authentication/screens/verification.dart'; import 'package:didvan/pages/authentication/screens/verification.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -16,7 +16,7 @@ class Authentication extends StatefulWidget {
class _AuthenticationState extends State<Authentication> { class _AuthenticationState extends State<Authentication> {
final List<Widget> _pages = const [ final List<Widget> _pages = const [
PhoneNumberInput(), UsernameInput(),
PasswordInput(), PasswordInput(),
Verification(), Verification(),
ResetPassword(), ResetPassword(),
@ -26,9 +26,18 @@ class _AuthenticationState extends State<Authentication> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Consumer<AuthenticationState>( body: Consumer<AuthenticationState>(
builder: (context, state, child) => AnimatedSwitcher( builder: (context, state, child) => WillPopScope(
duration: DesignConfig.mediumAnimationDuration, onWillPop: () async {
child: _pages[state.currentPageIndex], if (state.currentPageIndex == 0) {
return true;
}
state.currentPageIndex--;
return false;
},
child: AnimatedSwitcher(
duration: DesignConfig.mediumAnimationDuration,
child: _pages[state.currentPageIndex],
),
), ),
), ),
); );

View File

@ -1,8 +1,13 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/alert_data.dart';
import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/core_provider.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/utils/actions_sheet.dart';
class AuthenticationState extends CoreProvier { class AuthenticationState extends CoreProvier {
int _currentPageIndex = 0; int _currentPageIndex = 0;
String phoneNumber = ''; String username = '';
String password = ''; String password = '';
String verificationCode = ''; String verificationCode = '';
@ -12,4 +17,38 @@ class AuthenticationState extends CoreProvier {
} }
int get currentPageIndex => _currentPageIndex; int get currentPageIndex => _currentPageIndex;
Future<void> confirmUsername() async {
appState = AppState.isolatedBusy;
final RequestService service = RequestService(
RequestHelper.confirmUsername,
useAutherization: false,
body: {'username': username},
);
await service.post();
if (service.isSuccess && service.result['confirmed']) {
appState = AppState.idle;
currentPageIndex++;
} else {
appState = AppState.failed;
ActionSheetUtils.showAlert(AlertData(message: service.errorMessage));
}
}
Future<String?> login() async {
appState = AppState.isolatedBusy;
final RequestService service = RequestService(
RequestHelper.login,
useAutherization: false,
body: {'username': username, "password": password},
);
await service.post();
if (service.isSuccess && service.result['loggedIn']) {
appState = AppState.idle;
return service.result['token'];
} else {
appState = AppState.failed;
ActionSheetUtils.showAlert(AlertData(message: service.errorMessage));
}
}
} }

View File

@ -1,26 +1,43 @@
import 'package:didvan/pages/authentication/authentication_state.dart'; import 'package:didvan/pages/authentication/authentication_state.dart';
import 'package:didvan/pages/authentication/widgets/authentication_layout.dart'; import 'package:didvan/pages/authentication/widgets/authentication_layout.dart';
import 'package:didvan/providers/user_provider.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/widgets/didvan/button.dart'; import 'package:didvan/widgets/didvan/button.dart';
import 'package:didvan/widgets/didvan/text.dart'; import 'package:didvan/widgets/didvan/text.dart';
import 'package:didvan/widgets/didvan/text_field.dart'; import 'package:didvan/widgets/didvan/text_field.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class PasswordInput extends StatelessWidget { class PasswordInput extends StatefulWidget {
const PasswordInput({Key? key}) : super(key: key); const PasswordInput({Key? key}) : super(key: key);
@override
State<PasswordInput> createState() => _PasswordInputState();
}
class _PasswordInputState extends State<PasswordInput> {
final _formKey = GlobalKey<FormState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AuthenticationState state = context.read<AuthenticationState>(); final AuthenticationState state = context.read<AuthenticationState>();
return AuthenticationLayout( return AuthenticationLayout(
appBarTitle: 'ورود با شماره موبایل ' + state.phoneNumber, appBarTitle:
'ورود با ${state.username.contains('09') ? 'شماره موبایل' : 'نام کاربری'} ${state.username}',
children: [ children: [
DidvanTextField( Form(
onChanged: (value) => state.password = value, key: _formKey,
autoFocus: true, child: DidvanTextField(
title: 'کلمه عبور', onChanged: (value) => state.password = value,
hintText: 'کلمه عبور', autoFocus: true,
obsecureText: true, title: 'کلمه عبور',
hintText: 'کلمه عبور',
obsecureText: true,
validator: (value) => value!.length < 8
? 'کلمه عبور نمی‌تواند از 8 کاراکتر کمتر باشد'
: null,
),
), ),
const SizedBox( const SizedBox(
height: 32, height: 32,
@ -35,7 +52,7 @@ class PasswordInput extends StatelessWidget {
), ),
const Spacer(), const Spacer(),
DidvanButton( DidvanButton(
onPressed: () {}, onPressed: () => _onPressed(context),
title: 'ورود', title: 'ورود',
), ),
const SizedBox( const SizedBox(
@ -44,4 +61,18 @@ class PasswordInput extends StatelessWidget {
], ],
); );
} }
Future<void> _onPressed(BuildContext context) async {
if (!_formKey.currentState!.validate()) {
return;
}
final state = context.read<AuthenticationState>();
final token = await state.login();
if (token != null) {
final userProvider = context.read<UserProvider>();
await userProvider.setAndGetToken(token: token);
RequestService.token = token;
Navigator.of(context).pushReplacementNamed(Routes.home);
}
}
} }

View File

@ -6,8 +6,8 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class PhoneNumberInput extends StatelessWidget { class UsernameInput extends StatelessWidget {
const PhoneNumberInput({ const UsernameInput({
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -17,12 +17,15 @@ class PhoneNumberInput extends StatelessWidget {
return AuthenticationLayout( return AuthenticationLayout(
children: [ children: [
DidvanTextField( DidvanTextField(
title: 'شماره موبایل', initialValue: state.username,
textInputType: TextInputType.phone, title: 'نام کاربری یا شماره موبایل',
hintText: 'شماره موبایل', hintText: 'نام کاربری یا شماره موبایل',
textAlign: TextAlign.center, textAlign: TextAlign.center,
validator: (value) => value!.length < 4
? 'نام کاربری نمی‌تواند از 4 کاراکتر کوچکتر باشد'
: null,
onChanged: (value) { onChanged: (value) {
state.phoneNumber = value; state.username = value;
}, },
), ),
const SizedBox( const SizedBox(
@ -30,9 +33,7 @@ class PhoneNumberInput extends StatelessWidget {
), ),
DidvanButton( DidvanButton(
title: 'ورود', title: 'ورود',
onPressed: () { onPressed: state.confirmUsername,
state.currentPageIndex = 1;
},
), ),
const Spacer(), const Spacer(),
Padding( Padding(

View File

@ -26,7 +26,7 @@ class Verification extends StatelessWidget {
height: 8, height: 8,
), ),
DidvanText( DidvanText(
state.phoneNumber, state.username,
style: Theme.of(context).textTheme.subtitle1, style: Theme.of(context).textTheme.subtitle1,
), ),
const SizedBox( const SizedBox(
@ -65,7 +65,7 @@ class Verification extends StatelessWidget {
onPressed: () { onPressed: () {
state.currentPageIndex++; state.currentPageIndex++;
}, },
title: 'تایید', title: 'ارسال مجدد کد',
), ),
const SizedBox( const SizedBox(
height: 48, height: 48,

View File

@ -14,7 +14,6 @@ class AuthenticationLayout extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SingleChildScrollView( return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
physics: const NeverScrollableScrollPhysics(),
child: SizedBox( child: SizedBox(
height: MediaQuery.of(context).size.height, height: MediaQuery.of(context).size.height,
child: Column( child: Column(
@ -34,7 +33,9 @@ class AuthenticationLayout extends StatelessWidget {
right: 100, right: 100,
bottom: 40, bottom: 40,
), ),
child: DidvanVerticalLogo(), child: DidvanVerticalLogo(
height: 200,
),
), ),
...children, ...children,
], ],

View File

@ -1,6 +1,7 @@
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/main.dart'; import 'package:didvan/main.dart';
import 'package:didvan/providers/theme_provider.dart'; import 'package:didvan/providers/theme_provider.dart';
import 'package:didvan/providers/user_provider.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/utils/actions_sheet.dart'; import 'package:didvan/utils/actions_sheet.dart';
@ -36,8 +37,11 @@ class _SplashState extends State<Splash> {
_isGettingThemeData = false; _isGettingThemeData = false;
}), }),
); );
final String? token = await context.read<UserProvider>().setAndGetToken();
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
Navigator.of(context).pushReplacementNamed(Routes.home); Navigator.of(context).pushReplacementNamed(
token == null ? Routes.authenticaion : Routes.home,
);
} }
@override @override

View File

@ -1,10 +1,17 @@
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/utils/actions_sheet.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
class CoreProvier with ChangeNotifier { class CoreProvier with ChangeNotifier {
AppState _appState = AppState.idle; AppState _appState = AppState.idle;
set appState(AppState newState) { set appState(AppState newState) {
if (newState == AppState.isolatedBusy) {
ActionSheetUtils.showLogoLoadingIndicator();
}
if (_appState == AppState.isolatedBusy) {
ActionSheetUtils.pop();
}
_appState = newState; _appState = newState;
notifyListeners(); notifyListeners();
} }

View File

@ -1,3 +1,13 @@
import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/core_provider.dart';
import 'package:hive/hive.dart';
class UserProvider extends CoreProvier {} class UserProvider extends CoreProvier {
Future<String?> setAndGetToken({String? token}) async {
final box = await Hive.openBox('autherization');
if (token != null) {
await box.put('token', token);
} else {
return box.toMap()['token'];
}
}
}

View File

@ -11,7 +11,8 @@ class RequestService {
Map get result => _body['result'] ?? const {}; Map get result => _body['result'] ?? const {};
Map get errors => _body['errors'] ?? const {}; Map get errors => _body['errors'] ?? const {};
String? errorMessage; String errorMessage =
'خطا! لطفا اتصال اینترنت خود را بررسی و مجددا تلاش نمایید.';
dynamic _body; dynamic _body;
final Map<String, String> _headers = { final Map<String, String> _headers = {
@ -150,7 +151,7 @@ class RequestService {
_body = json.decode(response.body); _body = json.decode(response.body);
} }
} }
errorMessage = _errorMessageGenerator(); errorMessage = _errorMessageGenerator(response);
} }
bool _handleError(http.Response? response) { bool _handleError(http.Response? response) {
@ -162,8 +163,8 @@ class RequestService {
isSuccess = false; isSuccess = false;
return false; return false;
} }
if (response.statusCode != 200 && response.statusCode != 204) { if (response.statusCode != 200) {
String data; dynamic data;
if (response.body.isEmpty) { if (response.body.isEmpty) {
data = 'No results!'; data = 'No results!';
} else if (response.body.contains('<!DOCTYPE html>')) { } else if (response.body.contains('<!DOCTYPE html>')) {
@ -182,9 +183,15 @@ class RequestService {
return true; return true;
} }
String _errorMessageGenerator() { String _errorMessageGenerator(http.Response? response) {
return isSuccess String? error;
? result['msg'] if (response != null) {
: 'خطا! لطفا اتصال اینترنت خود را بررسی و مجددا تلاش نمایید.'; if (!response.body.contains('<!DOCTYPE html>')) {
if (result.isNotEmpty) {
error = result['msg'];
}
}
}
return error ?? 'خطا! لطفا اتصال اینترنت خود را بررسی و مجددا تلاش نمایید.';
} }
} }

View File

@ -1,8 +1,10 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/assets.dart'; import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/models/view/alert_data.dart';
import 'package:didvan/widgets/didvan/button.dart'; import 'package:didvan/widgets/didvan/button.dart';
import 'package:didvan/widgets/didvan/text.dart'; import 'package:didvan/widgets/didvan/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -15,13 +17,15 @@ class ActionSheetUtils {
static Future<void> showLogoLoadingIndicator() async { static Future<void> showLogoLoadingIndicator() async {
await showDialog( await showDialog(
context: context, context: context,
builder: (context) => _customSystemOverlayStyle( builder: (context) =>
child: Padding( // _customSystemOverlayStyle(
padding: EdgeInsets.symmetric( // child:
horizontal: MediaQuery.of(context).size.width / 3, Padding(
), padding: EdgeInsets.symmetric(
child: const RiveAnimation.asset(Assets.logoLoadingAnimation), horizontal: MediaQuery.of(context).size.width / 3,
), ),
child: const RiveAnimation.asset(Assets.logoLoadingAnimation),
// ),
), ),
); );
} }
@ -29,12 +33,29 @@ class ActionSheetUtils {
static AnnotatedRegion _customSystemOverlayStyle({required Widget child}) { static AnnotatedRegion _customSystemOverlayStyle({required Widget child}) {
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: DesignConfig.systemUiOverlayStyle.copyWith( value: DesignConfig.systemUiOverlayStyle.copyWith(
// systemNavigationBarColor: Colors.black45, systemNavigationBarColor: DesignConfig
), .systemUiOverlayStyle.systemNavigationBarColor!
.withBlue(20),
),
child: child, child: child,
); );
} }
static Future<void> showAlert(AlertData alertData) async {
await Flushbar(
margin: const EdgeInsets.symmetric(horizontal: 16),
message: alertData.message,
backgroundColor: Theme.of(context).colorScheme.focused,
borderRadius: DesignConfig.mediumBorderRadius,
messageColor: Theme.of(context).colorScheme.text,
flushbarPosition: FlushbarPosition.TOP,
duration: const Duration(seconds: 2),
boxShadows: [
BoxShadow(color: Theme.of(context).colorScheme.cardBorder),
],
).show(context);
}
static Future<void> showBottomSheet({required ActionSheetData data}) async { static Future<void> showBottomSheet({required ActionSheetData data}) async {
await showModalBottomSheet( await showModalBottomSheet(
context: context, context: context,
@ -103,4 +124,9 @@ class ActionSheetUtils {
), ),
); );
} }
static void pop() {
DesignConfig.updateSystemUiOverlayStyle();
Navigator.of(context).pop();
}
} }

View File

@ -24,7 +24,10 @@ class DidvanButton extends StatelessWidget {
), ),
height: 48, height: 48,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
onPressed: onPressed, onPressed: () {
FocusScope.of(context).unfocus();
onPressed?.call();
},
child: _childBuilder(), child: _childBuilder(),
), ),
); );

View File

@ -1,5 +1,7 @@
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/widgets/animated_visibility.dart';
import 'package:didvan/widgets/didvan/text.dart'; import 'package:didvan/widgets/didvan/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -36,11 +38,14 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
final FocusNode _focusNode = FocusNode(); final FocusNode _focusNode = FocusNode();
final TextEditingController _controller = TextEditingController(); final TextEditingController _controller = TextEditingController();
bool _hasError = false;
bool _hideContent = false; bool _hideContent = false;
String? _error;
@override @override
void initState() { void initState() {
if (widget.initialValue != null) {
_controller.text = widget.initialValue;
}
_hideContent = widget.obsecureText; _hideContent = widget.obsecureText;
_focusNode.addListener(() { _focusNode.addListener(() {
setState(() {}); setState(() {});
@ -57,7 +62,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
if (widget.title != null) if (widget.title != null)
DidvanText( DidvanText(
widget.title!, widget.title!,
color: !widget.enabled ? Theme.of(context).colorScheme.hint : null, color: _titleColor(),
), ),
if (widget.title != null) const SizedBox(height: 8), if (widget.title != null) const SizedBox(height: 8),
Container( Container(
@ -85,6 +90,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
enabled: widget.enabled, enabled: widget.enabled,
border: InputBorder.none, border: InputBorder.none,
hintText: widget.hintText, hintText: widget.hintText,
errorStyle: const TextStyle(height: 0),
hintStyle: Theme.of(context) hintStyle: Theme.of(context)
.textTheme .textTheme
.bodyText2! .bodyText2!
@ -92,6 +98,24 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
), ),
), ),
), ),
const SizedBox(height: 8),
AnimatedVisibility(
isVisible: _error != null,
duration: DesignConfig.lowAnimationDuration,
child: Row(
children: [
Icon(
DidvanIcons.lightbulb_exclamation_regular,
color: Theme.of(context).colorScheme.error,
size: 14,
),
DidvanText(
_error ?? '',
style: Theme.of(context).textTheme.caption,
color: Theme.of(context).colorScheme.error,
),
],
))
], ],
); );
} }
@ -99,12 +123,21 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
Color _borderColor() { Color _borderColor() {
if (_focusNode.hasFocus) { if (_focusNode.hasFocus) {
return Theme.of(context).colorScheme.primary; return Theme.of(context).colorScheme.primary;
} else if (_hasError) { } else if (_error != null) {
return Theme.of(context).colorScheme.error; return Theme.of(context).colorScheme.error;
} }
return Theme.of(context).colorScheme.border; return Theme.of(context).colorScheme.border;
} }
Color? _titleColor() {
if (!widget.enabled) {
return Theme.of(context).colorScheme.hint;
}
if (_error != null) {
return Theme.of(context).colorScheme.error;
}
}
Color _fillColor() { Color _fillColor() {
if (!widget.enabled) { if (!widget.enabled) {
return Theme.of(context).colorScheme.secondCTA; return Theme.of(context).colorScheme.secondCTA;
@ -112,7 +145,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
if (_focusNode.hasFocus) { if (_focusNode.hasFocus) {
return Theme.of(context).colorScheme.focused; return Theme.of(context).colorScheme.focused;
} }
if (_hasError) { if (_error != null) {
return Theme.of(context).colorScheme.errorLight; return Theme.of(context).colorScheme.errorLight;
} }
return Theme.of(context).colorScheme.surface; return Theme.of(context).colorScheme.surface;
@ -129,7 +162,9 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
}); });
}, },
child: Icon( child: Icon(
_hideContent ? Icons.remove_red_eye : Icons.remove_red_eye_outlined, _hideContent
? DidvanIcons.eye_regular
: DidvanIcons.eye_slash_regular,
), ),
), ),
); );
@ -143,6 +178,18 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
} }
String? _validator(String? value) { String? _validator(String? value) {
_hasError = false; if (widget.validator != null) {
final String? error = widget.validator!(value);
if (error != null) {
setState(() {
_error = error;
});
return '';
} else {
setState(() {
_error = null;
});
}
}
} }
} }

View File

@ -3,10 +3,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
class DidvanVerticalLogo extends StatelessWidget { class DidvanVerticalLogo extends StatelessWidget {
const DidvanVerticalLogo({Key? key}) : super(key: key); final double? height;
const DidvanVerticalLogo({Key? key, this.height}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SvgPicture.asset(Assets.verticalLogoWithText); return SvgPicture.asset(Assets.verticalLogoWithText, height: height);
} }
} }

View File

@ -1,6 +1,13 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
another_flushbar:
dependency: "direct main"
description:
name: another_flushbar
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.28"
async: async:
dependency: transitive dependency: transitive
description: description:

View File

@ -57,6 +57,7 @@ dependencies:
just_audio: ^0.9.18 just_audio: ^0.9.18
record_web: ^0.2.1 record_web: ^0.2.1
just_waveform: ^0.0.1 just_waveform: ^0.0.1
another_flushbar: ^1.10.28
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: