Compare commits

...

4 Commits

Author SHA1 Message Date
Kherel 804147b8d6 update 2021-03-26 14:38:39 +01:00
Kherel bc6c55b528 change http client 2021-03-26 00:30:34 +01:00
Kherel 20cca91e00 before change api_client 2021-03-25 21:09:56 +01:00
Kherel 7d12b85f89 fix markup 2021-03-25 09:54:39 +01:00
51 changed files with 1582 additions and 559 deletions

View File

@ -20,9 +20,11 @@
"domain": "Domain",
"saving": "Saving..",
"nickname": "nickname",
"loading": "loading",
"loading": "Loading...",
"later": "Настрою потом",
"reset": "Reset"
"reset": "Reset",
"details": "Details",
"no_data": "No data"
},
"more": {
"_comment": "'More' tab",
@ -54,26 +56,25 @@
"page_title": "Your Data Center",
"server": {
"card_title": "Server",
"status": "Status — Good",
"bottom_sheet": {
"1": "It's a virtual computer, where all your services live.",
"2": "1 CPU, RAM 4Gb, 40Gb — $5 per month",
"3": "Status — Good"
"1": "It's a virtual computer, where all your services live."
}
},
"domain": {
"card_title": "Domain",
"status": "Status — Good",
"bottom_sheet": {
"1": "It's your personal internet address that will point to the server and other services of yours.",
"2": "{} — expires on {}",
"3": "Status — Good"
"2": "{} — expires on {}"
}
},
"backup": {
"card_title": "Backup",
"status": "Status — Good",
"bottom_sheet": {
"1": "Will save your day in case of incident: hackers attack, server deletion, etc.",
"2": "3Gb/10Gb, last backup was yesterday {}",
"3": "Status — Good"
"2": "3Gb/10Gb, last backup was yesterday {}"
}
}
},

View File

@ -22,14 +22,16 @@
"nickname": "Никнейм",
"loading": "Загрузка",
"later": "Настрою потом",
"reset": "Reset"
"reset": "Reset",
"details": "Детальная информация",
"no_data": "Нет данных"
},
"more": {
"_comment": "вкладка еще",
"configuration_wizard": "Мастер Подключения",
"about_project": "О проекте SelfPrivacy",
"about_app": "О приложении",
"onboarding": "Onboarding",
"onboarding": "Приветствие",
"console": "Console",
"about_app_page": {
"text": "Тут любая служебная информация, v.{}"
@ -54,26 +56,25 @@
"page_title": "Ваш Дата-центр",
"server": {
"card_title": "Сервер",
"status": "Статус — в норме",
"bottom_sheet": {
"1": "Это виртульный компьютер на котором работают все ваши сервисы.",
"2": "1 CPU, RAM 4Gb, 40Gb — $5 в месяц",
"3": "Статус — в норме"
"1": "Это виртульный компьютер на котором работают все ваши сервисы."
}
},
"domain": {
"card_title": "Домен",
"status": "Статус — в норме",
"bottom_sheet": {
"1": "Это ваш личный адрес в интернете, который будет указывать на сервер и другие ваши сервисы.",
"2": "{} — продлен до {}",
"3": "Статус — в норме"
"2": "{} — продлен до {}"
}
},
"backup": {
"card_title": "Резервное копирование",
"status": "Статус — в норме",
"bottom_sheet": {
"1": "Выручит в любой ситуации: хакерская атака, удаление сервера и т.п.",
"2": "3Gb — бестплатно до 10Gb, последний вчера в {}",
"3": "Статус — в норме"
"2": "3Gb — бестплатно до 10Gb, последний вчера в {}"
}
}
},

7
build.yaml Normal file
View File

@ -0,0 +1,7 @@
targets:
$default:
builders:
json_serializable:
options:
create_factory: true
create_to_json: false

View File

@ -1,17 +1,23 @@
import 'package:get_it/get_it.dart';
import 'package:selfprivacy/logic/get_it/api_config.dart';
import 'package:selfprivacy/logic/get_it/console.dart';
import 'package:selfprivacy/logic/get_it/navigation.dart';
import 'package:selfprivacy/logic/get_it/timer.dart';
export 'package:selfprivacy/logic/get_it/api_config.dart';
export 'package:selfprivacy/logic/get_it/console.dart';
export 'package:selfprivacy/logic/get_it/navigation.dart';
export 'package:selfprivacy/logic/get_it/timer.dart';
final getIt = GetIt.instance;
void getItSetup() {
Future<void> getItSetup() async {
getIt.registerSingleton<NavigationService>(NavigationService());
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
getIt.registerSingleton<TimerModel>(TimerModel());
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
await getIt.allReady();
}

View File

@ -38,6 +38,12 @@ final headline4Style = defaultTextStyle.copyWith(
color: BrandColors.headlineColor,
);
final headline5Style = defaultTextStyle.copyWith(
fontSize: 15,
fontWeight: NamedFontWeight.medium,
color: BrandColors.headlineColor.withOpacity(0.8),
);
final body1Style = defaultTextStyle;
final body2Style = defaultTextStyle.copyWith(
color: BrandColors.textColor2,

View File

@ -1,29 +1,41 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/get_it/console.dart';
import 'package:selfprivacy/logic/models/message.dart';
abstract class ApiMap {
ApiMap() {
var client = Dio()..interceptors.add(ConsoleInterceptor());
(client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
Future<Dio> getClient() async {
var dio = Dio(await options);
if (hasLoger) {
dio.interceptors.add(PrettyDioLogger());
}
dio..interceptors.add(ConsoleInterceptor());
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(HttpClient client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
};
loggedClient = client;
return dio;
}
String? rootAddress;
late Dio loggedClient;
FutureOr<BaseOptions> get options;
void close() {
loggedClient.close();
abstract final String rootAddress;
abstract final bool hasLoger;
abstract final bool isWithToken;
ValidateStatus? validateStatus;
void close(Dio client) {
client.close();
validateStatus = null;
}
}

View File

@ -1,31 +1,36 @@
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';
class BackblazeApi extends ApiMap {
BackblazeApi([String? token]) {
if (token != null) {
loggedClient.options = BaseOptions(
headers: {'Authorization': 'Basic $token'},
baseUrl: rootAddress!,
);
BackblazeApi({this.hasLoger = false, this.isWithToken = true});
BaseOptions get options {
var options = BaseOptions(baseUrl: rootAddress);
if (isWithToken) {
var backblazeCredential = getIt<ApiConfigModel>().backblazeCredential;
var token = backblazeCredential!.applicationKey;
options.headers = {'Authorization': 'Basic $token'};
}
if (validateStatus != null) {
options.validateStatus = validateStatus!;
}
return options;
}
@override
String? rootAddress =
'https://api.backblazeb2.com/b2api/v2/b2_authorize_account';
String rootAddress = 'https://api.backblazeb2.com/b2api/v2/';
Future<bool> isValid(String token) async {
var options = Options(
headers: {'Authorization': 'Basic $token'},
validateStatus: (status) {
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
},
Future<bool> isValid(String encodedApiKey) async {
var client = await getClient();
Response response = await client.get(
'b2_authorize_account',
options: Options(headers: {'Authorization': 'Basic $encodedApiKey'}),
);
Response response = await loggedClient.get(rootAddress!, options: options);
close(client);
if (response.statusCode == HttpStatus.ok) {
return true;
} else if (response.statusCode == HttpStatus.unauthorized) {
@ -34,4 +39,10 @@ class BackblazeApi extends ApiMap {
throw Exception('code: ${response.statusCode}');
}
}
}
@override
bool hasLoger;
@override
bool isWithToken;
}

View File

@ -1,30 +1,40 @@
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';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/dns_records.dart';
class CloudflareApi extends ApiMap {
CloudflareApi([String? token]) {
if (token != null) {
loggedClient.options =
BaseOptions(headers: {'Authorization': 'Bearer $token'});
CloudflareApi({this.hasLoger = false, this.isWithToken = true});
BaseOptions get options {
var options = BaseOptions(baseUrl: rootAddress);
if (isWithToken) {
var token = getIt<ApiConfigModel>().cloudFlareKey;
assert(token != null);
options.headers = {'Authorization': 'Bearer $token'};
}
if (validateStatus != null) {
options.validateStatus = validateStatus!;
}
return options;
}
@override
String? rootAddress = 'https://api.cloudflare.com/client/v4';
String rootAddress = 'https://api.cloudflare.com/client/v4';
Future<bool> isValid(String token) async {
var url = '$rootAddress/user/tokens/verify';
var options = Options(
headers: {'Authorization': 'Bearer $token'},
validateStatus: (status) {
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
},
);
validateStatus = (status) {
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
};
Response response = await loggedClient.get(url, options: options);
var client = await getClient();
Response response = await client.get('/user/tokens/verify',
options: Options(headers: {'Authorization': 'Bearer $token'}));
close(client);
if (response.statusCode == HttpStatus.ok) {
return true;
@ -35,27 +45,19 @@ class CloudflareApi extends ApiMap {
}
}
Future<String?> getZoneId(String? token, String domain) async {
var url = '$rootAddress/zones';
var options = Options(
headers: {'Authorization': 'Bearer $token'},
validateStatus: (status) {
return status == HttpStatus.ok || status == HttpStatus.forbidden;
},
);
Response response = await loggedClient.get(
url,
options: options,
Future<String> getZoneId(String domain) async {
validateStatus = (status) {
return status == HttpStatus.ok || status == HttpStatus.forbidden;
};
var client = await getClient();
Response response = await client.get(
'/zones',
queryParameters: {'name': domain},
);
try {
return response.data['result'][0]['id'];
} catch (error) {
return null;
}
close(client);
return response.data['result'][0]['id'];
}
Future<void> removeSimilarRecords({
@ -65,20 +67,24 @@ class CloudflareApi extends ApiMap {
var domainName = cloudFlareDomain.domainName;
var domainZoneId = cloudFlareDomain.zoneId;
var url = '$rootAddress/zones/$domainZoneId/dns_records';
var url = '/zones/$domainZoneId/dns_records';
var client = await getClient();
Response response = await client.get(url);
var response = await loggedClient.get(url);
List records = response.data['result'] ?? [];
var allDeleteFutures = <Future>[];
for (var record in records) {
if (record['zone_name'] == domainName) {
allDeleteFutures.add(
loggedClient.delete('$url/${record["id"]}'),
client.delete('$url/${record["id"]}'),
);
}
}
await Future.wait(allDeleteFutures);
close(client);
}
Future<void> createMultipleDnsRecords({
@ -92,10 +98,11 @@ class CloudflareApi extends ApiMap {
var url = '$rootAddress/zones/$domainZoneId/dns_records';
var allCreateFutures = <Future>[];
var client = await getClient();
for (var record in listDnsRecords) {
allCreateFutures.add(
loggedClient.post(
client.post(
url,
data: record.toJson(),
),
@ -103,23 +110,9 @@ class CloudflareApi extends ApiMap {
}
await Future.wait(allCreateFutures);
close(client);
}
// setDkim(String dkimRecordString, String domainZoneId) {
// var txt3 = DnsRecords(
// type: 'TXT',
// name: 'selector._domainkey',
// content: dkimRecordString,
// ttl: 18000,
// );
// var url = '$rootAddress/zones/$domainZoneId/dns_records';
// loggedClient.post(
// url,
// data: txt3.toJson(),
// );
// }
List<DnsRecords> projectDnsRecords(String? domainName, String? ip4) {
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
@ -163,13 +156,22 @@ class CloudflareApi extends ApiMap {
Future<List<String>> domainList() async {
var url = '$rootAddress/zones?per_page=50';
var response = await loggedClient.get(
var client = await getClient();
var response = await client.get(
url,
queryParameters: {'per_page': 50},
);
close(client);
return response.data['result']
.map<String>((el) => el['name'] as String)
.toList();
}
@override
final bool hasLoger;
@override
final bool isWithToken;
}

View File

@ -2,33 +2,49 @@ import 'dart:convert';
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';
import 'package:selfprivacy/logic/models/hetzner_server_info.dart';
import 'package:selfprivacy/logic/models/server_details.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'package:selfprivacy/utils/password_generator2.dart';
class HetznerApi extends ApiMap {
HetznerApi([String? token]) {
if (token != null) {
loggedClient.options = BaseOptions(
headers: {'Authorization': 'Bearer $token'},
baseUrl: rootAddress!,
);
bool hasLoger;
bool isWithToken;
HetznerApi({this.hasLoger = false, this.isWithToken = true});
BaseOptions get options {
var options = BaseOptions(baseUrl: rootAddress);
if (isWithToken) {
var token = getIt<ApiConfigModel>().hetznerKey;
assert(token != null);
options.headers = {'Authorization': 'Bearer $token'};
}
if (validateStatus != null) {
options.validateStatus = validateStatus!;
}
return options;
}
@override
String? rootAddress = 'https://api.hetzner.cloud/v1/servers';
String rootAddress = 'https://api.hetzner.cloud/v1';
Future<bool> isValid(String token) async {
var options = Options(
headers: {'Authorization': 'Bearer $token'},
validateStatus: (status) {
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
},
validateStatus = (status) {
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
};
var client = await getClient();
Response response = await client.get(
'/servers',
options: Options(
headers: {'Authorization': 'Bearer $token'},
),
);
Response response = await loggedClient.get(rootAddress!, options: options);
close(client);
if (response.statusCode == HttpStatus.ok) {
return true;
@ -40,9 +56,9 @@ class HetznerApi extends ApiMap {
}
Future<HetznerServerDetails> createServer({
required String? cloudFlareKey,
required String cloudFlareKey,
required User rootUser,
required String? domainName,
required String domainName,
}) async {
var dbPassword = getRandomString(40);
@ -50,11 +66,12 @@ class HetznerApi extends ApiMap {
'''{"name":"selfprivacy-server","server_type":"cx11","start_after_create":false,"image":"ubuntu-20.04", "volumes":[],"networks":[],"user_data":"#cloud-config\\nruncmd:\\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.09 DOMAIN=$domainName LUSER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword} CF_TOKEN=$cloudFlareKey DB_PASSWORD=$dbPassword bash 2>&1 | tee /tmp/infect.log","labels":{},"automount":false}''',
);
Response response = await loggedClient.post(
rootAddress!,
var client = await getClient();
Response response = await client.post(
'/servers',
data: data,
);
client.close();
return HetznerServerDetails(
id: response.data['server']['id'],
ip4: response.data['server']['public_net']['ipv4']['ip'],
@ -62,20 +79,23 @@ class HetznerApi extends ApiMap {
);
}
Future<void> deleteSelfprivacyServer({
required String? cloudFlareKey,
}) async {
Response response = await loggedClient.get(rootAddress!);
Future<void> deleteSelfprivacyServer() async {
var client = await getClient();
Response response = await client.get('/servers');
List list = response.data['servers'];
var server = list.firstWhere((el) => el['name'] == 'selfprivacy-server');
await loggedClient.delete('$rootAddress/${server['id']}');
await client.delete('/servers/${server['id']}');
close(client);
}
Future<HetznerServerDetails> startServer({
required HetznerServerDetails server,
}) async {
await loggedClient.post('/${server.id}/actions/poweron');
var client = await getClient();
await client.post('/servers/${server.id}/actions/poweron');
close(client);
return server.copyWith(
startTime: DateTime.now(),
@ -85,10 +105,27 @@ class HetznerApi extends ApiMap {
Future<HetznerServerDetails> restart({
required HetznerServerDetails server,
}) async {
await loggedClient.post('/${server.id}/actions/poweron');
var client = await getClient();
await client.post('/servers/${server.id}/actions/poweron');
close(client);
return server.copyWith(
startTime: DateTime.now(),
);
}
metrics() async {
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
var client = await getClient();
await client.post('/servers/${hetznerServer!.id}/metrics');
close(client);
}
Future<HetznerServerInfo> getInfo() async {
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
var client = await getClient();
Response response = await client.get('/servers/${hetznerServer!.id}');
close(client);
return HetznerServerInfo.fromJson(response.data!['server']);
}
}

View File

@ -1,45 +1,45 @@
import 'dart:async';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'api_map.dart';
class ServerApi extends ApiMap {
ServerApi(String? domainName) {
loggedClient.options = BaseOptions(
baseUrl: 'https://api.$domainName',
);
bool hasLoger;
bool isWithToken;
ServerApi({this.hasLoger = false, this.isWithToken = true});
BaseOptions get options {
var options = BaseOptions();
if (isWithToken) {
var cloudFlareDomain = getIt<ApiConfigModel>().cloudFlareDomain;
var domainName = cloudFlareDomain!.domainName;
options = BaseOptions(baseUrl: 'https://api.$domainName');
}
return options;
}
Future<bool> isHttpServerWorking() async {
bool res;
Response response;
var client = await getClient();
try {
response = await loggedClient.get('/serviceStatus');
response = await client.get('/serviceStatus');
res = response.statusCode == HttpStatus.ok;
} catch (e) {
res = false;
}
close(client);
return res;
}
// Future<String> getDkim(String domainName) async {
// var response = await loggedClient.get(
// '/getDKIM',
// options: Options(responseType: ResponseType.plain),
// );
// return _decodeAndCutData(response.data, domainName);
// }
String get rootAddress =>
throw UnimplementedError('not used in with implementation');
}
// String _decodeAndCutData(String text, String domainName) {
// var decodedTextString = text.substring(1, text.length - 1);
// var stringToBase64 = utf8.fuse(base64);
// return stringToBase64
// .decode(decodedTextString)
// .replaceAll("selector._domainkey IN TXT ( ", "")
// .replaceAll("\"\n \"", "")
// .replaceAll(' ) ; ----- DKIM key selector for $domainName\n', '');
// }

View File

@ -74,7 +74,6 @@ class AppConfigCubit extends Cubit<AppConfigState> {
if (isMatch) {
var server = await repository.startServer(
state.hetznerKey,
state.hetznerServer!,
);
repository.saveServerDetails(server);
@ -111,33 +110,30 @@ class AppConfigCubit extends Cubit<AppConfigState> {
AppConfigState? state,
bool isImmediate = false,
}) async {
state = state ?? this.state;
var dataState = state ?? this.state;
var work = () async {
emit(TimerState(dataState: state!, isLoading: true));
emit(TimerState(dataState: dataState, isLoading: true));
var isServerWorking = await repository.isHttpServerWorking(
state.cloudFlareDomain!.domainName,
);
var isServerWorking = await repository.isHttpServerWorking();
if (isServerWorking) {
var pauseDuration = Duration(seconds: 30);
emit(TimerState(
dataState: state,
dataState: dataState,
timerStart: DateTime.now(),
isLoading: false,
duration: pauseDuration,
));
timer = Timer(pauseDuration, () async {
var hetznerServerDetails = await repository.restart(
state!.hetznerKey,
state.hetznerServer!,
dataState.hetznerServer!,
);
repository.saveIsServerReseted(true);
repository.saveServerDetails(hetznerServerDetails);
emit(
state.copyWith(
dataState.copyWith(
isServerReseted: true,
hetznerServer: hetznerServerDetails,
isLoading: false,
@ -155,7 +151,7 @@ class AppConfigCubit extends Cubit<AppConfigState> {
var pauseDuration = Duration(seconds: 60);
emit(
TimerState(
dataState: state,
dataState: dataState,
timerStart: DateTime.now(),
duration: pauseDuration,
isLoading: false,
@ -176,9 +172,7 @@ class AppConfigCubit extends Cubit<AppConfigState> {
var work = () async {
emit(TimerState(dataState: state!, isLoading: true));
var isServerWorking = await repository.isHttpServerWorking(
state.cloudFlareDomain!.domainName,
);
var isServerWorking = await repository.isHttpServerWorking();
if (isServerWorking) {
repository.saveHasFinalChecked(true);
@ -219,7 +213,7 @@ class AppConfigCubit extends Cubit<AppConfigState> {
}
void setCloudflareKey(String cloudFlareKey) {
repository.saveCloudFlare(cloudFlareKey);
repository.saveCloudFlareKey(cloudFlareKey);
emit(state.copyWith(cloudFlareKey: cloudFlareKey));
}
@ -246,7 +240,6 @@ class AppConfigCubit extends Cubit<AppConfigState> {
AppConfigState _stateCopy = state;
var onSuccess = (serverDetails) async {
await repository.createDnsRecords(
state.cloudFlareKey,
serverDetails.ip4,
state.cloudFlareDomain!,
);
@ -263,10 +256,9 @@ class AppConfigCubit extends Cubit<AppConfigState> {
try {
emit(state.copyWith(isLoading: true));
await repository.createServer(
state.hetznerKey,
state.rootUser!,
state.cloudFlareDomain!.domainName,
state.cloudFlareKey,
state.cloudFlareKey!,
onCancel: onCancel,
onSuccess: onSuccess,
);

View File

@ -4,6 +4,7 @@ import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/api_maps/server.dart';
import 'package:selfprivacy/logic/get_it/api_config.dart';
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/server_details.dart';
@ -21,19 +22,21 @@ class AppConfigRepository {
Box box = Hive.box(BNames.appConfig);
AppConfigState load() {
return AppConfigState(
hetznerKey: box.get(BNames.hetznerKey),
cloudFlareKey: box.get(BNames.cloudFlareKey),
cloudFlareDomain: box.get(BNames.cloudFlareDomain),
backblazeCredential: box.get(BNames.backblazeKey),
var res = AppConfigState(
hetznerKey: getIt<ApiConfigModel>().hetznerKey,
cloudFlareKey: getIt<ApiConfigModel>().cloudFlareKey,
cloudFlareDomain: getIt<ApiConfigModel>().cloudFlareDomain,
backblazeCredential: getIt<ApiConfigModel>().backblazeCredential,
hetznerServer: getIt<ApiConfigModel>().hetznerServer,
rootUser: box.get(BNames.rootUser),
hetznerServer: box.get(BNames.hetznerServer),
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isServerReseted: box.get(BNames.isServerReseted, defaultValue: false),
hasFinalChecked: box.get(BNames.hasFinalChecked, defaultValue: false),
error: null,
isLoading: box.get(BNames.isLoading, defaultValue: false),
);
return res;
}
void clearAppConfig() {
@ -41,12 +44,10 @@ class AppConfigRepository {
}
Future<HetznerServerDetails> startServer(
String? hetznerKey,
HetznerServerDetails hetznerServer,
) async {
var hetznerApi = HetznerApi(hetznerKey);
var hetznerApi = HetznerApi();
var serverDetails = await hetznerApi.startServer(server: hetznerServer);
hetznerApi.close();
return serverDetails;
}
@ -89,15 +90,14 @@ class AppConfigRepository {
}
Future<void> createServer(
String? hetznerKey,
User rootUser,
String? domainName,
String? cloudFlareKey, {
void Function()? onCancel,
String domainName,
String cloudFlareKey, {
required void Function() onCancel,
required Future<void> Function(HetznerServerDetails serverDetails)
onSuccess,
}) async {
var hetznerApi = HetznerApi(hetznerKey);
var hetznerApi = HetznerApi();
try {
var serverDetails = await hetznerApi.createServer(
@ -105,7 +105,6 @@ class AppConfigRepository {
rootUser: rootUser,
domainName: domainName,
);
hetznerApi.close();
saveServerDetails(serverDetails);
onSuccess(serverDetails);
} on DioError catch (e) {
@ -120,16 +119,13 @@ class AppConfigRepository {
text: 'basis.delete'.tr(),
isRed: true,
onPressed: () async {
await hetznerApi.deleteSelfprivacyServer(
cloudFlareKey: cloudFlareKey,
);
await hetznerApi.deleteSelfprivacyServer();
var serverDetails = await hetznerApi.createServer(
cloudFlareKey: cloudFlareKey,
rootUser: rootUser,
domainName: domainName,
);
hetznerApi.close();
await saveServerDetails(serverDetails);
onSuccess(serverDetails);
@ -138,8 +134,7 @@ class AppConfigRepository {
ActionButton(
text: 'basis.cancel'.tr(),
onPressed: () {
hetznerApi.close();
onCancel!();
onCancel();
},
),
],
@ -150,11 +145,10 @@ class AppConfigRepository {
}
Future<void> createDnsRecords(
String? cloudFlareKey,
String? ip4,
CloudFlareDomain cloudFlareDomain,
) async {
var cloudflareApi = CloudflareApi(cloudFlareKey);
var cloudflareApi = CloudflareApi();
await cloudflareApi.removeSimilarRecords(
ip4: ip4,
@ -165,53 +159,49 @@ class AppConfigRepository {
ip4: ip4,
cloudFlareDomain: cloudFlareDomain,
);
cloudflareApi.close();
}
Future<bool> isHttpServerWorking(String? domainName) async {
var api = ServerApi(domainName);
Future<bool> isHttpServerWorking() async {
var api = ServerApi();
var isHttpServerWorking = await api.isHttpServerWorking();
api.close();
return isHttpServerWorking;
}
Future<HetznerServerDetails> restart(
String? hetznerKey,
HetznerServerDetails server,
) async {
var hetznerApi = HetznerApi(hetznerKey);
var hetznerApi = HetznerApi();
return await hetznerApi.restart(server: server);
}
Future<void> saveServerDetails(HetznerServerDetails serverDetails) async {
await box.put(BNames.hetznerServer, serverDetails);
await getIt<ApiConfigModel>().storeServerDetails(serverDetails);
}
Future<void> saveHetznerKey(String key) async {
await getIt<ApiConfigModel>().storeHetznerKey(key);
}
Future<void> saveBackblazeKey(BackblazeCredential backblazeCredential) async {
await getIt<ApiConfigModel>().storeBackblazeCredential(backblazeCredential);
}
Future<void> saveCloudFlareKey(String key) async {
await getIt<ApiConfigModel>().storeCloudFlareKey(key);
}
Future<void> saveDomain(CloudFlareDomain cloudFlareDomain) async {
await getIt<ApiConfigModel>().storeCloudFlareDomain(cloudFlareDomain);
}
Future<void> saveIsServerStarted(bool value) async {
await box.put(BNames.isServerStarted, value);
}
Future<void> saveHetznerKey(String key) async {
await box.put(BNames.hetznerKey, key);
}
Future<void> saveIsServerReseted(bool value) async {
await box.put(BNames.isServerReseted, value);
}
Future<void> saveBackblazeKey(BackblazeCredential backblazeCredential) async {
await box.put(BNames.backblazeKey, backblazeCredential);
}
Future<void> saveCloudFlare(String key) async {
await box.put(BNames.cloudFlareKey, key);
}
void saveDomain(CloudFlareDomain cloudFlareDomain) async {
await box.put(BNames.cloudFlareDomain, cloudFlareDomain);
}
void saveRootUser(User rootUser) async {
await box.put(BNames.rootUser, rootUser);
}

View File

@ -92,7 +92,6 @@ class AppConfigState extends Equatable {
isServerReseted,
hasFinalChecked,
];
print('progress: $res');
return res;
}
}

View File

@ -5,8 +5,6 @@ import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
class BackblazeFormCubit extends FormCubit {
BackblazeApi apiClient = BackblazeApi();
BackblazeFormCubit(this.initializingCubit) {
//var regExp = RegExp(r"\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]");
keyId = FieldCubit(
@ -42,14 +40,14 @@ class BackblazeFormCubit extends FormCubit {
final AppConfigCubit initializingCubit;
// ignore: close_sinks
late final FieldCubit<String> keyId;
// ignore: close_sinks
late final FieldCubit<String> applicationKey;
@override
FutureOr<bool> asyncValidation() async {
late bool isKeyValid;
BackblazeApi apiClient = BackblazeApi(isWithToken: false);
try {
String encodedApiKey = encodedBackblazeKey(
keyId.state.value,
@ -67,11 +65,4 @@ class BackblazeFormCubit extends FormCubit {
}
return true;
}
@override
Future<void> close() async {
apiClient.close();
return super.close();
}
}

View File

@ -6,8 +6,6 @@ import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
class CloudFlareFormCubit extends FormCubit {
CloudflareApi apiClient = CloudflareApi();
CloudFlareFormCubit(this.initializingCubit) {
var regExp = RegExp(r"\s+|[!$%^&*()@+|~=`{}\[\]:<>?,.\/]");
apiKey = FieldCubit(
@ -35,6 +33,7 @@ class CloudFlareFormCubit extends FormCubit {
@override
FutureOr<bool> asyncValidation() async {
late bool isKeyValid;
CloudflareApi apiClient = CloudflareApi(isWithToken: false);
try {
isKeyValid = await apiClient.isValid(apiKey.state.value);
@ -51,8 +50,6 @@ class CloudFlareFormCubit extends FormCubit {
@override
Future<void> close() async {
apiClient.close();
return super.close();
}
}

View File

@ -4,19 +4,14 @@ import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
class DomainSetupCubit extends Cubit<DomainSetupState> {
DomainSetupCubit(this.initializingCubit) : super(Initial()) {
var token = initializingCubit.state.cloudFlareKey;
DomainSetupCubit(this.initializingCubit) : super(Initial());
assert(token != null, 'no cloudflare token');
api = CloudflareApi(token);
}
AppConfigCubit initializingCubit;
late CloudflareApi api;
final AppConfigCubit initializingCubit;
Future<void> load() async {
emit(Loading(LoadingTypes.loadingDomain));
var api = CloudflareApi();
var list = await api.domainList();
if (list.isEmpty) {
emit(Empty());
@ -29,20 +24,17 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
@override
Future<void> close() {
api.close();
return super.close();
}
Future<void> saveDomain() async {
assert(state is Loaded, 'wrong state');
var domainName = (state as Loaded).domain;
var api = CloudflareApi();
emit(Loading(LoadingTypes.saving));
var zoneId = await api.getZoneId(
initializingCubit.state.cloudFlareKey,
domainName,
);
var zoneId = await api.getZoneId(domainName);
var domain = CloudFlareDomain(
domainName: domainName,

View File

@ -1,68 +0,0 @@
// import 'dart:async';
// import 'package:cubit_form/cubit_form.dart';
// import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
// import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
// import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
// class DomainFormCubit extends FormCubit {
// CloudflareApi apiClient = CloudflareApi();
// DomainFormCubit(this.initializingCubit) {
// var regExp =
// RegExp(r"^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}");
// domainName = FieldCubit(
// initalValue: '',
// validations: [
// RequiredStringValidation('required'),
// ValidationModel<String>(
// (s) => !regExp.hasMatch(s),
// 'invalid domain format',
// ),
// ],
// );
// super.setFields([domainName]);
// }
// @override
// FutureOr<void> onSubmit() async {
// var domain = CloudFlareDomain(
// domainName: domainName.state.value,
// zoneId: zoneId,
// );
// initializingCubit.setDomain(domain);
// }
// final AppConfigCubit initializingCubit;
// FieldCubit<String> domainName;
// String zoneId;
// @override
// FutureOr<bool> asyncValidation() async {
// var key = initializingCubit.state.cloudFlareKey;
// String zoneId;
// try {
// zoneId = await apiClient.getZoneId(key, domainName.state.value);
// } catch (e) {
// addError(e);
// }
// if (zoneId == null) {
// domainName.setError('Domain not in the list');
// return false;
// }
// this.zoneId = zoneId;
// return true;
// }
// @override
// Future<void> close() async {
// apiClient.close();
// return super.close();
// }
// }

View File

@ -6,8 +6,6 @@ import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
class HetznerFormCubit extends FormCubit {
HetznerApi apiClient = HetznerApi();
HetznerFormCubit(this.initializingCubit) {
var regExp = RegExp(r"\s+|[-!$%^&*()@+|~=`{}\[\]:<>?,.\/]");
apiKey = FieldCubit(
@ -30,12 +28,13 @@ class HetznerFormCubit extends FormCubit {
final AppConfigCubit initializingCubit;
// ignore: close_sinks
late final FieldCubit<String> apiKey;
@override
FutureOr<bool> asyncValidation() async {
late bool isKeyValid;
HetznerApi apiClient = HetznerApi(isWithToken: false);
try {
isKeyValid = await apiClient.isValid(apiKey.state.value);
} catch (e) {
@ -48,11 +47,4 @@ class HetznerFormCubit extends FormCubit {
}
return true;
}
@override
Future<void> close() async {
apiClient.close();
return super.close();
}
}

View File

@ -1,13 +1,10 @@
import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/user.dart';
class RootUserFormCubit extends FormCubit {
HetznerApi apiClient = HetznerApi();
RootUserFormCubit(this.initializingCubit) {
var userRegExp = RegExp(r"\W");
var passwordRegExp = RegExp(r"[\n\r\s]+");
@ -46,17 +43,7 @@ class RootUserFormCubit extends FormCubit {
final AppConfigCubit initializingCubit;
// ignore: close_sinks
late final FieldCubit<String> userName;
// ignore: close_sinks
late final FieldCubit<String> password;
// ignore: close_sinks
late final FieldCubit<bool> isVisible;
@override
Future<void> close() async {
apiClient.close();
return super.close();
}
}

View File

@ -45,7 +45,6 @@ class UserFormCubit extends FormCubit {
usersCubit.addUser(user);
}
// ignore: close_sinks
late FieldCubit<String> login;
late FieldCubit<String> password;

View File

@ -0,0 +1,24 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_repository.dart';
import 'package:selfprivacy/logic/models/hetzner_server_info.dart';
part 'server_detailed_info_state.dart';
class ServerDetailsCubit extends Cubit<ServerDetailsState> {
ServerDetailsCubit() : super(ServerDetailsInitial());
ServerDetailsRepository repository = ServerDetailsRepository();
void check() async {
var isReadyToCheck = getIt<ApiConfigModel>().hetznerServer != null;
if (isReadyToCheck) {
emit(ServerDetailsLoading());
var data = await repository.load();
emit(Loaded(serverInfo: data, checkTime: DateTime.now()));
} else {
emit(ServerDetailsNotReady());
}
}
}

View File

@ -0,0 +1,9 @@
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/models/hetzner_server_info.dart';
class ServerDetailsRepository {
Future<HetznerServerInfo> load() async {
var client = HetznerApi();
return await client.getInfo();
}
}

View File

@ -0,0 +1,29 @@
part of 'server_detailed_info_cubit.dart';
abstract class ServerDetailsState extends Equatable {
const ServerDetailsState();
@override
List<Object> get props => [];
}
class ServerDetailsInitial extends ServerDetailsState {}
class ServerDetailsLoading extends ServerDetailsState {}
class ServerDetailsNotReady extends ServerDetailsState {}
class Loading extends ServerDetailsState {}
class Loaded extends ServerDetailsState {
final HetznerServerInfo serverInfo;
final DateTime checkTime;
Loaded({
required this.serverInfo,
required this.checkTime,
});
@override
List<Object> get props => [serverInfo, checkTime];
}

View File

@ -1,28 +0,0 @@
// import 'package:bloc/bloc.dart';
// import 'package:equatable/equatable.dart';
// import 'package:meta/meta.dart';
// import 'package:selfprivacy/logic/models/service.dart';
// import 'package:selfprivacy/logic/models/state_types.dart';
// export 'package:provider/provider.dart';
// export 'package:selfprivacy/logic/models/state_types.dart';
// part 'services_state.dart';
// class ServicesCubit extends Cubit<ServicesState> {
// ServicesCubit() : super(ServicesState(all));
// void connect(Service service) {
// var newState = state.updateElement(service, StateType.stable);
// emit(newState);
// }
// }
// final all = ServiceTypes.values
// .map(
// (type) => Service(
// state: StateType.uninitialized,
// type: type,
// ),
// )
// .toList();

View File

@ -1,26 +0,0 @@
// part of 'services_cubit.dart';
// @immutable
// class ServicesState extends Equatable{
// ServicesState(this.all);
// final List<Service> all;
// ServicesState updateElement(Service service, StateType newState) {
// var newList = [...all];
// var index = newList.indexOf(service);
// newList[index] = service.updateState(newState);
// return ServicesState(newList);
// }
// List<Service> get connected => all
// .where((service) => service.state != StateType.uninitialized)
// .toList();
// List<Service> get uninitialized => all
// .where((service) => service.state == StateType.uninitialized)
// .toList();
// @override
// List<Object> get props => all;
// }

View File

@ -0,0 +1,63 @@
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/models/backblaze_credential.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
import 'package:selfprivacy/logic/models/server_details.dart';
class ApiConfigModel {
Box _box = Hive.box(BNames.appConfig);
HetznerServerDetails? get hetznerServer => _hetznerServer;
String? get hetznerKey => _hetznerKey;
String? get cloudFlareKey => _cloudFlareKey;
BackblazeCredential? get backblazeCredential => _backblazeCredential;
CloudFlareDomain? get cloudFlareDomain => _cloudFlareDomain;
String? _hetznerKey;
String? _cloudFlareKey;
HetznerServerDetails? _hetznerServer;
BackblazeCredential? _backblazeCredential;
CloudFlareDomain? _cloudFlareDomain;
Future<void> storeHetznerKey(String value) async {
await _box.put(BNames.hetznerKey, value);
_hetznerKey = value;
}
Future<void> storeCloudFlareKey(String value) async {
await _box.put(BNames.cloudFlareKey, value);
_cloudFlareKey = value;
}
Future<void> storeBackblazeCredential(BackblazeCredential value) async {
await _box.put(BNames.backblazeKey, value);
_backblazeCredential = value;
}
Future<void> storeCloudFlareDomain(CloudFlareDomain value) async {
await _box.put(BNames.cloudFlareDomain, value);
_cloudFlareDomain = value;
}
Future<void> storeServerDetails(HetznerServerDetails value) async {
await _box.put(BNames.hetznerServer, value);
_hetznerServer = value;
}
clear() {
_hetznerKey = null;
_cloudFlareKey = null;
_backblazeCredential = null;
_cloudFlareDomain = null;
_hetznerServer = null;
}
void init() {
_hetznerKey = _box.get(BNames.hetznerKey);
_cloudFlareKey = _box.get(BNames.cloudFlareKey);
_backblazeCredential = _box.get(BNames.backblazeKey);
_cloudFlareDomain = _box.get(BNames.cloudFlareDomain);
_hetznerServer = _box.get(BNames.hetznerServer);
}
}

View File

@ -6,13 +6,13 @@ part 'backblaze_credential.g.dart';
@HiveType(typeId: 4)
class BackblazeCredential {
BackblazeCredential({this.keyId, this.applicationKey});
BackblazeCredential({required this.keyId, required this.applicationKey});
@HiveField(0)
final String? keyId;
final String keyId;
@HiveField(1)
final String? applicationKey;
final String applicationKey;
get encodedApiKey => encodedBackblazeKey(keyId, applicationKey);

View File

@ -17,8 +17,8 @@ class BackblazeCredentialAdapter extends TypeAdapter<BackblazeCredential> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return BackblazeCredential(
keyId: fields[0] as String?,
applicationKey: fields[1] as String?,
keyId: fields[0] as String,
applicationKey: fields[1] as String,
);
}

View File

@ -4,13 +4,16 @@ part 'cloudflare_domain.g.dart';
@HiveType(typeId: 3)
class CloudFlareDomain {
CloudFlareDomain({this.domainName, this.zoneId});
CloudFlareDomain({
required this.domainName,
required this.zoneId,
});
@HiveField(0)
final String? domainName;
final String domainName;
@HiveField(1)
final String? zoneId;
final String zoneId;
@override
String toString() {

View File

@ -17,8 +17,8 @@ class CloudFlareDomainAdapter extends TypeAdapter<CloudFlareDomain> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return CloudFlareDomain(
domainName: fields[0] as String?,
zoneId: fields[1] as String?,
domainName: fields[0] as String,
zoneId: fields[1] as String,
);
}

View File

@ -0,0 +1,89 @@
import 'package:json_annotation/json_annotation.dart';
part 'hetzner_server_info.g.dart';
@JsonSerializable()
class HetznerServerInfo {
final int id;
final String name;
final ServerStatus status;
final DateTime created;
@JsonKey(name: 'server_type')
final HetznerServerTypeInfo serverType;
@JsonKey(name: 'datacenter', fromJson: HetznerServerInfo.locationFromJson)
final HetznerLocation location;
static HetznerLocation locationFromJson(Map json) =>
HetznerLocation.fromJson(json['location']);
static HetznerServerInfo fromJson(Map<String, dynamic> json) =>
_$HetznerServerInfoFromJson(json);
HetznerServerInfo(
this.id,
this.name,
this.status,
this.created,
this.serverType,
this.location,
);
}
enum ServerStatus {
running,
initializing,
starting,
stopping,
off,
deleting,
migrating,
rebuilding,
unknown,
}
@JsonSerializable()
class HetznerServerTypeInfo {
final int cores;
final num memory;
final int disk;
final List<HetznerPriceInfo> prices;
HetznerServerTypeInfo(this.cores, this.memory, this.disk, this.prices);
static HetznerServerTypeInfo fromJson(Map<String, dynamic> json) =>
_$HetznerServerTypeInfoFromJson(json);
}
@JsonSerializable()
class HetznerPriceInfo {
HetznerPriceInfo(this.hourly, this.monthly);
@JsonKey(name: 'price_hourly', fromJson: HetznerPriceInfo.getPrice)
final double hourly;
@JsonKey(name: 'price_monthly', fromJson: HetznerPriceInfo.getPrice)
final double monthly;
static HetznerPriceInfo fromJson(Map<String, dynamic> json) =>
_$HetznerPriceInfoFromJson(json);
static double getPrice(Map json) => double.parse(json['gross'] as String);
}
@JsonSerializable()
class HetznerLocation {
final String country;
final String city;
final String description;
@JsonKey(name: 'network_zone')
final String zone;
HetznerLocation(this.country, this.city, this.description, this.zone);
static HetznerLocation fromJson(Map<String, dynamic> json) =>
_$HetznerLocationFromJson(json);
}

View File

@ -0,0 +1,84 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'hetzner_server_info.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
HetznerServerInfo _$HetznerServerInfoFromJson(Map<String, dynamic> json) {
return HetznerServerInfo(
json['id'] as int,
json['name'] as String,
_$enumDecode(_$ServerStatusEnumMap, json['status']),
DateTime.parse(json['created'] as String),
HetznerServerTypeInfo.fromJson(json['server_type'] as Map<String, dynamic>),
HetznerServerInfo.locationFromJson(json['datacenter'] as Map),
);
}
K _$enumDecode<K, V>(
Map<K, V> enumValues,
Object? source, {
K? unknownValue,
}) {
if (source == null) {
throw ArgumentError(
'A value must be provided. Supported values: '
'${enumValues.values.join(', ')}',
);
}
return enumValues.entries.singleWhere(
(e) => e.value == source,
orElse: () {
if (unknownValue == null) {
throw ArgumentError(
'`$source` is not one of the supported values: '
'${enumValues.values.join(', ')}',
);
}
return MapEntry(unknownValue, enumValues.values.first);
},
).key;
}
const _$ServerStatusEnumMap = {
ServerStatus.running: 'running',
ServerStatus.initializing: 'initializing',
ServerStatus.starting: 'starting',
ServerStatus.stopping: 'stopping',
ServerStatus.off: 'off',
ServerStatus.deleting: 'deleting',
ServerStatus.migrating: 'migrating',
ServerStatus.rebuilding: 'rebuilding',
ServerStatus.unknown: 'unknown',
};
HetznerServerTypeInfo _$HetznerServerTypeInfoFromJson(
Map<String, dynamic> json) {
return HetznerServerTypeInfo(
json['cores'] as int,
json['memory'] as num,
json['disk'] as int,
(json['prices'] as List<dynamic>)
.map((e) => HetznerPriceInfo.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
HetznerPriceInfo _$HetznerPriceInfoFromJson(Map<String, dynamic> json) {
return HetznerPriceInfo(
HetznerPriceInfo.getPrice(json['price_hourly'] as Map),
HetznerPriceInfo.getPrice(json['price_monthly'] as Map),
);
}
HetznerLocation _$HetznerLocationFromJson(Map<String, dynamic> json) {
return HetznerLocation(
json['country'] as String,
json['city'] as String,
json['description'] as String,
json['network_zone'] as String,
);
}

View File

@ -1,25 +0,0 @@
// import 'package:equatable/equatable.dart';
// import 'package:selfprivacy/logic/models/state_types.dart';
// enum ServiceTypes {
// messanger,
// mail,
// passwordManager,
// github,
// cloud,
// }
// class Service extends Equatable {
// const Service({required this.state, required this.type});
// final StateType state;
// final ServiceTypes type;
// Service updateState(StateType newState) => Service(
// state: newState,
// type: type,
// );
// @override
// List<Object?> get props => [state, type];
// }

View File

@ -15,12 +15,11 @@ import 'config/get_it_config.dart';
import 'config/localization.dart';
import 'logic/cubit/app_settings/app_settings_cubit.dart';
void main() async {
await HiveConfig.init();
Bloc.observer = SimpleBlocObserver();
Wakelock.enable();
getItSetup();
await getItSetup();
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();

View File

@ -34,7 +34,7 @@ class BrandButton {
onPressed: onPressed,
);
static iconText({
static emptyWithIconText({
Key? key,
required VoidCallback onPressed,
required String title,

View File

@ -6,6 +6,7 @@ enum TextType {
h2, // cards titles
h3, // titles in about page
h4, // caption
h5, // Table data
body1, // normal
body2, // with opacity
medium,
@ -63,10 +64,28 @@ class BrandText extends StatelessWidget {
textAlign: textAlign,
overflow: TextOverflow.ellipsis,
);
factory BrandText.h4(String? text, {TextStyle? style}) => BrandText(
factory BrandText.h4(
String? text, {
TextStyle? style,
TextAlign? textAlign,
}) =>
BrandText(
text,
type: TextType.h4,
style: style,
textAlign: textAlign,
);
factory BrandText.h5(
String? text, {
TextStyle? style,
TextAlign? textAlign,
}) =>
BrandText(
text,
type: TextType.h5,
style: style,
textAlign: textAlign,
);
factory BrandText.body1(String? text, {TextStyle? style}) => BrandText(
text,
@ -123,6 +142,11 @@ class BrandText extends StatelessWidget {
? headline4Style.copyWith(color: Colors.white)
: headline4Style;
break;
case TextType.h5:
style = isDark
? headline5Style.copyWith(color: Colors.white)
: headline5Style;
break;
case TextType.body1:
style = isDark ? body1Style.copyWith(color: Colors.white) : body1Style;
break;

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/ui/components/pre_styled_buttons.dart';
class OnePage extends StatelessWidget {
const OnePage({
Key? key,
required this.title,
required this.child,
}) : super(key: key);
final String title;
final Widget child;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
child: Column(
children: [
Container(
height: 51,
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 15),
child: BrandText.h4('basis.details'.tr()),
),
BrandDivider(),
],
),
preferredSize: Size.fromHeight(52),
),
body: child,
bottomNavigationBar: SafeArea(
child: Container(
decoration: BoxDecoration(boxShadow: kElevationToShadow[3]),
height: kBottomNavigationBarHeight,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
alignment: Alignment.center,
child: PreStyledButtons.close(
onPress: () => Navigator.of(context).pop()),
),
),
),
),
);
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:easy_localization/easy_localization.dart';
class PreStyledButtons {
static Widget close({
required VoidCallback onPress,
}) =>
_CloseButton(onPress: onPress);
}
class _CloseButton extends StatelessWidget {
const _CloseButton({Key? key, required this.onPress}) : super(key: key);
final VoidCallback onPress;
@override
Widget build(BuildContext context) {
return OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
BrandText.h4('basis.close'.tr()),
Icon(Icons.close),
],
),
);
}
}

View File

@ -408,7 +408,7 @@ class InitializingPage extends StatelessWidget {
Spacer(),
BrandButton.rised(
onPressed:
isLoading! ? null : appConfigCubit.createServerAndSetDnsRecords,
isLoading! ? null : () => appConfigCubit.createServerAndSetDnsRecords(),
title: isLoading ? 'basis.loading'.tr() : 'initializing.11'.tr(),
),
Spacer(flex: 2),

View File

@ -124,8 +124,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
BrandButton.rised(
onPressed: () {
context.read<AppSettingsCubit>().turnOffOnboarding();
Navigator.of(context)
.pushReplacement(materialRoute(widget.nextPage));
Navigator.of(context).pushAndRemoveUntil(
materialRoute(widget.nextPage),
(route) => false,
);
},
title: 'basis.got_it'.tr(),
),

View File

@ -10,9 +10,15 @@ import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.da
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
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/one_page/one_page.dart';
import 'package:selfprivacy/ui/pages/providers/settings/settings.dart';
import 'package:selfprivacy/ui/pages/server_details/server_details.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/utils/route_transitions/slide_bottom.dart';
import 'package:selfprivacy/utils/ui_helpers.dart';
var navigatorKey = GlobalKey<NavigatorState>();
class ProvidersPage extends StatefulWidget {
ProvidersPage({Key? key}) : super(key: key);
@ -27,9 +33,17 @@ class _ProvidersPageState extends State<ProvidersPage> {
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
final cards = ProviderType.values
.map((type) => _Card(
provider:
ProviderModel(state: StateType.uninitialized, type: type)))
.map(
(type) => Padding(
padding: EdgeInsets.only(bottom: 30),
child: _Card(
provider: ProviderModel(
state: isReady ? StateType.stable : StateType.uninitialized,
type: type,
),
),
),
)
.toList();
return Scaffold(
appBar: PreferredSize(
@ -56,9 +70,11 @@ class _Card extends StatelessWidget {
final ProviderModel provider;
@override
Widget build(BuildContext context) {
String? title;
late String title;
String? message;
String? stableText;
late String stableText;
late VoidCallback onTap;
AppConfigState appConfig = context.watch<AppConfigCubit>().state;
var domainName =
@ -67,30 +83,54 @@ class _Card extends StatelessWidget {
switch (provider.type) {
case ProviderType.server:
title = 'providers.server.card_title'.tr();
stableText = 'В норме';
stableText = 'providers.domain.status'.tr();
stableText = 'providers.server.status'.tr();
onTap = () => Navigator.of(context).push(
SlideBottomRoute(
OnePage(
title: title,
child: ServerDetails(),
),
),
);
break;
case ProviderType.domain:
title = 'providers.domain.card_title'.tr();
message = domainName;
stableText = 'Домен настроен';
stableText = 'providers.domain.status'.tr();
onTap = () => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _ProviderDetails(
provider: provider,
statusText: stableText,
);
},
);
break;
case ProviderType.backup:
title = 'providers.backup.card_title'.tr();
stableText = 'В норме';
stableText = 'providers.backup.status'.tr();
onTap = () => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _ProviderDetails(
provider: provider,
statusText: stableText,
);
},
);
break;
}
return GestureDetector(
onTap: () => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _ProviderDetails(
provider: provider,
statusText: stableText,
);
},
),
onTap: onTap,
child: BrandCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -131,20 +171,11 @@ class _ProviderDetails extends StatelessWidget {
var config = context.watch<AppConfigCubit>().state;
var domainName = config.isDomainFilled
? config.cloudFlareDomain!.domainName!
: 'example.com';
var domainName = UiHelpers.getDomainName(config);
switch (provider.type) {
case ProviderType.server:
title = 'providers.server.card_title'.tr();
children = [
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BrandText.body1('providers.server.bottom_sheet.2'.tr()),
SizedBox(height: 10),
BrandText.body1('providers.server.bottom_sheet.3'.tr()),
];
break;
throw ('wrong type');
case ProviderType.domain:
title = 'providers.domain.card_title'.tr();
children = [
@ -153,7 +184,7 @@ class _ProviderDetails extends StatelessWidget {
BrandText.body1(
'providers.domain.bottom_sheet.2'.tr(args: [domainName, 'Date'])),
SizedBox(height: 10),
BrandText.body1('providers.domain.bottom_sheet.3'.tr()),
BrandText.body1('providers.domain.status'.tr()),
];
break;
case ProviderType.backup:
@ -177,38 +208,7 @@ class _ProviderDetails extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 2,
),
child: PopupMenuButton<_PopupMenuItemType>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
onSelected: (_PopupMenuItemType result) {
switch (result) {
case _PopupMenuItemType.setting:
navigatorKey.currentState!
.push(materialRoute(SettingsPage()));
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<_PopupMenuItemType>(
value: _PopupMenuItemType.setting,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('basis.settings'.tr()),
),
),
],
),
),
),
SizedBox(height: 40),
Padding(
padding: brandPagePadding2,
child: Column(
@ -234,5 +234,3 @@ class _ProviderDetails extends StatelessWidget {
);
}
}
enum _PopupMenuItemType { setting }

View File

@ -0,0 +1,290 @@
import 'package:cubit_form/cubit_form.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.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';
import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart';
import 'package:selfprivacy/utils/named_font_weight.dart';
part 'server_settings.dart';
var navigatorKey = GlobalKey<NavigatorState>();
class ServerDetails extends StatefulWidget {
const ServerDetails({Key? key}) : super(key: key);
@override
_ServerDetailsState createState() => _ServerDetailsState();
}
class _ServerDetailsState extends State<ServerDetails>
with SingleTickerProviderStateMixin {
late TabController tabController;
@override
void dispose() {
tabController.dispose();
super.dispose();
}
@override
void initState() {
tabController = TabController(length: 2, vsync: this);
tabController.addListener(() {
setState(() {});
});
super.initState();
}
@override
Widget build(BuildContext context) {
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
var providerState = isReady ? StateType.stable : StateType.uninitialized;
late String title = 'providers.server.card_title'.tr();
return TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: tabController,
children: [
BlocProvider(
create: (context) => ServerDetailsCubit()..check(),
child: Builder(builder: (context) {
var details = context.watch<ServerDetailsCubit>().state;
if (details is ServerDetailsLoading ||
details is ServerDetailsInitial) {
return _TempMessage(message: 'basis.loading'.tr());
} else if (details is ServerDetailsNotReady) {
return _TempMessage(message: 'basis.no_data'.tr());
} else if (details is Loaded) {
var data = details.serverInfo;
var checkTime = details.checkTime;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
IconStatusMask(
status: providerState,
child: Icon(
BrandIcons.server,
size: 40,
color: Colors.white,
),
),
SizedBox(width: 10),
BrandText.h2(title),
Spacer(),
Padding(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 2,
),
child: PopupMenuButton<_PopupMenuItemType>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
onSelected: (_PopupMenuItemType result) {
switch (result) {
case _PopupMenuItemType.setting:
tabController.animateTo(1);
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<_PopupMenuItemType>(
value: _PopupMenuItemType.setting,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('basis.settings'.tr()),
),
),
],
),
),
],
),
SizedBox(height: 10),
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
SizedBox(height: 30),
Center(child: BrandText.h2('General information')),
SizedBox(height: 10),
Table(
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
defaultVerticalAlignment:
TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
getRowTitle('Last check'),
getRowValue(formater.format(checkTime)),
],
),
TableRow(
children: [
getRowTitle('Server Id'),
getRowValue(data.id.toString()),
],
),
TableRow(
children: [
getRowTitle('Status:'),
getRowValue(
'${data.status.toString().split('.')[1].toUpperCase()}',
isBold: true,
),
],
),
TableRow(
children: [
getRowTitle('CPU'),
getRowValue(
data.serverType.cores.toString(),
),
],
),
TableRow(
children: [
getRowTitle('Memory'),
getRowValue(
'${data.serverType.memory.toString()} GB',
),
],
),
TableRow(
children: [
getRowTitle('Disk Local'),
getRowValue(
'${data.serverType.disk.toString()} GB',
),
],
),
TableRow(
children: [
getRowTitle('Price monthly:'),
getRowValue(
'${data.serverType.prices[1].monthly.toString()}',
),
],
),
TableRow(
children: [
getRowTitle('Price hourly:'),
getRowValue(
'${data.serverType.prices[1].hourly.toString()}',
),
],
),
],
),
SizedBox(height: 30),
Center(child: BrandText.h2('Location')),
SizedBox(height: 10),
Table(
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
defaultVerticalAlignment:
TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
getRowTitle('Country'),
getRowValue(
'${data.location.country}',
),
],
),
TableRow(
children: [
getRowTitle('City'),
getRowValue(data.location.city),
],
),
TableRow(
children: [
getRowTitle('Description'),
getRowValue(data.location.description),
],
),
],
),
// BrandText.body1('providers.server.bottom_sheet.2'.tr()),
// SizedBox(height: 10),
// BrandText.body1('providers.server.bottom_sheet.3'.tr()),
],
),
),
],
);
} else {
throw Exception('wrong state');
}
}),
),
_ServerSettings(tabController: tabController),
],
);
}
Widget getRowTitle(String title) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: BrandText.h5(
title,
textAlign: TextAlign.right,
),
);
}
Widget getRowValue(String title, {bool isBold = false}) {
return BrandText.body1(
title,
style: isBold
? TextStyle(
fontWeight: NamedFontWeight.demiBold,
)
: null,
);
}
}
enum _PopupMenuItemType { setting }
class _TempMessage extends StatelessWidget {
const _TempMessage({
Key? key,
required this.message,
}) : super(key: key);
final String message;
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height - 100,
child: Center(
child: BrandText.body2(message),
),
);
}
}
final DateFormat formater = DateFormat('HH:mm:ss');

View File

@ -0,0 +1,136 @@
part of 'server_details.dart';
class _ServerSettings extends StatelessWidget {
const _ServerSettings({
Key? key,
required this.tabController,
}) : super(key: key);
final TabController tabController;
@override
Widget build(BuildContext context) {
return ListView(
padding: brandPagePadding2,
children: [
SizedBox(height: 10),
Container(
height: 52,
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 1),
child: Container(
child: Row(
children: [
IconButton(
icon: Icon(BrandIcons.arrow_left),
onPressed: () => tabController.animateTo(0),
),
SizedBox(width: 10),
BrandText.h4('basis.settings'.tr()),
],
),
),
),
BrandDivider(),
SwitcherBlock(
onChange: (_) {},
child: _TextColumn(
title: 'Allow Auto-upgrade',
value: 'Wether to allow automatic packages upgrades',
),
isActive: true,
),
SwitcherBlock(
onChange: (_) {},
child: _TextColumn(
title: 'Reboot after upgrade',
value: 'Reboot without prompt after applying updates',
),
isActive: false,
),
_Button(
onTap: () {},
child: _TextColumn(
title: 'Server Timezone',
value: 'Europe/Kyiv',
),
),
_Button(
onTap: () {},
child: _TextColumn(
title: 'Server Locale',
value: 'Default',
),
),
_Button(
onTap: () {},
child: _TextColumn(
hasWarning: true,
title: 'Factory Reset',
value: 'Restore default settings on your server',
),
)
],
);
}
}
class _Button extends StatelessWidget {
const _Button({
Key? key,
required this.onTap,
required this.child,
}) : super(key: key);
final Widget child;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.only(top: 20, bottom: 5),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
)),
child: child,
),
);
}
}
class _TextColumn extends StatelessWidget {
const _TextColumn({
Key? key,
required this.title,
required this.value,
this.hasWarning = false,
}) : super(key: key);
final String title;
final String value;
final bool hasWarning;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BrandText.body1(
title,
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
),
SizedBox(height: 5),
BrandText.body1(
value,
style: TextStyle(
fontSize: 13,
height: 1.53,
color: hasWarning ? BrandColors.warning : BrandColors.gray1,
),
),
],
);
}
}

View File

@ -4,6 +4,7 @@ import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
@ -13,6 +14,7 @@ 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:easy_localization/easy_localization.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'package:selfprivacy/utils/ui_helpers.dart';
import 'package:url_launcher/url_launcher.dart';
import '../rootRoute.dart';
@ -40,7 +42,14 @@ class _ServicesPageState extends State<ServicesPage> {
BrandText.body1('services.title'.tr()),
SizedBox(height: 24),
if (!isReady) ...[NotReadyCard(), SizedBox(height: 24)],
...ServiceTypes.values.map((t) => _Card(serviceType: t)).toList()
...ServiceTypes.values
.map((t) => Padding(
padding: EdgeInsets.only(
bottom: 30,
),
child: _Card(serviceType: t),
))
.toList()
],
),
);
@ -98,10 +107,10 @@ class _Card extends StatelessWidget {
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
var changeTab = context.read<ChangeTab>().onPress;
return GestureDetector(
onTap: () => showModalBottomSheet<void>(
onTap: () => showDialog<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
// isScrollControlled: true,
// backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _ServiceDetails(
serviceType: serviceType,
@ -163,9 +172,7 @@ class _ServiceDetails extends StatelessWidget {
late Widget child;
var config = context.watch<AppConfigCubit>().state;
var domainName = config.isDomainFilled
? config.cloudFlareDomain!.domainName!
: 'example.com';
var domainName = UiHelpers.getDomainName(config);
var linksStyle = body1Style.copyWith(
fontSize: 15,
@ -174,7 +181,6 @@ class _ServiceDetails extends StatelessWidget {
: BrandColors.black,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
// height: 1.1,
);
var textStyle = body1Style.copyWith(
@ -191,9 +197,10 @@ class _ServiceDetails extends StatelessWidget {
text: 'services.mail.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(child: SizedBox(width: 5)),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
padding: EdgeInsets.only(bottom: 0.8),
child: GestureDetector(
child: Text(
'services.mail.bottom_sheet.2'.tr(),
@ -229,9 +236,10 @@ class _ServiceDetails extends StatelessWidget {
.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(child: SizedBox(width: 5)),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
padding: EdgeInsets.only(bottom: 0.8),
child: GestureDetector(
onTap: () => _launchURL('https://password.$domainName'),
child: Text(
@ -252,9 +260,10 @@ class _ServiceDetails extends StatelessWidget {
text: 'services.video.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(child: SizedBox(width: 5)),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
padding: EdgeInsets.only(bottom: 0.8),
child: GestureDetector(
onTap: () => _launchURL('https://meet.$domainName'),
child: Text(
@ -275,9 +284,10 @@ class _ServiceDetails extends StatelessWidget {
text: 'services.cloud.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(child: SizedBox(width: 5)),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
padding: EdgeInsets.only(bottom: 0.8),
child: GestureDetector(
onTap: () => _launchURL('https://cloud.$domainName'),
child: Text(
@ -299,9 +309,10 @@ class _ServiceDetails extends StatelessWidget {
.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(child: SizedBox(width: 5)),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
padding: EdgeInsets.only(bottom: 0.8),
child: GestureDetector(
onTap: () => _launchURL('https://social.$domainName'),
child: Text(
@ -322,9 +333,10 @@ class _ServiceDetails extends StatelessWidget {
text: 'services.git.bottom_sheet.1'.tr(args: [domainName]),
style: textStyle,
),
WidgetSpan(child: SizedBox(width: 5)),
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(bottom: 0.8, left: 5),
padding: EdgeInsets.only(bottom: 0.8),
child: GestureDetector(
onTap: () => _launchURL('https://git.$domainName'),
child: Text(
@ -338,39 +350,312 @@ class _ServiceDetails extends StatelessWidget {
));
break;
}
return BrandModalSheet(
child: Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (_) {
return materialRoute(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 13),
IconStatusMask(
status: status,
child: Icon(icon, size: 40, color: Colors.white),
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: SingleChildScrollView(
child: Container(
width: 350,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconStatusMask(
status: status,
child: Icon(icon, size: 40, color: Colors.white),
),
SizedBox(height: 10),
BrandText.h2(title),
SizedBox(height: 10),
child,
SizedBox(height: 40),
Center(
child: Container(
child: BrandButton.rised(
onPressed: () => Navigator.of(context).pop(),
title: 'basis.close'.tr(),
),
),
SizedBox(height: 10),
BrandText.h1(title),
child,
],
),
)
],
),
);
},
),
],
),
)
],
),
),
),
);
}
void _launchURL(url) async =>
await canLaunch(url) ? await launch(url) : throw 'Could not launch $url';
void _launchURL(url) async {
var _possible = await canLaunch(url);
if (_possible) {
try {
await launch(
url,
forceSafariVC: true,
enableJavaScript: true,
);
} catch (e) {
print(e);
}
} else {
throw 'Could not launch $url';
}
}
}
// class _ServiceDetails extends StatelessWidget {
// const _ServiceDetails({
// Key? key,
// required this.serviceType,
// required this.icon,
// required this.status,
// required this.title,
// required this.changeTab,
// }) : super(key: key);
// final ServiceTypes serviceType;
// final IconData icon;
// final StateType status;
// final String title;
// final ValueChanged<int> changeTab;
// @override
// Widget build(BuildContext context) {
// late Widget child;
// var config = context.watch<AppConfigCubit>().state;
// var domainName = UiHelpers.getDomainName(config);
// var linksStyle = body1Style.copyWith(
// fontSize: 15,
// color: Theme.of(context).brightness == Brightness.dark
// ? Colors.white
// : BrandColors.black,
// fontWeight: FontWeight.bold,
// decoration: TextDecoration.underline,
// // height: 1.1,
// );
// var textStyle = body1Style.copyWith(
// color: Theme.of(context).brightness == Brightness.dark
// ? Colors.white
// : BrandColors.black,
// );
// switch (serviceType) {
// case ServiceTypes.mail:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.mail.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// child: Text(
// 'services.mail.bottom_sheet.2'.tr(),
// style: linksStyle,
// ),
// onTap: () {
// Navigator.of(context).pop();
// changeTab(2);
// },
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.messenger:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.messenger.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// )
// ],
// ));
// break;
// case ServiceTypes.passwordManager:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.password_manager.bottom_sheet.1'
// .tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://password.$domainName'),
// child: Text(
// 'password.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.video:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.video.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://meet.$domainName'),
// child: Text(
// 'meet.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.cloud:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.cloud.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://cloud.$domainName'),
// child: Text(
// 'cloud.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.socialNetwork:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.social_network.bottom_sheet.1'
// .tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://social.$domainName'),
// child: Text(
// 'social.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// case ServiceTypes.git:
// child = RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'services.git.bottom_sheet.1'.tr(args: [domainName]),
// style: textStyle,
// ),
// WidgetSpan(
// child: Padding(
// padding: EdgeInsets.only(bottom: 0.8, left: 5),
// child: GestureDetector(
// onTap: () => _launchURL('https://git.$domainName'),
// child: Text(
// 'git.$domainName',
// style: linksStyle,
// ),
// ),
// ),
// ),
// ],
// ));
// break;
// }
// return BrandModalSheet(
// child: Navigator(
// key: navigatorKey,
// initialRoute: '/',
// onGenerateRoute: (_) {
// return materialRoute(
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Padding(
// padding: brandPagePadding1,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// SizedBox(height: 13),
// IconStatusMask(
// status: status,
// child: Icon(icon, size: 40, color: Colors.white),
// ),
// SizedBox(height: 10),
// BrandText.h1(title),
// SizedBox(height: 10),
// child,
// ],
// ),
// )
// ],
// ),
// );
// },
// ),
// );
// }
// void _launchURL(url) async {
// var _possible = await canLaunch(url);
// if (_possible) {
// try {
// await launch(
// url,
// forceSafariVC: true,
// enableJavaScript: true,
// );
// } catch (e) {
// print(e);
// }
// } else {
// throw 'Could not launch $url';
// }
// }
// }

View File

@ -5,14 +5,12 @@ class _NewUser extends StatelessWidget {
Widget build(BuildContext context) {
var config = context.watch<AppConfigCubit>().state;
var domainName = config.isDomainFilled
? config.cloudFlareDomain!.domainName!
: 'example.com';
var domainName = UiHelpers.getDomainName(config);
return BrandModalSheet(
child: BlocProvider(
create: (context) =>
UserFormCubit(usersCubit: context.watch<UsersCubit>()),
UserFormCubit(usersCubit: context.read<UsersCubit>()),
child: Builder(builder: (context) {
var formCubitState = context.watch<UserFormCubit>().state;

View File

@ -12,9 +12,7 @@ class _UserDetails extends StatelessWidget {
Widget build(BuildContext context) {
var config = context.watch<AppConfigCubit>().state;
var domainName = config.isDomainFilled
? config.cloudFlareDomain!.domainName!
: 'example.com';
var domainName = UiHelpers.getDomainName(config);
return BrandModalSheet(
child: Column(
@ -44,8 +42,8 @@ class _UserDetails extends StatelessWidget {
),
onSelected: (PopupMenuItemType result) {
switch (result) {
case PopupMenuItemType.reset:
break;
// case PopupMenuItemType.reset:
// break;
case PopupMenuItemType.delete:
showDialog(
context: context,
@ -88,13 +86,13 @@ class _UserDetails extends StatelessWidget {
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<PopupMenuItemType>(
value: PopupMenuItemType.reset,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('users.reset_password'.tr()),
),
),
// PopupMenuItem<PopupMenuItemType>(
// value: PopupMenuItemType.reset,
// child: Container(
// padding: EdgeInsets.only(left: 5),
// child: Text('users.reset_password'.tr()),
// ),
// ),
PopupMenuItem<PopupMenuItemType>(
value: PopupMenuItemType.delete,
child: Container(
@ -145,7 +143,7 @@ class _UserDetails extends StatelessWidget {
SizedBox(height: 24),
BrandDivider(),
SizedBox(height: 20),
BrandButton.iconText(
BrandButton.emptyWithIconText(
title: 'users.send_regisration_data'.tr(),
icon: Icon(BrandIcons.share),
onPressed: () {},
@ -161,6 +159,6 @@ class _UserDetails extends StatelessWidget {
}
enum PopupMenuItemType {
reset,
// reset,
delete,
}

View File

@ -14,6 +14,7 @@ import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.da
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/utils/ui_helpers.dart';
part 'fab.dart';
part 'new_user.dart';

View File

@ -36,8 +36,10 @@ Function transitionsBuilder = (
class SlideBottomRoute extends PageRouteBuilder {
SlideBottomRoute(this.widget)
: super(
transitionDuration: Duration(milliseconds: 150),
pageBuilder: pageBuilder(widget),
transitionsBuilder: transitionsBuilder as Widget Function(BuildContext, Animation<double>, Animation<double>, Widget),
transitionsBuilder: transitionsBuilder as Widget Function(
BuildContext, Animation<double>, Animation<double>, Widget),
);
final Widget widget;

View File

@ -0,0 +1,9 @@
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
/// it's ui helpers use only for ui components, don't use for logic components.
class UiHelpers {
static String getDomainName(AppConfigState config) => config.isDomainFilled
? config.cloudFlareDomain!.domainName
: 'example.com';
}

View File

@ -357,6 +357,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
http:
dependency: transitive
description:
@ -574,6 +581,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
pretty_dio_logger:
dependency: "direct main"
description:
name: pretty_dio_logger
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0-beta-1"
process:
dependency: transitive
description:
@ -782,6 +796,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
unicons:
dependency: "direct main"
description:
name: unicons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
url_launcher:
dependency: "direct main"
description:

View File

@ -25,7 +25,9 @@ dependencies:
hive_flutter: ^1.0.0
json_annotation: ^4.0.0
package_info: ^2.0.0
pretty_dio_logger: ^1.1.1
provider: ^5.0.0
unicons: ^1.0.2
url_launcher: ^6.0.2
wakelock: ^0.5.0+2
@ -35,6 +37,7 @@ dev_dependencies:
basic_utils: ^3.0.0-nullsafety.1
build_runner: ^1.11.5
flutter_launcher_icons: ^0.9.0
hive_generator: ^1.0.0
json_serializable: ^4.0.2
flutter_icons: