forked from SelfPrivacy/selfprivacy.org.app
Compare commits
4 Commits
d06e8976c5
...
e925a1897a
Author | SHA1 | Date |
---|---|---|
Inex Code | e925a1897a | |
Inex Code | 914d56ff87 | |
Inex Code | 83a2d19e37 | |
Inex Code | 8de33ea19b |
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
@ -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": "Имя уже используется."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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"]))
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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"]))
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,40 +42,11 @@ 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(
|
||||
return BrandHeroScreen(
|
||||
heroIcon: BrandIcons.save,
|
||||
heroTitle: 'providers.backup.card_title'.tr(),
|
||||
heroSubtitle: 'providers.backup.bottom_sheet.1'.tr(),
|
||||
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,
|
||||
),
|
||||
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
|
||||
|
@ -91,15 +60,7 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
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,
|
||||
BrandCards.outlined(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
@ -108,23 +69,23 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
onTap: preventActions
|
||||
? null
|
||||
: () async {
|
||||
await context
|
||||
.read<BackupsCubit>()
|
||||
.createBackup();
|
||||
await context.read<BackupsCubit>().createBackup();
|
||||
},
|
||||
leading: Icon(
|
||||
Icons.add_circle_outline_rounded,
|
||||
color: BrandColors.textColor1,
|
||||
),
|
||||
title: BrandText.h5(
|
||||
'providers.backup.create_new'.tr()),
|
||||
title: Text(
|
||||
'providers.backup.create_new'.tr(),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
),
|
||||
if (backupStatus == BackupStatusEnum.backingUp)
|
||||
ListTile(
|
||||
title: BrandText.h5('providers.backup.creating'
|
||||
.tr(args: [
|
||||
(backupProgress * 100).round().toString()
|
||||
])),
|
||||
title: Text(
|
||||
'providers.backup.creating'.tr(
|
||||
args: [(backupProgress * 100).round().toString()]),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
subtitle: LinearProgressIndicator(
|
||||
value: backupProgress,
|
||||
backgroundColor: Colors.grey.withOpacity(0.2),
|
||||
|
@ -132,10 +93,11 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
),
|
||||
if (backupStatus == BackupStatusEnum.restoring)
|
||||
ListTile(
|
||||
title: BrandText.h5('providers.backup.restoring'
|
||||
.tr(args: [
|
||||
(backupProgress * 100).round().toString()
|
||||
])),
|
||||
title: Text(
|
||||
'providers.backup.restoring'.tr(
|
||||
args: [(backupProgress * 100).round().toString()]),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
subtitle: LinearProgressIndicator(
|
||||
backgroundColor: Colors.grey.withOpacity(0.2),
|
||||
),
|
||||
|
@ -146,8 +108,10 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
Icons.error_outline,
|
||||
color: BrandColors.red1,
|
||||
),
|
||||
title: BrandText.h5(
|
||||
'providers.backup.error_pending'.tr()),
|
||||
title: Text(
|
||||
'providers.backup.error_pending'.tr(),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -158,25 +122,18 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
// 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,
|
||||
BrandCards.outlined(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.refresh,
|
||||
color: BrandColors.textColor1,
|
||||
),
|
||||
title:
|
||||
BrandText.h5('providers.backup.restore'.tr()),
|
||||
title: Text(
|
||||
'providers.backup.restore'.tr(),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1.0,
|
||||
|
@ -197,13 +154,9 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
: () {
|
||||
var nav = getIt<NavigationService>();
|
||||
nav.showPopUpDialog(BrandAlert(
|
||||
title: 'providers.backup.restoring'
|
||||
.tr(),
|
||||
contentText:
|
||||
'providers.backup.restore_alert'
|
||||
.tr(args: [
|
||||
backup.time.toString()
|
||||
]),
|
||||
title: 'providers.backup.restoring'.tr(),
|
||||
contentText: 'providers.backup.restore_alert'
|
||||
.tr(args: [backup.time.toString()]),
|
||||
actions: [
|
||||
ActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
|
@ -232,15 +185,57 @@ class _BackupDetailsState extends State<BackupDetails>
|
|||
],
|
||||
),
|
||||
),
|
||||
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()),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
|
@ -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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,6 +54,8 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
state: isReady
|
||||
? (type == ProviderType.backup && !isBackupInitialized
|
||||
? StateType.uninitialized
|
||||
: (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),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue