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",
|
"status": "Status — Good",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "It's your personal internet address that will point to the server and other services of yours."
|
"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": {
|
"backup": {
|
||||||
|
@ -111,6 +143,7 @@
|
||||||
"restoring": "Restoring from backup",
|
"restoring": "Restoring from backup",
|
||||||
"error_pending": "Server returned error, check it below",
|
"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?",
|
"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",
|
"refetchBackups": "Refetch backup list",
|
||||||
"refetchingList": "In a few minutes list will be updated"
|
"refetchingList": "In a few minutes list will be updated"
|
||||||
}
|
}
|
||||||
|
@ -119,7 +152,8 @@
|
||||||
"_comment": "Card shown when user skips initial setup",
|
"_comment": "Card shown when user skips initial setup",
|
||||||
"1": "Please finish application setup using ",
|
"1": "Please finish application setup using ",
|
||||||
"2": "@:more.configuration_wizard",
|
"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": {
|
"services": {
|
||||||
"_comment": "Вкладка сервисы",
|
"_comment": "Вкладка сервисы",
|
||||||
|
@ -202,7 +236,7 @@
|
||||||
"delete_confirm_question": "Are you sure?",
|
"delete_confirm_question": "Are you sure?",
|
||||||
"reset_password": "Reset password",
|
"reset_password": "Reset password",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"send_regisration_data": "Share login credentials"
|
"send_registration_data": "Share login credentials"
|
||||||
},
|
},
|
||||||
"initializing": {
|
"initializing": {
|
||||||
"_comment": "initializing page",
|
"_comment": "initializing page",
|
||||||
|
@ -274,7 +308,7 @@
|
||||||
"invalid_format": "Invalid format",
|
"invalid_format": "Invalid format",
|
||||||
"root_name": "User name cannot be 'root'",
|
"root_name": "User name cannot be 'root'",
|
||||||
"key_format": "Invalid key format",
|
"key_format": "Invalid key format",
|
||||||
"length": "Length is [] shoud be {}",
|
"length": "Length is [] should be {}",
|
||||||
"user_alredy_exist": "Already exists"
|
"user_already_exist": "Already exists"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,38 @@
|
||||||
"status": "Статус — в норме",
|
"status": "Статус — в норме",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы."
|
"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": {
|
"backup": {
|
||||||
|
@ -98,7 +130,7 @@
|
||||||
"status": "Статус — в норме",
|
"status": "Статус — в норме",
|
||||||
"bottom_sheet": {
|
"bottom_sheet": {
|
||||||
"1": "Выручит Вас в любой ситуации: хакерская атака, удаление сервера и т.д.",
|
"1": "Выручит Вас в любой ситуации: хакерская атака, удаление сервера и т.д.",
|
||||||
"2": "Использовано 3Gb из бесплатых 10Gb. Последнее копирование была сделано вчера в {}."
|
"2": "Использовано 3Gb из бесплатых 10Gb. Последнее копирование была сделано вчера в {}."
|
||||||
},
|
},
|
||||||
"reuploadKey": "Принудительно обновить ключ",
|
"reuploadKey": "Принудительно обновить ключ",
|
||||||
"reuploadedKey": "Ключ на сервере обновлён",
|
"reuploadedKey": "Ключ на сервере обновлён",
|
||||||
|
@ -111,6 +143,7 @@
|
||||||
"restoring": "Восстановление из копии",
|
"restoring": "Восстановление из копии",
|
||||||
"error_pending": "Сервер вернул ошибку: проверьте её ниже.",
|
"error_pending": "Сервер вернул ошибку: проверьте её ниже.",
|
||||||
"restore_alert": "Вы собираетесь восстановить из копии созданной {}. Все текущие данные будут потеряны. Вы уверены?",
|
"restore_alert": "Вы собираетесь восстановить из копии созданной {}. Все текущие данные будут потеряны. Вы уверены?",
|
||||||
|
"refresh": "Обновить статус",
|
||||||
"refetchBackups": "Обновить список копий",
|
"refetchBackups": "Обновить список копий",
|
||||||
"refetchingList": "Через несколько минут список будет обновлён"
|
"refetchingList": "Через несколько минут список будет обновлён"
|
||||||
|
|
||||||
|
@ -120,7 +153,8 @@
|
||||||
"_comment": "Карточка показывающая когда человек скипнул настройку, на карте текст из 3 блоков, средний содержит ссыку на мастер подключения",
|
"_comment": "Карточка показывающая когда человек скипнул настройку, на карте текст из 3 блоков, средний содержит ссыку на мастер подключения",
|
||||||
"1": "Завершите настройку приложения используя ",
|
"1": "Завершите настройку приложения используя ",
|
||||||
"2": "@:more.configuration_wizard",
|
"2": "@:more.configuration_wizard",
|
||||||
"3": " для продолжения работы"
|
"3": " для продолжения работы",
|
||||||
|
"in_menu": "Сервер ещё не настроен, воспользуйтесь мастером подключения."
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"_comment": "Вкладка сервисы",
|
"_comment": "Вкладка сервисы",
|
||||||
|
@ -203,7 +237,7 @@
|
||||||
"delete_confirm_question": "Вы действительно хотите удалить учетную запись?",
|
"delete_confirm_question": "Вы действительно хотите удалить учетную запись?",
|
||||||
"reset_password": "Сбросить пароль",
|
"reset_password": "Сбросить пароль",
|
||||||
"account": "Учетная запись",
|
"account": "Учетная запись",
|
||||||
"send_regisration_data": "Поделиться реквизитами"
|
"send_registration_data": "Поделиться реквизитами"
|
||||||
},
|
},
|
||||||
"initializing": {
|
"initializing": {
|
||||||
"_comment": "initializing page",
|
"_comment": "initializing page",
|
||||||
|
@ -276,6 +310,6 @@
|
||||||
"root_name": "Имя пользователя не может быть'root'.",
|
"root_name": "Имя пользователя не может быть'root'.",
|
||||||
"key_format": "Неверный формат.",
|
"key_format": "Неверный формат.",
|
||||||
"length": "Длина строки [] должна быть {}.",
|
"length": "Длина строки [] должна быть {}.",
|
||||||
"user_alredy_exist": "Имя уже используется."
|
"user_already_exist": "Имя уже используется."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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_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/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/jobs/jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
|
@ -20,6 +21,7 @@ class BlocAndProviderConfig extends StatelessWidget {
|
||||||
var appConfigCubit = AppConfigCubit()..load();
|
var appConfigCubit = AppConfigCubit()..load();
|
||||||
var servicesCubit = ServicesCubit(appConfigCubit);
|
var servicesCubit = ServicesCubit(appConfigCubit);
|
||||||
var backupsCubit = BackupsCubit(appConfigCubit);
|
var backupsCubit = BackupsCubit(appConfigCubit);
|
||||||
|
var dnsRecordsCubit = DnsRecordsCubit(appConfigCubit);
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
|
@ -33,6 +35,7 @@ class BlocAndProviderConfig extends StatelessWidget {
|
||||||
BlocProvider(create: (_) => usersCubit..load(), lazy: false),
|
BlocProvider(create: (_) => usersCubit..load(), lazy: false),
|
||||||
BlocProvider(create: (_) => servicesCubit..load(), lazy: false),
|
BlocProvider(create: (_) => servicesCubit..load(), lazy: false),
|
||||||
BlocProvider(create: (_) => backupsCubit..load(), lazy: false),
|
BlocProvider(create: (_) => backupsCubit..load(), lazy: false),
|
||||||
|
BlocProvider(create: (_) => dnsRecordsCubit..load()),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) =>
|
create: (_) =>
|
||||||
JobsCubit(usersCubit: usersCubit, servicesCubit: servicesCubit),
|
JobsCubit(usersCubit: usersCubit, servicesCubit: servicesCubit),
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:selfprivacy/config/text_themes.dart';
|
||||||
|
|
||||||
import 'brand_colors.dart';
|
import 'brand_colors.dart';
|
||||||
|
|
||||||
final ligtTheme = ThemeData(
|
final lightTheme = ThemeData(
|
||||||
primaryColor: BrandColors.primary,
|
primaryColor: BrandColors.primary,
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
|
@ -38,16 +38,20 @@ final ligtTheme = ThemeData(
|
||||||
color: BrandColors.red1,
|
color: BrandColors.red1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
listTileTheme: ListTileThemeData(
|
||||||
|
minLeadingWidth: 24.0,
|
||||||
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: TextTheme(
|
||||||
headline1: headline1Style,
|
headline1: headline1Style,
|
||||||
headline2: headline2Style,
|
headline2: headline2Style,
|
||||||
caption: headline4Style,
|
headline3: headline3Style,
|
||||||
|
headline4: headline4Style,
|
||||||
bodyText1: body1Style,
|
bodyText1: body1Style,
|
||||||
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
|
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
var darkTheme = ligtTheme.copyWith(
|
var darkTheme = lightTheme.copyWith(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
scaffoldBackgroundColor: Color(0xFF202120),
|
scaffoldBackgroundColor: Color(0xFF202120),
|
||||||
iconTheme: IconThemeData(color: BrandColors.gray3),
|
iconTheme: IconThemeData(color: BrandColors.gray3),
|
||||||
|
@ -56,7 +60,8 @@ var darkTheme = ligtTheme.copyWith(
|
||||||
textTheme: TextTheme(
|
textTheme: TextTheme(
|
||||||
headline1: headline1Style.copyWith(color: BrandColors.white),
|
headline1: headline1Style.copyWith(color: BrandColors.white),
|
||||||
headline2: headline2Style.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),
|
bodyText1: body1Style.copyWith(color: BrandColors.white),
|
||||||
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
|
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
|
||||||
),
|
),
|
||||||
|
|
|
@ -12,7 +12,7 @@ import 'package:selfprivacy/logic/models/message.dart';
|
||||||
abstract class ApiMap {
|
abstract class ApiMap {
|
||||||
Future<Dio> getClient() async {
|
Future<Dio> getClient() async {
|
||||||
var dio = Dio(await options);
|
var dio = Dio(await options);
|
||||||
if (hasLoger) {
|
if (hasLogger) {
|
||||||
dio.interceptors.add(PrettyDioLogger());
|
dio.interceptors.add(PrettyDioLogger());
|
||||||
}
|
}
|
||||||
dio.interceptors.add(ConsoleInterceptor());
|
dio.interceptors.add(ConsoleInterceptor());
|
||||||
|
@ -38,7 +38,7 @@ abstract class ApiMap {
|
||||||
FutureOr<BaseOptions> get options;
|
FutureOr<BaseOptions> get options;
|
||||||
|
|
||||||
abstract final String rootAddress;
|
abstract final String rootAddress;
|
||||||
abstract final bool hasLoger;
|
abstract final bool hasLogger;
|
||||||
abstract final bool isWithToken;
|
abstract final bool isWithToken;
|
||||||
|
|
||||||
ValidateStatus? validateStatus;
|
ValidateStatus? validateStatus;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||||
|
@ -20,7 +21,7 @@ class BackblazeApplicationKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackblazeApi extends ApiMap {
|
class BackblazeApi extends ApiMap {
|
||||||
BackblazeApi({this.hasLoger = false, this.isWithToken = true});
|
BackblazeApi({this.hasLogger = false, this.isWithToken = true});
|
||||||
|
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
var options = BaseOptions(baseUrl: rootAddress);
|
var options = BaseOptions(baseUrl: rootAddress);
|
||||||
|
@ -142,7 +143,7 @@ class BackblazeApi extends ApiMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool hasLoger;
|
bool hasLogger;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isWithToken;
|
bool isWithToken;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/api_map.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';
|
import 'package:selfprivacy/logic/models/dns_records.dart';
|
||||||
|
|
||||||
class CloudflareApi extends ApiMap {
|
class CloudflareApi extends ApiMap {
|
||||||
CloudflareApi({this.hasLoger = false, this.isWithToken = true});
|
CloudflareApi({this.hasLogger = false, this.isWithToken = true});
|
||||||
|
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
var options = BaseOptions(baseUrl: rootAddress);
|
var options = BaseOptions(baseUrl: rootAddress);
|
||||||
|
@ -87,6 +88,36 @@ class CloudflareApi extends ApiMap {
|
||||||
close(client);
|
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({
|
Future<void> createMultipleDnsRecords({
|
||||||
String? ip4,
|
String? ip4,
|
||||||
required CloudFlareDomain cloudFlareDomain,
|
required CloudFlareDomain cloudFlareDomain,
|
||||||
|
@ -113,33 +144,33 @@ class CloudflareApi extends ApiMap {
|
||||||
close(client);
|
close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DnsRecords> projectDnsRecords(String? domainName, String? ip4) {
|
List<DnsRecord> projectDnsRecords(String? domainName, String? ip4) {
|
||||||
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
|
var domainA = DnsRecord(type: 'A', name: domainName, content: ip4);
|
||||||
|
|
||||||
var mx = DnsRecords(type: 'MX', name: '@', content: domainName);
|
var mx = DnsRecord(type: 'MX', name: '@', content: domainName);
|
||||||
var apiA = DnsRecords(type: 'A', name: 'api', content: ip4);
|
var apiA = DnsRecord(type: 'A', name: 'api', content: ip4);
|
||||||
var cloudA = DnsRecords(type: 'A', name: 'cloud', content: ip4);
|
var cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4);
|
||||||
var gitA = DnsRecords(type: 'A', name: 'git', content: ip4);
|
var gitA = DnsRecord(type: 'A', name: 'git', content: ip4);
|
||||||
var meetA = DnsRecords(type: 'A', name: 'meet', content: ip4);
|
var meetA = DnsRecord(type: 'A', name: 'meet', content: ip4);
|
||||||
var passwordA = DnsRecords(type: 'A', name: 'password', content: ip4);
|
var passwordA = DnsRecord(type: 'A', name: 'password', content: ip4);
|
||||||
var socialA = DnsRecords(type: 'A', name: 'social', content: ip4);
|
var socialA = DnsRecord(type: 'A', name: 'social', content: ip4);
|
||||||
var vpn = DnsRecords(type: 'A', name: 'vpn', content: ip4);
|
var vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4);
|
||||||
|
|
||||||
var txt1 = DnsRecords(
|
var txt1 = DnsRecord(
|
||||||
type: 'TXT',
|
type: 'TXT',
|
||||||
name: '_dmarc',
|
name: '_dmarc',
|
||||||
content: 'v=DMARC1; p=none',
|
content: 'v=DMARC1; p=none',
|
||||||
ttl: 18000,
|
ttl: 18000,
|
||||||
);
|
);
|
||||||
|
|
||||||
var txt2 = DnsRecords(
|
var txt2 = DnsRecord(
|
||||||
type: 'TXT',
|
type: 'TXT',
|
||||||
name: domainName,
|
name: domainName,
|
||||||
content: 'v=spf1 a mx ip4:$ip4 -all',
|
content: 'v=spf1 a mx ip4:$ip4 -all',
|
||||||
ttl: 18000,
|
ttl: 18000,
|
||||||
);
|
);
|
||||||
|
|
||||||
return <DnsRecords>[
|
return <DnsRecord>[
|
||||||
domainA,
|
domainA,
|
||||||
apiA,
|
apiA,
|
||||||
cloudA,
|
cloudA,
|
||||||
|
@ -159,7 +190,7 @@ class CloudflareApi extends ApiMap {
|
||||||
final domainZoneId = cloudFlareDomain.zoneId;
|
final domainZoneId = cloudFlareDomain.zoneId;
|
||||||
final url = '$rootAddress/zones/$domainZoneId/dns_records';
|
final url = '$rootAddress/zones/$domainZoneId/dns_records';
|
||||||
|
|
||||||
final dkimRecord = DnsRecords(
|
final dkimRecord = DnsRecord(
|
||||||
type: 'TXT',
|
type: 'TXT',
|
||||||
name: 'selector._domainkey',
|
name: 'selector._domainkey',
|
||||||
content: dkimRecordString,
|
content: dkimRecordString,
|
||||||
|
@ -191,7 +222,7 @@ class CloudflareApi extends ApiMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final bool hasLoger;
|
final bool hasLogger;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final bool isWithToken;
|
final bool isWithToken;
|
||||||
|
|
|
@ -9,10 +9,10 @@ import 'package:selfprivacy/logic/models/user.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
class HetznerApi extends ApiMap {
|
class HetznerApi extends ApiMap {
|
||||||
bool hasLoger;
|
bool hasLogger;
|
||||||
bool isWithToken;
|
bool isWithToken;
|
||||||
|
|
||||||
HetznerApi({this.hasLoger = false, this.isWithToken = true});
|
HetznerApi({this.hasLogger = false, this.isWithToken = true});
|
||||||
|
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
var options = BaseOptions(baseUrl: rootAddress);
|
var options = BaseOptions(baseUrl: rootAddress);
|
||||||
|
|
|
@ -12,10 +12,10 @@ import 'package:selfprivacy/logic/models/user.dart';
|
||||||
import 'api_map.dart';
|
import 'api_map.dart';
|
||||||
|
|
||||||
class ServerApi extends ApiMap {
|
class ServerApi extends ApiMap {
|
||||||
bool hasLoger;
|
bool hasLogger;
|
||||||
bool isWithToken;
|
bool isWithToken;
|
||||||
|
|
||||||
ServerApi({this.hasLoger = false, this.isWithToken = true});
|
ServerApi({this.hasLogger = false, this.isWithToken = true});
|
||||||
|
|
||||||
BaseOptions get options {
|
BaseOptions get options {
|
||||||
var options = BaseOptions();
|
var options = BaseOptions();
|
||||||
|
@ -262,11 +262,11 @@ extension UrlServerExt on ServiceTypes {
|
||||||
String get url {
|
String get url {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
// case ServiceTypes.mail:
|
// case ServiceTypes.mail:
|
||||||
// return ''; // cannot be swithch off
|
// return ''; // cannot be switch off
|
||||||
// case ServiceTypes.messenger:
|
// case ServiceTypes.messenger:
|
||||||
// return ''; // external service
|
// return ''; // external service
|
||||||
// case ServiceTypes.video:
|
// case ServiceTypes.video:
|
||||||
// return ''; // jeetsu meet not working
|
// return ''; // jitsi meet not working
|
||||||
case ServiceTypes.passwordManager:
|
case ServiceTypes.passwordManager:
|
||||||
return 'bitwarden';
|
return 'bitwarden';
|
||||||
case ServiceTypes.cloud:
|
case ServiceTypes.cloud:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:ionicons/ionicons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
|
|
||||||
enum InitializingSteps {
|
enum InitializingSteps {
|
||||||
setHeznerKey,
|
setHetznerKey,
|
||||||
setCloudFlareKey,
|
setCloudFlareKey,
|
||||||
setDomainName,
|
setDomainName,
|
||||||
setRootUser,
|
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 'dart:async';
|
||||||
|
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
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/api_maps/cloudflare.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.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 {
|
class CloudFlareFormCubit extends FormCubit {
|
||||||
CloudFlareFormCubit(this.initializingCubit) {
|
CloudFlareFormCubit(this.initializingCubit) {
|
||||||
|
@ -15,7 +16,7 @@ class CloudFlareFormCubit extends FormCubit {
|
||||||
RequiredStringValidation('validations.required'.tr()),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
|
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
|
||||||
LegnthStringValidationWithLenghShowing(
|
LengthStringValidationWithLengthShowing(
|
||||||
40, 'validations.length'.tr(args: ["40"]))
|
40, 'validations.length'.tr(args: ["40"]))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -42,7 +42,7 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
|
||||||
);
|
);
|
||||||
|
|
||||||
initializingCubit.setDomain(domain);
|
initializingCubit.setDomain(domain);
|
||||||
emit(DomainSetted());
|
emit(DomainSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,4 +67,4 @@ class Loaded extends DomainSetupState {
|
||||||
Loaded(this.domain);
|
Loaded(this.domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DomainSetted extends DomainSetupState {}
|
class DomainSet extends DomainSetupState {}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
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: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 {
|
class HetznerFormCubit extends FormCubit {
|
||||||
HetznerFormCubit(this.initializingCubit) {
|
HetznerFormCubit(this.initializingCubit) {
|
||||||
|
@ -15,7 +16,7 @@ class HetznerFormCubit extends FormCubit {
|
||||||
RequiredStringValidation('validations.required'.tr()),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
|
(s) => regExp.hasMatch(s), 'validations.key_format'.tr()),
|
||||||
LegnthStringValidationWithLenghShowing(
|
LengthStringValidationWithLengthShowing(
|
||||||
64, 'validations.length'.tr(args: ["64"]))
|
64, 'validations.length'.tr(args: ["64"]))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
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/cubit/jobs/jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/job.dart';
|
import 'package:selfprivacy/logic/models/job.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
class UserFormCubit extends FormCubit {
|
class UserFormCubit extends FormCubit {
|
||||||
|
@ -25,7 +25,7 @@ class UserFormCubit extends FormCubit {
|
||||||
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
|
(s) => s.toLowerCase() == 'root', 'validations.root_name'.tr()),
|
||||||
ValidationModel(
|
ValidationModel(
|
||||||
(login) => users.any((user) => user.login == login),
|
(login) => users.any((user) => user.login == login),
|
||||||
'validations.user_alredy_exist'.tr(),
|
'validations.user_already_exist'.tr(),
|
||||||
),
|
),
|
||||||
RequiredStringValidation('validations.required'.tr()),
|
RequiredStringValidation('validations.required'.tr()),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
|
||||||
class LegnthStringValidationWithLenghShowing extends ValidationModel<String> {
|
class LengthStringValidationWithLengthShowing extends ValidationModel<String> {
|
||||||
LegnthStringValidationWithLenghShowing(int length, String errorText)
|
LengthStringValidationWithLengthShowing(int length, String errorText)
|
||||||
: super((n) => n.length != length, errorText);
|
: super((n) => n.length != length, errorText);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -21,7 +21,7 @@ class HetznerMetricsRepository {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var api = HetznerApi(hasLoger: true);
|
var api = HetznerApi(hasLogger: true);
|
||||||
|
|
||||||
var results = await Future.wait([
|
var results = await Future.wait([
|
||||||
api.getMetrics(start, end, 'cpu'),
|
api.getMetrics(start, end, 'cpu'),
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'package:json_annotation/json_annotation.dart';
|
||||||
part 'dns_records.g.dart';
|
part 'dns_records.g.dart';
|
||||||
|
|
||||||
@JsonSerializable(createToJson: true, createFactory: false)
|
@JsonSerializable(createToJson: true, createFactory: false)
|
||||||
class DnsRecords {
|
class DnsRecord {
|
||||||
DnsRecords({
|
DnsRecord({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.content,
|
required this.content,
|
||||||
|
|
|
@ -6,7 +6,7 @@ part of 'dns_records.dart';
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
Map<String, dynamic> _$DnsRecordsToJson(DnsRecords instance) =>
|
Map<String, dynamic> _$DnsRecordsToJson(DnsRecord instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'type': instance.type,
|
'type': instance.type,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
final formater = new DateFormat('hh:mm');
|
final formatter = new DateFormat('hh:mm');
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
Message({this.text, this.type = MessageType.normal}) : time = DateTime.now();
|
Message({this.text, this.type = MessageType.normal}) : time = DateTime.now();
|
||||||
|
@ -8,7 +8,7 @@ class Message {
|
||||||
final String? text;
|
final String? text;
|
||||||
final DateTime time;
|
final DateTime time;
|
||||||
final MessageType type;
|
final MessageType type;
|
||||||
String get timeString => formater.format(time);
|
String get timeString => formatter.format(time);
|
||||||
|
|
||||||
static Message warn({String? text}) => Message(
|
static Message warn({String? text}) => Message(
|
||||||
text: text,
|
text: text,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/hive_config.dart';
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.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:selfprivacy/ui/pages/rootRoute.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class MyApp extends StatelessWidget {
|
||||||
var appSettings = context.watch<AppSettingsCubit>().state;
|
var appSettings = context.watch<AppSettingsCubit>().state;
|
||||||
|
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
|
value: SystemUiOverlayStyle.light, // Manually changing appbar color
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
scaffoldMessengerKey:
|
scaffoldMessengerKey:
|
||||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||||
|
@ -46,7 +46,7 @@ class MyApp extends StatelessWidget {
|
||||||
locale: context.locale,
|
locale: context.locale,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'SelfPrivacy',
|
title: 'SelfPrivacy',
|
||||||
theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
|
theme: appSettings.isDarkModeOn ? darkTheme : lightTheme,
|
||||||
home: appSettings.isOnbordingShowing
|
home: appSettings.isOnbordingShowing
|
||||||
? OnboardingPage(nextPage: InitializingPage())
|
? OnboardingPage(nextPage: InitializingPage())
|
||||||
: RootPage(),
|
: RootPage(),
|
||||||
|
|
|
@ -20,6 +20,9 @@ class BrandCards {
|
||||||
shadow: bigShadow,
|
shadow: bigShadow,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
);
|
);
|
||||||
|
static Widget outlined({required Widget child}) => _OutlinedCard(
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BrandCard extends StatelessWidget {
|
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 = [
|
final bigShadow = [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
offset: Offset(0, 4),
|
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:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
|
|
||||||
final _kBottomTabBarHeight = 51;
|
final _kBottomTabBarHeight = 51;
|
||||||
|
|
||||||
|
@ -62,12 +62,12 @@ class _BrandTabBarState extends State<BrandTabBar> {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getIconButton(String label, IconData iconData, int index) {
|
_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.white
|
||||||
: BrandColors.black;
|
: BrandColors.black;
|
||||||
|
|
||||||
var isActive = currentIndex == index;
|
var isActive = currentIndex == index;
|
||||||
var color = isActive ? acitivColor : BrandColors.inactive;
|
var color = isActive ? activeColor : BrandColors.inactive;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => widget.controller!.animateTo(index),
|
onTap: () => widget.controller!.animateTo(index),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
|
|
||||||
class BrandTimer extends StatefulWidget {
|
class BrandTimer extends StatefulWidget {
|
||||||
const BrandTimer({
|
const BrandTimer({
|
||||||
|
@ -30,7 +30,7 @@ class _BrandTimerState extends State<BrandTimer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
_timerStart() {
|
_timerStart() {
|
||||||
_timeString = diffenceFromStart;
|
_timeString = differenceFromStart;
|
||||||
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
|
timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
|
||||||
var timePassed = DateTime.now().difference(widget.startDateTime);
|
var timePassed = DateTime.now().difference(widget.startDateTime);
|
||||||
if (timePassed > widget.duration) {
|
if (timePassed > widget.duration) {
|
||||||
|
@ -62,11 +62,11 @@ class _BrandTimerState extends State<BrandTimer> {
|
||||||
|
|
||||||
void _getTime() {
|
void _getTime() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_timeString = diffenceFromStart;
|
_timeString = differenceFromStart;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String get diffenceFromStart =>
|
String get differenceFromStart =>
|
||||||
_durationToString(DateTime.now().difference(widget.startDateTime));
|
_durationToString(DateTime.now().difference(widget.startDateTime));
|
||||||
|
|
||||||
String _durationToString(Duration duration) {
|
String _durationToString(Duration duration) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.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/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.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/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/action_button/action_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.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_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_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.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>();
|
var navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
@ -44,203 +42,200 @@ class _BackupDetailsState extends State<BackupDetails>
|
||||||
var backups = context.watch<BackupsCubit>().state.backups;
|
var backups = context.watch<BackupsCubit>().state.backups;
|
||||||
var refreshing = context.watch<BackupsCubit>().state.refreshing;
|
var refreshing = context.watch<BackupsCubit>().state.refreshing;
|
||||||
|
|
||||||
return Scaffold(
|
return BrandHeroScreen(
|
||||||
appBar: PreferredSize(
|
heroIcon: BrandIcons.save,
|
||||||
child: Column(
|
heroTitle: 'providers.backup.card_title'.tr(),
|
||||||
children: [
|
heroSubtitle: 'providers.backup.bottom_sheet.1'.tr(),
|
||||||
Container(
|
children: [
|
||||||
height: 51,
|
if (isReady && !isBackupInitialized)
|
||||||
alignment: Alignment.center,
|
BrandButton.rised(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 15),
|
onPressed: preventActions
|
||||||
child: BrandText.h4('basis.details'.tr()),
|
? null
|
||||||
),
|
: () async {
|
||||||
BrandDivider(),
|
await context.read<BackupsCubit>().createBucket();
|
||||||
],
|
},
|
||||||
),
|
text: 'providers.backup.initialize'.tr(),
|
||||||
preferredSize: Size.fromHeight(52),
|
),
|
||||||
),
|
if (backupStatus == BackupStatusEnum.initializing)
|
||||||
body: SingleChildScrollView(
|
BrandText.body1('providers.backup.waitingForRebuild'.tr()),
|
||||||
physics: ClampingScrollPhysics(),
|
if (backupStatus != BackupStatusEnum.initializing &&
|
||||||
child: Column(
|
backupStatus != BackupStatusEnum.noKey)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
BrandCards.outlined(
|
||||||
children: [
|
child: Column(
|
||||||
Padding(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
padding: paddingH15V0,
|
children: [
|
||||||
child: Column(
|
if (backupStatus == BackupStatusEnum.initialized)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
ListTile(
|
||||||
children: [
|
onTap: preventActions
|
||||||
_Header(
|
? null
|
||||||
providerState: providerState,
|
: () async {
|
||||||
refreshing: refreshing,
|
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),
|
if (backupStatus == BackupStatusEnum.backingUp)
|
||||||
BrandText.h2('providers.backup.card_title'.tr()),
|
ListTile(
|
||||||
SizedBox(height: 10),
|
title: Text(
|
||||||
BrandText.body1('providers.backup.bottom_sheet.1'.tr()),
|
'providers.backup.creating'.tr(
|
||||||
SizedBox(height: 20),
|
args: [(backupProgress * 100).round().toString()]),
|
||||||
if (isReady && !isBackupInitialized)
|
style: Theme.of(context).textTheme.headline6,
|
||||||
BrandButton.rised(
|
|
||||||
onPressed: preventActions
|
|
||||||
? null
|
|
||||||
: () async {
|
|
||||||
await context.read<BackupsCubit>().createBucket();
|
|
||||||
},
|
|
||||||
text: 'providers.backup.initialize'.tr(),
|
|
||||||
),
|
),
|
||||||
if (backupStatus == BackupStatusEnum.initializing)
|
subtitle: LinearProgressIndicator(
|
||||||
BrandText.body1('providers.backup.waitingForRebuild'.tr()),
|
value: backupProgress,
|
||||||
if (backupStatus != BackupStatusEnum.initializing &&
|
backgroundColor: Colors.grey.withOpacity(0.2),
|
||||||
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()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
),
|
||||||
// Card with a list of existing backups
|
if (backupStatus == BackupStatusEnum.restoring)
|
||||||
// Each list item has a date
|
ListTile(
|
||||||
// When clicked, starts the restore action
|
title: Text(
|
||||||
if (backupStatus != BackupStatusEnum.initializing &&
|
'providers.backup.restoring'.tr(
|
||||||
backupStatus != BackupStatusEnum.noKey)
|
args: [(backupProgress * 100).round().toString()]),
|
||||||
Card(
|
style: Theme.of(context).textTheme.headline6,
|
||||||
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.error)
|
subtitle: LinearProgressIndicator(
|
||||||
BrandText.body1(backupError.toString()),
|
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()),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
Widget _stepCheck(AppConfigCubit appConfigCubit) {
|
||||||
assert(appConfigCubit.state is AppConfigNotFinished, 'wronge state');
|
assert(appConfigCubit.state is AppConfigNotFinished, 'wrong state');
|
||||||
var state = appConfigCubit.state as TimerState;
|
var state = appConfigCubit.state as TimerState;
|
||||||
late int doneCount;
|
late int doneCount;
|
||||||
late String? text;
|
late String? text;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.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/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/cubit/providers/providers_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/provider.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_bottom_sheet/brand_bottom_sheet.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_header/brand_header.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/components/not_ready_card/not_ready_card.dart';
|
||||||
import 'package:selfprivacy/ui/helpers/modals.dart';
|
import 'package:selfprivacy/ui/helpers/modals.dart';
|
||||||
import 'package:selfprivacy/ui/pages/backup_details/backup_details.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:selfprivacy/ui/pages/server_details/server_details.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
import 'package:selfprivacy/utils/ui_helpers.dart';
|
|
||||||
|
|
||||||
var navigatorKey = GlobalKey<NavigatorState>();
|
var navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
@ -32,6 +32,18 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
|
var isReady = context.watch<AppConfigCubit>().state is AppConfigFinished;
|
||||||
var isBackupInitialized = context.watch<BackupsCubit>().state.isInitialized;
|
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
|
final cards = ProviderType.values
|
||||||
.map(
|
.map(
|
||||||
|
@ -42,7 +54,9 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
state: isReady
|
state: isReady
|
||||||
? (type == ProviderType.backup && !isBackupInitialized
|
? (type == ProviderType.backup && !isBackupInitialized
|
||||||
? StateType.uninitialized
|
? StateType.uninitialized
|
||||||
: StateType.stable)
|
: (type == ProviderType.domain)
|
||||||
|
? getDnsStatus()
|
||||||
|
: StateType.stable)
|
||||||
: StateType.uninitialized,
|
: StateType.uninitialized,
|
||||||
type: type,
|
type: type,
|
||||||
),
|
),
|
||||||
|
@ -102,33 +116,21 @@ class _Card extends StatelessWidget {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ProviderType.domain:
|
case ProviderType.domain:
|
||||||
title = 'providers.domain.card_title'.tr();
|
title = 'providers.domain.screen_title'.tr();
|
||||||
message = domainName;
|
message = domainName;
|
||||||
stableText = 'providers.domain.status'.tr();
|
stableText = 'providers.domain.status'.tr();
|
||||||
|
|
||||||
onTap = () => showBrandBottomSheet<void>(
|
onTap = () => Navigator.of(context).push(materialRoute(
|
||||||
context: context,
|
DnsDetailsPage(),
|
||||||
builder: (BuildContext context) {
|
));
|
||||||
return _ProviderDetails(
|
|
||||||
provider: provider,
|
|
||||||
statusText: stableText,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case ProviderType.backup:
|
case ProviderType.backup:
|
||||||
title = 'providers.backup.card_title'.tr();
|
title = 'providers.backup.card_title'.tr();
|
||||||
stableText = 'providers.backup.status'.tr();
|
stableText = 'providers.backup.status'.tr();
|
||||||
|
|
||||||
onTap = () => showBrandBottomSheet(
|
onTap = () => Navigator.of(context).push(materialRoute(
|
||||||
context: context,
|
BackupDetails(),
|
||||||
builder: (BuildContext context) {
|
));
|
||||||
return BrandBottomSheet(
|
|
||||||
isExpended: true,
|
|
||||||
child: BackupDetails(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return GestureDetector(
|
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(
|
TableRow(
|
||||||
children: [
|
children: [
|
||||||
getRowTitle('Last check:'),
|
getRowTitle('Last check:'),
|
||||||
getRowValue(formater.format(checkTime)),
|
getRowValue(formatter.format(checkTime)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
TableRow(
|
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 jobsCubit = context.watch<JobsCubit>();
|
||||||
var jobState = jobsCubit.state;
|
var jobState = jobsCubit.state;
|
||||||
|
|
||||||
var switchebleService = switchableServices.contains(serviceType);
|
var switchableService = switchableServices.contains(serviceType);
|
||||||
var hasSwitchJob = switchebleService &&
|
var hasSwitchJob = switchableService &&
|
||||||
jobState is JobsStateWithJobs &&
|
jobState is JobsStateWithJobs &&
|
||||||
jobState.jobList
|
jobState.jobList
|
||||||
.any((el) => el is ServiceToggleJob && el.type == serviceType);
|
.any((el) => el is ServiceToggleJob && el.type == serviceType);
|
||||||
|
|
||||||
var isSwithOn = isReady &&
|
var isSwitchOn = isReady &&
|
||||||
(!switchableServices.contains(serviceType) ||
|
(!switchableServices.contains(serviceType) ||
|
||||||
serviceState.isEnableByType(serviceType));
|
serviceState.isEnableByType(serviceType));
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ class _Card extends StatelessWidget {
|
||||||
var domainName = UiHelpers.getDomainName(config);
|
var domainName = UiHelpers.getDomainName(config);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: isSwithOn
|
onTap: isSwitchOn
|
||||||
? () => showDialog<void>(
|
? () => showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
// isScrollControlled: true,
|
// isScrollControlled: true,
|
||||||
|
@ -124,7 +124,7 @@ class _Card extends StatelessWidget {
|
||||||
return _ServiceDetails(
|
return _ServiceDetails(
|
||||||
serviceType: serviceType,
|
serviceType: serviceType,
|
||||||
status:
|
status:
|
||||||
isSwithOn ? StateType.stable : StateType.uninitialized,
|
isSwitchOn ? StateType.stable : StateType.uninitialized,
|
||||||
title: serviceType.title,
|
title: serviceType.title,
|
||||||
icon: serviceType.icon,
|
icon: serviceType.icon,
|
||||||
changeTab: changeTab,
|
changeTab: changeTab,
|
||||||
|
@ -140,10 +140,10 @@ class _Card extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
IconStatusMask(
|
IconStatusMask(
|
||||||
status:
|
status:
|
||||||
isSwithOn ? StateType.stable : StateType.uninitialized,
|
isSwitchOn ? StateType.stable : StateType.uninitialized,
|
||||||
child: Icon(serviceType.icon, size: 30, color: Colors.white),
|
child: Icon(serviceType.icon, size: 30, color: Colors.white),
|
||||||
),
|
),
|
||||||
if (isReady && switchebleService) ...[
|
if (isReady && switchableService) ...[
|
||||||
Spacer(),
|
Spacer(),
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
|
|
|
@ -152,7 +152,7 @@ class _UserDetails extends StatelessWidget {
|
||||||
BrandDivider(),
|
BrandDivider(),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
BrandButton.emptyWithIconText(
|
BrandButton.emptyWithIconText(
|
||||||
title: 'users.send_regisration_data'.tr(),
|
title: 'users.send_registration_data'.tr(),
|
||||||
icon: Icon(BrandIcons.share),
|
icon: Icon(BrandIcons.share),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Share.share(
|
Share.share(
|
||||||
|
|
|
@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
Color stringToColor(String string) {
|
Color stringToColor(String string) {
|
||||||
var number = string.codeUnits.reduce((a, b) => a + b);
|
var number = string.codeUnits.reduce((a, b) => a + b);
|
||||||
var index = number % colorPallete.length;
|
var index = number % colorPalette.length;
|
||||||
return colorPallete[index];
|
return colorPalette[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
var originalColor = Color(0xFFDBD8BD);
|
var originalColor = Color(0xFFDBD8BD);
|
||||||
var count = 40;
|
var count = 40;
|
||||||
var colorPallete = List.generate(
|
var colorPalette = List.generate(
|
||||||
count,
|
count,
|
||||||
(index) => HSLColor.fromColor(originalColor)
|
(index) => HSLColor.fromColor(originalColor)
|
||||||
.withHue((index) * 360.0 / count)
|
.withHue((index) * 360.0 / count)
|
||||||
|
|
Loading…
Reference in New Issue