Compare commits

...

4 Commits

Author SHA1 Message Date
Inex Code e925a1897a Fix some typos 2022-02-16 10:28:29 +03:00
Inex Code 914d56ff87 DNS records cubit and screen 2022-02-16 10:09:53 +03:00
Inex Code 83a2d19e37 Introduce new brand screen, use it for backups 2022-02-16 10:01:05 +03:00
Inex Code 8de33ea19b Fix typo 2022-02-10 12:50:37 +03:00
35 changed files with 976 additions and 457 deletions

View File

@ -91,6 +91,38 @@
"status": "Status — Good",
"bottom_sheet": {
"1": "It's your personal internet address that will point to the server and other services of yours."
},
"screen_title": "Domain and DNS",
"states": {
"ok": "Records are OK",
"error": "Problems found",
"error_subtitle": "Tap here to fix them",
"refreshing": "Refreshing status...",
"uninitialized": "Data is not retrieved yet"
},
"record_description": {
"root": "Root domain",
"api": "SelfPrivacy API",
"cloud": "File cloud",
"git": "Git server",
"meet": "Video conference",
"social": "Social network",
"password": "Password manager",
"vpn": "VPN",
"mx": "MX record",
"dmarc": "DMARC record",
"spf": "SPF record",
"dkim": "DKIM key"
},
"cards": {
"services": {
"title": "Services",
"subtitle": "Type “A” records required for each service."
},
"email": {
"title": "Email",
"subtitle": "Records necessary for secure email exchange."
}
}
},
"backup": {
@ -111,6 +143,7 @@
"restoring": "Restoring from backup",
"error_pending": "Server returned error, check it below",
"restore_alert": "You are about to restore from backup created on {}. All current data will be lost. Are you sure?",
"refresh": "Refresh status",
"refetchBackups": "Refetch backup list",
"refetchingList": "In a few minutes list will be updated"
}
@ -119,7 +152,8 @@
"_comment": "Card shown when user skips initial setup",
"1": "Please finish application setup using ",
"2": "@:more.configuration_wizard",
"3": " for further work"
"3": " for further work",
"in_menu": "Server is not set up yet. Please finish setup using setup wizard for further work."
},
"services": {
"_comment": "Вкладка сервисы",
@ -202,7 +236,7 @@
"delete_confirm_question": "Are you sure?",
"reset_password": "Reset password",
"account": "Account",
"send_regisration_data": "Share login credentials"
"send_registration_data": "Share login credentials"
},
"initializing": {
"_comment": "initializing page",
@ -274,7 +308,7 @@
"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"
"length": "Length is [] should be {}",
"user_already_exist": "Already exists"
}
}

View File

@ -91,6 +91,38 @@
"status": "Статус — в норме",
"bottom_sheet": {
"1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы."
},
"screen_title": "Домен и DNS",
"states": {
"ok": "Записи в норме",
"error": "Обнаружены проблемы",
"error_subtitle": "Нажмите здесь, чтобы исправить",
"refreshing": "Обновление данных...",
"uninitialized": "Данные ещё не получены"
},
"record_description": {
"root": "Корневой домен",
"api": "SelfPrivacy API",
"cloud": "Файловое облако",
"git": "Git сервер",
"meet": "Видеоконференции",
"social": "Социальная сеть",
"password": "Менеджер паролей",
"vpn": "VPN",
"mx": "MX запись",
"dmarc": "DMARC запись",
"spf": "SPF запись",
"dkim": "DKIM ключ"
},
"cards": {
"services": {
"title": "Сервисы",
"subtitle": "Записи типа “A” необходимые для работы сервисов."
},
"email": {
"title": "Электронная почта",
"subtitle": "Записи необходимые для безопасного обмена электронной почтой."
}
}
},
"backup": {
@ -98,7 +130,7 @@
"status": "Статус — в норме",
"bottom_sheet": {
"1": "Выручит Вас в любой ситуации: хакерская атака, удаление сервера и т.д.",
"2": "Использовано 3Gb из бесплатых 10Gb. Последнее копирование была сделано вчера в {}."
"2": "Использовано 3Gb из бесплатых 10Gb. Последнее копирование была сделано вчера в {}."
},
"reuploadKey": "Принудительно обновить ключ",
"reuploadedKey": "Ключ на сервере обновлён",
@ -111,6 +143,7 @@
"restoring": "Восстановление из копии",
"error_pending": "Сервер вернул ошибку: проверьте её ниже.",
"restore_alert": "Вы собираетесь восстановить из копии созданной {}. Все текущие данные будут потеряны. Вы уверены?",
"refresh": "Обновить статус",
"refetchBackups": "Обновить список копий",
"refetchingList": "Через несколько минут список будет обновлён"
@ -120,7 +153,8 @@
"_comment": "Карточка показывающая когда человек скипнул настройку, на карте текст из 3 блоков, средний содержит ссыку на мастер подключения",
"1": "Завершите настройку приложения используя ",
"2": "@:more.configuration_wizard",
"3": " для продолжения работы"
"3": " для продолжения работы",
"in_menu": "Сервер ещё не настроен, воспользуйтесь мастером подключения."
},
"services": {
"_comment": "Вкладка сервисы",
@ -203,7 +237,7 @@
"delete_confirm_question": "Вы действительно хотите удалить учетную запись?",
"reset_password": "Сбросить пароль",
"account": "Учетная запись",
"send_regisration_data": "Поделиться реквизитами"
"send_registration_data": "Поделиться реквизитами"
},
"initializing": {
"_comment": "initializing page",
@ -276,6 +310,6 @@
"root_name": "Имя пользователя не может быть'root'.",
"key_format": "Неверный формат.",
"length": "Длина строки [] должна быть {}.",
"user_alredy_exist": "Имя уже используется."
"user_already_exist": "Имя уже используется."
}
}

View File

