From 90d64d8f51127c92f7fca19f0a099f16ae4a0a84 Mon Sep 17 00:00:00 2001 From: Kherel Date: Thu, 2 Sep 2021 21:32:07 +0200 Subject: [PATCH] update --- assets/translations/en.json | 11 +- assets/translations/ru.json | 11 +- ios/Podfile.lock | 2 +- lib/config/get_it_config.dart | 3 +- lib/config/hive_config.dart | 16 +- lib/logic/api_maps/hetzner.dart | 2 +- lib/logic/api_maps/server.dart | 9 + lib/logic/common_enum/common_enum.dart | 4 +- lib/logic/cubit/jobs/jobs_cubit.dart | 28 ++- lib/logic/cubit/services/services_state.dart | 8 - lib/logic/get_it/ssh_helper.dart | 33 ++++ lib/logic/models/job.dart | 7 + lib/ui/components/brand_text/brand_text.dart | 8 +- lib/ui/pages/more/more.dart | 190 ++++++++++++++++--- lib/ui/pages/services/services.dart | 85 +++++++-- pubspec.lock | 58 ++++-- pubspec.yaml | 5 +- 17 files changed, 394 insertions(+), 86 deletions(-) create mode 100644 lib/logic/get_it/ssh_helper.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index 2ee5202d..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.{}" @@ -223,7 +227,8 @@ "createUser": "Create", "serviceTurnOff": "Turn off", "serviceTurnOn": "Turn on", - "jobAdded": "Job added" + "jobAdded": "Job added", + "runJobs": "Run jobs" }, "validations": { "required": "Required", @@ -233,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 9fa3fc62..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.{}" }, @@ -216,7 +220,8 @@ "createUser": "Создать запись", "serviceTurnOff": "Остановить", "serviceTurnOn": "Запустить", - "jobAdded": "Задача добавленна" + "jobAdded": "Задача добавленна", + "runJobs": "Запустите задачи" }, "validations": { "required": "обязательное поле", @@ -226,4 +231,4 @@ "length": "Длина строки [] должна быть {}", "user_alredy_exist": "Имя уже используется" } -} \ No newline at end of file +} 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/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 0e6f1c5b..be47b879 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -22,19 +22,21 @@ class HiveConfig { 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 { + static Future getEncriptedKey(String encKey) async { final secureStorage = FlutterSecureStorage(); - var hasEncryptionKey = await secureStorage.containsKey(key: BNames.key); + 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!); } } @@ -49,6 +51,7 @@ class BNames { 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 1bd4d10d..2b714aad 100644 --- a/lib/logic/api_maps/hetzner.dart +++ b/lib/logic/api_maps/hetzner.dart @@ -94,7 +94,7 @@ class HetznerApi extends ApiMap { /// 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":[], "ssh_keys":["kherel"], "user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/development/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"}'''); + '''{"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 7c729789..6e5686f6 100644 --- a/lib/logic/api_maps/server.dart +++ b/lib/logic/api_maps/server.dart @@ -96,6 +96,15 @@ class ServerApi extends ApiMap { 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 { diff --git a/lib/logic/common_enum/common_enum.dart b/lib/logic/common_enum/common_enum.dart index b0ceb1f6..e3d81309 100644 --- a/lib/logic/common_enum/common_enum.dart +++ b/lib/logic/common_enum/common_enum.dart @@ -1,7 +1,7 @@ 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'; -import 'package:unicons/unicons.dart'; enum InitializingSteps { setHeznerKey, @@ -86,7 +86,7 @@ extension ServiceTypesExt on ServiceTypes { case ServiceTypes.git: return BrandIcons.git; case ServiceTypes.vpn: - return UniconsLine.cloud_lock; + return Ionicons.shield_checkmark_outline; } } diff --git a/lib/logic/cubit/jobs/jobs_cubit.dart b/lib/logic/cubit/jobs/jobs_cubit.dart index 19ad09de..d9d902a9 100644 --- a/lib/logic/cubit/jobs/jobs_cubit.dart +++ b/lib/logic/cubit/jobs/jobs_cubit.dart @@ -4,6 +4,7 @@ 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/get_it/ssh_helper.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/logic/models/user.dart'; @@ -40,7 +41,7 @@ class JobsCubit extends Cubit { emit(newState); } - void createOrRemove(ServiceToggleJob job) { + void createOrRemoveServiceToggleJob(ServiceToggleJob job) { var newJobsList = []; if (state is JobsStateWithJobs) { newJobsList.addAll((state as JobsStateWithJobs).jobList); @@ -53,6 +54,26 @@ class JobsCubit extends Cubit { 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)); } } @@ -61,7 +82,6 @@ class JobsCubit extends Cubit { if (state is JobsStateWithJobs) { var jobs = (state as JobsStateWithJobs).jobList; emit(JobsStateLoading()); - var newUsers = []; for (var job in jobs) { if (job is CreateUserJob) { @@ -75,6 +95,10 @@ class JobsCubit extends Cubit { servicesCubit.turnOffList([job.type]); } } + if (job is CreateSSHKeyJob) { + await getIt().generateKeys(); + api.sendSsh(getIt().savedPubKey!); + } } usersCubit.addUsers(newUsers); diff --git a/lib/logic/cubit/services/services_state.dart b/lib/logic/cubit/services/services_state.dart index ed84ffa8..a41b5a59 100644 --- a/lib/logic/cubit/services/services_state.dart +++ b/lib/logic/cubit/services/services_state.dart @@ -1,13 +1,5 @@ part of 'services_cubit.dart'; -const switchableServices = [ - ServiceTypes.passwordManager, - ServiceTypes.cloud, - ServiceTypes.socialNetwork, - ServiceTypes.git, - ServiceTypes.vpn, -]; - class ServicesState extends Equatable { const ServicesState({ required this.isPasswordManagerEnable, 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 index d1fb0193..c06d4ebd 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -45,3 +45,10 @@ class ServiceToggleJob extends Job { @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/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/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/services/services.dart b/lib/ui/pages/services/services.dart index 3a1de59b..0ba92220 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_theme.dart'; @@ -21,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); @@ -72,10 +82,20 @@ class _Card extends StatelessWidget { var serviceState = context.watch().state; var jobsCubit = context.watch(); - var hasSwitcher = switchableServices.contains(serviceType); + 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, @@ -84,7 +104,7 @@ class _Card extends StatelessWidget { return _ServiceDetails( serviceType: serviceType, status: - isReady ? StateType.stable : StateType.uninitialized, + isSwithOn ? StateType.stable : StateType.uninitialized, title: serviceType.title, icon: serviceType.icon, changeTab: changeTab, @@ -99,22 +119,21 @@ class _Card extends StatelessWidget { Row( children: [ IconStatusMask( - status: isReady ? StateType.stable : StateType.uninitialized, + status: + isSwithOn ? StateType.stable : StateType.uninitialized, child: Icon(serviceType.icon, size: 30, color: Colors.white), ), - if (hasSwitcher) ...[ + if (isReady && switchebleService) ...[ Spacer(), Builder( builder: (context) { late bool isActive; - var jobState = jobsCubit.state; - if (jobState is JobsStateWithJobs && - jobState.jobList.any((el) => - el is ServiceToggleJob && - el.type == serviceType)) { - isActive = (jobState.jobList.firstWhere((el) => - el is ServiceToggleJob && - el.type == serviceType) as ServiceToggleJob) + if (hasSwitchJob) { + isActive = ((jobState as JobsStateWithJobs) + .jobList + .firstWhere((el) => + el is ServiceToggleJob && + el.type == serviceType) as ServiceToggleJob) .needToTurnOn; } else { isActive = serviceState.isEnableByType(serviceType); @@ -122,7 +141,8 @@ class _Card extends StatelessWidget { return BrandSwitch( value: isActive, - onChanged: (value) => jobsCubit.createOrRemove( + onChanged: (value) => + jobsCubit.createOrRemoveServiceToggleJob( ServiceToggleJob( type: serviceType, needToTurnOn: value, @@ -134,11 +154,38 @@ class _Card extends StatelessWidget { ] ], ), - SizedBox(height: 10), - BrandText.h2(serviceType.title), - SizedBox(height: 10), - BrandText.body2(serviceType.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, + ), + ), + ) + ], + ), + ) ], ), ), diff --git a/pubspec.lock b/pubspec.lock index 48ca09b2..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: @@ -29,13 +29,20 @@ packages: url: "https://pub.dartlang.org" source: hosted 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 main" 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: @@ -489,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: @@ -610,7 +617,7 @@ packages: source: hosted version: "2.0.1" pointycastle: - dependency: transitive + dependency: "direct main" description: name: pointycastle url: "https://pub.dartlang.org" @@ -658,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: @@ -810,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: @@ -851,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: @@ -873,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: @@ -880,13 +915,6 @@ 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: "2.0.1" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2ea2e7c0..e3ed9839 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,10 +31,13 @@ dependencies: pretty_dio_logger: ^1.1.1 provider: ^6.0.0 share_plus: ^2.1.4 - unicons: ^2.0.1 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: