Merge pull request 'jobs' (#28) from jobs into master

Reviewed-on: kherel/selfprivacy.org.app#28
master
Illia Chub 2 years ago
commit b130960113
  1. 23
      assets/translations/en.json
  2. 16
      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. 47
      lib/logic/api_maps/server.dart
  8. 12
      lib/logic/cubit/app_config/app_config_state.dart
  9. 3
      lib/logic/cubit/forms/initializing/backblaze_form_cubit.dart
  10. 8
      lib/logic/cubit/forms/initializing/cloudflare_form_cubit.dart
  11. 7
      lib/logic/cubit/forms/initializing/hetzner_form_cubit.dart
  12. 11
      lib/logic/cubit/forms/initializing/root_user_form_cubit.dart
  13. 25
      lib/logic/cubit/forms/user/user_form_cubit.dart
  14. 44
      lib/logic/cubit/jobs/jobs_cubit.dart
  15. 28
      lib/logic/cubit/jobs/jobs_state.dart
  16. 24
      lib/logic/cubit/users/users_cubit.dart
  17. 15
      lib/logic/models/jobs/job.dart
  18. 57
      lib/main.dart
  19. 59
      lib/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart
  20. 21
      lib/ui/components/brand_loader/brand_loader.dart
  21. 1
      lib/ui/components/brand_md/brand_md.dart
  22. 120
      lib/ui/components/brand_modal_sheet/brand_modal_sheet.dart
  23. 1
      lib/ui/components/brand_text/brand_text.dart
  24. 112
      lib/ui/components/jobs_content/jobs_content.dart
  25. 42
      lib/ui/components/pre_styled_buttons/flash.dart
  26. 3
      lib/ui/components/pre_styled_buttons/pre_styled_buttons.dart
  27. 4
      lib/ui/components/progress_bar/progress_bar.dart
  28. 14
      lib/ui/helpers/modals.dart
  29. 33
      lib/ui/pages/initializing/initializing.dart
  30. 87
      lib/ui/pages/providers/providers.dart
  31. 1
      lib/ui/pages/server_details/cpu_chart.dart
  32. 83
      lib/ui/pages/server_details/server_details.dart
  33. 31
      lib/ui/pages/services/services.dart
  34. 36
      lib/ui/pages/users/new_user.dart
  35. 12
      lib/ui/pages/users/user.dart
  36. 21
      lib/ui/pages/users/user_details.dart
  37. 9
      lib/ui/pages/users/users.dart
  38. 51
      lib/utils/extensions/text_extensions.dart
  39. 167
      pubspec.lock
  40. 3
      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",
@ -34,7 +36,7 @@
"onboarding": "Onboarding",
"console": "Console",
"about_app_page": {
"text": "Тут любая служебная информация, v.{}"
"text": "Application version v.{}"
},
"settings": {
"title": "Application settings",
@ -188,7 +190,9 @@
"20": "\n",
"21": "One more restart to apply your security certificates.",
"22": "Create master account",
"23": "Enter a nickname and strong password"
"23": "Enter a nickname and strong password",
"finish": "Everything is initialized",
"checks": "Checks have been completed \n{} ouf of {}"
},
"modals": {
"_comment": "messages in modals",
@ -198,7 +202,8 @@
"4": "Purge all authentication keys?",
"5": "Yes, purge all my tokens",
"6": "Delete the server and volume?",
"7": "Yes"
"7": "Yes",
"8": "Remove task"
},
"timer": {
"sec": "{} sec"
@ -208,5 +213,13 @@
"title": "Jobs list",
"start": "Start",
"empty": "No jobs"
},
"validations": {
"required": "Required",
"invalid_format": "Invalid format",
"root_name": "User name cannot be 'root'",
"key_format": "Invalid key format",
"length": "Length is [] shoud be {}",
"user_alredy_exist": "Already exists"
}
}

@ -24,7 +24,9 @@
"later": "Настрою потом",
"reset": "Reset",
"details": "Детальная информация",
"no_data": "Нет данных"
"no_data": "Нет данных",
"wait": "Ожидайте",
"remove": "Удалить"
},
"more": {
"_comment": "вкладка еще",
@ -188,7 +190,9 @@
"20": "\n2 Заходим в созданный нами проект. Если такового - нет, значит создаём.\n3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний — Security (с иконкой ключика).\n4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.\n5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.\n6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.",
"21": "Сейчас будет дополнительная перезагрузка для активации сертификатов безопастности",
"22": "Создайте главную учетную запись",
"23": "Введите никнейм и сложный пароль"
"23": "Введите никнейм и сложный пароль",
"finish": "Все инициализировано",
"checks": "Проверок выполнено: \n{} / {}"
},
"modals": {
"_comment": "messages in modals",
@ -209,5 +213,13 @@
"title": "Задачи",
"start": "Начать выполенение",
"empty": "Пусто"
},
"validations": {
"required": "обязательное поле",
"invalid_format": "Неверный формат",
"root_name": "Имя пользователя не может быть'root'",
"key_format": "Неверный формат",
"length": "Длина строки [] должна быть {}",
"user_alredy_exist": "Имя уже используется"
}
}

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

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'api_map.dart';
@ -40,6 +41,52 @@ class ServerApi extends ApiMap {
return res;
}
Future<bool> createUser(User user) async {
bool res;
Response response;
var client = await getClient();
try {
response = await client.post(
'/createUser',
options: Options(
headers: {
"X-User": user.login,
"X-Password":
'\$6\$${user.hashPassword.salt}\$${user.hashPassword.hash}',
},
),
);
res = response.statusCode == HttpStatus.ok;
} catch (e) {
print(e);
res = false;
}
close(client);
return res;
}
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;
}
}

@ -85,7 +85,17 @@ class AppConfigState extends Equatable {
bool get isServerCreated => hetznerServer != null;
bool get isFullyInitilized => _fulfilementList.every((el) => el!);
int get progress => _fulfilementList.where((el) => el!).length;
int get progress => _fulfilementList.where((el) => el!).length ;
int get porgressBar {
if (progress < 6) {
return progress;
} else if (progress < 10) {
return 6;
} else {
return 7;
}
}
List<bool?> get _fulfilementList {
var res = [

@ -3,6 +3,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/backblaze.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
import 'package:easy_localization/easy_localization.dart';
class BackblazeFormCubit extends FormCubit {
BackblazeFormCubit(this.initializingCubit) {
@ -10,7 +11,7 @@ class BackblazeFormCubit extends FormCubit {
keyId = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
RequiredStringValidation('validations.required'.tr()),
//ValidationModel<String>(
//(s) => regExp.hasMatch(s), 'invalid key format'),
//LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')

@ -4,6 +4,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import 'package:easy_localization/easy_localization.dart';
class CloudFlareFormCubit extends FormCubit {
CloudFlareFormCubit(this.initializingCubit) {
@ -11,10 +12,11 @@ class CloudFlareFormCubit extends FormCubit {
apiKey = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => regExp.hasMatch(s), 'invalid key format'),
LegnthStringValidationWithLenghShowing(40, 'length is [] shoud be 40')
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LegnthStringValidationWithLenghShowing(
40, 'validations.length'.tr(args: ["40"]))
],
);

@ -4,6 +4,7 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:easy_localization/easy_localization.dart';
class HetznerFormCubit extends FormCubit {
HetznerFormCubit(this.initializingCubit) {
@ -11,10 +12,10 @@ class HetznerFormCubit extends FormCubit {
apiKey = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => regExp.hasMatch(s), 'invalid key format'),
LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LegnthStringValidationWithLenghShowing(64, 'validations.length'.tr(args: ["64"]))
],
);

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'package:easy_localization/easy_localization.dart';
class RootUserFormCubit extends FormCubit {
RootUserFormCubit(this.initializingCubit) {
@ -12,18 +13,20 @@ class RootUserFormCubit extends FormCubit {
userName = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
ValidationModel<String>(
(s) => userRegExp.hasMatch(s), 'invalid format'),
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
],
);
password = FieldCubit(
initalValue: '',
validations: [
RequiredStringValidation('required'),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => passwordRegExp.hasMatch(s), 'invalid format'),
(s) => passwordRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
],
);

@ -1,13 +1,16 @@
import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/models/jobs/job.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'package:selfprivacy/utils/password_generator.dart';
import 'package:easy_localization/easy_localization.dart';
class UserFormCubit extends FormCubit {
UserFormCubit({
required this.usersCubit,
required this.jobsCubit,
required List<User> users,
User? user,
}) {
var isEdit = user != null;
@ -18,18 +21,22 @@ class UserFormCubit extends FormCubit {
login = FieldCubit(
initalValue: isEdit ? user!.login : '',
validations: [
RequiredStringValidation('required'),
ValidationModel(
(login) => users.any((user) => user.login == login),
'validations.user_alredy_exist'.tr(),
),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => userRegExp.hasMatch(s), 'invalid format'),
(s) => userRegExp.hasMatch(s), 'validations.invalid_format'.tr()),
],
);
password = FieldCubit(
initalValue: isEdit ? user!.password : genPass(),
validations: [
RequiredStringValidation('required'),
ValidationModel<String>(
(s) => passwordRegExp.hasMatch(s), 'invalid format'),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>((s) => passwordRegExp.hasMatch(s),
'validations.invalid_format'.tr()),
],
);
@ -42,7 +49,7 @@ class UserFormCubit extends FormCubit {
login: login.state.value,
password: password.state.value,
);
usersCubit.addUser(user);
jobsCubit.addJob(CreateUserJob(user: user));
}
late FieldCubit<String> login;
@ -52,5 +59,5 @@ class UserFormCubit extends FormCubit {
password.externalSetValue(genPass());
}
late UsersCubit usersCubit;
final JobsCubit jobsCubit;
}

@ -1,27 +1,53 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/logic/models/job.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());
List<Job> jobsList = [];
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);
}
void applyAll() {
print(state.jobList);
emit(JobsState.emtpy());
Future<void> applyAll() async {
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();
}
}
}

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

@ -1,6 +1,10 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/utils/password_generator2.dart';
import '../user.dart';
@immutable
class Job extends Equatable {
Job({
String? id,
@ -13,3 +17,14 @@ class Job extends Equatable {
@override
List<Object> get props => [id, title];
}
class CreateUserJob extends Job {
CreateUserJob({
required this.user,
}) : super(title: 'Create ${user.login}');
final User user;
@override
List<Object> get props => [id, title];
}

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

@ -1,25 +1,58 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/brand_colors.dart';
class BrandBottomSheet extends StatelessWidget {
const BrandBottomSheet({Key? key, required this.child}) : super(key: key);
const BrandBottomSheet({
Key? key,
required this.child,
this.isExpended = false,
}) : super(key: key);
final Widget child;
final bool isExpended;
@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top -
60,
child: Scaffold(
body: SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: Container(
padding: paddingH15V0,
child: child,
var mainHeight = MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top -
100;
late Widget innerWidget;
if (isExpended) {
innerWidget = Scaffold(
body: child,
);
} else {
final ThemeData themeData = Theme.of(context);
innerWidget = Material(
color: themeData.scaffoldBackgroundColor,
child: IntrinsicHeight(child: child),
);
}
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: mainHeight + 4 + 6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Container(
height: 4,
width: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
color: BrandColors.gray4,
),
),
),
SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: mainHeight),
child: innerWidget,
),
),
),
],
),
);
}

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

@ -56,6 +56,7 @@ class _BrandMarkdownState extends State<BrandMarkdown> {
),
);
return Markdown(
shrinkWrap: true,
styleSheet: markdown,
onTapLink: (String text, String? href, String title) {
if (href != null) {

@ -1,63 +1,63 @@
import 'package:flutter/material.dart';
// import 'package:flutter/material.dart';
var navigatorKey = GlobalKey<NavigatorState>();
// var navigatorKey = GlobalKey<NavigatorState>();
class BrandModalSheet extends StatelessWidget {
const BrandModalSheet({
Key? key,
this.child,
}) : super(key: key);
// class BrandModalSheet extends StatelessWidget {
// const BrandModalSheet({
// Key? key,
// this.child,
// }) : super(key: key);
final Widget? child;
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
minChildSize: 0.95,
initialChildSize: 1,
maxChildSize: 1,
builder: (context, scrollController) {
return SingleChildScrollView(
controller: scrollController,
physics: ClampingScrollPhysics(),
child: Container(
child: Column(
children: [
GestureDetector(
onTap: () => Navigator.of(context).pop(),
behavior: HitTestBehavior.opaque,
child: Container(
width: double.infinity,
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 132, bottom: 6),
child: Container(
height: 4,
width: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
color: Color(0xFFE3E3E3).withOpacity(0.65),
),
),
),
),
),
),
Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - 132,
maxHeight: MediaQuery.of(context).size.height - 132,
),
decoration: BoxDecoration(
borderRadius:
BorderRadius.vertical(top: Radius.circular(20)),
color: Theme.of(context).scaffoldBackgroundColor,
),
width: double.infinity,
child: child),
],
),
),
);
});
}
}
// final Widget? child;
// @override
// Widget build(BuildContext context) {
// return DraggableScrollableSheet(
// minChildSize: 1,
// initialChildSize: 1,
// maxChildSize: 1,
// builder: (context, scrollController) {
// return SingleChildScrollView(
// controller: scrollController,
// physics: ClampingScrollPhysics(),
// child: Container(
// child: Column(
// children: [
// GestureDetector(
// onTap: () => Navigator.of(context).pop(),
// behavior: HitTestBehavior.opaque,
// child: Container(
// width: double.infinity,
// child: Center(
// child: Padding(
// padding: EdgeInsets.only(top: 132, bottom: 6),
// child: Container(
// height: 4,
// width: 30,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(2),
// color: Color(0xFFE3E3E3).withOpacity(0.65),
// ),
// ),
// ),
// ),
// ),
// ),
// Container(
// constraints: BoxConstraints(
// minHeight: MediaQuery.of(context).size.height - 132,
// maxHeight: MediaQuery.of(context).size.height - 132,
// ),
// decoration: BoxDecoration(
// borderRadius:
// BorderRadius.vertical(top: Radius.circular(20)),
// color: Theme.of(context).scaffoldBackgroundColor,
// ),
// width: double.infinity,
// child: child),
// ],
// ),
// ),
// );
// });
// }
// }

@ -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,9 +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 {
@ -11,57 +15,71 @@ class JobsContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
var jobs = context.watch<JobsCubit>().state;
return Column(
children: [
SizedBox(height: 15),
Center(
child: BrandText.h2(
'jobs.title'.tr(),
),
),
if (jobs.isEmpty)
Padding(
padding: const EdgeInsets.only(top: 50),
child: 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
],
);
},
);
}
}

@ -15,12 +15,25 @@ class _BrandFlashButtonState extends State<_BrandFlashButton>
@override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 600));
AnimationController(vsync: this, duration: Duration(milliseconds: 800));
_colorTween = ColorTween(
begin: BrandColors.black,
end: BrandColors.primary,
).animate(_animationController);
super.initState();
WidgetsBinding.instance!.addPostFrameCallback(_afterLayout);
}
void _afterLayout(_) {
if (Theme.of(context).brightness == Brightness.dark) {
setState(() {
_colorTween = ColorTween(
begin: BrandColors.white,
end: BrandColors.primary,
).animate(_animationController);
});
}
}
@override
@ -29,29 +42,27 @@ 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();
}
},
child: IconButton(
onPressed: () {
showCupertinoModalBottomSheet(
expand: false,
showBrandBottomSheet(
context: context,
builder: (context) => BrandBottomSheet(
isExpended: true,
child: JobsContent(),
),
);
@ -59,9 +70,14 @@ class _BrandFlashButtonState extends State<_BrandFlashButton>
icon: AnimatedBuilder(
animation: _colorTween,
builder: (context, child) {
return Icon(
icon,
color: _colorTween.value,
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(
icon,
color: _colorTween.value,
),
);
}),
),

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ionicons/ionicons.dart';
import 'package:selfprivacy/config/brand_colors.dart';
@ -6,8 +7,8 @@ import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
part 'close.dart';
part 'flash.dart';

@ -53,9 +53,9 @@ class _ProgressBarState extends State<ProgressBar> {
width: 10,
),
);
even.add(
odd.add(
SizedBox(
width: 10,
width: 20,
),
);

@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
Future<T?> showBrandBottomSheet<T>({
required BuildContext context,
required WidgetBuilder builder,
}) =>
showCupertinoModalBottomSheet<T>(
builder: builder,
barrierColor: Colors.black45,
context: context,
shadow: BoxShadow(color: Colors.transparent),
backgroundColor: Colors.transparent,
);

@ -10,10 +10,10 @@ import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.da
import 'package:selfprivacy/logic/cubit/forms/initializing/root_user_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.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_md/brand_md.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
@ -36,7 +36,7 @@ class InitializingPage extends StatelessWidget {
() => _stepCheck(cubit),
() => _stepCheck(cubit),
() => _stepCheck(cubit),
() => Container(child: Text('Everythigng is initialized'))
() => Container(child: Center(child: Text('initializing.finish'.tr())))
][cubit.state.progress]();
return BlocListener<AppConfigCubit, AppConfigState>(
listener: (context, state) {
@ -59,12 +59,9 @@ class InitializingPage extends StatelessWidget {
'Domain',
'User',
'Server',
'',
'',
'',
'',
'✅ Check',
],
activeIndex: cubit.state.progress,
activeIndex: cubit.state.porgressBar,
),
),
_addCard(
@ -443,21 +440,29 @@ class InitializingPage extends StatelessWidget {
Widget _stepCheck(AppConfigCubit appConfigCubit) {
assert(appConfigCubit.state is TimerState, 'wronge state');
var state = appConfigCubit.state as TimerState;
late int doneCount;
late String? text;
if (state.isServerResetedSecondTime) {