Refactor FieldCubit creation for user forms

Move all cubit fields to a special factory which encapsulates all logic related to their default properties, which leaves possibility for future dependency inversion on that factory or future factories of other cubit fields (in case we will have to replace it with other implementations).
pull/89/head
NaiJi ✨ 2022-05-04 19:58:47 +03:00
parent 4c99579f13
commit 4c7cf05578
8 changed files with 79 additions and 60 deletions

View File

@ -66,7 +66,6 @@ class ServerApi extends ApiMap {
Future<ApiResponse<User>> createUser(User user) async {
var client = await getClient();
// POST request with JSON body containing username and password
var makeErrorApiReponse = (int status) {
return ApiResponse(

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
class FieldCubitFactory {
FieldCubitFactory(this.context);
/* A common user login field.
* - Available characters are lowercase a-z, digits and underscore _
* - Must start with either a-z or underscore
* - Must be no longer than 'userMaxLength' characters
* - Must not be empty
* - Must not be a reserved root login
* - Must be unique
*/
FieldCubit<String> createUserLoginField() {
final userAllowedRegExp = RegExp(r"^[a-z_][a-z0-9_]+$");
const userMaxLength = 31;
return FieldCubit(
initalValue: '',
validations: [
ValidationModel<String>(
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
ValidationModel(
(login) => context.read<UsersCubit>().state.isLoginRegistered(login),
'validations.user_already_exist'.tr(),
),
RequiredStringValidation('validations.required'.tr()),
LengthStringLongerValidation(userMaxLength),
ValidationModel<String>((s) => !userAllowedRegExp.hasMatch(s),
'validations.invalid_format'.tr()),
],
);
}
/* A common user password field.
* - Must fail on the regural expression of invalid matches: [\n\r\s]+
* - Must not be empty
*/
FieldCubit<String> createUserPasswordField() {
var passwordForbiddenRegExp = RegExp(r"[\n\r\s]+");
return FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(password) => passwordForbiddenRegExp.hasMatch(password),
'validations.invalid_format'.tr()),
],
);
}
final BuildContext context;
}

View File

@ -2,33 +2,14 @@ import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'package:easy_localization/easy_localization.dart';
class RootUserFormCubit extends FormCubit {
RootUserFormCubit(this.initializingCubit) {
var userRegExp = RegExp(r"\W");
var passwordRegExp = RegExp(r"[\n\r\s]+");
userName = FieldCubit(
initalValue: '',
validations: [
ValidationModel<String>(
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
],
);
password = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>((s) => passwordRegExp.hasMatch(s),
'validations.invalid_format'.tr()),
],
);
RootUserFormCubit(
this.initializingCubit, final FieldCubitFactory fieldFactory) {
userName = fieldFactory.createUserLoginField();
password = fieldFactory.createUserPasswordField();
isVisible = FieldCubit(initalValue: false);

View File

@ -1,8 +1,7 @@
import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/models/job.dart';
import 'package:selfprivacy/logic/models/user.dart';
@ -11,41 +10,16 @@ import 'package:selfprivacy/utils/password_generator.dart';
class UserFormCubit extends FormCubit {
UserFormCubit({
required this.jobsCubit,
required List<User> users,
required FieldCubitFactory fieldFactory,
User? user,
}) {
var isEdit = user != null;
var userAllowedRegExp = RegExp(r"^[a-z_][a-z0-9_]+$");
const userMaxLength = 31;
var passwordForbiddenRegExp = RegExp(r"[\n\r\s]+");
login = FieldCubit(
initalValue: isEdit ? user!.login : '',
validations: [
ValidationModel<String>(
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
ValidationModel(
(login) => users.any((user) => user.login == login),
'validations.user_already_exist'.tr(),
),
RequiredStringValidation('validations.required'.tr()),
LengthStringLongerValidation(userMaxLength),
ValidationModel<String>((s) => !userAllowedRegExp.hasMatch(s),
'validations.invalid_format'.tr()),
],
);
password = FieldCubit(
initalValue:
isEdit ? (user?.password ?? '') : StringGenerators.userPassword(),
validations: [
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>((s) => passwordForbiddenRegExp.hasMatch(s),
'validations.invalid_format'.tr()),
],
);
login = fieldFactory.createUserLoginField();
login.setValue(isEdit ? user!.login : '');
password = fieldFactory.createUserPasswordField();
password.setValue(
isEdit ? (user?.password ?? '') : StringGenerators.userPassword());
super.addFields([login, password]);
}

View File

@ -22,5 +22,11 @@ class UsersState extends AppConfigDependendState {
);
}
bool isLoginRegistered(String login) {
return users.any((user) => user.login == login) ||
login == rootUser.login ||
login == primaryUser.login;
}
bool get isEmpty => users.isEmpty;
}

View File

@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/cloudflare_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/domain_cloudflare.dart';
@ -352,7 +353,8 @@ class InitializingPage extends StatelessWidget {
Widget _stepUser(AppConfigCubit initializingCubit) {
return BlocProvider(
create: (context) => RootUserFormCubit(initializingCubit),
create: (context) =>
RootUserFormCubit(initializingCubit, FieldCubitFactory(context)),
child: Builder(builder: (context) {
var formCubitState = context.watch<RootUserFormCubit>().state;

View File

@ -24,7 +24,7 @@ class _NewUser extends StatelessWidget {
}
return UserFormCubit(
jobsCubit: jobCubit,
users: users,
fieldFactory: FieldCubitFactory(context),
);
},
child: Builder(builder: (context) {

View File

@ -6,6 +6,7 @@ import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart';
import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';