mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-26 18:56:38 +00:00
update
This commit is contained in:
parent
26607251d9
commit
90d64d8f51
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": "Имя уже используется"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<void> getItSetup() async {
|
||||
|
@ -17,6 +17,7 @@ Future<void> getItSetup() async {
|
|||
|
||||
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
|
||||
getIt.registerSingleton<TimerModel>(TimerModel());
|
||||
getIt.registerSingleton<SSHModel>(SSHModel()..init());
|
||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
||||
|
||||
await getIt.allReady();
|
||||
|
|
|
@ -22,19 +22,21 @@ class HiveConfig {
|
|||
await Hive.openBox<User>(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<Uint8List> getEncriptedKey() async {
|
||||
static Future<Uint8List> 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";
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -96,6 +96,15 @@ class ServerApi extends ApiMap {
|
|||
client.post('/services/${type.url}/${needToTurnOn ? 'enable' : 'disable'}');
|
||||
client.close();
|
||||
}
|
||||
|
||||
Future<void> sendSsh(String ssh) async {
|
||||
var client = await getClient();
|
||||
client.post(
|
||||
'/services/ssh/enable',
|
||||
data: {"public_key": ssh},
|
||||
);
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
extension UrlServerExt on ServiceTypes {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<JobsState> {
|
|||
emit(newState);
|
||||
}
|
||||
|
||||
void createOrRemove(ServiceToggleJob job) {
|
||||
void createOrRemoveServiceToggleJob(ServiceToggleJob job) {
|
||||
var newJobsList = <Job>[];
|
||||
if (state is JobsStateWithJobs) {
|
||||
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||
|
@ -53,6 +54,26 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
removeJob(removingJob.id);
|
||||
} else {
|
||||
newJobsList.add(job);
|
||||
getIt<NavigationService>().showSnackBar(SnackBar(
|
||||
content: Text('jobs.jobAdded'.tr()),
|
||||
duration: const Duration(seconds: 2),
|
||||
));
|
||||
emit(JobsStateWithJobs(newJobsList));
|
||||
}
|
||||
}
|
||||
|
||||
void createShhJobIfNotExist(CreateSSHKeyJob job) {
|
||||
var newJobsList = <Job>[];
|
||||
if (state is JobsStateWithJobs) {
|
||||
newJobsList.addAll((state as JobsStateWithJobs).jobList);
|
||||
}
|
||||
var isExistInJobList = newJobsList.any((el) => el is CreateSSHKeyJob);
|
||||
if (!isExistInJobList) {
|
||||
newJobsList.add(job);
|
||||
getIt<NavigationService>().showSnackBar(SnackBar(
|
||||
content: Text('jobs.jobAdded'.tr()),
|
||||
duration: const Duration(seconds: 2),
|
||||
));
|
||||
emit(JobsStateWithJobs(newJobsList));
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +82,6 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
if (state is JobsStateWithJobs) {
|
||||
var jobs = (state as JobsStateWithJobs).jobList;
|
||||
emit(JobsStateLoading());
|
||||
|
||||
var newUsers = <User>[];
|
||||
for (var job in jobs) {
|
||||
if (job is CreateUserJob) {
|
||||
|
@ -75,6 +95,10 @@ class JobsCubit extends Cubit<JobsState> {
|
|||
servicesCubit.turnOffList([job.type]);
|
||||
}
|
||||
}
|
||||
if (job is CreateSSHKeyJob) {
|
||||
await getIt<SSHModel>().generateKeys();
|
||||
api.sendSsh(getIt<SSHModel>().savedPubKey!);
|
||||
}
|
||||
}
|
||||
|
||||
usersCubit.addUsers(newUsers);
|
||||
|
|
|
@ -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,
|
||||
|
|
33
lib/logic/get_it/ssh_helper.dart
Normal file
33
lib/logic/get_it/ssh_helper.dart
Normal file
|
@ -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<void> 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);
|
||||
}
|
||||
}
|
|
@ -45,3 +45,10 @@ class ServiceToggleJob extends Job {
|
|||
@override
|
||||
List<Object> get props => [id, title, type, needToTurnOn];
|
||||
}
|
||||
|
||||
class CreateSSHKeyJob extends Job {
|
||||
CreateSSHKeyJob() : super(title: '${"more.create_ssh_key".tr()}');
|
||||
|
||||
@override
|
||||
List<Object> get props => [id, title];
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<JobsCubit>();
|
||||
|
||||
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<void>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<ServicesCubit>().state;
|
||||
var jobsCubit = context.watch<JobsCubit>();
|
||||
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<void>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
58
pubspec.lock
58
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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue