diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..80a3e35b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# editorconfig.org +root = true + +[*] +indent_size = 2 +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.dart] +max_line_length = 150 + +[*.md] +trim_trailing_whitespace = false diff --git a/assets/translations/en.json b/assets/translations/en.json index 0b9b3f5e..42178a89 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -26,7 +26,8 @@ "details": "Details", "no_data": "No data", "wait": "Wait", - "remove": "Remove" + "remove": "Remove", + "apply": "Apply" }, "more": { "_comment": "'More' tab", @@ -34,6 +35,9 @@ "about_project": "About us", "about_app": "About application", "onboarding": "Onboarding", + "create_ssh_key": "Create ssh key", + "generate_key": "Generate key", + "generate_key_text": "You can generate ssh key", "console": "Console", "about_app_page": { "text": "Application version v.{}" @@ -147,6 +151,13 @@ "bottom_sheet": { "1": "You can connect and create a new user here:" } + }, + "vpn": { + "title": "VPN Server", + "subtitle": "Private VPN server", + "bottom_sheet": { + "1": "Openconnect VPN Server. Engine for secure and scalable VPN infrastructure" + } } }, "users": { @@ -212,7 +223,12 @@ "_comment": "Jobs list", "title": "Jobs list", "start": "Start", - "empty": "No jobs" + "empty": "No jobs", + "createUser": "Create", + "serviceTurnOff": "Turn off", + "serviceTurnOn": "Turn on", + "jobAdded": "Job added", + "runJobs": "Run jobs" }, "validations": { "required": "Required", @@ -222,4 +238,4 @@ "length": "Length is [] shoud be {}", "user_alredy_exist": "Already exists" } -} \ No newline at end of file +} diff --git a/assets/translations/ru.json b/assets/translations/ru.json index bc89ab19..ac731421 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -26,7 +26,8 @@ "details": "Детальная информация", "no_data": "Нет данных", "wait": "Ожидайте", - "remove": "Удалить" + "remove": "Удалить", + "apply": "Подать" }, "more": { "_comment": "вкладка еще", @@ -35,6 +36,9 @@ "about_app": "О приложении", "onboarding": "Приветствие", "console": "Консоль", + "create_ssh_key": "Создать ssh ключ", + "generate_key": "Сгенерировать ключ", + "generate_key_text": "Вы сможете сгенерировать ключ", "about_app_page": { "text": "Версия приложения: v.{}" }, @@ -212,7 +216,12 @@ "_comment": "Jobs list", "title": "Задачи", "start": "Начать выполенение", - "empty": "Пусто" + "empty": "Пусто", + "createUser": "Создать запись", + "serviceTurnOff": "Остановить", + "serviceTurnOn": "Запустить", + "jobAdded": "Задача добавленна", + "runJobs": "Запустите задачи" }, "validations": { "required": "обязательное поле", @@ -222,4 +231,4 @@ "length": "Длина строки [] должна быть {}", "user_alredy_exist": "Имя уже используется" } -} \ No newline at end of file +} diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78..f2872cf4 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 285337b4..373cec68 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -44,7 +44,7 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wakelock/ios" SPEC CHECKSUMS: - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c diff --git a/ios/build/Pods.build/Release-iphonesimulator/Flutter.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/Flutter.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/Flutter.build/dgph differ diff --git a/ios/build/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph differ diff --git a/ios/build/Pods.build/Release-iphonesimulator/flutter_secure_storage.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/flutter_secure_storage.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/flutter_secure_storage.build/dgph differ diff --git a/ios/build/Pods.build/Release-iphonesimulator/package_info.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/package_info.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/package_info.build/dgph differ diff --git a/ios/build/Pods.build/Release-iphonesimulator/path_provider.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/path_provider.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/path_provider.build/dgph differ diff --git a/ios/build/Pods.build/Release-iphonesimulator/share_plus.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/share_plus.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/share_plus.build/dgph differ diff --git a/ios/build/Pods.build/Release-iphonesimulator/shared_preferences.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/shared_preferences.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/shared_preferences.build/dgph differ diff --git a/ios/build/Pods.build/Release-iphonesimulator/url_launcher.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/url_launcher.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/url_launcher.build/dgph differ diff --git a/ios/build/Pods.build/Release-iphonesimulator/wakelock.build/dgph b/ios/build/Pods.build/Release-iphonesimulator/wakelock.build/dgph new file mode 100644 index 00000000..0c4ca337 Binary files /dev/null and b/ios/build/Pods.build/Release-iphonesimulator/wakelock.build/dgph differ diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 5fe71380..bc59e531 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -4,6 +4,7 @@ import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; +import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; class BlocAndProviderConfig extends StatelessWidget { @@ -13,11 +14,9 @@ class BlocAndProviderConfig extends StatelessWidget { @override Widget build(BuildContext context) { - // var platformBrightness = - // SchedulerBinding.instance.window.platformBrightness; - // var isDark = platformBrightness == Brightness.dark; var isDark = false; var usersCubit = UsersCubit(); + var servicesCubit = ServicesCubit(); return MultiProvider( providers: [ BlocProvider( @@ -28,11 +27,17 @@ class BlocAndProviderConfig extends StatelessWidget { ), BlocProvider( lazy: false, - create: (_) => AppConfigCubit()..load(), + create: (_) => AppConfigCubit(servicesCubit)..load(), ), BlocProvider(create: (_) => ProvidersCubit()), BlocProvider(create: (_) => usersCubit..load(), lazy: false), - BlocProvider(create: (_) => JobsCubit(usersCubit)), + BlocProvider(create: (_) => servicesCubit..load(), lazy: false), + BlocProvider( + create: (_) => JobsCubit( + usersCubit: usersCubit, + servicesCubit: servicesCubit, + ), + ), ], child: child, ); diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index 3f01e6d3..99b61cc8 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -2,6 +2,7 @@ import 'package:get_it/get_it.dart'; import 'package:selfprivacy/logic/get_it/api_config.dart'; import 'package:selfprivacy/logic/get_it/console.dart'; import 'package:selfprivacy/logic/get_it/navigation.dart'; +import 'package:selfprivacy/logic/get_it/ssh_helper.dart'; import 'package:selfprivacy/logic/get_it/timer.dart'; export 'package:selfprivacy/logic/get_it/api_config.dart'; @@ -9,7 +10,6 @@ export 'package:selfprivacy/logic/get_it/console.dart'; export 'package:selfprivacy/logic/get_it/navigation.dart'; export 'package:selfprivacy/logic/get_it/timer.dart'; - final getIt = GetIt.instance; Future getItSetup() async { @@ -17,6 +17,7 @@ Future getItSetup() async { getIt.registerSingleton(ConsoleModel()); getIt.registerSingleton(TimerModel()); + getIt.registerSingleton(SSHModel()..init()); getIt.registerSingleton(ApiConfigModel()..init()); await getIt.allReady(); diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index e704b50c..be47b879 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -20,22 +20,23 @@ class HiveConfig { await Hive.openBox(BNames.appSettings); await Hive.openBox(BNames.users); + await Hive.openBox(BNames.servicesState); - var cipher = HiveAesCipher(await getEncriptedKey()); - + var cipher = HiveAesCipher(await getEncriptedKey(BNames.key)); await Hive.openBox(BNames.appConfig, encryptionCipher: cipher); + var sshCipher = HiveAesCipher(await getEncriptedKey(BNames.sshEnckey)); + await Hive.openBox(BNames.sshConfig, encryptionCipher: sshCipher); } - static Future getEncriptedKey() async { - final FlutterSecureStorage secureStorage = FlutterSecureStorage(); - var containsEncryptionKey = - await secureStorage.containsKey(key: BNames.key); - if (!containsEncryptionKey) { + static Future getEncriptedKey(String encKey) async { + final secureStorage = FlutterSecureStorage(); + var hasEncryptionKey = await secureStorage.containsKey(key: encKey); + if (!hasEncryptionKey) { var key = Hive.generateSecureKey(); - await secureStorage.write(key: BNames.key, value: base64UrlEncode(key)); + await secureStorage.write(key: encKey, value: base64UrlEncode(key)); } - String? string = await secureStorage.read(key: BNames.key); + String? string = await secureStorage.read(key: encKey); return base64Url.decode(string!); } } @@ -47,8 +48,10 @@ class BNames { static String users = 'users'; static String appSettings = 'appSettings'; + static String servicesState = 'servicesState'; static String key = 'key'; + static String sshEnckey = 'sshEngkey'; static String cloudFlareDomain = 'cloudFlareDomain'; static String hetznerKey = 'hetznerKey'; @@ -61,4 +64,7 @@ class BNames { static String isLoading = 'isLoading'; static String isServerResetedFirstTime = 'isServerResetedFirstTime'; static String isServerResetedSecondTime = 'isServerResetedSecondTime'; + static String sshConfig = 'sshConfig'; + static String sshPrivateKey = "sshPrivateKey"; + static String sshPublicKey = "sshPublicKey"; } diff --git a/lib/logic/api_maps/hetzner.dart b/lib/logic/api_maps/hetzner.dart index 037db18c..2b714aad 100644 --- a/lib/logic/api_maps/hetzner.dart +++ b/lib/logic/api_maps/hetzner.dart @@ -7,7 +7,7 @@ import 'package:selfprivacy/logic/api_maps/api_map.dart'; import 'package:selfprivacy/logic/models/hetzner_server_info.dart'; import 'package:selfprivacy/logic/models/server_details.dart'; import 'package:selfprivacy/logic/models/user.dart'; -import 'package:selfprivacy/utils/password_generator2.dart'; +import 'package:selfprivacy/utils/password_generator.dart'; class HetznerApi extends ApiMap { bool hasLoger; @@ -73,30 +73,28 @@ class HetznerApi extends ApiMap { required User rootUser, required String domainName, }) async { - var dbPassword = getRandomString(40); - - const chars = - 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; - - var dbStorageName = getRandomString(6, chars); var client = await getClient(); Response dbCreateResponse = await client.post( '/volumes', data: { "size": 10, - "name": dbStorageName, + "name": StringGenerators.dbStorageName(), "labels": {"labelkey": "value"}, "location": "fsn1", "automount": false, "format": "ext4" }, ); + + var dbPassword = StringGenerators.dbPassword(); var dbId = dbCreateResponse.data['volume']['id']; + /// add ssh key when you need it: e.g. "ssh_keys":["kherel"] + /// check the branch name, it could be "development" or "master". + var data = jsonDecode( - '''{"name":"$domainName","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[$dbId], "networks":[], "user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword.hash} SALT=${rootUser.hashPassword.salt} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}''' - ); + '''{"name":"$domainName","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[$dbId], "networks":[], "user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-21.05 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":true, "location": "fsn1"}'''); Response serverCreateResponse = await client.post( '/servers', diff --git a/lib/logic/api_maps/server.dart b/lib/logic/api_maps/server.dart index aae49538..6e5686f6 100644 --- a/lib/logic/api_maps/server.dart +++ b/lib/logic/api_maps/server.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/models/user.dart'; import 'api_map.dart'; @@ -89,4 +90,44 @@ class ServerApi extends ApiMap { close(client); return res; } + + Future switchService(ServiceTypes type, bool needToTurnOn) async { + var client = await getClient(); + client.post('/services/${type.url}/${needToTurnOn ? 'enable' : 'disable'}'); + client.close(); + } + + Future sendSsh(String ssh) async { + var client = await getClient(); + client.post( + '/services/ssh/enable', + data: {"public_key": ssh}, + ); + client.close(); + } +} + +extension UrlServerExt on ServiceTypes { + String get url { + switch (this) { + // case ServiceTypes.mail: + // return ''; // cannot be swithch off + // case ServiceTypes.messenger: + // return ''; // external service + // case ServiceTypes.video: + // return ''; // jeetsu meet not working + case ServiceTypes.passwordManager: + return 'bitwarden'; + case ServiceTypes.cloud: + return 'nextcloud'; + case ServiceTypes.socialNetwork: + return 'pleroma'; + case ServiceTypes.git: + return 'gitea'; + case ServiceTypes.vpn: + return 'ocserv'; + default: + throw Exception('wrong state'); + } + } } diff --git a/lib/logic/common_enum/common_enum.dart b/lib/logic/common_enum/common_enum.dart index 912c4cb6..e3d81309 100644 --- a/lib/logic/common_enum/common_enum.dart +++ b/lib/logic/common_enum/common_enum.dart @@ -1,3 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:ionicons/ionicons.dart'; +import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; + enum InitializingSteps { setHeznerKey, setCloudFlareKey, @@ -10,3 +15,80 @@ enum InitializingSteps { } enum Period { hour, day, month } +enum ServiceTypes { + mail, + messenger, + passwordManager, + video, + cloud, + socialNetwork, + git, + vpn, +} + +extension ServiceTypesExt on ServiceTypes { + String get title { + switch (this) { + case ServiceTypes.mail: + return 'services.mail.title'.tr(); + case ServiceTypes.messenger: + return 'services.messenger.title'.tr(); + case ServiceTypes.passwordManager: + return 'services.password_manager.title'.tr(); + case ServiceTypes.video: + return 'services.video.title'.tr(); + case ServiceTypes.cloud: + return 'services.cloud.title'.tr(); + case ServiceTypes.socialNetwork: + return 'services.social_network.title'.tr(); + case ServiceTypes.git: + return 'services.git.title'.tr(); + case ServiceTypes.vpn: + return 'services.vpn.title'.tr(); + } + } + + String get subtitle { + switch (this) { + case ServiceTypes.mail: + return 'services.mail.subtitle'.tr(); + case ServiceTypes.messenger: + return 'services.messenger.subtitle'.tr(); + case ServiceTypes.passwordManager: + return 'services.password_manager.subtitle'.tr(); + case ServiceTypes.video: + return 'services.video.subtitle'.tr(); + case ServiceTypes.cloud: + return 'services.cloud.subtitle'.tr(); + case ServiceTypes.socialNetwork: + return 'services.social_network.subtitle'.tr(); + case ServiceTypes.git: + return 'services.git.subtitle'.tr(); + case ServiceTypes.vpn: + return 'services.vpn.subtitle'.tr(); + } + } + + IconData get icon { + switch (this) { + case ServiceTypes.mail: + return BrandIcons.envelope; + case ServiceTypes.messenger: + return BrandIcons.messanger; + case ServiceTypes.passwordManager: + return BrandIcons.key; + case ServiceTypes.video: + return BrandIcons.webcam; + case ServiceTypes.cloud: + return BrandIcons.upload; + case ServiceTypes.socialNetwork: + return BrandIcons.social; + case ServiceTypes.git: + return BrandIcons.git; + case ServiceTypes.vpn: + return Ionicons.shield_checkmark_outline; + } + } + + String get txt => this.toString().split('.')[1]; +} diff --git a/lib/logic/cubit/app_config/app_config_cubit.dart b/lib/logic/cubit/app_config/app_config_cubit.dart index d8b8f36e..e86cead1 100644 --- a/lib/logic/cubit/app_config/app_config_cubit.dart +++ b/lib/logic/cubit/app_config/app_config_cubit.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/cloudflare_domain.dart'; @@ -43,9 +44,10 @@ part 'app_config_state.dart'; /// c. if server is okay set that fully checked class AppConfigCubit extends Cubit { - AppConfigCubit() : super(InitialAppConfigState()); + AppConfigCubit(this.servicesCubit) : super(InitialAppConfigState()); final repository = AppConfigRepository(); + final ServicesCubit servicesCubit; Future load() async { var state = await repository.load(); @@ -232,6 +234,7 @@ class AppConfigCubit extends Cubit { if (isServerWorking) { await repository.saveHasFinalChecked(true); + servicesCubit.allOn(); emit(state.copyWith( hasFinalChecked: true, @@ -259,12 +262,16 @@ class AppConfigCubit extends Cubit { void clearAppConfig() { closeTimer(); + servicesCubit.allOff(); + repository.clearAppConfig(); emit(InitialAppConfigState()); } Future serverDelete() async { closeTimer(); + servicesCubit.allOff(); + if (state.hetznerServer != null) { await repository.deleteServer(state.cloudFlareDomain!); } diff --git a/lib/logic/cubit/forms/user/user_form_cubit.dart b/lib/logic/cubit/forms/user/user_form_cubit.dart index a30b90f8..dd4d2bcf 100644 --- a/lib/logic/cubit/forms/user/user_form_cubit.dart +++ b/lib/logic/cubit/forms/user/user_form_cubit.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:cubit_form/cubit_form.dart'; import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart'; -import 'package:selfprivacy/logic/models/jobs/job.dart'; +import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/user.dart'; -import 'package:selfprivacy/utils/password_generator.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:selfprivacy/utils/password_generator.dart'; class UserFormCubit extends FormCubit { UserFormCubit({ @@ -32,7 +32,7 @@ class UserFormCubit extends FormCubit { ); password = FieldCubit( - initalValue: isEdit ? user!.password : genPass(), + initalValue: isEdit ? user!.password : StringGenerators.userPassword(), validations: [ RequiredStringValidation('validations.required'.tr()), ValidationModel((s) => passwordRegExp.hasMatch(s), @@ -56,7 +56,7 @@ class UserFormCubit extends FormCubit { late FieldCubit password; void genNewPassword() { - password.externalSetValue(genPass()); + password.externalSetValue(StringGenerators.userPassword()); } final JobsCubit jobsCubit; diff --git a/lib/logic/cubit/jobs/jobs_cubit.dart b/lib/logic/cubit/jobs/jobs_cubit.dart index aebe2ddd..d9d902a9 100644 --- a/lib/logic/cubit/jobs/jobs_cubit.dart +++ b/lib/logic/cubit/jobs/jobs_cubit.dart @@ -1,19 +1,27 @@ +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/server.dart'; +import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; -import 'package:selfprivacy/logic/models/jobs/job.dart'; +import 'package:selfprivacy/logic/get_it/ssh_helper.dart'; +import 'package:selfprivacy/logic/models/job.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/logic/models/user.dart'; export 'package:provider/provider.dart'; +import 'package:easy_localization/easy_localization.dart'; part 'jobs_state.dart'; class JobsCubit extends Cubit { - JobsCubit(this.usersCubit) : super(JobsStateEmpty()); + JobsCubit({ + required this.usersCubit, + required this.servicesCubit, + }) : super(JobsStateEmpty()); final api = ServerApi(); final UsersCubit usersCubit; + final ServicesCubit servicesCubit; void addJob(Job job) { var newJobsList = []; @@ -21,6 +29,10 @@ class JobsCubit extends Cubit { newJobsList.addAll((state as JobsStateWithJobs).jobList); } newJobsList.add(job); + getIt().showSnackBar(SnackBar( + content: Text('jobs.jobAdded'.tr()), + duration: const Duration(seconds: 2), + )); emit(JobsStateWithJobs(newJobsList)); } @@ -29,16 +41,63 @@ class JobsCubit extends Cubit { emit(newState); } + void createOrRemoveServiceToggleJob(ServiceToggleJob job) { + var newJobsList = []; + if (state is JobsStateWithJobs) { + newJobsList.addAll((state as JobsStateWithJobs).jobList); + } + var needToRemoveJob = + newJobsList.any((el) => el is ServiceToggleJob && el.type == job.type); + if (needToRemoveJob) { + var removingJob = newJobsList + .firstWhere(((el) => el is ServiceToggleJob && el.type == job.type)); + removeJob(removingJob.id); + } else { + newJobsList.add(job); + getIt().showSnackBar(SnackBar( + content: Text('jobs.jobAdded'.tr()), + duration: const Duration(seconds: 2), + )); + emit(JobsStateWithJobs(newJobsList)); + } + } + + void createShhJobIfNotExist(CreateSSHKeyJob job) { + var newJobsList = []; + if (state is JobsStateWithJobs) { + newJobsList.addAll((state as JobsStateWithJobs).jobList); + } + var isExistInJobList = newJobsList.any((el) => el is CreateSSHKeyJob); + if (!isExistInJobList) { + newJobsList.add(job); + getIt().showSnackBar(SnackBar( + content: Text('jobs.jobAdded'.tr()), + duration: const Duration(seconds: 2), + )); + emit(JobsStateWithJobs(newJobsList)); + } + } + Future applyAll() async { if (state is JobsStateWithJobs) { var jobs = (state as JobsStateWithJobs).jobList; emit(JobsStateLoading()); - var newUsers = []; for (var job in jobs) { if (job is CreateUserJob) { newUsers.add(job.user); await api.createUser(job.user); + } else if (job is ServiceToggleJob) { + await api.switchService(job.type, job.needToTurnOn); + if (job.needToTurnOn) { + servicesCubit.turnOnist([job.type]); + } else { + servicesCubit.turnOffList([job.type]); + } + } + if (job is CreateSSHKeyJob) { + await getIt().generateKeys(); + api.sendSsh(getIt().savedPubKey!); } } diff --git a/lib/logic/cubit/services/services_cubit.dart b/lib/logic/cubit/services/services_cubit.dart new file mode 100644 index 00000000..c5fb6cf3 --- /dev/null +++ b/lib/logic/cubit/services/services_cubit.dart @@ -0,0 +1,63 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:hive/hive.dart'; +import 'package:selfprivacy/config/hive_config.dart'; +import 'package:selfprivacy/logic/common_enum/common_enum.dart'; + +part 'services_state.dart'; + +class ServicesCubit extends Cubit { + ServicesCubit() : super(ServicesState.allOff()); + + Box box = Hive.box(BNames.servicesState); + + void load() { + emit( + ServicesState( + isPasswordManagerEnable: + box.get(ServiceTypes.passwordManager.txt, defaultValue: false), + isCloudEnable: box.get(ServiceTypes.cloud.txt, defaultValue: false), + isGitEnable: box.get(ServiceTypes.git.txt, defaultValue: false), + isSocialNetworkEnable: + box.get(ServiceTypes.socialNetwork.txt, defaultValue: false), + isVpnEnable: box.get(ServiceTypes.vpn.txt, defaultValue: false), + ), + ); + } + + void allOn() { + box.put(ServiceTypes.passwordManager.txt, true); + box.put(ServiceTypes.cloud.txt, true); + box.put(ServiceTypes.git.txt, true); + box.put(ServiceTypes.socialNetwork.txt, true); + box.put(ServiceTypes.vpn.txt, true); + + emit(ServicesState.allOn()); + } + + void allOff() { + box.put(ServiceTypes.passwordManager.txt, false); + box.put(ServiceTypes.cloud.txt, false); + box.put(ServiceTypes.git.txt, false); + box.put(ServiceTypes.socialNetwork.txt, false); + box.put(ServiceTypes.vpn.txt, false); + + emit(ServicesState.allOff()); + } + + void turnOffList(List list) { + for (final service in list) { + box.put(service.txt, false); + } + + emit(state.disableList(list)); + } + + void turnOnist(List list) { + for (final service in list) { + box.put(service.txt, true); + } + + emit(state.enableList(list)); + } +} diff --git a/lib/logic/cubit/services/services_state.dart b/lib/logic/cubit/services/services_state.dart new file mode 100644 index 00000000..a41b5a59 --- /dev/null +++ b/lib/logic/cubit/services/services_state.dart @@ -0,0 +1,93 @@ +part of 'services_cubit.dart'; + +class ServicesState extends Equatable { + const ServicesState({ + required this.isPasswordManagerEnable, + required this.isCloudEnable, + required this.isGitEnable, + required this.isSocialNetworkEnable, + required this.isVpnEnable, + }); + + final bool isPasswordManagerEnable; + final bool isCloudEnable; + final bool isGitEnable; + final bool isSocialNetworkEnable; + final bool isVpnEnable; + + factory ServicesState.allOff() => ServicesState( + isPasswordManagerEnable: false, + isCloudEnable: false, + isGitEnable: false, + isSocialNetworkEnable: false, + isVpnEnable: false, + ); + factory ServicesState.allOn() => ServicesState( + isPasswordManagerEnable: true, + isCloudEnable: true, + isGitEnable: true, + isSocialNetworkEnable: true, + isVpnEnable: true, + ); + + ServicesState enableList( + List list, + ) => + ServicesState( + isPasswordManagerEnable: list.contains(ServiceTypes.passwordManager) + ? true + : isPasswordManagerEnable, + isCloudEnable: list.contains(ServiceTypes.cloud) ? true : isCloudEnable, + isGitEnable: + list.contains(ServiceTypes.git) ? true : isPasswordManagerEnable, + isSocialNetworkEnable: list.contains(ServiceTypes.socialNetwork) + ? true + : isPasswordManagerEnable, + isVpnEnable: + list.contains(ServiceTypes.vpn) ? true : isPasswordManagerEnable, + ); + + ServicesState disableList( + List list, + ) => + ServicesState( + isPasswordManagerEnable: list.contains(ServiceTypes.passwordManager) + ? false + : isPasswordManagerEnable, + isCloudEnable: + list.contains(ServiceTypes.cloud) ? false : isCloudEnable, + isGitEnable: + list.contains(ServiceTypes.git) ? false : isPasswordManagerEnable, + isSocialNetworkEnable: list.contains(ServiceTypes.socialNetwork) + ? false + : isPasswordManagerEnable, + isVpnEnable: + list.contains(ServiceTypes.vpn) ? false : isPasswordManagerEnable, + ); + + @override + List get props => [ + isPasswordManagerEnable, + isCloudEnable, + isGitEnable, + isSocialNetworkEnable, + isVpnEnable + ]; + + bool isEnableByType(ServiceTypes type) { + switch (type) { + case ServiceTypes.passwordManager: + return isPasswordManagerEnable; + case ServiceTypes.cloud: + return isCloudEnable; + case ServiceTypes.socialNetwork: + return isSocialNetworkEnable; + case ServiceTypes.git: + return isGitEnable; + case ServiceTypes.vpn: + return isVpnEnable; + default: + throw Exception('wrong state'); + } + } +} diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index ecc0c897..5d634dc4 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -34,21 +34,3 @@ class UsersCubit extends Cubit { emit(UsersState(users)); } } - -// final initMockUsers = [ -// User(login: 'Heartbreaking.Goose', password: genPass()), -// User(login: 'Alma.lawson', password: genPass()), -// User(login: 'Bee.gees', password: genPass()), -// User(login: 'Bim.jennings', password: genPass()), -// User(login: 'Debra.holt', password: genPass()), -// User(login: 'Georgia.young', password: genPass()), -// User(login: 'Kenzi.lawson', password: genPass()), -// User(login: 'Le.jennings', password: genPass()), -// User(login: 'Kirill.Zh', password: genPass()), -// User(login: 'Tina.Bolton', password: genPass()), -// User(login: 'Rebekah.Lynn', password: genPass()), -// User(login: 'Aleena.Armstrong', password: genPass()), -// User(login: 'Rosemary.Williams', password: genPass()), -// User(login: 'Sullivan.Nixon', password: genPass()), -// User(login: 'Aleena.Armstrong', password: genPass()), -// ]; diff --git a/lib/logic/get_it/navigation.dart b/lib/logic/get_it/navigation.dart index 92074c93..6d179fa7 100644 --- a/lib/logic/get_it/navigation.dart +++ b/lib/logic/get_it/navigation.dart @@ -2,7 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; class NavigationService { + final GlobalKey scaffoldMessengerKey = + GlobalKey(); final GlobalKey navigatorKey = GlobalKey(); + NavigatorState? get navigator => navigatorKey.currentState; void showPopUpDialog(AlertDialog dialog) { @@ -13,4 +16,10 @@ class NavigationService { builder: (_) => dialog, ); } + + void showSnackBar(SnackBar snackBar) { + final state = scaffoldMessengerKey.currentState!; + + state.showSnackBar(snackBar); + } } diff --git a/lib/logic/get_it/ssh_helper.dart b/lib/logic/get_it/ssh_helper.dart new file mode 100644 index 00000000..46ded1a6 --- /dev/null +++ b/lib/logic/get_it/ssh_helper.dart @@ -0,0 +1,33 @@ +import 'dart:developer'; + +import 'package:hive/hive.dart'; +import 'package:pointycastle/pointycastle.dart'; +import 'package:rsa_encrypt/rsa_encrypt.dart'; +import 'package:selfprivacy/config/hive_config.dart'; +import 'package:pointycastle/api.dart' as crypto; +import 'package:ssh_key/ssh_key.dart' as ssh_key; + +class SSHModel { + Box _box = Hive.box(BNames.sshConfig); + String? savedPrivateKey; + String? savedPubKey; + + Future generateKeys() async { + var helper = RsaKeyHelper(); + crypto.AsymmetricKeyPair pair = + await helper.computeRSAKeyPair(helper.getSecureRandom()); + var privateKey = pair.privateKey as RSAPrivateKey; + var publicKey = pair.publicKey as RSAPublicKey; + + savedPrivateKey = helper.encodePrivateKeyToPemPKCS1(privateKey); + savedPubKey = publicKey.encode(ssh_key.PubKeyEncoding.openSsh); + + await _box.put(BNames.sshPrivateKey, savedPrivateKey); + await _box.put(BNames.sshPublicKey, savedPubKey); + } + + void init() { + savedPrivateKey = _box.get(BNames.sshPrivateKey); + savedPubKey = _box.get(BNames.sshPublicKey); + } +} diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart new file mode 100644 index 00000000..c06d4ebd --- /dev/null +++ b/lib/logic/models/job.dart @@ -0,0 +1,54 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/common_enum/common_enum.dart'; +import 'package:selfprivacy/utils/password_generator.dart'; +import 'package:easy_localization/easy_localization.dart'; + +import 'user.dart'; + +@immutable +class Job extends Equatable { + Job({ + String? id, + required this.title, + }) : id = id ?? StringGenerators.simpleId(); + + final String title; + final String id; + + @override + List get props => [id, title]; +} + +class CreateUserJob extends Job { + CreateUserJob({ + required this.user, + }) : super(title: '${"jobs.createUser".tr()} ${user.login}'); + + final User user; + + @override + List get props => [id, title, user]; +} + +class ServiceToggleJob extends Job { + ServiceToggleJob({ + required this.type, + required this.needToTurnOn, + }) : super( + title: + '${needToTurnOn ? "jobs.serviceTurnOn".tr() : "jobs.serviceTurnOff".tr()} ${type.title}'); + + final ServiceTypes type; + final bool needToTurnOn; + + @override + List get props => [id, title, type, needToTurnOn]; +} + +class CreateSSHKeyJob extends Job { + CreateSSHKeyJob() : super(title: '${"more.create_ssh_key".tr()}'); + + @override + List get props => [id, title]; +} diff --git a/lib/logic/models/jobs/job.dart b/lib/logic/models/jobs/job.dart deleted file mode 100644 index 3d6bc386..00000000 --- a/lib/logic/models/jobs/job.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:selfprivacy/utils/password_generator2.dart'; - -import '../user.dart'; - -@immutable -class Job extends Equatable { - Job({ - String? id, - required this.title, - }) : id = id ?? getRandomString(5); - - final String title; - final String id; - - @override - List get props => [id, title]; -} - -class CreateUserJob extends Job { - CreateUserJob({ - required this.user, - }) : super(title: 'Create ${user.login}'); - - final User user; - - @override - List get props => [id, title]; -} diff --git a/lib/logic/models/user.dart b/lib/logic/models/user.dart index c50d13fe..0d1c48b3 100644 --- a/lib/logic/models/user.dart +++ b/lib/logic/models/user.dart @@ -4,6 +4,7 @@ import 'package:crypt/crypt.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/utils/color_utils.dart'; import 'package:hive/hive.dart'; +import 'package:selfprivacy/utils/password_generator.dart'; part 'user.g.dart'; @@ -25,7 +26,10 @@ class User extends Equatable { Color get color => stringToColor(login); - Crypt get hashPassword => Crypt.sha512(password); + Crypt get hashPassword => Crypt.sha512( + password, + salt: StringGenerators.passwordSalt(), + ); String toString() { return login; diff --git a/lib/main.dart b/lib/main.dart index 67199de0..cfd595ae 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,11 +16,11 @@ import 'config/localization.dart'; import 'logic/cubit/app_settings/app_settings_cubit.dart'; void main() async { + WidgetsFlutterBinding.ensureInitialized(); await HiveConfig.init(); Bloc.observer = SimpleBlocObserver(); Wakelock.enable(); await getItSetup(); - WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); runApp(MyApp()); @@ -37,6 +37,8 @@ class MyApp extends StatelessWidget { return AnnotatedRegion( value: SystemUiOverlayStyle.light, // Manually changnig appbar color child: MaterialApp( + scaffoldMessengerKey: + getIt.get().scaffoldMessengerKey, navigatorKey: getIt.get().navigatorKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, diff --git a/lib/ui/components/brand_switch/brand_switch.dart b/lib/ui/components/brand_switch/brand_switch.dart new file mode 100644 index 00000000..60c411cf --- /dev/null +++ b/lib/ui/components/brand_switch/brand_switch.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +import 'package:selfprivacy/config/brand_colors.dart'; + +class BrandSwitch extends StatelessWidget { + const BrandSwitch({ + Key? key, + required this.onChanged, + required this.value, + }) : super(key: key); + + final ValueChanged onChanged; + final bool value; + + @override + Widget build(BuildContext context) { + return Switch( + activeColor: BrandColors.green1, + activeTrackColor: BrandColors.green2, + value: value, + onChanged: onChanged, + ); + } +} diff --git a/lib/ui/components/brand_text/brand_text.dart b/lib/ui/components/brand_text/brand_text.dart index 9f272782..beaf2937 100644 --- a/lib/ui/components/brand_text/brand_text.dart +++ b/lib/ui/components/brand_text/brand_text.dart @@ -52,10 +52,16 @@ class BrandText extends StatelessWidget { type: TextType.onboardingTitle, style: style, ); - factory BrandText.h2(String? text, {TextStyle? style}) => BrandText( + factory BrandText.h2( + String? text, { + TextStyle? style, + TextAlign? textAlign, + }) => + BrandText( text, type: TextType.h2, style: style, + textAlign: textAlign, ); factory BrandText.h3(String text, {TextStyle? style, TextAlign? textAlign}) => BrandText( diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index aa4b1c65..10879f0e 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -36,11 +36,7 @@ class JobsContent extends StatelessWidget { children: [ Expanded( child: BrandCards.small( - child: Row( - children: [ - BrandText.body1(j.title), - ], - ), + child: Text(j.title), ), ), SizedBox(width: 10), diff --git a/lib/ui/pages/more/app_settings/app_setting.dart b/lib/ui/pages/more/app_settings/app_setting.dart index 6959b343..a9a15c99 100644 --- a/lib/ui/pages/more/app_settings/app_setting.dart +++ b/lib/ui/pages/more/app_settings/app_setting.dart @@ -7,6 +7,7 @@ import 'package:selfprivacy/ui/components/action_button/action_button.dart'; import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart'; import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; +import 'package:selfprivacy/ui/components/brand_switch/brand_switch.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/utils/named_font_weight.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -52,9 +53,7 @@ class _AppSettingsPageState extends State { ), ), SizedBox(width: 5), - Switch( - activeColor: BrandColors.green1, - activeTrackColor: BrandColors.green2, + BrandSwitch( value: Theme.of(context).brightness == Brightness.dark, onChanged: (value) => context .read() @@ -120,69 +119,72 @@ class _AppSettingsPageState extends State { ], ), ), - Container( - padding: EdgeInsets.only(top: 20, bottom: 5), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(width: 1, color: BrandColors.dividerColor), - )), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: _TextColumn( - title: 'more.settings.5'.tr(), - value: 'more.settings.6'.tr(), - ), - ), - SizedBox(width: 5), - ElevatedButton( - style: ElevatedButton.styleFrom( - primary: BrandColors.red1, - ), - child: Text( - 'basis.delete'.tr(), - style: TextStyle( - color: BrandColors.white, - fontWeight: NamedFontWeight.demiBold, - ), - ), - onPressed: () { - showDialog( - context: context, - builder: (_) { - return BrandAlert( - title: 'modals.3'.tr(), - contentText: 'modals.6'.tr(), - acitons: [ - ActionButton( - text: 'modals.7'.tr(), - isRed: true, - onPressed: () async { - await context - .read() - .serverDelete(); - Navigator.of(context).pop(); - }), - ActionButton( - text: 'basis.cancel'.tr(), - ), - ], - ); - }, - ); - }, - ), - ], - ), - ) + // deleteServer(context) ], ), ); }), ); } + + Widget deleteServer(BuildContext context) { + // todo: need to check + return Container( + padding: EdgeInsets.only(top: 20, bottom: 5), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(width: 1, color: BrandColors.dividerColor), + )), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: _TextColumn( + title: 'more.settings.5'.tr(), + value: 'more.settings.6'.tr(), + ), + ), + SizedBox(width: 5), + ElevatedButton( + style: ElevatedButton.styleFrom( + primary: BrandColors.red1, + ), + child: Text( + 'basis.delete'.tr(), + style: TextStyle( + color: BrandColors.white, + fontWeight: NamedFontWeight.demiBold, + ), + ), + onPressed: () { + showDialog( + context: context, + builder: (_) { + return BrandAlert( + title: 'modals.3'.tr(), + contentText: 'modals.6'.tr(), + acitons: [ + ActionButton( + text: 'modals.7'.tr(), + isRed: true, + onPressed: () async { + await context.read().serverDelete(); + Navigator.of(context).pop(); + }), + ActionButton( + text: 'basis.cancel'.tr(), + ), + ], + ); + }, + ); + }, + ), + ], + ), + ); + } } class _TextColumn extends StatelessWidget { diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 4c1a2d9b..da9b116a 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -1,10 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:ionicons/ionicons.dart'; 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/jobs/jobs_cubit.dart'; +import 'package:selfprivacy/logic/models/job.dart'; +import 'package:selfprivacy/logic/models/state_types.dart'; +import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; +import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; import 'package:selfprivacy/ui/pages/initializing/initializing.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:selfprivacy/ui/pages/rootRoute.dart'; @@ -21,9 +28,14 @@ class MorePage extends StatelessWidget { @override Widget build(BuildContext context) { + var jobsCubit = context.watch(); + return Scaffold( appBar: PreferredSize( - child: BrandHeader(title: 'basis.more'.tr()), + child: BrandHeader( + title: 'basis.more'.tr(), + hasFlashButton: true, + ), preferredSize: Size.fromHeight(52), ), body: ListView( @@ -63,6 +75,25 @@ class MorePage extends StatelessWidget { iconData: BrandIcons.terminal, goTo: Console(), ), + _MoreMenuTapItem( + title: 'more.create_ssh_key'.tr(), + iconData: Ionicons.key_outline, + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return _MoreDetails( + title: 'more.create_ssh_key'.tr(), + icon: Ionicons.key_outline, + onTap: () { + jobsCubit.createShhJobIfNotExist(CreateSSHKeyJob()); + }, + text: 'more.generate_key_text'.tr(), + ); + }, + ); + }, + ), ], ), ) @@ -72,6 +103,73 @@ class MorePage extends StatelessWidget { } } +class _MoreDetails extends StatelessWidget { + const _MoreDetails({ + Key? key, + required this.icon, + required this.title, + required this.onTap, + required this.text, + }) : super(key: key); + final String title; + final IconData icon; + final Function onTap; + final String text; + @override + Widget build(BuildContext context) { + var textStyle = body1Style.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : BrandColors.black); + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SingleChildScrollView( + child: Container( + width: 350, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: paddingH15V30, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconStatusMask( + status: StateType.stable, + child: Icon(icon, size: 40, color: Colors.white), + ), + SizedBox(height: 10), + BrandText.h2(title), + SizedBox(height: 10), + Text( + text, + style: textStyle, + ), + SizedBox(height: 40), + Center( + child: Container( + child: BrandButton.rised( + onPressed: () { + Navigator.of(context).pop(); + onTap(); + }, + text: 'more.generate_key'.tr(), + ), + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} + class _NavItem extends StatelessWidget { const _NavItem({ Key? key, @@ -88,29 +186,73 @@ class _NavItem extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: () => Navigator.of(context).push(materialRoute(goTo)), - child: Container( - padding: EdgeInsets.symmetric(vertical: 24), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 1.0, - color: BrandColors.dividerColor, - ), - ), - ), - child: Row( - children: [ - BrandText.body1(title), - Spacer(), - SizedBox( - width: 56, - child: Icon( - iconData, - size: 20, - ), - ), - ], - ), + child: _MoreMenuItem( + iconData: iconData, + title: title, + ), + ); + } +} + +class _MoreMenuTapItem extends StatelessWidget { + const _MoreMenuTapItem({ + Key? key, + required this.iconData, + required this.onTap, + required this.title, + }) : super(key: key); + + final IconData iconData; + final Function onTap; + final String title; + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + onTap(); + }, + child: _MoreMenuItem( + iconData: iconData, + title: title, + ), + ); + } +} + +class _MoreMenuItem extends StatelessWidget { + const _MoreMenuItem({ + Key? key, + required this.iconData, + required this.title, + }) : super(key: key); + + final IconData iconData; + final String title; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(vertical: 24), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1.0, + color: BrandColors.dividerColor, + ), + ), + ), + child: Row( + children: [ + BrandText.body1(title), + Spacer(), + SizedBox( + width: 56, + child: Icon( + iconData, + size: 20, + ), + ), + ], ), ); } diff --git a/lib/ui/pages/server_details/cpu_chart.dart b/lib/ui/pages/server_details/cpu_chart.dart index 88163caa..96ee94ce 100644 --- a/lib/ui/pages/server_details/cpu_chart.dart +++ b/lib/ui/pages/server_details/cpu_chart.dart @@ -51,7 +51,7 @@ class CpuChart extends StatelessWidget { interval: 20, rotateAngle: 90.0, showTitles: true, - getTextStyles: (value) => const TextStyle( + getTextStyles: (_, __) => const TextStyle( fontSize: 10, color: Colors.purple, fontWeight: FontWeight.bold, @@ -60,7 +60,7 @@ class CpuChart extends StatelessWidget { return bottomTitle(value.toInt()); }), leftTitles: SideTitles( - getTextStyles: (value) => progressTextStyleLight.copyWith( + getTextStyles: (_, __) => progressTextStyleLight.copyWith( color: Theme.of(context).brightness == Brightness.dark ? BrandColors.gray4 : null, diff --git a/lib/ui/pages/server_details/network_charts.dart b/lib/ui/pages/server_details/network_charts.dart index cb352267..44d1c0ba 100644 --- a/lib/ui/pages/server_details/network_charts.dart +++ b/lib/ui/pages/server_details/network_charts.dart @@ -71,7 +71,7 @@ class NetworkChart extends StatelessWidget { interval: 20, rotateAngle: 90.0, showTitles: true, - getTextStyles: (value) => const TextStyle( + getTextStyles: (_, __) => const TextStyle( fontSize: 10, color: Colors.purple, fontWeight: FontWeight.bold, @@ -87,7 +87,7 @@ class NetworkChart extends StatelessWidget { ].reduce(max) * 1.2 / 10, - getTextStyles: (value) => progressTextStyleLight.copyWith( + getTextStyles: (_, __) => progressTextStyleLight.copyWith( color: Theme.of(context).brightness == Brightness.dark ? BrandColors.gray4 : null, diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 3957e22f..0ba92220 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -1,13 +1,19 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; 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/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; +import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; -import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; +import 'package:selfprivacy/ui/components/brand_switch/brand_switch.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; @@ -17,6 +23,14 @@ import 'package:url_launcher/url_launcher.dart'; import '../rootRoute.dart'; +const switchableServices = [ + ServiceTypes.passwordManager, + ServiceTypes.cloud, + ServiceTypes.socialNetwork, + ServiceTypes.git, + ServiceTypes.vpn, +]; + class ServicesPage extends StatefulWidget { ServicesPage({Key? key}) : super(key: key); @@ -63,52 +77,25 @@ class _Card extends StatelessWidget { final ServiceTypes serviceType; @override Widget build(BuildContext context) { - String title; - IconData iconData; - String subtitle; - - switch (serviceType) { - case ServiceTypes.mail: - iconData = BrandIcons.envelope; - title = 'services.mail.title'.tr(); - subtitle = 'services.mail.subtitle'.tr(); - break; - case ServiceTypes.messenger: - iconData = BrandIcons.messanger; - title = 'services.messenger.title'.tr(); - subtitle = 'services.messenger.subtitle'.tr(); - break; - case ServiceTypes.passwordManager: - iconData = BrandIcons.key; - title = 'services.password_manager.title'.tr(); - subtitle = 'services.password_manager.subtitle'.tr(); - break; - case ServiceTypes.video: - iconData = BrandIcons.webcam; - title = 'services.video.title'.tr(); - subtitle = 'services.video.subtitle'.tr(); - break; - case ServiceTypes.cloud: - iconData = BrandIcons.upload; - title = 'services.cloud.title'.tr(); - subtitle = 'services.cloud.subtitle'.tr(); - break; - case ServiceTypes.socialNetwork: - iconData = BrandIcons.social; - title = 'services.social_network.title'.tr(); - subtitle = 'services.social_network.subtitle'.tr(); - break; - case ServiceTypes.git: - iconData = BrandIcons.git; - title = 'services.git.title'.tr(); - subtitle = 'services.git.subtitle'.tr(); - break; - } - var isReady = context.watch().state.isFullyInitilized; var changeTab = context.read().onPress; + + var serviceState = context.watch().state; + var jobsCubit = context.watch(); + var jobState = jobsCubit.state; + + var switchebleService = switchableServices.contains(serviceType); + var hasSwitchJob = switchebleService && + jobState is JobsStateWithJobs && + jobState.jobList + .any((el) => el is ServiceToggleJob && el.type == serviceType); + + var isSwithOn = isReady && + (!switchableServices.contains(serviceType) || + serviceState.isEnableByType(serviceType)); + return GestureDetector( - onTap: isReady + onTap: isSwithOn ? () => showDialog( context: context, // isScrollControlled: true, @@ -117,9 +104,9 @@ class _Card extends StatelessWidget { return _ServiceDetails( serviceType: serviceType, status: - isReady ? StateType.stable : StateType.uninitialized, - title: title, - icon: iconData, + isSwithOn ? StateType.stable : StateType.uninitialized, + title: serviceType.title, + icon: serviceType.icon, changeTab: changeTab, ); }, @@ -129,15 +116,76 @@ class _Card extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - IconStatusMask( - status: isReady ? StateType.stable : StateType.uninitialized, - child: Icon(iconData, size: 30, color: Colors.white), + Row( + children: [ + IconStatusMask( + status: + isSwithOn ? StateType.stable : StateType.uninitialized, + child: Icon(serviceType.icon, size: 30, color: Colors.white), + ), + if (isReady && switchebleService) ...[ + Spacer(), + Builder( + builder: (context) { + late bool isActive; + if (hasSwitchJob) { + isActive = ((jobState as JobsStateWithJobs) + .jobList + .firstWhere((el) => + el is ServiceToggleJob && + el.type == serviceType) as ServiceToggleJob) + .needToTurnOn; + } else { + isActive = serviceState.isEnableByType(serviceType); + } + + return BrandSwitch( + value: isActive, + onChanged: (value) => + jobsCubit.createOrRemoveServiceToggleJob( + ServiceToggleJob( + type: serviceType, + needToTurnOn: value, + ), + ), + ); + }, + ), + ] + ], ), - SizedBox(height: 10), - BrandText.h2(title), - SizedBox(height: 10), - BrandText.body2(subtitle), - SizedBox(height: 10), + ClipRect( + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 10), + BrandText.h2(serviceType.title), + SizedBox(height: 10), + BrandText.body2(serviceType.subtitle), + SizedBox(height: 10), + ], + ), + if (hasSwitchJob) + Positioned( + bottom: 30, + left: 0, + right: 0, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 3, + sigmaY: 2, + ), + child: BrandText.h2( + 'jobs.runJobs'.tr(), + textAlign: TextAlign.center, + ), + ), + ) + ], + ), + ) ], ), ), @@ -145,16 +193,6 @@ class _Card extends StatelessWidget { } } -enum ServiceTypes { - mail, - messenger, - passwordManager, - video, - cloud, - socialNetwork, - git, -} - class _ServiceDetails extends StatelessWidget { const _ServiceDetails({ Key? key, @@ -353,7 +391,12 @@ class _ServiceDetails extends StatelessWidget { ], )); break; + case ServiceTypes.vpn: + child = Text( + 'services.vpn.bottom_sheet.1'.tr(), + ); } + return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), @@ -413,252 +456,3 @@ class _ServiceDetails extends StatelessWidget { } } } - - -// class _ServiceDetails extends StatelessWidget { -// const _ServiceDetails({ -// Key? key, -// required this.serviceType, -// required this.icon, -// required this.status, -// required this.title, -// required this.changeTab, -// }) : super(key: key); - -// final ServiceTypes serviceType; -// final IconData icon; -// final StateType status; -// final String title; -// final ValueChanged changeTab; - -// @override -// Widget build(BuildContext context) { -// late Widget child; - -// var config = context.watch().state; -// var domainName = UiHelpers.getDomainName(config); - -// var linksStyle = body1Style.copyWith( -// fontSize: 15, -// color: Theme.of(context).brightness == Brightness.dark -// ? Colors.white -// : BrandColors.black, -// fontWeight: FontWeight.bold, -// decoration: TextDecoration.underline, -// // height: 1.1, -// ); - -// var textStyle = body1Style.copyWith( -// color: Theme.of(context).brightness == Brightness.dark -// ? Colors.white -// : BrandColors.black, -// ); -// switch (serviceType) { -// case ServiceTypes.mail: -// child = RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'services.mail.bottom_sheet.1'.tr(args: [domainName]), -// style: textStyle, -// ), -// WidgetSpan( -// child: Padding( -// padding: EdgeInsets.only(bottom: 0.8, left: 5), -// child: GestureDetector( -// child: Text( -// 'services.mail.bottom_sheet.2'.tr(), -// style: linksStyle, -// ), -// onTap: () { -// Navigator.of(context).pop(); -// changeTab(2); -// }, -// ), -// ), -// ), -// ], -// )); -// break; -// case ServiceTypes.messenger: -// child = RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'services.messenger.bottom_sheet.1'.tr(args: [domainName]), -// style: textStyle, -// ) -// ], -// )); -// break; -// case ServiceTypes.passwordManager: -// child = RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'services.password_manager.bottom_sheet.1' -// .tr(args: [domainName]), -// style: textStyle, -// ), -// WidgetSpan( -// child: Padding( -// padding: EdgeInsets.only(bottom: 0.8, left: 5), -// child: GestureDetector( -// onTap: () => _launchURL('https://password.$domainName'), -// child: Text( -// 'password.$domainName', -// style: linksStyle, -// ), -// ), -// ), -// ), -// ], -// )); -// break; -// case ServiceTypes.video: -// child = RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'services.video.bottom_sheet.1'.tr(args: [domainName]), -// style: textStyle, -// ), -// WidgetSpan( -// child: Padding( -// padding: EdgeInsets.only(bottom: 0.8, left: 5), -// child: GestureDetector( -// onTap: () => _launchURL('https://meet.$domainName'), -// child: Text( -// 'meet.$domainName', -// style: linksStyle, -// ), -// ), -// ), -// ), -// ], -// )); -// break; -// case ServiceTypes.cloud: -// child = RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'services.cloud.bottom_sheet.1'.tr(args: [domainName]), -// style: textStyle, -// ), -// WidgetSpan( -// child: Padding( -// padding: EdgeInsets.only(bottom: 0.8, left: 5), -// child: GestureDetector( -// onTap: () => _launchURL('https://cloud.$domainName'), -// child: Text( -// 'cloud.$domainName', -// style: linksStyle, -// ), -// ), -// ), -// ), -// ], -// )); -// break; -// case ServiceTypes.socialNetwork: -// child = RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'services.social_network.bottom_sheet.1' -// .tr(args: [domainName]), -// style: textStyle, -// ), -// WidgetSpan( -// child: Padding( -// padding: EdgeInsets.only(bottom: 0.8, left: 5), -// child: GestureDetector( -// onTap: () => _launchURL('https://social.$domainName'), -// child: Text( -// 'social.$domainName', -// style: linksStyle, -// ), -// ), -// ), -// ), -// ], -// )); -// break; -// case ServiceTypes.git: -// child = RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: 'services.git.bottom_sheet.1'.tr(args: [domainName]), -// style: textStyle, -// ), -// WidgetSpan( -// child: Padding( -// padding: EdgeInsets.only(bottom: 0.8, left: 5), -// child: GestureDetector( -// onTap: () => _launchURL('https://git.$domainName'), -// child: Text( -// 'git.$domainName', -// style: linksStyle, -// ), -// ), -// ), -// ), -// ], -// )); -// break; -// } -// return BrandModalSheet( - // child: Navigator( - // key: navigatorKey, - // initialRoute: '/', - // onGenerateRoute: (_) { - // return materialRoute( -// Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Padding( -// padding: brandPagePadding1, -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// SizedBox(height: 13), -// IconStatusMask( -// status: status, -// child: Icon(icon, size: 40, color: Colors.white), -// ), -// SizedBox(height: 10), -// BrandText.h1(title), -// SizedBox(height: 10), -// child, -// ], -// ), -// ) -// ], -// ), -// ); -// }, -// ), -// ); -// } - -// void _launchURL(url) async { -// var _possible = await canLaunch(url); - -// if (_possible) { -// try { -// await launch( -// url, -// forceSafariVC: true, -// enableJavaScript: true, -// ); -// } catch (e) { -// print(e); -// } -// } else { -// throw 'Could not launch $url'; -// } -// } -// } - diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index da994ef9..3088116d 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -6,7 +6,7 @@ import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.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'; -import 'package:selfprivacy/logic/models/jobs/job.dart'; +import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/user.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; diff --git a/lib/utils/password_generator.dart b/lib/utils/password_generator.dart index a0ce6273..f7bdd2d6 100644 --- a/lib/utils/password_generator.dart +++ b/lib/utils/password_generator.dart @@ -1,138 +1,99 @@ import 'dart:math'; -var generator = PasswordGenerator(); +Random _rnd = Random(); -String genPass() { - generator.generate(8); - return generator.getGeneratedValue(); -} - -///Generates a password. -/// -///The password [_generatedValue] is of a specified length, including letters [_letterGen] of mixed cases, -///numbers [_numGen], and symbols[_symGen] depending on user choice. -class PasswordGenerator { - late bool _letterGen; - late bool _numGen; - late bool _symGen; - late String _generatedValue; - - ///Constructor. - /// - ///[_letterGen] is true to make password generation possible from the opening of the application, and - ///[_generatedValue] is intialized to the value below so the text containing it can be first generated - ///upon users request - PasswordGenerator() { - _letterGen = true; - _numGen = true; - _symGen = false; - _generatedValue = "Press Generate"; - } - - ///Call to generate a value, of [n] length - void generate(int n) { - //Discards the old value - _generatedValue = ""; - - ///Cannot generate a value without any character types selected - if (!_letterGen && !_numGen && !_symGen) { - _generatedValue = "No character type selected"; - return; - } - - ///'Randomly' selectes caracter type to generate and append [toAppend] to [_generatedValue] - // ignore: unnecessary_statements - for (n; n > 0; n--) { - String? toAppend; - var random = new Random(); - - ///loops until a valid character is generated, meaning the user has to check the character value - ///to be generated. 'Randomly' picks a character type. - while (toAppend == null) { - int selector = random.nextInt(3); - - if (selector == 0) { - toAppend = _generateLetter(); - } else if (selector == 1) { - toAppend = _generateNumber(); - } else { - toAppend = _generateSymbol(); - } - } - - _generatedValue += toAppend; - toAppend = null; - } - } - - ///Generates a letter when called. - String _generateLetter() { - if (!_letterGen) return ''; - - ///Finds the integer value for the range between a-z and A-Z, with [base] UTF-16 value for lowercase letters and - ///[baseUpper] UTF-16 value for uppercase letters - int base = "a".codeUnitAt(0); - int baseUpper = "A".codeUnitAt(0); - int maxRand = ("z".codeUnitAt(0) - base) + 1; - Random random = new Random(); - - ///Randomly selects between upper and lower case generation, randomly generates value from [maxRand], then adding base, - ///which creates a UTF-16 encoded character to be converted into a string of one character between a-z/A-Z. - ///This string is then returned. - if (random.nextInt(2) == 0) { - return String.fromCharCodes([random.nextInt(maxRand) + base]); - } else { - return String.fromCharCodes([random.nextInt(maxRand) + baseUpper]); - } - } - - ///Generates a number when called - String? _generateNumber() { - if (!_numGen) return null; - - ///Finds the integer value for the range between 0-9 - int base = "0".codeUnitAt(0); - int maxRand = ("9".codeUnitAt(0) - base) + 1; - Random random = new Random(); - - ///Randomly generates value from [maxRand], then adding base, which creates a UTF-16 encoded character to be converted into a - ///string of one character between 0-9. This string is then returned. - return String.fromCharCodes([random.nextInt(maxRand) + base]); - } - - ///Generates a symbol when called - String? _generateSymbol() { - if (!_symGen) return null; - - ///Finds the integer value for the range between symbols !-. - - ///(note) which includes symbols !"#$%&'()*+,=. - int base = "!".codeUnitAt(0); - int maxRand = (".".codeUnitAt(0) - base) + 1; - Random random = new Random(); - - ///Randomly generates value from [maxRand], then adding base, which creates a UTF-16 encoded character to be - ///converted into a string of one character between !-. . This string is then returned. - return String.fromCharCodes([random.nextInt(maxRand) + base]); - } - - ///Toggles letter generation - void checkLetterGen(bool value) { - _letterGen = value; - } - - ///Toggles number generation - void checkNumGen(bool value) { - _numGen = value; - } - - ///Toggles symbol generation - void checkSymGen(bool value) { - _symGen = value; - } - - ///Returns the generated value to be used by generator app - String getGeneratedValue() { - return _generatedValue; - } +typedef StringGeneratorFunction = String Function(); + +class StringGenerators { + static const letters = 'abcdefghijklmnopqrstuvwxyz'; + static const numbers = '1234567890'; + static const symbols = '_'; + + static String getRandomString( + int length, { + hasLowercaseLetters = false, + hasUppercaseLetters = false, + hasNumbers = false, + hasSymbols = false, + isStrict = false, + }) { + var chars = ''; + if (hasLowercaseLetters) chars += letters; + if (hasUppercaseLetters) chars += letters.toUpperCase(); + if (hasNumbers) chars += numbers; + if (hasSymbols) chars += symbols; + + assert(chars.isNotEmpty, 'chart empty'); + + if (!isStrict) { + return genString(length, chars); + } + + var res = ''; + var loose = length; + if (hasLowercaseLetters) { + loose -= 1; + res += genString(1, letters); + } + if (hasUppercaseLetters) { + loose -= 1; + res += genString(1, letters.toUpperCase()); + } + if (hasNumbers) { + loose -= 1; + res += genString(1, numbers.toUpperCase()); + } + if (hasSymbols) { + loose -= 1; + res += genString(1, symbols); + } + res += genString(loose, chars); + + var shuffledlist = res.split('')..shuffle(); + return shuffledlist.join(); + } + + static String genString(int length, String chars) { + return String.fromCharCodes( + Iterable.generate( + length, + (_) => chars.codeUnitAt( + _rnd.nextInt(chars.length), + ), + ), + ); + } + + static StringGeneratorFunction userPassword = () => getRandomString( + 8, + hasLowercaseLetters: true, + hasUppercaseLetters: true, + hasNumbers: true, + isStrict: true, + ); + + static StringGeneratorFunction passwordSalt = () => getRandomString( + 8, + hasLowercaseLetters: true, + ); + + static StringGeneratorFunction simpleId = () => getRandomString( + 5, + hasLowercaseLetters: true, + ); + + static StringGeneratorFunction dbPassword = () => getRandomString( + 40, + hasLowercaseLetters: true, + hasUppercaseLetters: true, + hasNumbers: true, + hasSymbols: true, + ); + + static StringGeneratorFunction dbStorageName = () => getRandomString( + 6, + hasLowercaseLetters: true, + hasUppercaseLetters: true, + hasNumbers: true, + ); } diff --git a/lib/utils/password_generator2.dart b/lib/utils/password_generator2.dart deleted file mode 100644 index 36114d55..00000000 --- a/lib/utils/password_generator2.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:math'; - -const _chars = - 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890_'; -Random _rnd = Random(); - -String getRandomString(int length, [chars = _chars]) => String.fromCharCodes( - Iterable.generate( - length, - (_) => chars.codeUnitAt( - _rnd.nextInt(chars.length), - ), - ), - ); diff --git a/pubspec.lock b/pubspec.lock index dca1a951..944a7ea6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -14,7 +14,7 @@ packages: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "1.7.1" + version: "1.7.2" archive: dependency: transitive description: @@ -28,28 +28,35 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.2.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.6.1" + version: "2.8.1" basic_utils: - dependency: "direct dev" + dependency: "direct main" description: name: basic_utils url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.5.0" bloc: dependency: transitive description: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "7.0.0" + version: "7.1.0" boolean_selector: dependency: transitive description: @@ -63,42 +70,42 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.0" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.7" + version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.0.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.12.2" + version: "2.1.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.12" + version: "7.1.0" built_collection: dependency: transitive description: @@ -112,7 +119,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.1.0" + version: "8.1.2" characters: dependency: transitive description: @@ -126,7 +133,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" checked_yaml: dependency: transitive description: @@ -140,7 +147,7 @@ packages: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.3" clock: dependency: transitive description: @@ -154,7 +161,7 @@ packages: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.1.0" collection: dependency: transitive description: @@ -168,7 +175,7 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" coverage: dependency: transitive description: @@ -196,7 +203,7 @@ packages: name: cubit_form url: "https://pub.dartlang.org" source: hosted - version: "1.0.16" + version: "1.0.18" cupertino_icons: dependency: "direct main" description: @@ -210,7 +217,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.3" dio: dependency: "direct main" description: @@ -259,7 +266,7 @@ packages: name: extended_masked_text url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.3.1" fake_async: dependency: transitive description: @@ -294,7 +301,7 @@ packages: name: fl_chart url: "https://pub.dartlang.org" source: hosted - version: "0.35.0" + version: "0.40.0" flutter: dependency: "direct main" description: flutter @@ -306,14 +313,14 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "7.0.1" + version: "7.2.0" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.9.0" + version: "0.9.2" flutter_localizations: dependency: transitive description: flutter @@ -325,14 +332,14 @@ packages: name: flutter_markdown url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.5" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "4.2.1" flutter_test: dependency: "direct dev" description: flutter @@ -343,13 +350,20 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" get_it: dependency: "direct main" description: name: get_it url: "https://pub.dartlang.org" source: hosted - version: "6.1.1" + version: "7.2.0" glob: dependency: transitive description: @@ -363,7 +377,7 @@ packages: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "2.0.0" hive: dependency: "direct main" description: @@ -426,7 +440,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.3" ionicons: dependency: "direct main" description: @@ -454,7 +468,7 @@ packages: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "4.1.3" + version: "4.1.4" logging: dependency: transitive description: @@ -482,7 +496,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" mime: dependency: transitive description: @@ -552,14 +566,14 @@ packages: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" path_provider_platform_interface: dependency: transitive description: @@ -573,7 +587,7 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.3" pedantic: dependency: transitive description: @@ -594,21 +608,21 @@ packages: name: platform url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" pointycastle: - dependency: transitive + dependency: "direct main" description: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "3.1.2" + version: "3.3.2" pool: dependency: transitive description: @@ -629,14 +643,14 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.2.1" + version: "4.2.3" provider: dependency: "direct main" description: name: provider url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "6.0.0" pub_semver: dependency: transitive description: @@ -651,6 +665,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + rsa_encrypt: + dependency: "direct main" + description: + name: rsa_encrypt + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" share_plus: dependency: "direct main" description: @@ -664,7 +692,7 @@ packages: name: share_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" share_plus_macos: dependency: transitive description: @@ -699,21 +727,21 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.7" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shared_preferences_platform_interface: dependency: transitive description: @@ -727,21 +755,21 @@ packages: name: shared_preferences_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.1.4" + version: "1.2.0" shelf_packages_handler: dependency: transitive description: @@ -755,7 +783,7 @@ packages: name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0" shelf_web_socket: dependency: transitive description: @@ -774,14 +802,14 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.3" source_helper: dependency: transitive description: name: source_helper url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.1" source_map_stack_trace: dependency: transitive description: @@ -803,6 +831,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.1" + ssh_key: + dependency: "direct main" + description: + name: ssh_key + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" stack_trace: dependency: transitive description: @@ -844,21 +879,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.16.8" + version: "1.17.10" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.2" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.19" + version: "0.4.0" timing: dependency: transitive description: @@ -866,6 +901,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + tuple: + dependency: transitive + description: + name: tuple + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" typed_data: dependency: transitive description: @@ -873,55 +915,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" - unicons: - dependency: "direct main" - description: - name: unicons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" url_launcher: dependency: "direct main" description: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.0.6" + version: "6.0.9" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.4" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.2" vector_math: dependency: transitive description: @@ -942,35 +977,35 @@ packages: name: wakelock url: "https://pub.dartlang.org" source: hosted - version: "0.5.2" + version: "0.5.3+3" wakelock_macos: dependency: transitive description: name: wakelock_macos url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+1" + version: "0.1.0+2" wakelock_platform_interface: dependency: transitive description: name: wakelock_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" wakelock_web: dependency: transitive description: name: wakelock_web url: "https://pub.dartlang.org" source: hosted - version: "0.2.0+1" + version: "0.2.0+2" wakelock_windows: dependency: transitive description: name: wakelock_windows url: "https://pub.dartlang.org" source: hosted - version: "0.1.0" + version: "0.1.0+1" watcher: dependency: transitive description: @@ -998,7 +1033,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.7" xdg_directories: dependency: transitive description: @@ -1021,5 +1056,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.13.0 <3.0.0" - flutter: ">=2.0.0" + dart: ">=2.13.4 <3.0.0" + flutter: ">=2.2.3" diff --git a/pubspec.yaml b/pubspec.yaml index a9f7d600..e3ed9839 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 0.1.3+5 environment: - sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.0.0" + sdk: '>=2.13.4 <3.0.0' + flutter: ">=2.2.3" dependencies: flutter: @@ -16,31 +16,33 @@ dependencies: dio: ^4.0.0-beta7 easy_localization: ^3.0.0 either_option: ^2.0.1-dev.1 - equatable: ^2.0.0 - fl_chart: ^0.35.0 - flutter_bloc: ^7.0.0 + equatable: ^2.0.3 + fl_chart: ^0.40.0 + flutter_bloc: ^7.1.0 flutter_markdown: ^0.6.0 flutter_secure_storage: ^4.1.0 - get_it: ^6.0.0 + get_it: ^7.2.0 hive: ^2.0.0 hive_flutter: ^1.0.0 - ionicons: ^0.1.2 json_annotation: ^4.0.0 modal_bottom_sheet: ^2.0.0 nanoid: ^1.0.0 package_info: ^2.0.0 pretty_dio_logger: ^1.1.1 - provider: ^5.0.0 + provider: ^6.0.0 share_plus: ^2.1.4 - unicons: ^1.0.2 url_launcher: ^6.0.2 wakelock: ^0.5.0+2 + basic_utils: ^3.4.0 + ionicons: ^0.1.2 + pointycastle: ^3.3.2 + rsa_encrypt: ^2.0.0 + ssh_key: ^0.7.0 dev_dependencies: flutter_test: sdk: flutter - basic_utils: ^3.0.0-nullsafety.1 - build_runner: ^1.11.5 + build_runner: ^2.1.1 flutter_launcher_icons: ^0.9.0 hive_generator: ^1.0.0 json_serializable: ^4.0.2 diff --git a/test/widget_test.dart b/test/widget_test.dart index 6eed7a46..aaa33419 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +1,122 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:selfprivacy/main.dart'; +import 'package:selfprivacy/utils/password_generator.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + group('StringGenerators', () { + group('Basic', () { + test('assert chart empty', () { + expect(() { + StringGenerators.getRandomString(8); + }, throwsAssertionError); + }); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); + test('only lowercase string', () { + var length = 8; + var generatedString = + StringGenerators.getRandomString(length, hasLowercaseLetters: true); - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); + expect(generatedString, isNot(matches(regExpNewLines))); + expect(generatedString, isNot(matches(regExpWhiteSpaces))); + expect(generatedString, isNot(matches(regExpNumbers))); + expect(generatedString, isNot(matches(regExpUppercaseLetters))); + expect(generatedString, isNot(matches(regExpSymbols))); + expect(generatedString.length, equals(length)); + expect(generatedString, matches(regExpLowercaseLetters)); + }); - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + test('only uppercase string', () { + var length = 8; + var generatedString = StringGenerators.getRandomString(length, + hasLowercaseLetters: false, hasUppercaseLetters: true); + + expect(generatedString, isNot(matches(regExpNewLines))); + expect(generatedString, isNot(matches(regExpWhiteSpaces))); + expect(generatedString, isNot(matches(regExpNumbers))); + expect(generatedString, isNot(matches(regExpLowercaseLetters))); + expect(generatedString, isNot(matches(regExpSymbols))); + expect(generatedString.length, equals(length)); + expect(generatedString, matches(regExpUppercaseLetters)); + }); + + test('only numbers string', () { + var length = 8; + var generatedString = StringGenerators.getRandomString(length, + hasLowercaseLetters: false, + hasUppercaseLetters: false, + hasNumbers: true); + + expect(generatedString, isNot(matches(regExpNewLines))); + expect(generatedString, isNot(matches(regExpWhiteSpaces))); + expect(generatedString, isNot(matches(regExpUppercaseLetters))); + expect(generatedString, isNot(matches(regExpLowercaseLetters))); + expect(generatedString, isNot(matches(regExpSymbols))); + expect(generatedString.length, equals(length)); + expect(generatedString, matches(regExpNumbers)); + }); + + test('only symbols string', () { + var length = 8; + var generatedString = StringGenerators.getRandomString( + length, + hasLowercaseLetters: false, + hasUppercaseLetters: false, + hasNumbers: false, + hasSymbols: true, + ); + + expect(generatedString, isNot(matches(regExpNewLines))); + expect(generatedString, isNot(matches(regExpWhiteSpaces))); + expect(generatedString, isNot(matches(regExpUppercaseLetters))); + expect(generatedString, isNot(matches(regExpLowercaseLetters))); + expect(generatedString, isNot(matches(regExpNumbers))); + expect(generatedString.length, equals(length)); + expect(generatedString, matches(regExpSymbols)); + }); + }); + + group('Strict mode', () { + test('All', () { + var length = 5; + var generatedString = StringGenerators.getRandomString(length, + hasLowercaseLetters: true, + hasUppercaseLetters: true, + hasNumbers: true, + hasSymbols: true, + isStrict: true); + + expect(generatedString, isNot(matches(regExpNewLines))); + expect(generatedString, isNot(matches(regExpWhiteSpaces))); + expect(generatedString, matches(regExpLowercaseLetters)); + expect(generatedString, matches(regExpUppercaseLetters)); + expect(generatedString, matches(regExpNumbers)); + expect(generatedString, matches(regExpSymbols)); + expect(generatedString.length, equals(length)); + }); + test('Lowercase letters and numbers', () { + var length = 3; + var generatedString = StringGenerators.getRandomString(length, + hasLowercaseLetters: true, + hasUppercaseLetters: false, + hasNumbers: true, + hasSymbols: false, + isStrict: true); + + expect(generatedString, isNot(matches(regExpNewLines))); + expect(generatedString, isNot(matches(regExpWhiteSpaces))); + expect(generatedString, isNot(matches(regExpUppercaseLetters))); + expect(generatedString, isNot(matches(regExpSymbols))); + expect(generatedString, matches(regExpLowercaseLetters)); + expect(generatedString, matches(regExpNumbers)); + expect(generatedString.length, equals(length)); + }); + }); }); } + +var regExpNewLines = RegExp(r"[\n\r]+"); +var regExpWhiteSpaces = RegExp(r"[\s]+"); +var regExpUppercaseLetters = RegExp(r"[A-Z]"); +var regExpLowercaseLetters = RegExp(r"[a-z]"); +var regExpNumbers = RegExp(r"[0-9]"); +var regExpSymbols = RegExp(r'(?:_|[^\w\s])+');