@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
@ -20,6 +21,7 @@ class BlocAndProviderConfig extends StatelessWidget {
var appConfigCubit = AppConfigCubit()..load();
var servicesCubit = ServicesCubit(appConfigCubit);
var backupsCubit = BackupsCubit(appConfigCubit);
var dnsRecordsCubit = DnsRecordsCubit(appConfigCubit);
return MultiProvider(
providers: [
BlocProvider(
@ -33,6 +35,7 @@ class BlocAndProviderConfig extends StatelessWidget {
BlocProvider(create: (_) => usersCubit..load(), lazy: false),
BlocProvider(create: (_) => servicesCubit..load(), lazy: false),
BlocProvider(create: (_) => backupsCubit..load(), lazy: false),
BlocProvider(create: (_) => dnsRecordsCubit..load()),
BlocProvider(
create: (_) =>
JobsCubit(usersCubit: usersCubit, servicesCubit: servicesCubit),

View File

@ -3,7 +3,7 @@ import 'package:selfprivacy/config/text_themes.dart';
import 'brand_colors.dart';
final ligtTheme = ThemeData(
final lightTheme = ThemeData(
primaryColor: BrandColors.primary,
fontFamily: 'Inter',
brightness: Brightness.light,
@ -38,16 +38,20 @@ final ligtTheme = ThemeData(
color: BrandColors.red1,
),
),
listTileTheme: ListTileThemeData(
minLeadingWidth: 24.0,
),
textTheme: TextTheme(
headline1: headline1Style,
headline2: headline2Style,
caption: headline4Style,
headline3: headline3Style,
headline4: headline4Style,
bodyText1: body1Style,
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
),
);
var darkTheme = ligtTheme.copyWith(
var darkTheme = lightTheme.copyWith(
brightness: Brightness.dark,
scaffoldBackgroundColor: Color(0xFF202120),
iconTheme: IconThemeData(color: BrandColors.gray3),
@ -56,7 +60,8 @@ var darkTheme = ligtTheme.copyWith(
textTheme: TextTheme(
headline1: headline1Style.copyWith(color: BrandColors.white),
headline2: headline2Style.copyWith(color: BrandColors.white),
caption: headline4Style.copyWith(color: BrandColors.white),
headline3: headline3Style.copyWith(color: BrandColors.white),
headline4: headline4Style.copyWith(color: BrandColors.white),
bodyText1: body1Style.copyWith(color: BrandColors.white),
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
),

View File

@ -12,7 +12,7 @@ import 'package:selfprivacy/logic/models/message.dart';
abstract class ApiMap {
Future<Dio> getClient() async {
var dio = Dio(await options);
if (hasLoger) {
if (hasLogger) {
dio.interceptors.add(PrettyDioLogger());
}
dio.interceptors.add(ConsoleInterceptor());
@ -38,7 +38,7 @@ abstract class ApiMap {
FutureOr<BaseOptions> get options;
abstract final String rootAddress;
abstract final bool hasLoger;
abstract final bool hasLogger;
abstract final bool isWithToken;
ValidateStatus? validateStatus;

View File

@ -1,4 +1,5 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/api_map.dart';
@ -20,7 +21,7 @@ class BackblazeApplicationKey {
}
class BackblazeApi extends ApiMap {
BackblazeApi({this.hasLoger = false, this.isWithToken = true});
BackblazeApi({this.hasLogger = false, this.isWithToken = true});
BaseOptions get options {
var options = BaseOptions(baseUrl: rootAddress);
@ -142,7 +143,7 @@ class BackblazeApi extends ApiMap {
}
@override
bool hasLoger;
bool hasLogger;
@override
bool isWithToken;

View File

@ -1,4 +1,5 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/api_map.dart';
@ -6,7 +7,7 @@ import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/dns_records.dart';
class CloudflareApi extends ApiMap {
CloudflareApi({this.hasLoger = false, this.isWithToken = true});
CloudflareApi({this.hasLogger = false, this.isWithToken = true});
BaseOptions get options {
var options = BaseOptions(baseUrl: rootAddress);
@ -87,6 +88,36 @@ class CloudflareApi extends ApiMap {
close(client);
}
Future<List<DnsRecord>> getDnsRecords({
required CloudFlareDomain cloudFlareDomain,
}) async {
var domainName = cloudFlareDomain.domainName;
var domainZoneId = cloudFlareDomain.zoneId;
var url = '/zones/$domainZoneId/dns_records';
var client = await getClient();
Response response = await client.get(url);
List records = response.data['result'] ?? [];
var allRecords = <DnsRecord>[];
for (var record in records) {
if (record['zone_name'] == domainName) {
allRecords.add(DnsRecord(
name: record['name'],
type: record['type'],
content: record['content'],
ttl: record['ttl'],
proxied: record['proxied'],
));
}
}
close(client);
return allRecords;
}
Future<void> createMultipleDnsRecords({
String? ip4,
required CloudFlareDomain cloudFlareDomain,
@ -113,33 +144,33 @@ class CloudflareApi extends ApiMap {
close(client);
}
List<DnsRecords> projectDnsRecords(String? domainName, String? ip4) {
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
List<DnsRecord> projectDnsRecords(String? domainName, String? ip4) {
var domainA = DnsRecord(type: 'A', name: domainName, content: ip4);
var mx = DnsRecords(type: 'MX', name: '@', content: domainName);
var apiA = DnsRecords(type: 'A', name: 'api', content: ip4);
var cloudA = DnsRecords(type: 'A', name: 'cloud', content: ip4);
var gitA = DnsRecords(type: 'A', name: 'git', content: ip4);
var meetA = DnsRecords(type: 'A', name: 'meet', content: ip4);
var passwordA = DnsRecords(type: 'A', name: 'password', content: ip4);
var socialA = DnsRecords(type: 'A', name: 'social', content: ip4);
var vpn = DnsRecords(type: 'A', name: 'vpn', content: ip4);
var mx = DnsRecord(type: 'MX', name: '@', content: domainName);
var apiA = DnsRecord(type: 'A', name: 'api', content: ip4);
var cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4);
var gitA = DnsRecord(type: 'A', name: 'git', content: ip4);
var meetA = DnsRecord(type: 'A', name: 'meet', content: ip4);
var passwordA = DnsRecord(type: 'A', name: 'password', content: ip4);
var socialA = DnsRecord(type: 'A', name: 'social', content: ip4);
var vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
var txt1 = DnsRecords(
var txt1 = DnsRecord(
type: 'TXT',
name: '_dmarc',
content: 'v=DMARC1; p=none',
ttl: 18000,
);
var txt2 = DnsRecords(
var txt2 = DnsRecord(
type: 'TXT',
name: domainName,
content: 'v=spf1 a mx ip4:$ip4 -all',
ttl: 18000,
);
return <DnsRecords>[
return <DnsRecord>[
domainA,
apiA,
cloudA,
@ -159,7 +190,7 @@ class CloudflareApi extends ApiMap {
final domainZoneId = cloudFlareDomain.zoneId;
final url = '$rootAddress/zones/$domainZoneId/dns_records';
final dkimRecord = DnsRecords(
final dkimRecord = DnsRecord(
type: 'TXT',
name: 'selector._domainkey',
content: dkimRecordString,
@ -191,7 +222,7 @@ class CloudflareApi extends ApiMap {
}
@override
final bool hasLoger;
final bool hasLogger;
@override
final bool isWithToken;

View File

@ -9,10 +9,10 @@ import 'package:selfprivacy/logic/models/user.dart';
import 'package:selfprivacy/utils/password_generator.dart';
class HetznerApi extends ApiMap {
bool hasLoger;
bool hasLogger;
bool isWithToken;
HetznerApi({this.hasLoger = false, this.isWithToken = true});
HetznerApi({this.hasLogger = false, this.isWithToken = true});
BaseOptions get options {
var options = BaseOptions(baseUrl: rootAddress);

View File

@ -12,10 +12,10 @@ import 'package:selfprivacy/logic/models/user.dart';
import 'api_map.dart';
class ServerApi extends ApiMap {
bool hasLoger;
bool hasLogger;
bool isWithToken;
ServerApi({this.hasLoger = false, this.isWithToken = true});
ServerApi({this.hasLogger = false, this.isWithToken = true});
BaseOptions get options {
var options = BaseOptions();
@ -262,11 +262,11 @@ extension UrlServerExt on ServiceTypes {
String get url {
switch (this) {
// case ServiceTypes.mail:
// return ''; // cannot be swithch off
// return ''; // cannot be switch off
// case ServiceTypes.messenger:
// return ''; // external service
// case ServiceTypes.video:
// return ''; // jeetsu meet not working
// return ''; // jitsi meet not working
case ServiceTypes.passwordManager:
return 'bitwarden';
case ServiceTypes.cloud:

View File

@ -4,7 +4,7 @@ import 'package:ionicons/ionicons.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
enum InitializingSteps {
setHeznerKey,
setHetznerKey,
setCloudFlareKey,
setDomainName,
setRootUser,

View File

@ -0,0 +1,184 @@
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/dns_records.dart';
import '../../api_maps/cloudflare.dart';
import '../../api_maps/server.dart';
part 'dns_records_state.dart';
class DnsRecordsCubit extends AppConfigDependendCubit<DnsRecordsState> {
DnsRecordsCubit(AppConfigCubit appConfigCubit)
: super(appConfigCubit,
DnsRecordsState(dnsState: DnsRecordsStatus.refreshing));
final api = ServerApi();
final cloudflare = CloudflareApi();
Future<void> load() async {
emit(DnsRecordsState(
dnsState: DnsRecordsStatus.refreshing,
dnsRecords: _getDesiredDnsRecords(
appConfigCubit.state.cloudFlareDomain?.domainName, "", "")));
print('Loading DNS status');
if (appConfigCubit.state is AppConfigFinished) {
final CloudFlareDomain? domain = appConfigCubit.state.cloudFlareDomain;
final String? ipAddress = appConfigCubit.state.hetznerServer?.ip4;
if (domain != null && ipAddress != null) {
final List<DnsRecord> records =
await cloudflare.getDnsRecords(cloudFlareDomain: domain);
final dkimPublicKey = await api.getDkim();
final desiredRecords =
_getDesiredDnsRecords(domain.domainName, ipAddress, dkimPublicKey);
List<DesiredDnsRecord> foundRecords = [];
for (final record in desiredRecords) {
if (record.description ==
'providers.domain.record_description.dkim') {
final foundRecord = records.firstWhere(
(r) => r.name == record.name && r.type == record.type,
orElse: () => DnsRecord(
name: record.name,
type: record.type,
content: '',
ttl: 800,
proxied: false));
// remove all spaces and tabulators from
// the foundRecord.content and the record.content
// to compare them
final foundContent =
foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
final content = record.content.replaceAll(RegExp(r'\s+'), '');
if (foundContent == content) {
foundRecords.add(record.copyWith(isSatisfied: true));
} else {
foundRecords.add(record.copyWith(isSatisfied: false));
}
} else {
if (records.any((r) =>
r.name == record.name &&
r.type == record.type &&
r.content == record.content)) {
foundRecords.add(record.copyWith(isSatisfied: true));
} else {
foundRecords.add(record.copyWith(isSatisfied: false));
}
}
}
emit(DnsRecordsState(
dnsRecords: foundRecords,
dnsState: foundRecords.any((r) => r.isSatisfied == false)
? DnsRecordsStatus.error
: DnsRecordsStatus.good,
));
} else {
emit(DnsRecordsState());
}
}
}
@override
void onChange(Change<DnsRecordsState> change) {
print(change);
super.onChange(change);
}
@override
Future<void> clear() async {
emit(DnsRecordsState(dnsState: DnsRecordsStatus.error));
}
Future<void> refresh() async {
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
await load();
}
Future<void> fix() async {
emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing));
final CloudFlareDomain? domain = appConfigCubit.state.cloudFlareDomain;
final String? ipAddress = appConfigCubit.state.hetznerServer?.ip4;
final dkimPublicKey = await api.getDkim();
await cloudflare.removeSimilarRecords(cloudFlareDomain: domain!);
await cloudflare.createMultipleDnsRecords(
cloudFlareDomain: domain, ip4: ipAddress);
await cloudflare.setDkim(dkimPublicKey, domain);
await load();
}
List<DesiredDnsRecord> _getDesiredDnsRecords(
String? domainName, String? ipAddress, String? dkimPublicKey) {
if (domainName == null || ipAddress == null || dkimPublicKey == null) {
return [];
}
return [
DesiredDnsRecord(
name: domainName,
content: ipAddress,
description: 'providers.domain.record_description.root',
),
DesiredDnsRecord(
name: 'api.$domainName',
content: ipAddress,
description: 'providers.domain.record_description.api',
),
DesiredDnsRecord(
name: 'cloud.$domainName',
content: ipAddress,
description: 'providers.domain.record_description.cloud',
),
DesiredDnsRecord(
name: 'git.$domainName',
content: ipAddress,
description: 'providers.domain.record_description.git',
),
DesiredDnsRecord(
name: 'meet.$domainName',
content: ipAddress,
description: 'providers.domain.record_description.meet',
),
DesiredDnsRecord(
name: 'social.$domainName',
content: ipAddress,
description: 'providers.domain.record_description.social',
),
DesiredDnsRecord(
name: 'password.$domainName',
content: ipAddress,
description: 'providers.domain.record_description.password',
),
DesiredDnsRecord(
name: 'vpn.$domainName',
content: ipAddress,
description: 'providers.domain.record_description.vpn',
),
DesiredDnsRecord(
name: domainName,
content: domainName,
description: 'providers.domain.record_description.mx',
type: 'MX',
category: DnsRecordsCategory.email,
),
DesiredDnsRecord(
name: '_dmarc.$domainName',
content: 'v=DMARC1; p=none',
description: 'providers.domain.record_description.dmarc',
type: 'TXT',
category: DnsRecordsCategory.email,
),
DesiredDnsRecord(
name: domainName,
content: 'v=spf1 a mx ip4:$ipAddress -all',
description: 'providers.domain.record_description.spf',
type: 'TXT',
category: DnsRecordsCategory.email,
),
DesiredDnsRecord(
name: 'selector._domainkey.$domainName',
content: dkimPublicKey,
description: 'providers.domain.record_description.dkim',
type: 'TXT',
category: DnsRecordsCategory.email,
),
];
}
}

View File

@ -0,0 +1,76 @@
part of 'dns_records_cubit.dart';
enum DnsRecordsStatus {
uninitialized,
refreshing,
good,
error,
}
enum DnsRecordsCategory {
services,
email,
other,
}
class DnsRecordsState extends AppConfigDependendState {
const DnsRecordsState({
this.dnsState = DnsRecordsStatus.uninitialized,
this.dnsRecords = const [],
});
final DnsRecordsStatus dnsState;
final List<DesiredDnsRecord> dnsRecords;
@override
List<Object> get props => [
dnsState,
dnsRecords,
];
DnsRecordsState copyWith({
DnsRecordsStatus? dnsState,
List<DesiredDnsRecord>? dnsRecords,
}) {
return DnsRecordsState(
dnsState: dnsState ?? this.dnsState,
dnsRecords: dnsRecords ?? this.dnsRecords,
);
}
}
class DesiredDnsRecord {
const DesiredDnsRecord({
required this.name,
this.type = "A",
required this.content,
this.description = '',
this.category = DnsRecordsCategory.services,
this.isSatisfied = false,
});
final String name;
final String type;
final String content;
final String description;
final DnsRecordsCategory category;
final bool isSatisfied;
DesiredDnsRecord copyWith({
String? name,
String? type,
String? content,
String? description,
DnsRecordsCategory? category,
bool? isSatisfied,
}) {
return DesiredDnsRecord(
name: name ?? this.name,
type: type ?? this.type,
content: content ?? this.content,
description: description ?? this.description,
category: category ?? this.category,
isSatisfied: isSatisfied ?? this.isSatisfied,
);
}
}

View File

@ -1,10 +1,11 @@
import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.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';
import '../validations/validations.dart';
class CloudFlareFormCubit extends FormCubit {
CloudFlareFormCubit(this.initializingCubit) {
@ -15,7 +16,7 @@ class CloudFlareFormCubit extends FormCubit {
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LegnthStringValidationWithLenghShowing(
LengthStringValidationWithLengthShowing(
40, 'validations.length'.tr(args: ["40"]))
],
);

View File

@ -42,7 +42,7 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
);
initializingCubit.setDomain(domain);
emit(DomainSetted());
emit(DomainSet());
}
}
@ -67,4 +67,4 @@ class Loaded extends DomainSetupState {
Loaded(this.domain);
}
class DomainSetted extends DomainSetupState {}
class DomainSet extends DomainSetupState {}

View File

@ -1,10 +1,11 @@
import 'dart:async';
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';
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import '../validations/validations.dart';
class HetznerFormCubit extends FormCubit {
HetznerFormCubit(this.initializingCubit) {
@ -15,7 +16,7 @@ class HetznerFormCubit extends FormCubit {
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
LegnthStringValidationWithLenghShowing(
LengthStringValidationWithLengthShowing(
64, 'validations.length'.tr(args: ["64"]))
],
);

View File

@ -1,10 +1,10 @@
import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/models/job.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/utils/password_generator.dart';
class UserFormCubit extends FormCubit {
@ -25,7 +25,7 @@ class UserFormCubit extends FormCubit {
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
ValidationModel(
(login) => users.any((user) => user.login == login),
'validations.user_alredy_exist'.tr(),
'validations.user_already_exist'.tr(),
),
RequiredStringValidation('validations.required'.tr()),
ValidationModel<String>(

View File

@ -1,7 +1,7 @@
import 'package:cubit_form/cubit_form.dart';
class LegnthStringValidationWithLenghShowing extends ValidationModel<String> {
LegnthStringValidationWithLenghShowing(int length, String errorText)
class LengthStringValidationWithLengthShowing extends ValidationModel<String> {
LengthStringValidationWithLengthShowing(int length, String errorText)
: super((n) => n.length != length, errorText);
@override

View File

@ -21,7 +21,7 @@ class HetznerMetricsRepository {
break;
}
var api = HetznerApi(hasLoger: true);
var api = HetznerApi(hasLogger: true);
var results = await Future.wait([
api.getMetrics(start, end, 'cpu'),

View File

@ -3,8 +3,8 @@ import 'package:json_annotation/json_annotation.dart';
part 'dns_records.g.dart';
@JsonSerializable(createToJson: true, createFactory: false)
class DnsRecords {
DnsRecords({
class DnsRecord {
DnsRecord({
required this.type,
required this.name,
required this.content,

View File

@ -6,7 +6,7 @@ part of 'dns_records.dart';
// JsonSerializableGenerator
// **************************************************************************
Map<String, dynamic> _$DnsRecordsToJson(DnsRecords instance) =>
Map<String, dynamic> _$DnsRecordsToJson(DnsRecord instance) =>
<String, dynamic>{
'type': instance.type,
'name': instance.name,

View File

@ -1,6 +1,6 @@
import 'package:intl/intl.dart';
final formater = new DateFormat('hh:mm');
final formatter = new DateFormat('hh:mm');
class Message {
Message({this.text, this.type = MessageType.normal}) : time = DateTime.now();
@ -8,7 +8,7 @@ class Message {
final String? text;
final DateTime time;
final MessageType type;
String get timeString => formater.format(time);
String get timeString => formatter.format(time);
static Message warn({String? text}) => Message(
text: text,

View File

@ -1,10 +1,10 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'package:wakelock/wakelock.dart';
@ -36,7 +36,7 @@ class MyApp extends StatelessWidget {
var appSettings = context.watch<AppSettingsCubit>().state;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
value: SystemUiOverlayStyle.light, // Manually changing appbar color
child: MaterialApp(
scaffoldMessengerKey:
getIt.get<NavigationService>().scaffoldMessengerKey,
@ -46,7 +46,7 @@ class MyApp extends StatelessWidget {
locale: context.locale,
debugShowCheckedModeBanner: false,
title: 'SelfPrivacy',
theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
theme: appSettings.isDarkModeOn ? darkTheme : lightTheme,
home: appSettings.isOnbordingShowing
? OnboardingPage(nextPage: InitializingPage())
: RootPage(),

View File

@ -20,6 +20,9 @@ class BrandCards {
shadow: bigShadow,
borderRadius: BorderRadius.circular(10),
);
static Widget outlined({required Widget child}) => _OutlinedCard(
child: child,
);
}
class _BrandCard extends StatelessWidget {
@ -52,6 +55,29 @@ class _BrandCard extends StatelessWidget {
}
}
class _OutlinedCard extends StatelessWidget {
const _OutlinedCard({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
return Card(
elevation: 0.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(
color: Colors.grey.withOpacity(0.2),
width: 1,
),
),
child: child,
);
}
}
final bigShadow = [
BoxShadow(
offset: Offset(0, 4),

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
class BrandHeroScreen extends StatelessWidget {
const BrandHeroScreen({
Key? key,
this.headerTitle = '',
this.hasBackButton = true,
this.hasFlashButton = true,
required this.children,
this.heroIcon,
this.heroTitle,
this.heroSubtitle,
}) : super(key: key);
final List<Widget> children;
final String headerTitle;
final bool hasBackButton;
final bool hasFlashButton;
final IconData? heroIcon;
final String? heroTitle;
final String? heroSubtitle;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(52.0),
child: BrandHeader(
title: headerTitle,
hasBackButton: hasBackButton,
hasFlashButton: hasFlashButton,
),
),
body: ListView(
padding: EdgeInsets.all(16.0),
children: <Widget>[
if (heroIcon != null)
Icon(
heroIcon,
size: 48.0,
),
SizedBox(height: 16.0),
if (heroTitle != null)
Text(heroTitle!,
style: Theme.of(context).textTheme.headline2,
textAlign: TextAlign.center),
SizedBox(height: 8.0),
if (heroSubtitle != null)
Text(heroSubtitle!,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center),
SizedBox(height: 16.0),
...children,
],
),
),
);
}
}

View File

@ -1,7 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:easy_localization/easy_localization.dart';
final _kBottomTabBarHeight = 51;
@ -62,12 +62,12 @@ class _BrandTabBarState extends State<BrandTabBar> {
}
_getIconButton(String label, IconData iconData, int index) {
var acitivColor = Theme.of(context).brightness == Brightness.dark
var activeColor = Theme.of(context).brightness == Brightness.dark
? BrandColors.white
: BrandColors.black;
var isActive = currentIndex == index;
var color = isActive ? acitivColor : BrandColors.inactive;
var color = isActive ? activeColor : BrandColors.inactive;
return InkWell(
onTap: () => widget.controller!.animateTo(index),
child: Padding(

View File

@ -1,9 +1,9 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/utils/named_font_weight.dart';
import 'package:easy_localization/easy_localization.dart';
class BrandTimer extends StatefulWidget {
const BrandTimer({
@ -30,7 +30,7 @@ class _BrandTimerState extends State<BrandTimer> {
}
_timerStart() {
_timeString = diffenceFromStart;
_timeString = differenceFromStart;
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
var timePassed = DateTime.now().difference(widget.startDateTime);
if (timePassed > widget.duration) {
@ -62,11 +62,11 @@ class _BrandTimerState extends State<BrandTimer> {
void _getTime() {
setState(() {
_timeString = diffenceFromStart;
_timeString = differenceFromStart;
});
}
String get diffenceFromStart =>
String get differenceFromStart =>
_durationToString(DateTime.now().difference(widget.startDateTime));
String _durationToString(Duration duration) {

View File

@ -1,6 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
@ -9,13 +9,11 @@ import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.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_hero_screen/brand_hero_screen.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:easy_localization/easy_localization.dart';
part 'header.dart';
import '../../components/brand_cards/brand_cards.dart';
var navigatorKey = GlobalKey<NavigatorState>();
@ -44,203 +42,200 @@ class _BackupDetailsState extends State<BackupDetails>
var backups = context.watch<BackupsCubit>().state.backups;
var refreshing = context.watch<BackupsCubit>().state.refreshing;
return Scaffold(
appBar: PreferredSize(
child: Column(
children: [
Container(
height: 51,
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 15),
child: BrandText.h4('basis.details'.tr()),
),
BrandDivider(),
],
),
preferredSize: Size.fromHeight(52),
),
body: SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: paddingH15V0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Header(
providerState: providerState,
refreshing: refreshing,
return BrandHeroScreen(
heroIcon: BrandIcons.save,
heroTitle: 'providers.backup.card_title'.tr(),
heroSubtitle: 'providers.backup.bottom_sheet.1'.tr(),
children: [
if (isReady && !isBackupInitialized)
BrandButton.rised(
onPressed: preventActions
? null
: () async {
await context.read<BackupsCubit>().createBucket();
},
text: 'providers.backup.initialize'.tr(),
),
if (backupStatus == BackupStatusEnum.initializing)
BrandText.body1('providers.backup.waitingForRebuild'.tr()),
if (backupStatus != BackupStatusEnum.initializing &&
backupStatus != BackupStatusEnum.noKey)
BrandCards.outlined(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (backupStatus == BackupStatusEnum.initialized)
ListTile(
onTap: preventActions
? null
: () async {
await context.read<BackupsCubit>().createBackup();
},
leading: Icon(
Icons.add_circle_outline_rounded,
),
title: Text(
'providers.backup.create_new'.tr(),
style: Theme.of(context).textTheme.headline6,
),
),
SizedBox(height: 10),
BrandText.h2('providers.backup.card_title'.tr()),
SizedBox(height: 10),
BrandText.body1('providers.backup.bottom_sheet.1'.tr()),
SizedBox(height: 20),
if (isReady && !isBackupInitialized)
BrandButton.rised(
onPressed: preventActions
? null
: () async {
await context.read<BackupsCubit>().createBucket();
},
text: 'providers.backup.initialize'.tr(),
if (backupStatus == BackupStatusEnum.backingUp)
ListTile(
title: Text(
'providers.backup.creating'.tr(
args: [(backupProgress * 100).round().toString()]),
style: Theme.of(context).textTheme.headline6,
),
if (backupStatus == BackupStatusEnum.initializing)
BrandText.body1('providers.backup.waitingForRebuild'.tr()),
if (backupStatus != BackupStatusEnum.initializing &&
backupStatus != BackupStatusEnum.noKey)
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(
color: Colors.grey.withOpacity(0.2),
width: 1,
),
),
elevation: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (backupStatus == BackupStatusEnum.initialized)
ListTile(
onTap: preventActions
? null
: () async {
await context
.read<BackupsCubit>()
.createBackup();
},
leading: Icon(
Icons.add_circle_outline_rounded,
color: BrandColors.textColor1,
),
title: BrandText.h5(
'providers.backup.create_new'.tr()),
),
if (backupStatus == BackupStatusEnum.backingUp)
ListTile(
title: BrandText.h5('providers.backup.creating'
.tr(args: [
(backupProgress * 100).round().toString()
])),
subtitle: LinearProgressIndicator(
value: backupProgress,
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
if (backupStatus == BackupStatusEnum.restoring)
ListTile(
title: BrandText.h5('providers.backup.restoring'
.tr(args: [
(backupProgress * 100).round().toString()
])),
subtitle: LinearProgressIndicator(
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
if (backupStatus == BackupStatusEnum.error)
ListTile(
leading: Icon(
Icons.error_outline,
color: BrandColors.red1,
),
title: BrandText.h5(
'providers.backup.error_pending'.tr()),
),
],
),
subtitle: LinearProgressIndicator(
value: backupProgress,
backgroundColor: Colors.grey.withOpacity(0.2),
),
SizedBox(height: 16),
// Card with a list of existing backups
// Each list item has a date
// When clicked, starts the restore action
if (backupStatus != BackupStatusEnum.initializing &&
backupStatus != BackupStatusEnum.noKey)
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(
color: Colors.grey.withOpacity(0.2),
width: 1,
),
),
elevation: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
leading: Icon(
Icons.refresh,
color: BrandColors.textColor1,
),
title:
BrandText.h5('providers.backup.restore'.tr()),
),
Divider(
height: 1.0,
),
if (backups.isEmpty)
ListTile(
leading: Icon(
Icons.error_outline,
),
title: Text('providers.backup.no_backups'.tr()),
),
if (backups.isNotEmpty)
Column(
children: backups.map((backup) {
return ListTile(
onTap: preventActions
? null
: () {
var nav = getIt<NavigationService>();
nav.showPopUpDialog(BrandAlert(
title: 'providers.backup.restoring'
.tr(),
contentText:
'providers.backup.restore_alert'
.tr(args: [
backup.time.toString()
]),
actions: [
ActionButton(
text: 'basis.cancel'.tr(),
),
ActionButton(
onPressed: () => {
context
.read<BackupsCubit>()
.restoreBackup(backup.id)
},
text: 'modals.yes'.tr(),
)
],
));
},
title: Text(
MaterialLocalizations.of(context)
.formatShortDate(backup.time) +
' ' +
TimeOfDay.fromDateTime(backup.time)
.format(context),
),
);
}).toList(),
),
],
),
),
if (backupStatus == BackupStatusEnum.restoring)
ListTile(
title: Text(
'providers.backup.restoring'.tr(
args: [(backupProgress * 100).round().toString()]),
style: Theme.of(context).textTheme.headline6,
),
if (backupStatus == BackupStatusEnum.error)
BrandText.body1(backupError.toString()),
],
),
subtitle: LinearProgressIndicator(
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
if (backupStatus == BackupStatusEnum.error)
ListTile(
leading: Icon(
Icons.error_outline,
color: BrandColors.red1,
),
title: Text(
'providers.backup.error_pending'.tr(),
style: Theme.of(context).textTheme.headline6,
),
),
],
),
SizedBox(height: 10),
],
),
SizedBox(height: 16),
// Card with a list of existing backups
// Each list item has a date
// When clicked, starts the restore action
if (backupStatus != BackupStatusEnum.initializing &&
backupStatus != BackupStatusEnum.noKey)
BrandCards.outlined(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
leading: Icon(
Icons.refresh,
),
title: Text(
'providers.backup.restore'.tr(),
style: Theme.of(context).textTheme.headline6,
),
),
Divider(
height: 1.0,
),
if (backups.isEmpty)
ListTile(
leading: Icon(
Icons.error_outline,
),
title: Text('providers.backup.no_backups'.tr()),
),
if (backups.isNotEmpty)
Column(
children: backups.map((backup) {
return ListTile(
onTap: preventActions
? null
: () {
var nav = getIt<NavigationService>();
nav.showPopUpDialog(BrandAlert(
title: 'providers.backup.restoring'.tr(),
contentText: 'providers.backup.restore_alert'
.tr(args: [backup.time.toString()]),
actions: [
ActionButton(
text: 'basis.cancel'.tr(),
),
ActionButton(
onPressed: () => {
context
.read<BackupsCubit>()
.restoreBackup(backup.id)
},
text: 'modals.yes'.tr(),
)
],
));
},
title: Text(
MaterialLocalizations.of(context)
.formatShortDate(backup.time) +
' ' +
TimeOfDay.fromDateTime(backup.time)
.format(context),
),
);
}).toList(),
),
],
),
),
SizedBox(height: 16),
BrandCards.outlined(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text(
'providers.backup.refresh'.tr(),
),
onTap: refreshing
? null
: () => {context.read<BackupsCubit>().updateBackups()},
enabled: !refreshing,
),
if (providerState != StateType.uninitialized)
Column(
children: [
Divider(
height: 1.0,
),
ListTile(
title: Text(
'providers.backup.refetchBackups'.tr(),
),
onTap: preventActions
? null
: () => {
context
.read<BackupsCubit>()
.forceUpdateBackups()
},
),
Divider(
height: 1.0,
),
ListTile(
title: Text(
'providers.backup.reuploadKey'.tr(),
),
onTap: preventActions
? null
: () => {context.read<BackupsCubit>().reuploadKey()},
),
],
),
],
),
),
),
if (backupStatus == BackupStatusEnum.error)
BrandText.body1(backupError.toString()),
],
);
}
}

View File

@ -1,80 +0,0 @@
part of 'backup_details.dart';
class _Header extends StatelessWidget {
const _Header(
{Key? key, required this.providerState, required this.refreshing})
: super(key: key);
final StateType providerState;
final bool refreshing;
@override
Widget build(BuildContext context) {
return Row(
children: [
IconStatusMask(
status: providerState,
child: Icon(
BrandIcons.save,
size: 40,
color: Colors.white,
),
),
Spacer(),
Padding(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 2,
),
child: IconButton(
onPressed: refreshing
? null
: () => {context.read<BackupsCubit>().updateBackups()},
icon: const Icon(Icons.refresh_rounded),
),
),
Padding(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 2,
),
child: PopupMenuButton<_PopupMenuItemType>(
enabled: providerState != StateType.uninitialized,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
onSelected: (_PopupMenuItemType result) {
switch (result) {
case _PopupMenuItemType.reuploadKey:
context.read<BackupsCubit>().reuploadKey();
break;
case _PopupMenuItemType.refetchBackups:
context.read<BackupsCubit>().forceUpdateBackups();
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<_PopupMenuItemType>(
value: _PopupMenuItemType.reuploadKey,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('providers.backup.reuploadKey'.tr()),
),
),
PopupMenuItem<_PopupMenuItemType>(
value: _PopupMenuItemType.refetchBackups,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('providers.backup.refetchBackups'.tr()),
),
),
],
),
),
],
);
}
}
enum _PopupMenuItemType { reuploadKey, refetchBackups }

View File

@ -0,0 +1,219 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
class DnsDetailsPage extends StatefulWidget {
@override
_DnsDetailsPageState createState() => _DnsDetailsPageState();
}
class _DnsDetailsPageState extends State<DnsDetailsPage> {
Widget _getStateCard(DnsRecordsStatus dnsState, Function fixCallback) {
var description = '';
var subtitle = '';
var icon = Icon(
Icons.check,
color: Colors.green,
);
switch (dnsState) {
case DnsRecordsStatus.uninitialized:
description = 'providers.domain.states.uninitialized'.tr();
icon = Icon(
Icons.refresh,
);
break;
case DnsRecordsStatus.refreshing:
description = 'providers.domain.states.refreshing'.tr();
icon = Icon(
Icons.refresh,
);
break;
case DnsRecordsStatus.good:
description = 'providers.domain.states.ok'.tr();
icon = Icon(
Icons.check,
color: Colors.green,
);
break;
case DnsRecordsStatus.error:
description = 'providers.domain.states.error'.tr();
subtitle = 'providers.domain.states.error_subtitle'.tr();
icon = Icon(
Icons.error,
color: Colors.red,
);
break;
}
return ListTile(
onTap: dnsState == DnsRecordsStatus.error ? () => fixCallback() : null,
title: Text(
description,
style: Theme.of(context).textTheme.headline6,
),
subtitle: subtitle != '' ? Text(subtitle) : null,
leading: icon,
);
}
@override
Widget build(BuildContext context) {
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
final domain = getIt<ApiConfigModel>().cloudFlareDomain?.domainName ?? '';
var dnsCubit = context.watch<DnsRecordsCubit>().state;
print(dnsCubit.dnsState);
if (!isReady) {
return BrandHeroScreen(
hasBackButton: true,
headerTitle: '',
heroIcon: BrandIcons.globe,
heroTitle: 'providers.domain.screen_title'.tr(),
children: <Widget>[
BrandCards.outlined(
child: ListTile(
title: Text(
'not_ready_card.in_menu'.tr(),
style: Theme.of(context).textTheme.headline6,
),
),
),
],
);
}
return BrandHeroScreen(
hasBackButton: true,
heroSubtitle: domain,
heroIcon: BrandIcons.globe,
heroTitle: 'providers.domain.screen_title'.tr(),
children: <Widget>[
BrandCards.outlined(
child: Column(
children: [
_getStateCard(dnsCubit.dnsState, () {
context.read<DnsRecordsCubit>().fix();
}),
],
),
),
SizedBox(height: 16.0),
// Outlined card with a list of A records and their
// status.
BrandCards.outlined(
child: Column(
children: <Widget>[
ListTile(
title: Text(
'providers.domain.cards.services.title'.tr(),
style: Theme.of(context).textTheme.headline6,
),
subtitle: Text(
'providers.domain.cards.services.subtitle'.tr(),
style: Theme.of(context).textTheme.caption,
),
),
...dnsCubit.dnsRecords
.where(
(dnsRecord) =>
dnsRecord.category == DnsRecordsCategory.services,
)
.map(
(dnsRecord) => Column(
children: [
Divider(
height: 1.0,
),
ListTile(
leading: Icon(
dnsRecord.isSatisfied
? Icons.check
: dnsCubit.dnsState ==
DnsRecordsStatus.refreshing
? Icons.refresh
: Icons.error,
color: dnsRecord.isSatisfied
? Colors.green
: dnsCubit.dnsState ==
DnsRecordsStatus.refreshing
? Colors.grey
: Colors.red,
),
title: Text(
dnsRecord.description.tr(),
style: Theme.of(context).textTheme.labelLarge,
),
subtitle: Text(
dnsRecord.name,
style: Theme.of(context).textTheme.caption,
),
),
],
),
)
.toList(),
],
),
),
SizedBox(height: 16.0),
BrandCards.outlined(
child: Column(
children: <Widget>[
ListTile(
title: Text(
'providers.domain.cards.email.title'.tr(),
style: Theme.of(context).textTheme.headline6,
),
subtitle: Text(
'providers.domain.cards.email.subtitle'.tr(),
style: Theme.of(context).textTheme.caption,
),
),
...dnsCubit.dnsRecords
.where(
(dnsRecord) =>
dnsRecord.category == DnsRecordsCategory.email,
)
.map(
(dnsRecord) => Column(
children: [
Divider(
height: 1.0,
),
ListTile(
leading: Icon(
dnsRecord.isSatisfied
? Icons.check
: dnsCubit.dnsState ==
DnsRecordsStatus.refreshing
? Icons.refresh
: Icons.error,
color: dnsRecord.isSatisfied
? Colors.green
: dnsCubit.dnsState ==
DnsRecordsStatus.refreshing
? Colors.grey
: Colors.red,
),
title: Text(
dnsRecord.description.tr(),
style: Theme.of(context).textTheme.labelLarge,
),
),
],
),
)
.toList(),
],
),
),
],
);
}
}

View File

@ -438,7 +438,7 @@ class InitializingPage extends StatelessWidget {
}
Widget _stepCheck(AppConfigCubit appConfigCubit) {
assert(appConfigCubit.state is AppConfigNotFinished, 'wronge state');
assert(appConfigCubit.state is AppConfigNotFinished, 'wrong state');
var state = appConfigCubit.state as TimerState;
late int doneCount;
late String? text;

View File

@ -1,11 +1,11 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart';
import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart';
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/models/provider.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart';
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
@ -14,9 +14,9 @@ import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:selfprivacy/ui/helpers/modals.dart';
import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart';
import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart';
import 'package:selfprivacy/ui/pages/server_details/server_details.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/utils/ui_helpers.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
var navigatorKey = GlobalKey<NavigatorState>();
@ -32,6 +32,18 @@ class _ProvidersPageState extends State<ProvidersPage> {
Widget build(BuildContext context) {
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
var isBackupInitialized = context.watch<BackupsCubit>().state.isInitialized;
var dnsStatus = context.watch<DnsRecordsCubit>().state.dnsState;
StateType getDnsStatus() {
if (dnsStatus == DnsRecordsStatus.uninitialized ||
dnsStatus == DnsRecordsStatus.refreshing) {
return StateType.uninitialized;
}
if (dnsStatus == DnsRecordsStatus.error) {
return StateType.warning;
}
return StateType.stable;
}
final cards = ProviderType.values
.map(
@ -42,7 +54,9 @@ class _ProvidersPageState extends State<ProvidersPage> {
state: isReady
? (type == ProviderType.backup && !isBackupInitialized
? StateType.uninitialized
: StateType.stable)
: (type == ProviderType.domain)
? getDnsStatus()
: StateType.stable)
: StateType.uninitialized,
type: type,
),
@ -102,33 +116,21 @@ class _Card extends StatelessWidget {
break;
case ProviderType.domain:
title = 'providers.domain.card_title'.tr();
title = 'providers.domain.screen_title'.tr();
message = domainName;
stableText = 'providers.domain.status'.tr();
onTap = () => showBrandBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return _ProviderDetails(
provider: provider,
statusText: stableText,
);
},
);
onTap = () => Navigator.of(context).push(materialRoute(
DnsDetailsPage(),
));
break;
case ProviderType.backup:
title = 'providers.backup.card_title'.tr();
stableText = 'providers.backup.status'.tr();
onTap = () => showBrandBottomSheet(
context: context,
builder: (BuildContext context) {
return BrandBottomSheet(
isExpended: true,
child: BackupDetails(),
);
},
);
onTap = () => Navigator.of(context).push(materialRoute(
BackupDetails(),
));
break;
}
return GestureDetector(
@ -155,77 +157,3 @@ class _Card extends StatelessWidget {
);
}
}
class _ProviderDetails extends StatelessWidget {
const _ProviderDetails({
Key? key,
required this.provider,
required this.statusText,
}) : super(key: key);
final ProviderModel provider;
final String? statusText;
@override
Widget build(BuildContext context) {
late String title;
late List<Widget> children;
var config = context.watch<AppConfigCubit>().state;
var domainName = UiHelpers.getDomainName(config);
switch (provider.type) {
case ProviderType.server:
throw ('wrong type');
case ProviderType.domain:
title = 'providers.domain.card_title'.tr();
children = [
BrandText.body1('providers.domain.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BrandText.body1(domainName),
SizedBox(height: 10),
BrandText.body1('providers.domain.status'.tr()),
];
break;
case ProviderType.backup:
title = 'providers.backup.card_title'.tr();
children = [
BrandText.body1('providers.backup.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BrandText.body1(
'providers.backup.bottom_sheet.2'.tr(args: [domainName, 'Time'])),
SizedBox(height: 10),
BrandText.body1('providers.backup.status'.tr()),
];
break;
}
return BrandBottomSheet(
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 40),
Padding(
padding: paddingH15V0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconStatusMask(
status: provider.state,
child: Icon(provider.icon, size: 40, color: Colors.white),
),
SizedBox(height: 10),
BrandText.h1(title),
SizedBox(height: 10),
...children,
SizedBox(height: 30),
],
),
)
],
),
),
);
}
}

View File

@ -28,7 +28,7 @@ class _TextDetails extends StatelessWidget {
TableRow(
children: [
getRowTitle('Last check:'),
getRowValue(formater.format(checkTime)),
getRowValue(formatter.format(checkTime)),
],
),
TableRow(
@ -168,4 +168,4 @@ class _TempMessage extends StatelessWidget {
}
}
final DateFormat formater = DateFormat('HH:mm:ss');
final DateFormat formatter = DateFormat('HH:mm:ss');

View File

@ -101,13 +101,13 @@ class _Card extends StatelessWidget {
var jobsCubit = context.watch<JobsCubit>();
var jobState = jobsCubit.state;
var switchebleService = switchableServices.contains(serviceType);
var hasSwitchJob = switchebleService &&
var switchableService = switchableServices.contains(serviceType);
var hasSwitchJob = switchableService &&
jobState is JobsStateWithJobs &&
jobState.jobList
.any((el) => el is ServiceToggleJob && el.type == serviceType);
var isSwithOn = isReady &&
var isSwitchOn = isReady &&
(!switchableServices.contains(serviceType) ||
serviceState.isEnableByType(serviceType));
@ -115,7 +115,7 @@ class _Card extends StatelessWidget {
var domainName = UiHelpers.getDomainName(config);
return GestureDetector(
onTap: isSwithOn
onTap: isSwitchOn
? () => showDialog<void>(
context: context,
// isScrollControlled: true,
@ -124,7 +124,7 @@ class _Card extends StatelessWidget {
return _ServiceDetails(
serviceType: serviceType,
status:
isSwithOn ? StateType.stable : StateType.uninitialized,
isSwitchOn ? StateType.stable : StateType.uninitialized,
title: serviceType.title,
icon: serviceType.icon,
changeTab: changeTab,
@ -140,10 +140,10 @@ class _Card extends StatelessWidget {
children: [
IconStatusMask(
status:
isSwithOn ? StateType.stable : StateType.uninitialized,
isSwitchOn ? StateType.stable : StateType.uninitialized,
child: Icon(serviceType.icon, size: 30, color: Colors.white),
),
if (isReady && switchebleService) ...[
if (isReady && switchableService) ...[
Spacer(),
Builder(
builder: (context) {

View File

@ -152,7 +152,7 @@ class _UserDetails extends StatelessWidget {
BrandDivider(),
SizedBox(height: 20),
BrandButton.emptyWithIconText(
title: 'users.send_regisration_data'.tr(),
title: 'users.send_registration_data'.tr(),
icon: Icon(BrandIcons.share),
onPressed: () {
Share.share(

View File

@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
Color stringToColor(String string) {
var number = string.codeUnits.reduce((a, b) => a + b);
var index = number % colorPallete.length;
return colorPallete[index];
var index = number % colorPalette.length;
return colorPalette[index];
}
var originalColor = Color(0xFFDBD8BD);
var count = 40;
var colorPallete = List.generate(
var colorPalette = List.generate(
count,
(index) => HSLColor.fromColor(originalColor)
.withHue((index) * 360.0 / count)