master
Kherel 2021-09-02 21:32:07 +02:00
parent 26607251d9
commit 90d64d8f51
17 changed files with 394 additions and 86 deletions

View File

@ -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"
}
}
}

View File

@ -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": "Имя уже используется"
}
}
}

View File

@ -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

View File

@ -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();

View File

@ -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";
}

View File

@ -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',

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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,

View 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);
}
}

View File

@ -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];
}

View File

@ -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(

View File

@ -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,
),
),
],
),
);
}

View File

@ -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,
),
),
)
],
),
)
],
),
),

View File

@ -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:

View File

@ -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: