master
Kherel Kechil 2 years ago
parent 21611e63c7
commit f53ad044c1
  1. 7
      assets/translations/en.json
  2. 4
      assets/translations/ru.json
  3. 6
      ios/Podfile.lock
  4. 6
      lib/config/bloc_config.dart
  5. 3
      lib/config/hive_config.dart
  6. 2
      lib/logic/api_maps/hetzner.dart
  7. 25
      lib/logic/api_maps/server.dart
  8. 37
      lib/logic/cubit/jobs/jobs_cubit.dart
  9. 28
      lib/logic/cubit/jobs/jobs_state.dart
  10. 24
      lib/logic/cubit/users/users_cubit.dart
  11. 57
      lib/main.dart
  12. 21
      lib/ui/components/brand_loader/brand_loader.dart
  13. 1
      lib/ui/components/brand_text/brand_text.dart
  14. 109
      lib/ui/components/jobs_content/jobs_content.dart
  15. 13
      lib/ui/components/pre_styled_buttons/flash.dart
  16. 1
      lib/ui/pages/server_details/cpu_chart.dart
  17. 8
      lib/ui/pages/users/user.dart
  18. 17
      lib/ui/pages/users/user_details.dart
  19. 4
      lib/ui/pages/users/users.dart
  20. 51
      lib/utils/extensions/text_extensions.dart
  21. 42
      pubspec.lock
  22. 1
      pubspec.yaml

@ -19,12 +19,14 @@
"connect": "Connect",
"domain": "Domain",
"saving": "Saving..",
"nickname": "nickname",
"nickname": "Nickname",
"loading": "Loading...",
"later": "I will setup it later",
"reset": "Reset",
"details": "Details",
"no_data": "No data"
"no_data": "No data",
"wait": "Wait",
"remove": "Remove"
},
"more": {
"_comment": "'More' tab",
@ -191,7 +193,6 @@
"23": "Enter a nickname and strong password",
"finish": "Everything is initialized",
"checks": "Checks have been completed \n{} ouf of {}"
},
"modals": {
"_comment": "messages in modals",

@ -24,7 +24,9 @@
"later": "Настрою потом",
"reset": "Reset",
"details": "Детальная информация",
"no_data": "Нет данных"
"no_data": "Нет данных",
"wait": "Ожидайте",
"remove": "Удалить"
},
"more": {
"_comment": "вкладка еще",

@ -6,6 +6,8 @@ PODS:
- Flutter
- path_provider (0.0.1):
- Flutter
- share_plus (0.0.1):
- Flutter
- shared_preferences (0.0.1):
- Flutter
- url_launcher (0.0.1):
@ -18,6 +20,7 @@ DEPENDENCIES:
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
@ -31,6 +34,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
url_launcher:
@ -43,6 +48,7 @@ SPEC CHECKSUMS:
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f

@ -17,7 +17,7 @@ class BlocAndProviderConfig extends StatelessWidget {
// SchedulerBinding.instance.window.platformBrightness;
// var isDark = platformBrightness == Brightness.dark;
var isDark = false;
var usersCubit = UsersCubit();
return MultiProvider(
providers: [
BlocProvider(
@ -31,8 +31,8 @@ class BlocAndProviderConfig extends StatelessWidget {
create: (_) => AppConfigCubit()..load(),
),
BlocProvider(create: (_) => ProvidersCubit()),
BlocProvider(create: (_) => UsersCubit()),
BlocProvider(create: (_) => JobsCubit()),
BlocProvider(create: (_) => usersCubit..load(), lazy: false),
BlocProvider(create: (_) => JobsCubit(usersCubit)),
],
child: child,
);

@ -19,6 +19,8 @@ class HiveConfig {
Hive.registerAdapter(HetznerDataBaseAdapter());
await Hive.openBox(BNames.appSettings);
await Hive.openBox<User>(BNames.users);
var cipher = HiveAesCipher(await getEncriptedKey());
await Hive.openBox(BNames.appConfig, encryptionCipher: cipher);
@ -42,6 +44,7 @@ class BNames {
static String appConfig = 'appConfig';
static String isDarkModeOn = 'isDarkModeOn';
static String isOnbordingShowing = 'isOnbordingShowing';
static String users = 'users';
static String appSettings = 'appSettings';

@ -95,7 +95,7 @@ class HetznerApi extends ApiMap {
var dbId = dbCreateResponse.data['volume']['id'];
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-20.09 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} 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"}'''
);
Response serverCreateResponse = await client.post(

@ -51,8 +51,9 @@ class ServerApi extends ApiMap {
'/createUser',
options: Options(
headers: {
"X-Username": user.login,
"X-Password": user.password,
"X-User": user.login,
"X-Password":
'\$6\$${user.hashPassword.salt}\$${user.hashPassword.hash}',
},
),
);
@ -68,4 +69,24 @@ class ServerApi extends ApiMap {
String get rootAddress =>
throw UnimplementedError('not used in with implementation');
Future<bool> apply() async {
bool res;
Response response;
var client = await getClient();
try {
response = await client.get(
'/apply',
);
res = response.statusCode == HttpStatus.ok;
} catch (e) {
print(e);
res = false;
}
close(client);
return res;
}
}

@ -1,32 +1,53 @@
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/users/users_cubit.dart';
import 'package:selfprivacy/logic/models/jobs/job.dart';
import 'package:equatable/equatable.dart';
import 'package:selfprivacy/logic/models/user.dart';
export 'package:provider/provider.dart';
part 'jobs_state.dart';
class JobsCubit extends Cubit<JobsState> {
JobsCubit() : super(JobsState.emtpy());
JobsCubit(this.usersCubit) : super(JobsStateEmpty());
final api = ServerApi();
final UsersCubit usersCubit;
void addJob(Job job) {
final newState = state.addJob(job);
emit(newState);
var newJobsList = <Job>[];
if (state is JobsStateWithJobs) {
newJobsList.addAll((state as JobsStateWithJobs).jobList);
}
newJobsList.add(job);
emit(JobsStateWithJobs(newJobsList));
}
void removeJob(String id) {
final newState = state.removeById(id);
final newState = (state as JobsStateWithJobs).removeById(id);
emit(newState);
}
Future<void> applyAll() async {
for (var job in state.jobList) {
if (job is CreateUserJob) {
// await api.createUser(job.user);
if (state is JobsStateWithJobs) {
var jobs = (state as JobsStateWithJobs).jobList;
emit(JobsStateLoading());
var newUsers = <User>[];
for (var job in jobs) {
if (job is CreateUserJob) {
newUsers.add(job.user);
await api.createUser(job.user);
}
}
usersCubit.addUsers(newUsers);
await api.apply();
emit(JobsStateEmpty());
getIt<NavigationService>().navigator!.pop();
}
emit(JobsState.emtpy());
}
}

@ -1,25 +1,27 @@
part of 'jobs_cubit.dart';
class JobsState extends Equatable {
const JobsState(this.jobList);
final List<Job> jobList;
abstract class JobsState extends Equatable {
@override
List<Object?> get props => [];
}
static JobsState emtpy() => JobsState([]);
class JobsStateLoading extends JobsState {}
bool get isEmpty => jobList.isEmpty;
class JobsStateEmpty extends JobsState {}
JobsState addJob(Job job) {
var newJobsList = [...jobList];
newJobsList.add(job);
return JobsState(newJobsList);
}
class JobsStateWithJobs extends JobsState {
JobsStateWithJobs(this.jobList);
final List<Job> jobList;
JobsState removeById(String id) {
var newJobsList = jobList.where((element) => element.id != id).toList();
return JobsState(newJobsList);
if (newJobsList.isEmpty) {
return JobsStateEmpty();
}
return JobsStateWithJobs(newJobsList);
}
@override
List<Object> get props => jobList;
List<Object?> get props => jobList;
}

@ -1,23 +1,35 @@
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/models/user.dart';
export 'package:provider/provider.dart';
part 'users_state.dart';
class UsersCubit extends Cubit<UsersState> {
UsersCubit() : super(UsersState([]));
UsersCubit() : super(UsersState(<User>[]));
Box<User> box = Hive.box<User>(BNames.users);
void addUser(User user) {
var users = [...state.users];
users.add(user);
void load() async {
var loadedUsers = box.values.toList();
if (loadedUsers.isNotEmpty) {
emit(UsersState(loadedUsers));
}
}
emit(UsersState(users));
void addUsers(List<User> users) async {
var newUserList = <User>[...state.users, ...users];
await box.addAll(users);
emit(UsersState(newUserList));
}
void remove(User? user) {
void remove(User user) async {
var users = [...state.users];
var index = users.indexOf(user);
users.remove(user);
await box.deleteAt(index);
emit(UsersState(users));
}

@ -23,40 +23,41 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
runApp(
Localization(
child: BlocAndProviderConfig(
child: MyApp(),
),
),
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
AppSettingsState appSettings = context.watch<AppSettingsCubit>().state;
return Localization(
child: BlocAndProviderConfig(
child: Builder(builder: (context) {
var appSettings = context.watch<AppSettingsCubit>().state;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
child: MaterialApp(
navigatorKey: getIt.get<NavigationService>().navigatorKey,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
title: 'SelfPrivacy',
theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
home: appSettings.isOnbordingShowing
? OnboardingPage(nextPage: InitializingPage())
: RootPage(),
builder: (BuildContext context, Widget? widget) {
Widget error = Text('...rendering error...');
if (widget is Scaffold || widget is Navigator)
error = Scaffold(body: Center(child: error));
ErrorWidget.builder = (FlutterErrorDetails errorDetails) => error;
return widget!;
},
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
child: MaterialApp(
navigatorKey: getIt.get<NavigationService>().navigatorKey,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
title: 'SelfPrivacy',
theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
home: appSettings.isOnbordingShowing
? OnboardingPage(nextPage: InitializingPage())
: RootPage(),
builder: (BuildContext context, Widget? widget) {
Widget error = Text('...rendering error...');
if (widget is Scaffold || widget is Navigator)
error = Scaffold(body: Center(child: error));
ErrorWidget.builder =
(FlutterErrorDetails errorDetails) => error;
return widget!;
},
),
);
}),
),
);
}

@ -0,0 +1,21 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
class BrandLoader {
static horizontal() => _HorizontalLoader();
}
class _HorizontalLoader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('basis.wait'.tr()),
SizedBox(height: 10),
LinearProgressIndicator(minHeight: 3),
],
);
}
}

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/text_themes.dart';
export 'package:selfprivacy/utils/extensions/text_extensions.dart';
enum TextType {
h1, // right now only at onboarding and opened providers

@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.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_loader/brand_loader.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class JobsContent extends StatelessWidget {
@ -12,55 +15,71 @@ class JobsContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
var jobs = context.watch<JobsCubit>().state;
return ListView(
padding: paddingH15V0,
children: [
SizedBox(height: 15),
Center(
child: BrandText.h2(
'jobs.title'.tr(),
),
),
SizedBox(height: 20),
if (jobs.isEmpty) BrandText.body1('jobs.empty'.tr()),
if (!jobs.isEmpty) ...[
...jobs.jobList
.map(
(j) => Row(
children: [
Expanded(
child: BrandCards.small(
child: Row(
children: [
BrandText.body1(j.title),
],
return BlocBuilder<JobsCubit, JobsState>(
builder: (context, state) {
late final List<Widget> widgets;
if (state is JobsStateEmpty) {
widgets = [
SizedBox(height: 80),
Center(child: BrandText.body1('jobs.empty'.tr())),
];
} else if (state is JobsStateLoading) {
widgets = [
SizedBox(height: 80),
BrandLoader.horizontal(),
];
} else if (state is JobsStateWithJobs) {
widgets = [
...state.jobList
.map(
(j) => Row(
children: [
Expanded(
child: BrandCards.small(
child: Row(
children: [
BrandText.body1(j.title),
],
),
),
),
),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: BrandColors.red1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: BrandColors.red1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () =>
context.read<JobsCubit>().removeJob(j.id),
child: Text('basis.remove'.tr()),
),
onPressed: () =>
context.read<JobsCubit>().removeJob(j.id),
child: Text('Remove'),
),
],
),
)
.toList(),
SizedBox(height: 20),
BrandButton.rised(
onPressed: () => context.read<JobsCubit>().applyAll(),
text: 'jobs.start'.tr(),
),
],
],
],
),
)
.toList(),
SizedBox(height: 20),
BrandButton.rised(
onPressed: () => context.read<JobsCubit>().applyAll(),
text: 'jobs.start'.tr(),
),
];
}
return ListView(
padding: paddingH15V0,
children: [
SizedBox(height: 15),
Center(
child: BrandText.h2(
'jobs.title'.tr(),
),
),
SizedBox(height: 20),
...widgets
],
);
},
);
}
}

@ -42,20 +42,18 @@ class _BrandFlashButtonState extends State<_BrandFlashButton>
super.dispose();
}
late bool wasPrevStateIsEmpty;
bool wasPrevStateIsEmpty = true;
@override
Widget build(BuildContext context) {
var hasNoJobs = context.watch<JobsCubit>().state.isEmpty;
wasPrevStateIsEmpty = hasNoJobs;
var icon = hasNoJobs ? Ionicons.flash_outline : Ionicons.flash;
return BlocListener<JobsCubit, JobsState>(
listener: (context, state) {
if (wasPrevStateIsEmpty && state.jobList.isNotEmpty) {
if (wasPrevStateIsEmpty && state is! JobsStateEmpty) {
wasPrevStateIsEmpty = false;
_animationController.forward();
} else if (!wasPrevStateIsEmpty && state.jobList.isEmpty) {
} else if (!wasPrevStateIsEmpty && state is JobsStateEmpty) {
wasPrevStateIsEmpty = true;
_animationController.reverse();
}
},
@ -73,6 +71,7 @@ class _BrandFlashButtonState extends State<_BrandFlashButton>
animation: _colorTween,
builder: (context, child) {
var v = _animationController.value;
var icon = v > 0.5 ? Ionicons.flash : Ionicons.flash_outline;
return Transform.scale(
scale: 1 + (v < 0.5 ? v : 1 - v) * 2,
child: Icon(

@ -82,7 +82,6 @@ class CpuChart extends StatelessWidget {
double appliedInterval,
double value,
) {
print(value);
if (value < 0) {
return false;
} else if (value == 0) {

@ -1,9 +1,9 @@
part of 'users.dart';
class _User extends StatelessWidget {
const _User({Key? key, this.user}) : super(key: key);
const _User({Key? key, required this.user}) : super(key: key);
final User? user;
final User user;
@override
Widget build(BuildContext context) {
return InkWell(
@ -24,12 +24,12 @@ class _User extends StatelessWidget {
width: 17,
height: 17,
decoration: BoxDecoration(
color: user!.color,
color: user.color,
shape: BoxShape.circle,
),
),
SizedBox(width: 20),
BrandText.h4(user!.login),
BrandText.h4(user.login),
],
),
),

@ -3,10 +3,10 @@ part of 'users.dart';
class _UserDetails extends StatelessWidget {
const _UserDetails({
Key? key,
this.user,
required this.user,
}) : super(key: key);
final User? user;
final User user;
@override
Widget build(BuildContext context) {
@ -23,7 +23,7 @@ class _UserDetails extends StatelessWidget {
Container(
height: 200,
decoration: BoxDecoration(
color: user!.color,
color: user.color,
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
@ -116,7 +116,7 @@ class _UserDetails extends StatelessWidget {
horizontal: 15,
),
child: BrandText.h1(
user!.login,
user.login,
softWrap: true,
overflow: TextOverflow.ellipsis,
)),
@ -133,14 +133,14 @@ class _UserDetails extends StatelessWidget {
Container(
height: 40,
alignment: Alignment.centerLeft,
child: BrandText.h4('${user!.login}@$domainName'),
child: BrandText.h4('${user.login}@$domainName'),
),
SizedBox(height: 14),
BrandText.small('basis.password'.tr()),
Container(
height: 40,
alignment: Alignment.centerLeft,
child: BrandText.h4(user!.password),
child: BrandText.h4(user.password),
),
SizedBox(height: 24),
BrandDivider(),
@ -148,7 +148,10 @@ class _UserDetails extends StatelessWidget {
BrandButton.emptyWithIconText(
title: 'users.send_regisration_data'.tr(),
icon: Icon(BrandIcons.share),
onPressed: () {},
onPressed: () {
Share.share(
'login: ${user.login}, password: ${user.password}');
},
),
SizedBox(height: 20),
],

@ -17,6 +17,7 @@ import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
import 'package:selfprivacy/utils/ui_helpers.dart';
import 'package:share_plus/share_plus.dart';
part 'fab.dart';
part 'new_user.dart';
@ -33,7 +34,6 @@ class UsersPage extends StatelessWidget {
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
final users = usersCubitState.users;
final isEmpty = usersCubitState.isEmpty;
Widget child;
if (!isReady) {
@ -48,7 +48,7 @@ class UsersPage extends StatelessWidget {
)
: ListView(
children: [
...users.map((user) => _User(user: user)),
...users.map((user) => _User(user: user)).toList(),
],
);
}

@ -0,0 +1,51 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
extension TextExtension on Text {
Text withColor(Color color) => Text(
data!,
key: this.key,
strutStyle: this.strutStyle,
textAlign: this.textAlign,
textDirection: this.textDirection,
locale: this.locale,
softWrap: this.softWrap,
overflow: this.overflow,
textScaleFactor: this.textScaleFactor,
maxLines: this.maxLines,
semanticsLabel: this.semanticsLabel,
textWidthBasis: textWidthBasis ?? this.textWidthBasis,
style: this.style != null
? this.style!.copyWith(color: color)
: TextStyle(color: color),
);
Text copyWith({
Key? key,
StrutStyle? strutStyle,
TextAlign? textAlign,
TextDirection? textDirection,
Locale? locale,
bool? softWrap,
TextOverflow? overflow,
double? textScaleFactor,
int? maxLines,
String? semanticsLabel,
TextWidthBasis? textWidthBasis,
TextStyle? style,
}) {
return Text(data!,
key: key ?? this.key,
strutStyle: strutStyle ?? this.strutStyle,
textAlign: textAlign ?? this.textAlign,
textDirection: textDirection ?? this.textDirection,
locale: locale ?? this.locale,
softWrap: softWrap ?? this.softWrap,
overflow: overflow ?? this.overflow,
textScaleFactor: textScaleFactor ?? this.textScaleFactor,
maxLines: maxLines ?? this.maxLines,
semanticsLabel: semanticsLabel ?? this.semanticsLabel,
textWidthBasis: textWidthBasis ?? this.textWidthBasis,
style: style != null ? this.style?.merge(style) ?? style : this.style);
}
}

@ -651,6 +651,48 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
share_plus:
dependency: "direct main"
description:
name: share_plus
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
share_plus_linux:
dependency: transitive
description:
name: share_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
share_plus_macos:
dependency: transitive
description:
name: share_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
share_plus_web:
dependency: transitive
description:
name: share_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
share_plus_windows:
dependency: transitive
description:
name: share_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences:
dependency: transitive
description:

@ -31,6 +31,7 @@ dependencies:
package_info: ^2.0.0
pretty_dio_logger: ^1.1.1
provider: ^5.0.0
share_plus: ^2.1.4
unicons: ^1.0.2
url_launcher: ^6.0.2
wakelock: ^0.5.0+2

Loading…
Cancel
Save